Adding private variables to an existing Raft class is sometimes very necessary. There're many ways to add new variables. In this article we will consider the method provided by Whitebrim. (Discord Whitebrim#4444)
We will store variables in the newly created class and add an extension for original Raft class. You can read how C# extensions work here.
We need to create new ConditionalWeakTable<TKey, TValue>. It will store our data classes and link them to original classes. This class is specifically created for our case, if you're interested you can read more about it here.
In this example we will add new private bool isLocked variable to SteeringWheel class:
First, we need to create data class to store all data we need to add:
You must add [Serializable] attribute that our class could be saved. Also you need to create default constructor with no parameters to initialize class with default values.
Second, we need to create an extension class to store and handle data:
Then you need to patch original class. In our case we need to change displaying text depending on IsLocked state. We will patch OnIsRayed() method inside SteeringWheel class:
[HarmonyPatch(typeof(SteeringWheel),"OnIsRayed")]classSteeringWheelPatchOnIsRayed{privatestaticvoidPostfix(SteeringWheel __instance,refDisplayTextManager ___displayText) {if (!__instance.GetAdditionalData().isLocked)___displayText.ShowText("Press to LOCK rotation",MyInput.Keybinds["RMB"].MainKey,2,0,false);else___displayText.ShowText("Press to UNLOCK rotation",MyInput.Keybinds["RMB"].MainKey,2,0,false);if (MyInput.GetButtonDown("RMB"))__instance.GetAdditionalData().isLocked=!__instance.GetAdditionalData().isLocked; // Toggle bool }}
publicclassBetterSteeringWheel:Mod{privateHarmonyInstance harmonyInstance;publicvoidStart() { harmonyInstance =newHarmony("com.whitebrim.bettersteeringwheel"); // It's custom patch name, you need to name your patch differentlyharmonyInstance.PatchAll(Assembly.GetExecutingAssembly()); }publicvoidOnModUnload() {harmonyInstance.UnpatchAll();Destroy(gameObject); }}
You can access new data using class instance. SteeringWheel.GetAdditionalData().
To save private variables you need to patch RDG_%RaftClassName% class and method that is invoked inside switch in SaveAndLoad class in RestoreRGDGame(RGD_Game game) method.
In this example we will use SteeringWheel class and save data from Adding private variables section.
First, we have to add new link RGD Class -> our additional data class (ConditionalWeakTable):
Second, we need to patch RGD_SteeringWheel class. There're two constructors (one for saving, another for loading) and GetObjectData method (to control flow of Serialization).
classRGD_SteeringWheelPatch{ [HarmonyPatch(typeof(RGD_SteeringWheel),MethodType.Constructor,newType[]{ typeof(RGDType),typeof(SteeringWheel) })]classRGD_SteeringWheelConstructor1// Constructor for saving {privatestaticvoidPrefix(RGD_SteeringWheel __instance,refSteeringWheel steeringWheel) {__instance.AddData(steeringWheel.GetAdditionalData()); } } [HarmonyPatch(typeof(RGD_SteeringWheel),MethodType.Constructor,newType[] { typeof(SerializationInfo),typeof(StreamingContext) })]classRGD_SteeringWheelConstructor2// Constructor for loading {privatestaticvoidPrefix(RGD_SteeringWheel __instance,refSerializationInfo info) {try {__instance.AddData(JsonUtility.FromJson<SteeringWheelAdditionalData>(info.GetString("AdditionalData"))); // Loads from Json that we will create in GetObjectData }catch (Exception) { } } } [HarmonyPatch(typeof(RGD_SteeringWheel),"GetObjectData")]classGetObjectData// Interfering with the serialization flow and adding our custom data to the save {privatestaticvoidPostfix(RGD_SteeringWheel __instance,refSerializationInfo info) {SteeringWheelAdditionalData value;if (SteeringWheelExtension.RGD_data.TryGetValue(__instance,out value))info.AddValue("AdditionalData",JsonUtility.ToJson(value)); // We need to use json because worlds loads before mod compiles } }}
Lastly, we need to find method that loads saved block data and patch it. This method is invoked inside switch in SaveAndLoad class in RestoreRGDGame(RGD_Game game) method:
switch (rgd.type) {caseRGDType.Block: { // Important codebreak; }caseRGDType.Block_Door: { // Important codebreak; } // etc...
Our case:
case RGDType.Block_SteeringWheel: {RGD_SteeringWheel rgd_SteeringWheel = rgd asRGD_SteeringWheel;Block block19 =this.RestoreBlock(network_Player.BlockCreator, rgd_SteeringWheel);if (block19 !=null) {SteeringWheel componentInChildren3 =block19.GetComponentInChildren<SteeringWheel>();if (componentInChildren3 !=null) {componentInChildren3.RestoreWheel(rgd_SteeringWheel); // <- This line } }break; }
In our case method is called RestoreWheel(RGD_SteeringWheel rgdWheel). This method contains instructions on how to deserialize RGD class to retrieve saved data. We will add our custom instructions: