[From Galileo's Geist: FactionManager save/load: Questions/Suggestions

Announcements, support questions, and discussion for Love/Hate.
Post Reply
User avatar
Tony Li
Posts: 20642
Joined: Thu Jul 18, 2013 1:27 pm

[From Galileo's Geist: FactionManager save/load: Questions/Suggestions

Post by Tony Li »

[Re-posting Galileo's Geist's forum post while we work out an access issue.]

Hi all! Just got Love/Hate after much thought, and really like the tool, but running into an issue with loading saved FactionManager data. Perhaps a misunderstanding, perhaps not. Anyway, here's my big question/suggestion, with some background first.

Here is essentially what I'm doing:
I have my own save system already built which I'm adding onto simply by adding JSON components. I have the following test save data within that JSON:

Code: Select all

"factionManagerData": "7,0,Player,0,0,0,0,0,0,0,2,STAND-IN,0,0,0,0,0,0,0,3,Hu_1 (Wanderer),0,0,0,0,0,0,0,4,Hu_2 (to-ny),0,0,0,0,0,0,0,5,Hu_3 (fredyryk),0,0,0,0,0,0,0,6,Hu_4 (sammy sam),0,0,0,0,0,0,0,7,Hu_5 (g'iles),0,0,0,0,0,0,0,"
Some of the Faction Members represented there were created at runtime thusly:

Code: Select all

int newFactionId = FactionManager.instance.factionDatabase.CreateNewFaction(
    newHu.TitleTag,
    $"Faction for: {newHu.TitleTag}"
);
On top of a custom, preexisting FactionDatabase asset that had:

geist1.png
geist1.png (8.71 KiB) Viewed 7653 times

And the data string was saved with:

Code: Select all

string factionManagerData = FactionManager.instance.SerializeToString();
I then changed my runtime code to create a different, smaller set of Faction Members and their Factions. So I run the game, it loads the first two Factions from the FactionDatabase, then adds these three on top:

geist2.png
geist2.png (19.1 KiB) Viewed 7653 times

At this point I try my loading system. Whether or not I clear FactionManager data/Factions first runs into this same issue, so I'll represent it in the case of not doing any data clearing or Faction deletion before the 'Load Game' step. When I execute the following (data string hardcoded for clarity)...

Code: Select all

string factionManagerData = "7,0,Player,0,0,0,0,0,0,0,2,STAND-IN,0,0,0,0,0,0,0,3,Hu_1 (Wanderer),0,0,0,0,0,0,0,4,Hu_2 (to-ny),0,0,0,0,0,0,0,5,Hu_3 (fredyryk),0,0,0,0,0,0,0,6,Hu_4 (sammy sam),0,0,0,0,0,0,0,7,Hu_5 (g'iles),0,0,0,0,0,0,0,";
FactionManager.instance.DeserializeFromString(factionManagerData)
My FactionManager shows the following Faction list in the inspector:

geist3.png
geist3.png (20.82 KiB) Viewed 7653 times

And for reference, here is the FactionManager#DeserializeFromString() method in the source code (Version 1.10.40.1):
Spoiler

Code: Select all

        public void DeserializeFromString(string s)
        {
            if (string.IsOrEmpty(s) || factionDatabase == ) return;

            var traitCount = factionDatabase.relationshipTraitDefinitions.Length;
            var data = new Queue<string>(s.Split(','));
            if (data.Count < 1) return;

            // Get faction count:
            var factionCount = SafeConvert.ToInt(data.Dequeue());

            for (int f = 0; f < Mathf.Min(factionCount, factionDatabase.factions.Length); f++)
            {
                var faction = factionDatabase.factions[f];

                // Get faction ID and name:
                faction.id = SafeConvert.ToInt(data.Dequeue());
                faction.name = SafeConvert.FromSerializedElement(data.Dequeue());

                // Get faction personality trait values:
                for (int p = 0; p < faction.traits.Length; p++)
                {
                    faction.traits[p] = SafeConvert.ToFloat(data.Dequeue());
                }

                // Get faction parents:
                var parents = new List<int>();
                var parentCount = SafeConvert.ToInt(data.Dequeue());
                for (int p = 0; p < parentCount; p++)
                {
                    parents.Add(SafeConvert.ToInt(data.Dequeue()));
                }
                faction.parents = parents.ToArray();

                // Release faction's old relationships:
                for (int r = 0; r < faction.relationships.Count; r++)
                {
                    var relationship = faction.relationships[r];
                    Relationship.Release(relationship);
                }
                faction.relationships.Clear();

                // Get faction's relationships:
                var relationshipCount = SafeConvert.ToInt(data.Dequeue());
                for (int r = 0; r < relationshipCount; r++)
                {
                    var id = SafeConvert.ToInt(data.Dequeue());
                    var inheritable = (SafeConvert.ToInt(data.Dequeue()) == 1);
                    var traits = new float[traitCount];
                    for (int i = 0; i < traitCount; i++)
                    {
                        traits[i] = SafeConvert.ToFloat(data.Dequeue());
                    }
                    var relationship = Relationship.GetNew(id, inheritable, traits);
                    faction.relationships.Add(relationship);
                }
            }
        }
As you can see:
  • FactionManager instance serialization and deserialization does not include the Faction#description field. This not only removes the information while serializing but allows for misalignment as it does not clear existing 'description' fields during deserialization. I assume this is just a small oversight, and should either be fully supported or fully ignored.
  • It appears that FactionManager#DeserializeFromString() will only load individual Faction data into existing Faction objects in the manager's list, and so it capped by that count. It's possible this is intentional, but I am surprised by this behavior. To me, this means the De/Serialization features effectively do not support runtime Faction generation and deletion. I could write workarounds for this, but that gives me concern, and again, I don't want to go down that path if it represents a deeper misunderstanding on my part that will only cause me more friction in the future, especially as I'm still grasping the finer details of the edit-vs-runtime relationship between FactionManager / FactionManager#FactionDatabase / FactionDabase.asset / FactionManager#factions. Anyway, my gut says there should be a more confident way to save and load the entirety of the FactionManager and its memory-representation of the FactionDatabase.
Thanks for reading! I welcome all thoughts and questions and help of any kind.
User avatar
Tony Li
Posts: 20642
Joined: Thu Jul 18, 2013 1:27 pm

Re: [From Galileo's Geist: FactionManager save/load: Questions/Suggestions

Post by Tony Li »

Hi,

The FactionDatabase object is serializable. You can serialize and deserialize the whole thing:

Code: Select all

string s = JsonUtility.ToJson(FactionManager.instance.factionDatabase);
and:

Code: Select all

JsonUtility.FromJsonOverwrite(s, FactionManager.instance.factionDatabase);
Galileo's Geist
Posts: 1
Joined: Sun Nov 12, 2023 10:05 am

Re: [From Galileo's Geist: FactionManager save/load: Questions/Suggestions

Post by Galileo's Geist »

Thanks for the reply! This solved my main issue, and allowed me to move forward. I'd still like to note that the FactionManager built-in SerializeToString() and DeserializeFromString() have the unexpected/incomplete behavior above regarding Faction count loading and Faction description saving/loading. I could personally see this being something helpful to address at some point.
But I'm all set on this topic. Thanks so much!
User avatar
Tony Li
Posts: 20642
Joined: Thu Jul 18, 2013 1:27 pm

Re: [From Galileo's Geist: FactionManager save/load: Questions/Suggestions

Post by Tony Li »

Hi,

Point well taken. It's on the roadmap to address in the upcoming version or the version after that.
Post Reply