Skip to content

Commit

Permalink
Extra attributes for patching patching convenience (#388)
Browse files Browse the repository at this point in the history
Changes/additions:

- Created `MpCompatAttributes` file
- Moved `MpCompatFor` and `MpCompatRequireMod` attributes to the new file to keep all attributes together
- Added abstract `MpCompatPatch` attribute with subclasses `MpCompat(Prefix/Postfix/Transpiler/Finalizer)` for convenience of patching (they are based on equivalent attributes from Multiplayer, with some additions and inclusion of the finalizer)
- Added `MpCompatSync(Method/Field/Worker)` attributes for convenience of registering them
- Added static class `MpCompatPatchLoader` which `LoadPatch` method that will go through all methods/fields in a single type and apply Harmony patches and register Multiplayer sync methods/fields/workers marked by the earlier mentioned attributes
- Modified Simple Sidearms and Vanilla Factions Expanded - Ancients patches to use those new features, presenting an example usage
  • Loading branch information
SokyranTheDragon authored Dec 23, 2023
1 parent e68f642 commit 379d87f
Show file tree
Hide file tree
Showing 5 changed files with 506 additions and 78 deletions.
52 changes: 15 additions & 37 deletions Source/Mods/SimpleSidearms.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;

using HarmonyLib;
using JetBrains.Annotations;
using Multiplayer.API;
using Verse;

Expand All @@ -13,10 +14,14 @@ namespace Multiplayer.Compat
[MpCompatFor("PeteTimesSix.SimpleSidearms")]
public class SimpleSidearmsCompat
{
// TODO: Suggest the author to encapsulate this, would simplify things so much
[MpCompatSyncField("SimpleSidearms.rimworld.CompSidearmMemory", "primaryWeaponMode")]
private static ISyncField primaryWeaponModeSyncField;

Check warning on line 19 in Source/Mods/SimpleSidearms.cs

View workflow job for this annotation

GitHub Actions / Build workshop

Field 'SimpleSidearmsCompat.primaryWeaponModeSyncField' is never assigned to, and will always have its default value null

Check warning on line 19 in Source/Mods/SimpleSidearms.cs

View workflow job for this annotation

GitHub Actions / Build workshop

Field 'SimpleSidearmsCompat.primaryWeaponModeSyncField' is never assigned to, and will always have its default value null

public SimpleSidearmsCompat(ModContentPack mod)
{
MpCompatPatchLoader.LoadPatch(this);

Type type;

// Gizmo interactions
Expand Down Expand Up @@ -55,27 +60,8 @@ public SimpleSidearmsCompat(ModContentPack mod)
foreach (string method in methods) {
MP.RegisterSyncMethod(AccessTools.Method(type, method));
}

// TODO: Suggest the author to encapsulate this, would simplify things so much
primaryWeaponModeSyncField = MP.RegisterSyncField(AccessTools.Field(type, "primaryWeaponMode"));
}

// Required for primaryWeaponMode
{
type = AccessTools.TypeByName("SimpleSidearms.rimworld.Gizmo_SidearmsList");

MpCompat.harmony.Patch(AccessTools.Method(type, "handleInteraction"),
prefix: new HarmonyMethod(typeof(SimpleSidearmsCompat), nameof(HandleInteractionPrefix)),
postfix: new HarmonyMethod(typeof(SimpleSidearmsCompat), nameof(HandleInteractionPostfix)));
}

// Used often in the Set* methods for CompSidearmMemory
{
type = AccessTools.TypeByName("SimpleSidearms.rimworld.ThingDefStuffDefPair");

MP.RegisterSyncWorker<object>(SyncWorkerForThingDefStuffDefPair, type);
}

// Patched sync methods
{
// When undrafted, the pawns will remove their temporary forced weapon.
Expand All @@ -84,28 +70,12 @@ public SimpleSidearmsCompat(ModContentPack mod)
// When dropping a weapon, it'll cause the pawn to about preferences towards them.
PatchingUtilities.PatchCancelInInterface("PeteTimesSix.SimpleSidearms.Intercepts.ITab_Pawn_Gear_InterfaceDrop_Prefix:InterfaceDrop");
}

// Stop verb init in interface
{
type = AccessTools.TypeByName("PeteTimesSix.SimpleSidearms.Utilities.GettersFilters");

var methods = new[]
{
AccessTools.DeclaredMethod(type, "isManualUse"),
AccessTools.DeclaredMethod(type, "isDangerousWeapon"),
AccessTools.DeclaredMethod(type, "isEMPWeapon"),
MpMethodUtil.GetLambda(type, "findBestRangedWeapon", lambdaOrdinal: 8)
};

foreach (var method in methods)
MpCompat.harmony.Patch(method, prefix: new HarmonyMethod(typeof(SimpleSidearmsCompat), nameof(PrePrimaryVerbMethodCall)));

MP.RegisterSyncMethod(typeof(SimpleSidearmsCompat), nameof(SyncInitVerbsForComp));
}
}

#region ThingDefStuffDefPair

// Used often in the Set* methods for CompSidearmMemory
[MpCompatSyncWorker("SimpleSidearms.rimworld.ThingDefStuffDefPair")]
private static void SyncWorkerForThingDefStuffDefPair(SyncWorker sync, ref object obj)
{
var traverse = Traverse.Create(obj);
Expand All @@ -126,6 +96,8 @@ private static void SyncWorkerForThingDefStuffDefPair(SyncWorker sync, ref objec

#region primaryWeaponMode field watch

// Required for primaryWeaponMode
[MpCompatPrefix("SimpleSidearms.rimworld.Gizmo_SidearmsList", "handleInteraction")]
private static void HandleInteractionPrefix(ThingComp ___pawnMemory)
{
if (MP.IsInMultiplayer) {
Expand All @@ -134,6 +106,7 @@ private static void HandleInteractionPrefix(ThingComp ___pawnMemory)
}
}

[MpCompatPostfix("SimpleSidearms.rimworld.Gizmo_SidearmsList", "handleInteraction")]
private static void HandleInteractionPostfix()
{
if (MP.IsInMultiplayer)
Expand All @@ -144,6 +117,10 @@ private static void HandleInteractionPostfix()

#region Stop verb init in interface

[MpCompatPrefix("PeteTimesSix.SimpleSidearms.Utilities.GettersFilters", "isManualUse")]
[MpCompatPrefix("PeteTimesSix.SimpleSidearms.Utilities.GettersFilters", "isDangerousWeapon")]
[MpCompatPrefix("PeteTimesSix.SimpleSidearms.Utilities.GettersFilters", "isEMPWeapon")]
[MpCompatPrefix("PeteTimesSix.SimpleSidearms.Utilities.GettersFilters", "findBestRangedWeapon", 8)]
private static bool PrePrimaryVerbMethodCall(ThingWithComps __0, ref bool __result)
{
if (!PatchingUtilities.ShouldCancel)
Expand All @@ -166,6 +143,7 @@ private static bool PrePrimaryVerbMethodCall(ThingWithComps __0, ref bool __resu
return false;
}

[MpCompatSyncMethod]
private static void SyncInitVerbsForComp(CompEquippable comp)
{
if (comp.verbTracker.verbs == null)
Expand Down
12 changes: 6 additions & 6 deletions Source/Mods/VanillaFactionsAncients.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
Expand Down Expand Up @@ -32,13 +31,14 @@ public VanillaFactionsAncients(ModContentPack mod)
// (Method inside of LatePatch)
var type = AccessTools.TypeByName("VFEAncients.Operation");
operationPodField = AccessTools.FieldRefAccess<ThingComp>(type, "Pod");
MP.RegisterSyncWorker<object>(SyncOperation, type, true);

LongEventHandler.ExecuteWhenFinished(LatePatch);
}

public static void LatePatch()
{
MpCompatPatchLoader.LoadPatch<VanillaFactionsAncients>();

// Ancient PD turret - toggle aiming at drop pods, enemies, explosive projectiles
MpCompat.RegisterLambdaMethod("VFEAncients.Building_TurretPD", "GetGizmos", 1, 3, 5);

Expand All @@ -58,15 +58,12 @@ public static void LatePatch()
var powerDefType = AccessTools.TypeByName("VFEAncients.PowerDef");
var tupleType = typeof(Tuple<,>).MakeGenericType(powerDefType, powerDefType);
onChosen = CompileCallOnChosen(powerDefType, tupleType);
MP.RegisterSyncMethod(typeof(VanillaFactionsAncients), nameof(SyncedChoosePower));
MP.RegisterSyncWorker<Window>(SyncDialogChoosePower, choosePowerDialogType);
DialogUtilities.RegisterPauseLock(choosePowerDialogType);
MpCompat.harmony.Patch(AccessTools.Method(choosePowerDialogType, nameof(Window.DoWindowContents)),
transpiler: new HarmonyMethod(typeof(VanillaFactionsAncients), nameof(ReplaceButtons)));
}

#region SyncWorkers

[MpCompatSyncWorker("VFEAncients.Operation", isImplicit = true)]
private static void SyncOperation(SyncWorker sync, ref object operation)
{
if (sync.isWriting)
Expand All @@ -85,6 +82,7 @@ private static void SyncOperation(SyncWorker sync, ref object operation)
}
}

[MpCompatSyncWorker("VFEAncients.Dialog_ChoosePowers")]
private static void SyncDialogChoosePower(SyncWorker sync, ref Window window)
{
if (!sync.isWriting)
Expand All @@ -106,6 +104,7 @@ private static bool ChoosePower(Rect rect, string text, bool drawBackground, boo
return false;
}

[MpCompatSyncMethod]
private static void SyncedChoosePower(Def power, Def weakness)
{
var dialog = Find.WindowStack.windows.FirstOrDefault(x => x.GetType() == choosePowerDialogType);
Expand All @@ -115,6 +114,7 @@ private static void SyncedChoosePower(Def power, Def weakness)
dialog.Close();
}

[MpCompatTranspiler("VFEAncients.Dialog_ChoosePowers", nameof(Window.DoWindowContents))]
private static IEnumerable<CodeInstruction> ReplaceButtons(IEnumerable<CodeInstruction> instr)
{
var target = AccessTools.Method(typeof(Widgets), nameof(Widgets.ButtonText), new[] { typeof(Rect), typeof(string), typeof(bool), typeof(bool), typeof(bool), typeof(TextAnchor?) });
Expand Down
Loading

0 comments on commit 379d87f

Please sign in to comment.