+
+SaveSystem
provides wrappers and extensions to the game's save system.
+SyncDataAsJson
+The Old Way
+To sync your custom data to savegames, you need to define a CampaignBehaviorBase
and override its SyncData(IDataStore dataStore)
method. From there, you'd make one or more calls to dataStore.SyncData<T>(string key, ref T myObject)
to actually synchronize the data to the savegame.
+This seems dandy right up until you realize that it's actually quite a pain in practice, because in many cases, SyncData<T>
will not only fail to work correctly with some standard types (e.g., simple containers like Dictionary<string, string>
but also the game's implementation will often force the save files to depend upon your mod being loaded — not to mention the need to define your own SaveableTypeDefiner
and pick arbitrary unique "base IDs" for your mod just to get anything done. If the save file ends up depending upon your mod being loaded, then your users are unnecessarily screwed when they disable it.
+In short, the old way is a pretty decent try, but it falls short on safely removing your mod from a savegame in all cases, the general amount of error-prone configuration required, too many of your wasted hours trying to synchronize data types that should obviously be handled by default but simply aren't, and wasted time marshalling things like MBGUID
s back to object references.
+The New Way: SyncDataAsJson
+We've developed a drop-in replacement for the aforementioned SyncData<T>
: SyncDataAsJson<T>
. It doesn't suffer from any of the aforementioned issues, and as an extension method of IDataStore
, you use it exactly like you would've used SyncData<T>
. However, your custom data is now, behind the scenes, serialized by ButterLib into a simple string
(or similar primitive type).
+SyncDataAsJson<T>
doing its own serialization to a primitive type behind the scenes means:
+
+Your mod will never save custom data that prevents the game from loading properly when your mod is disabled
+
+You will never need to define another SaveableTypeDefiner
+
+Far more standard types, especially involving standard containers, will be handled automatically, and in the off chance that they aren't, you (and we) have the power to add custom type serializers.
+
+You will never again need to manually save MBGUID
values or manually restore them to MBObjectBase
-derived object references in order to properly save, say, a Clan
reference again.
+
+
+The serialization engine, Newtonsoft.Json
, in order to allow SyncDataAsJson<T>
to operate as a drop-in replacement for the old method, has been outfit with a custom contract resolver and a number of special type converters.
+The engine's custom contract resolver will only serialize data tagged with the TaleWorlds SaveableField
or SaveableProperty
attributes. Likewise, custom types intended for serialization must still use the SaveableClass
or SaveableStruct
attributes. Note that the ID numbers required by these attributes are an artifact of the old system and don't actually matter to SyncDataAsJson<T>
, but you're advised to fill them out like normal for new types in the event that you need to go back to SyncData<T>
for some reason.
+An example usage follows. Remember, the only thing that's really changed here is using SyncDataAsJson<T>
instead of SyncData<T>
.
+using Bannerlord.ButterLib.SaveSystem.Extensions;
+
+public class CustomBehavior : CampaignBehaviorBase
+{
+ public override void SyncData(IDataStore dataStore)
+ {
+ dataStore.SyncDataAsJson("KeyForMyClass", ref _myClassObj);
+ // ... perhaps more SyncDataAsJson calls for other data ...
+ }
+
+ [Serializable] // If the struct/class has the Serializable attribute`SaveableField and SaveableProperty will be ignored
+ public MyStruct
+ {
+ public int X;
+ public int Y;
+ public int Z;
+ }
+
+ private MyClass // Why private? Just to point out that access levels aren't an issue.
+ {
+ public int _unsavedField = 42;
+
+ [SaveableField(1)]
+ public Dictionary<Hero, int> _heroButterGiftAmounts = new Dictionary<Hero, int>();
+
+ [SaveableProperty(2)]
+ public string Name { get; set; } = "The Butter Lord";
+ }
+
+ private MyClass _myClassObj = new MyClass();
+
+ // ... other campaign behavior code to, presumably, give a lot of butter away everyday
+}
+
+This extension is the next step in the evolution of best practices and just plain less frustrating practices for the synchronization of your mod's custom data to savegames.
+Notes:
+
+Built-in MBObjectBase
-derived types (e.g., Hero
or Town
) have a custom converter. They are serialized as their Id
property. MBObjectManager
is used to resolve these numeric IDs to the correct, live game object references at deserialization time.
+
+Custom MBObjectBase
types are not serialized (i.e., custom types derived from MBObjectBase
that aren't registered with the game's official object manager). While we do not know if such types even exist, we consider this to be non-ideal and intend to fix it in the future for completeness. One of the proposed solutions is to have our own registry of such custom objects and to resolve them from it — basically a custom MBObjectManager
.
+
+
+
+
This page was last modified at 09/11/2023 16:51:51 +03:00 (UTC).
Commit Message
Author: Vitalii Mikhailov
+Commit: 981e81e2dfcd0e6002f637a8ed1d5d1e07461867
+
Improved logging
+
+Fixed IMBObjectExtensionDataStore
+