From c7a46f27309bdb78a9911b2911e05a29a3a019b1 Mon Sep 17 00:00:00 2001 From: SokyranTheDragon <36712560+SokyranTheDragon@users.noreply.github.com> Date: Tue, 22 Oct 2024 19:29:41 +0200 Subject: [PATCH] Updated Ancient Urban Ruins compat (#487) - Patched gizmo to fill a map portal (stairs, elevetars, etc.) - Needs to be enabled in the settings - Not available for all map portals - Added support for electrical boxes - You link an input and output electrical box (both named, mode can be toggled) to transfer power between one and the other, across levels - Syncing is done via a single sync method lambda, a normal sync method (called by using a method replaced with a transpiler), and 5 sync fields (2 of them potentially called on 2 targets) - Added support for material elevators - You link an input (unnamed) and output (named) elevator using their ITab, and the elevator will transport items from one level to another, across levels - Syncing is done via a single sync method lambda and a sync field - The normal (pawn) elevators don't require syncing, as they were already synced through vanilla handling of map portals and patches that already exist in this compat - The only incompatible part was gizmo to fill the elevator, which is fixed here as well --- Source/Mods/AncientUrbanRuins.cs | 189 ++++++++++++++++++++++++++++++- 1 file changed, 188 insertions(+), 1 deletion(-) diff --git a/Source/Mods/AncientUrbanRuins.cs b/Source/Mods/AncientUrbanRuins.cs index aad4e32..f4eae8b 100644 --- a/Source/Mods/AncientUrbanRuins.cs +++ b/Source/Mods/AncientUrbanRuins.cs @@ -7,6 +7,7 @@ using Multiplayer.API; using RimWorld; using RimWorld.Planet; +using UnityEngine; using Verse; namespace Multiplayer.Compat; @@ -28,13 +29,40 @@ public class AncientUrbanRuins // MapParent_Custom private static AccessTools.FieldRef customMapEntranceField; + // Tab_LevelPower + private static FastInvokeHandler levelPowerTabCompGetter; + // CompPowerPlantLevel + private static Type powerPlantLevelCompType; + private static FastInvokeHandler powerPlantLevelCompLinkedCompGetter; + private static AccessTools.FieldRef powerPlantLevelCompTargetOutputLevelField; + [MpCompatSyncField("AncientMarket_Libraray.CompPowerPlantLevel", "name")] + protected static ISyncField powerPlantLevelCompNameSyncField; + [MpCompatSyncField("AncientMarket_Libraray.CompPowerPlantLevel", "outputMode")] + protected static ISyncField powerPlantLevelCompOutputModeSyncField; + [MpCompatSyncField("AncientMarket_Libraray.CompPowerPlantLevel", "targetPowerOutput")] + protected static ISyncField powerPlantLevelTargetOutputLevelSyncField; + [MpCompatSyncField("AncientMarket_Libraray.CompPowerPlantLevel", "comp")] + protected static ISyncField powerPlantLevelCompLinkedThingSyncField; + [MpCompatSyncField("AncientMarket_Libraray.CompPowerPlantLevel", "linked")] + protected static ISyncField powerPlantLevelCompLinkedCompSyncField; + + // Tab_LevelTransmit + private static FastInvokeHandler levelTransmitTabReceiverGetter; + // Building_Transmit + [MpCompatSyncField("AncientMarket_Libraray.Building_Receive", "name")] + protected static ISyncField buildingTransmitNameSyncField; + #endregion #region Main patch public AncientUrbanRuins(ModContentPack mod) { - // Mod uses 3 different assemblies, 2 of them use the same namespace. + // Mod uses several 6 different assemblies, 2 of them use the same namespace. + // It seems the mod quite often but increments a number in the assemblies + // name rather than keeping the same name - for example, currently there's + // an assembly called "AncientMarket_Libraray(66).dll". + // They seem to ocassionally restore the name to a one without a number. MpCompatPatchLoader.LoadPatch(this); MpSyncWorkers.Requires(); @@ -67,9 +95,12 @@ public AncientUrbanRuins(ModContentPack mod) MpCompat.RegisterLambdaDelegate("AncientMarket_Libraray.BuildingTrader", nameof(Thing.GetFloatMenuOptions), 0); // Start dialogue (seems unused/related feature is unfinished) MpCompat.RegisterLambdaDelegate("AncientMarket_Libraray.CompDialogable", nameof(ThingComp.CompFloatMenuOptions), 0); + // Destroy site LongEventHandler.ExecuteWhenFinished(() => MpCompat.RegisterLambdaMethod("AncientMarket_Libraray.CustomSite", nameof(WorldObject.GetGizmos), 1)); + // Toggle plan to fill the portal + MpCompat.RegisterLambdaMethod("AncientMarket_Libraray.CompFillPortal", nameof(ThingComp.CompGetGizmosExtra), 0); } #endregion @@ -97,6 +128,46 @@ public AncientUrbanRuins(ModContentPack mod) } #endregion + + #region ITab + + { + var thingAsIdSerializer = Serializer.New((Thing t) => t.thingIDNumber, + id => MP.TryGetThingById(id, out var thing) ? thing : null); + + // Cross-map power transmit tab + var type = AccessTools.TypeByName("AncientMarket_Libraray.Tab_LevelPower"); + levelPowerTabCompGetter = MethodInvoker.GetHandler( + AccessTools.DeclaredPropertyGetter(type, "Comp")); + + // Link 2 power transmitters. + // They are most likely on separate maps, so we need to transform + // the argument (since we can't transform the selected things). + MpCompat.RegisterLambdaMethod(type, nameof(ITab.FillTab), 1)[0] + .TransformArgument(0, thingAsIdSerializer) + .SetContext(SyncContext.MapSelected) + .CancelIfAnyArgNull(); + + type = powerPlantLevelCompType = AccessTools.TypeByName("AncientMarket_Libraray.CompPowerPlantLevel"); + powerPlantLevelCompLinkedCompGetter = MethodInvoker.GetHandler( + AccessTools.DeclaredPropertyGetter(type, "LinkedComp")); + powerPlantLevelCompTargetOutputLevelField = AccessTools.FieldRefAccess(type, "targetPowerOutput"); + + // Cross-map item transmit tab + type = AccessTools.TypeByName("AncientMarket_Libraray.Tab_LevelTransmit"); + levelTransmitTabReceiverGetter = MethodInvoker.GetHandler( + AccessTools.DeclaredPropertyGetter(type, "receive")); + + // Select a target receiver for a transmitter. + // They are most likely on separate maps, so we need to transform + // the argument (since we can't transform the selected things). + MpCompat.RegisterLambdaMethod(type, nameof(ITab.FillTab), 1)[0] + .TransformArgument(0, thingAsIdSerializer) + .SetContext(SyncContext.MapSelected) + .CancelIfAnyArgNull(); + } + + #endregion } #endregion @@ -254,4 +325,120 @@ private static IEnumerable ReplaceIndexerSetterWithSyncedTimeta } #endregion + + #region Power transfer ITab + + [MpCompatPrefix("AncientMarket_Libraray.Tab_LevelPower", nameof(ITab.FillTab))] + private static void PreLevelPowerITabFillTab(ITab __instance, ref bool __state) + { + if (!MP.IsInMultiplayer) + return; + + var comp = levelPowerTabCompGetter(__instance); + if (comp == null) + return; + + __state = true; + MP.WatchBegin(); + powerPlantLevelCompNameSyncField.Watch(comp); + powerPlantLevelCompOutputModeSyncField.Watch(comp); + powerPlantLevelTargetOutputLevelSyncField.Watch(comp); + // Watch the linked thing/comp (and do the same for the linked thing, + // if there's one). They'll ever only be set to null in here. + powerPlantLevelCompLinkedThingSyncField.Watch(comp); + powerPlantLevelCompLinkedCompSyncField.Watch(comp); + + var linkedComp = powerPlantLevelCompLinkedCompGetter(comp); + if (linkedComp != null) + { + powerPlantLevelCompLinkedThingSyncField.Watch(linkedComp); + powerPlantLevelCompLinkedCompSyncField.Watch(linkedComp); + } + } + + [MpCompatFinalizer("AncientMarket_Libraray.Tab_LevelPower", nameof(ITab.FillTab))] + private static void PostLevelPowerITabFillTab(bool __state) + { + if (__state) + MP.WatchEnd(); + } + + [MpCompatSyncMethod(cancelIfAnyArgNull = true)] + private static void SyncedAcceptPowerChange(CompPowerPlant comp) + => comp.PowerOutput = -powerPlantLevelCompTargetOutputLevelField(comp); + + private static bool ReplacedAcceptPowerChangeButton(Rect rect, string label, bool drawBackground, bool doMouseoverSound, bool active, TextAnchor? overrideTextAnchor) + { + var result = Widgets.ButtonText(rect, label, drawBackground, doMouseoverSound, active, overrideTextAnchor); + if (!MP.IsInMultiplayer || !result) + return result; + + // Shouldn't happen unless mod makes some changes + if (Find.Selector.SingleSelectedThing is not ThingWithComps target) + return false; + + // We could probably just call: + // Find.Selector.SingleSelectedThing.TryGetComp(). + // This should handle situations where there's multiple power plant + // comps, event though it's not really needed here at the moment. + foreach (var comp in target.AllComps) + { + if (comp is CompPowerPlant powerPlantComp && powerPlantLevelCompType.IsInstanceOfType(powerPlantComp)) + { + SyncedAcceptPowerChange(powerPlantComp); + break; + } + } + + return false; + } + + [MpCompatTranspiler("AncientMarket_Libraray.Tab_LevelPower", nameof(ITab.FillTab))] + private static IEnumerable ReplaceApplyButton(IEnumerable instr, MethodBase baseMethod) + { + var target = AccessTools.DeclaredMethod(typeof(Widgets), nameof(Widgets.ButtonText), + [typeof(Rect), typeof(string), typeof(bool), typeof(bool), typeof(bool), typeof(TextAnchor?)]); + var replacement = MpMethodUtil.MethodOf(ReplacedAcceptPowerChangeButton); + + return instr.ReplaceMethod(target, replacement, baseMethod, targetText: "Apply", expectedReplacements: 1); + } + + #endregion + + #region Resource elevator ITab + + [MpCompatPrefix("AncientMarket_Libraray.Tab_LevelTransmit", nameof(ITab.FillTab))] + private static void PreLevelTransmitITabFillTab(ITab __instance, ref bool __state) + { + if (!MP.IsInMultiplayer) + return; + + var selThing = levelTransmitTabReceiverGetter(__instance); + if (selThing == null) + return; + + // If a receiver is selected then watch changes to its name + __state = true; + MP.WatchBegin(); + buildingTransmitNameSyncField.Watch(selThing); + } + + [MpCompatFinalizer("AncientMarket_Libraray.Tab_LevelTransmit", nameof(ITab.FillTab))] + private static void PostLevelTransmitITabFillTab(bool __state) + { + if (__state) + MP.WatchEnd(); + } + + #endregion + + #region Shared + + [MpCompatSyncWorker("AncientMarket_Libraray.Tab_LevelPower", shouldConstruct = true)] + [MpCompatSyncWorker("AncientMarket_Libraray.Tab_LevelTransmit", shouldConstruct = true)] + private static void NoSync(SyncWorker sync, ref ITab tab) + { + } + + #endregion } \ No newline at end of file