Skip to content

Commit

Permalink
Updated Ancient Urban Ruins compat (#487)
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
SokyranTheDragon authored Oct 22, 2024
1 parent 24fb4c8 commit c7a46f2
Showing 1 changed file with 188 additions and 1 deletion.
189 changes: 188 additions & 1 deletion Source/Mods/AncientUrbanRuins.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Multiplayer.API;
using RimWorld;
using RimWorld.Planet;
using UnityEngine;
using Verse;

namespace Multiplayer.Compat;
Expand All @@ -28,13 +29,40 @@ public class AncientUrbanRuins
// MapParent_Custom
private static AccessTools.FieldRef<PocketMapParent, MapPortal> customMapEntranceField;

// Tab_LevelPower
private static FastInvokeHandler levelPowerTabCompGetter;
// CompPowerPlantLevel
private static Type powerPlantLevelCompType;
private static FastInvokeHandler powerPlantLevelCompLinkedCompGetter;
private static AccessTools.FieldRef<CompPowerPlant, float> 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<PocketMapParent>();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<float>(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
Expand Down Expand Up @@ -254,4 +325,120 @@ private static IEnumerable<CodeInstruction> 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<CompPowerPlant>().
// 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<CodeInstruction> ReplaceApplyButton(IEnumerable<CodeInstruction> 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
}

0 comments on commit c7a46f2

Please sign in to comment.