Skip to content

Commit

Permalink
Fix MVCF verb manager ticking with async time (#415)
Browse files Browse the repository at this point in the history
Should fix the issue with mini-turret packs (and similar apparel) shooting on maps that are paused, along with a couple of other issues.

The issue happened due to the verb managers ticking inside of world component. The change here was to:
- In existing WorldComp ticking, only tick verb managers that have no pawn or a pawn with no map
- Added map ticking where the comp's pawn map is checked and and ticked if the map was ticked
- However, this is inactive if async time is off and the mod should tick as it normally would in such cases
  • Loading branch information
SokyranTheDragon authored Jan 10, 2024
1 parent f1a9875 commit 8846a4a
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 9 deletions.
104 changes: 95 additions & 9 deletions Source/Mods/VanillaExpandedFramework.cs
Original file line number Diff line number Diff line change
Expand Up @@ -672,11 +672,11 @@ private static void SyncCompGlower(SyncWorker sync, ref CompGlower glower)
private static IEnumerable<string> mvcfEnabledFeaturesSet;

// VerbManager
private static FastInvokeHandler mvcfPawnGetter;
private static AccessTools.FieldRef<object, IList> mvcfVerbsField;
private static FastInvokeHandler mvcfVerbManagerPawnGetter;
private static FastInvokeHandler mvcfVerbManagerTickMethod;
private static AccessTools.FieldRef<object, IList> mvcfVerbManagerVerbsField;

// PawnVerbUtility
private static AccessTools.FieldRef<object, object> mvcfPawnVerbUtilityField;
private delegate object GetManager(Pawn p, bool createIfMissing);
private static GetManager mvcfPawnVerbUtilityGetManager;

Expand All @@ -689,8 +689,17 @@ private static void SyncCompGlower(SyncWorker sync, ref CompGlower glower)
// VerbComp
private static AccessTools.FieldRef<object, object> mvcfVerbCompParentField;

// WorldComponent_MVCF
private static AccessTools.FieldRef<WorldComponent> mvcfWorldCompInstanceField;
private static AccessTools.FieldRef<WorldComponent, IList> mvcfWorldCompTickManagersField;

// WeakReference<VerbManager>
private static FastInvokeHandler mvcfWeakReferenceTryGetVerbManagerMethod;

private static void PatchMVCF()
{
PatchingUtilities.SetupAsyncTime();

MpCompat.harmony.Patch(AccessTools.Method(typeof(Pawn), nameof(Pawn.SpawnSetup)),
postfix: new HarmonyMethod(typeof(VanillaExpandedFramework), nameof(EverybodyGetsVerbManager)));

Expand All @@ -702,12 +711,15 @@ private static void PatchMVCF()

type = AccessTools.TypeByName("MVCF.Utilities.PawnVerbUtility");
mvcfPawnVerbUtilityGetManager = AccessTools.MethodDelegate<GetManager>(AccessTools.Method(type, "Manager"));
mvcfPawnVerbUtilityField = AccessTools.FieldRefAccess<object>(type, "managers");

type = AccessTools.TypeByName("MVCF.VerbManager");
MP.RegisterSyncWorker<object>(SyncVerbManager, type, isImplicit: true);
mvcfPawnGetter = MethodInvoker.GetHandler(AccessTools.PropertyGetter(type, "Pawn"));
mvcfVerbsField = AccessTools.FieldRefAccess<IList>(type, "verbs");
mvcfVerbManagerPawnGetter = MethodInvoker.GetHandler(AccessTools.PropertyGetter(type, "Pawn"));
mvcfVerbManagerTickMethod = MethodInvoker.GetHandler(AccessTools.DeclaredMethod(type, "Tick"));
mvcfVerbManagerVerbsField = AccessTools.FieldRefAccess<IList>(type, "verbs");

type = typeof(System.WeakReference<>).MakeGenericType(type);
mvcfWeakReferenceTryGetVerbManagerMethod = MethodInvoker.GetHandler(AccessTools.DeclaredMethod(type, "TryGetTarget"));

type = AccessTools.TypeByName("MVCF.ManagedVerb");
mvcfManagedVerbManagerGetter = MethodInvoker.GetHandler(AccessTools.PropertyGetter(type, "Manager"));
Expand Down Expand Up @@ -738,6 +750,16 @@ private static void PatchMVCF()

// Changes the verb, so when called before syncing (especially if the original method is canceled by another mod) - will cause issues.
PatchingUtilities.PatchCancelInInterfaceSetResultToTrue("MVCF.PatchSets.PatchSet_MultiVerb:Prefix_OrderForceTarget");

// Verb ticking
type = AccessTools.TypeByName("MVCF.WorldComponent_MVCF");
mvcfWorldCompInstanceField = AccessTools.StaticFieldRefAccess<WorldComponent>(AccessTools.DeclaredField(type, "Instance"));
mvcfWorldCompTickManagersField = AccessTools.FieldRefAccess<IList>(type, "TickManagers");

MpCompat.harmony.Patch(AccessTools.DeclaredMethod(type, nameof(WorldComponent.WorldComponentTick)),
transpiler: new HarmonyMethod(typeof(VanillaExpandedFramework), nameof(ReplaceTickWithConditionalTick)));
MpCompat.harmony.Patch(AccessTools.DeclaredMethod(typeof(MapComponentUtility), nameof(MapComponentUtility.MapComponentTick)),
postfix: new HarmonyMethod(typeof(VanillaExpandedFramework), nameof(TickVerbManagersForMap)));
}

// Initialize the VerbManager early, we expect it to exist on every player.
Expand Down Expand Up @@ -768,7 +790,7 @@ private static void SyncVerbManager(SyncWorker sync, ref object obj)
{
if (sync.isWriting)
// Sync the pawn that has the VerbManager
sync.Write((Pawn)mvcfPawnGetter(obj, Array.Empty<object>()));
sync.Write((Pawn)mvcfVerbManagerPawnGetter(obj, Array.Empty<object>()));
else
{
var pawn = sync.Read<Pawn>();
Expand All @@ -787,7 +809,7 @@ private static void SyncManagedVerb(SyncWorker sync, ref object obj)
// Get the VerbManager from inside of the ManagedVerb itself
var verbManager = mvcfManagedVerbManagerGetter(obj);
// Find the ManagedVerb inside of list of all verbs
var managedVerbsList = mvcfVerbsField(verbManager);
var managedVerbsList = mvcfVerbManagerVerbsField(verbManager);
var index = managedVerbsList.IndexOf(obj);

// Sync the index of the verb as well as the manager (if it's valid)
Expand All @@ -807,7 +829,7 @@ private static void SyncManagedVerb(SyncWorker sync, ref object obj)
SyncVerbManager(sync, ref verbManager);

// Find the ManagedVerb with specific index inside of list of all verbs
var managedVerbsList = mvcfVerbsField(verbManager);
var managedVerbsList = mvcfVerbManagerVerbsField(verbManager);
obj = managedVerbsList[index];
}
}
Expand Down Expand Up @@ -837,6 +859,70 @@ private static void SyncVerbComp(SyncWorker sync, ref object verbComp)
}
}

private static void TickVerbManagersForMap(Map map)
{
// Map-specific ticking is only enabled in MP with async on.
if (!MP.IsInMultiplayer || !PatchingUtilities.IsAsyncTime)
return;

var managers = mvcfWorldCompTickManagersField(mvcfWorldCompInstanceField());
// Null or empty check
if (managers is not { Count: > 0 })
return;

// out parameter
var args = new object[1];
foreach (var weakRef in managers)
{
if ((bool)mvcfWeakReferenceTryGetVerbManagerMethod(weakRef, args))
{
var manager = args[0];
if (mvcfVerbManagerPawnGetter(manager) is Pawn pawn && pawn.MapHeld == map)
mvcfVerbManagerTickMethod(manager);
}
}
}

private static void TickOnlyNonMapManagers(object manager)
{
// Normal ticking (tied to world). Only do if not in MP, async is off,
// the pawn is null (shouldn't happen?) or the pawn has no map.
if (!MP.IsInMultiplayer ||
!PatchingUtilities.IsAsyncTime ||
mvcfVerbManagerPawnGetter(manager) is not Pawn pawn ||
pawn.MapHeld == null)
{
mvcfVerbManagerTickMethod(manager);
}
}

private static IEnumerable<CodeInstruction> ReplaceTickWithConditionalTick(IEnumerable<CodeInstruction> instr, MethodBase baseMethod)
{
var target = AccessTools.DeclaredMethod("MVCF.VerbManager:Tick", Type.EmptyTypes);
var replacement = AccessTools.DeclaredMethod(typeof(VanillaExpandedFramework), nameof(TickOnlyNonMapManagers));
var replacedCount = 0;

foreach (var ci in instr)
{
if (ci.Calls(target))
{
ci.opcode = OpCodes.Call;
ci.operand = replacement;

replacedCount++;
}

yield return ci;
}

const int expected = 1;
if (replacedCount != expected)
{
var name = (baseMethod.DeclaringType?.Namespace).NullOrEmpty() ? baseMethod.Name : $"{baseMethod.DeclaringType!.Name}:{baseMethod.Name}";
Log.Warning($"Patched incorrect number of VerbManager.Tick calls (patched {replacedCount}, expected {expected}) for method {name}");
}
}

#endregion

#region Pipe System
Expand Down
37 changes: 37 additions & 0 deletions Source/PatchingUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -693,5 +693,42 @@ private static void PostTryGainMemory(Thought_Memory newThought)
}

#endregion

#region Async Time

private static bool isAsyncTimeSetup = false;
private static bool isAsyncTimeSuccessful = false;
private static AccessTools.FieldRef<object> multiplayerGameField;
private static AccessTools.FieldRef<object, object> gameGameCompField;
private static AccessTools.FieldRef<object, bool> gameCompAsyncTimeField;

public static bool IsAsyncTime
=> isAsyncTimeSuccessful &&
gameCompAsyncTimeField(gameGameCompField(multiplayerGameField()));

public static void SetupAsyncTime()
{
if (isAsyncTimeSetup)
return;
isAsyncTimeSetup = true;

try
{
multiplayerGameField = AccessTools.StaticFieldRefAccess<object>(
AccessTools.DeclaredField("Multiplayer.Client.Multiplayer:game"));
gameGameCompField = AccessTools.FieldRefAccess<object>(
"Multiplayer.Client.MultiplayerGame:gameComp");
gameCompAsyncTimeField = AccessTools.FieldRefAccess<bool>(
"Multiplayer.Client.Comp.MultiplayerGameComp:asyncTime");

isAsyncTimeSuccessful = true;
}
catch (Exception e)
{
Log.Error($"Encountered an exception while settings up async time:\n{e}");
}
}

#endregion
}
}

0 comments on commit 8846a4a

Please sign in to comment.