Skip to content

Commit

Permalink
Fix and update Vanilla Expanded mods for 1.5 (#445)
Browse files Browse the repository at this point in the history
This should fix the issues I've found so far, along with a handful of changes/improvements.

Vanilla Expanded Framework:
- Slightly modified the modular patching
  - Changed where the patch method is called, so it'll include the try/catch block in late patches as well
  - Added a message log behind (for debug builds only) that will log what is being patched before applying the patch
- Vanilla Furniture Expanded patch was moved to late patch to prevent issues due to the mod accessing DefOfs in the patched methods

Vanilla Events Expanded:
- Stopped pushing/popping the RNG state where it's not needed
  - I've originally made those patches when I didn't fully understand how MP and RimWorld work, so I assumed that those would be needed

Vanilla Factions Expanded - Empire:
- Added a null map check to the quest generation
  - This is an issue with the mod itself which will happen if `TestRun` method is called for a faction without maps (Vanilla-Expanded/VanillaFactionsExpanded-Empire#8)
  - After deeper investigation I've realized it always happens in MP due to MP's `FactionRepeater` trying to repeat a quest generation for all factions, including spectator faction (which doesn't have any maps)

Vanilla Furniture Expanded - Security:
- Stopped pushing/popping the RNG state where it's not needed
  - Same reason as for Vanilla Events Expanded
- Changed patch target from `Draw` to `DrawAt`

Vanilla Psycasts Expanded:
- Changed the patching to work similar as Vanilla Expanded Framework - made it modular so if a single module fails then it won't break everything after it
- Removed an empty method (PatchCurrentMapUsage)
- Renamed PatchGizmosAndFlecks to PatchMotesAndFlecks
- Slightly reorganized where some of the patches are
- Completely reworked psyset renaming code (won't do anything meaningful until mering rwmt/Multiplayer#443)
  - Added `Dialog_RenamePsysetMp` class, which will handle renaming and allow MP to sync it
  - Added `PsysetRenameHolder` class, which hold the psyset and psycasting hediff and will be used for syncing psysets
    - We cannot sync psysets by themselves as they don't store any reference to their parent, and we need a workaround by using the hediff to sync them
  - Changed psycasting ITab code to create our rename dialog instead of the VPE one, as well as making the code pass the hediff to the constructor
  • Loading branch information
SokyranTheDragon authored May 24, 2024
1 parent 02a3b98 commit becce73
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 102 deletions.
7 changes: 2 additions & 5 deletions Source/Mods/VanillaEventsExpanded.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,13 @@ public VEE(ModContentPack mod)
"VEE.RegularEvents.CaravanAnimalWI:GenerateGroup",
"VEE.RegularEvents.MeteoriteShower:TryExecuteWorker",
"VEE.RegularEvents.WeaponPod:TryExecuteWorker",
"VEE.RegularEvents.EarthQuake:DamageInRadius",
};

PatchingUtilities.PatchSystemRand(methodsForAll, false);
// This method only calls other methods that use RNG calls
PatchingUtilities.PatchPushPopRand("VEE.RegularEvents.EarthQuake:TryExecuteWorker");
// Only patch System.Random out, as this methods is only called by other ones
PatchingUtilities.PatchSystemRand("VEE.RegularEvents.EarthQuake:DamageInRadius", false);

// Unity RNG
PatchingUtilities.PatchUnityRand("VEE.Shuttle:Tick");
PatchingUtilities.PatchUnityRand("VEE.Shuttle:Tick", false);

// Current map usage, picks between rain and snow based on current map temperature, instead of using map it affects
PatchingUtilities.ReplaceCurrentMapUsage("VEE.PurpleEvents.PsychicRain:ForcedWeather");
Expand Down
33 changes: 21 additions & 12 deletions Source/Mods/VanillaExpandedFramework.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ class VanillaExpandedFramework
public VanillaExpandedFramework(ModContentPack mod)
{
(Action patchMethod, string componentName, bool latePatch)[] patches =
{
[
(PatchItemProcessor, "Item Processor", false),
(PatchOtherRng, "Other RNG", false),
(PatchVFECoreDebug, "Debug Gizmos", false),
(PatchAbilities, "Abilities", true),
(PatchHireableFactions, "Hireable Factions", false),
(PatchVanillaFurnitureExpanded, "Vanilla Furniture Expanded", false),
(PatchVanillaFurnitureExpanded, "Vanilla Furniture Expanded", true),
(PatchVanillaFactionMechanoids, "Vanilla Faction Mechanoids", false),
(PatchAnimalBehaviour, "Animal Behaviour", false),
(PatchExplosiveTrialsEffect, "Explosive Trials Effect", false),
Expand All @@ -47,21 +47,30 @@ public VanillaExpandedFramework(ModContentPack mod)
(PatchExtraPregnancyApproaches, "Extra Pregnancy Approaches", false),
(PatchWorkGiverDeliverResources, "Building stuff requiring non-construction skill", false),
(PatchExpandableProjectile, "Expandable projectile", false),
};
];

foreach (var (patchMethod, componentName, latePatch) in patches)
{
try
if (latePatch)
LongEventHandler.ExecuteWhenFinished(ApplyPatch);
else
ApplyPatch();

void ApplyPatch()
{
if (latePatch)
LongEventHandler.ExecuteWhenFinished(patchMethod);
else
try
{
#if DEBUG
Log.Message($"Patching Vanilla Expanded Framework - {componentName}");
#endif

patchMethod();
}
catch (Exception e)
{
Log.Error($"Encountered an error patching {componentName} part of Vanilla Expanded Framework - this part of the mod may not work properly!");
Log.Error(e.ToString());
}
catch (Exception e)
{
Log.Error($"Encountered an error patching {componentName} part of Vanilla Expanded Framework - this part of the mod may not work properly!");
Log.Error(e.ToString());
}
}
}
}
Expand Down
9 changes: 1 addition & 8 deletions Source/Mods/VanillaFurnitureExpandedSecurity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,7 @@ private static void LateSyncMethods()

// RNG fix
{
var methods = new[]
{
"VFESecurity.Building_Shield:Notify_EnergyDepleted",
"VFESecurity.Building_Shield:Draw",
};

PatchingUtilities.PatchPushPopRand(AccessTools.Method("VFESecurity.Building_Shield:AbsorbDamage", new[] { typeof(float), typeof(DamageDef), typeof(float) }));
PatchingUtilities.PatchPushPopRand(methods);
PatchingUtilities.PatchPushPopRand("VFESecurity.Building_Shield:DrawAt");
}
}

Expand Down
180 changes: 103 additions & 77 deletions Source/Mods/VanillaPsycastsExpanded.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,42 @@ public class VanillaPsycastsExpanded

public VanillaPsycastsExpanded(ModContentPack mod)
{
InitializeCommonData();
PatchPsysets();
PatchPsyringDialog();
RegisterSyncMethods();

LongEventHandler.ExecuteWhenFinished(LatePatch);
}
(Action patchMethod, string componentName, bool latePatch)[] patches =
[
(InitializeCommonData, "Shared patch data", false),
(PatchPsysets, "Psysets", false),
(PatchPsyringDialog, "Psyring", false),
(RegisterSyncGizmos, "General gizmos", true),
(PatchPsychicStatusGizmo, "Psychic status gizmo", true),
(PatchPsycasterHediff, "Skill point usage", true),
(PatchMotesAndFlecks, "Motes and flecks", true),
(PatchITab, "Psychic abilities tab transpiler (psysets)", true),
];

foreach (var (patchMethod, componentName, latePatch) in patches)
{
if (latePatch)
LongEventHandler.ExecuteWhenFinished(ApplyPatch);
else
ApplyPatch();

private static void LatePatch()
{
PatchPsychicStatusGizmo();
PatchPsycasterHediff();
PatchGizmosAndFlecks();
PatchITab();
PatchSkipdoor();
void ApplyPatch()
{
try
{
#if DEBUG
Log.Message($"Patching Vanilla Psycasts Expanded - {componentName}");
#endif

patchMethod();
}
catch (Exception e)
{
Log.Error($"Encountered an error patching {componentName} part of Vanilla Expanded Framework - this part of the mod may not work properly!");
Log.Error(e.ToString());
}
}
}
}

#endregion
Expand All @@ -45,7 +66,6 @@ private static void LatePatch()
private static ISyncField syncPsychicEntropyLimit;
private static ISyncField syncPsychicEntropyTargetFocus;
private static AccessTools.FieldRef<object, Pawn_PsychicEntropyTracker> psychicEntropyGetter;
private static Hediff currentHediff;

// Hediff_PsycastAbilities
private static FastInvokeHandler removePsysetMethod;
Expand Down Expand Up @@ -110,11 +130,15 @@ private static void PostPsyfocusTarget()
// HashSet<AbilityDef>
private static Type abilityDefHashSetType;
private static FastInvokeHandler abilityDefHashSetAddMethod;


private static void PatchPsysets()
{
// Init
MP.RegisterSyncMethod(typeof(VanillaPsycastsExpanded), nameof(SyncPsyset));
MP.RegisterSyncMethod(typeof(VanillaPsycastsExpanded), nameof(SyncRemovePsyset));
MP.RegisterSyncMethod(typeof(VanillaPsycastsExpanded), nameof(SyncEnsurePsysetExists));
MP.RegisterSyncWorker<PsysetRenameHolder>(SyncPsysetRenameHolder);

var abilityDefType = AccessTools.TypeByName("VFECore.Abilities.AbilityDef");
abilityDefHashSetType = typeof(HashSet<>).MakeGenericType(abilityDefType);
abilityDefHashSetAddMethod = MethodInvoker.GetHandler(AccessTools.Method(abilityDefHashSetType, "Add"));
Expand Down Expand Up @@ -143,10 +167,6 @@ private static void PatchPsysets()
MpCompat.harmony.Patch(method,
prefix: new HarmonyMethod(typeof(VanillaPsycastsExpanded), nameof(PrePsysetInnerClassMethod)),
postfix: new HarmonyMethod(typeof(VanillaPsycastsExpanded), nameof(PostPsysetInnerClassMethod)));

// Set name dialog
MpCompat.harmony.Patch(AccessTools.Method("VanillaPsycastsExpanded.UI.Dialog_RenamePsyset:SetName"),
prefix: new HarmonyMethod(typeof(VanillaPsycastsExpanded), nameof(PreSetPsysetName)));
}

private static void ReplacedRemovePsyset(Hediff hediff, object psyset)
Expand Down Expand Up @@ -257,69 +277,87 @@ private static void SyncPsyset(Hediff hediff, int psysetIndex, Def[] defs)
psysetAbilitiesField(psyset) = (IEnumerable)set;
}

private static bool PreSetPsysetName(string name, object ___psyset)
{
if (!MP.IsInMultiplayer || currentHediff == null)
return true;

// We use currentHediff to sync the psyset being renamed
// No way to access hediff/pawn/anything useful from this dialog or psyset itself
var hediff = currentHediff;
currentHediff = null;
var index = hediffPsysetsList(hediff).IndexOf(___psyset);
if (index < 0)
return true;

SyncRenamePsyset(hediff, index, name);

return false;
}

private static void SyncRenamePsyset(Hediff hediff, int index, string name)
{
var psyset = hediffPsysetsList(hediff)[index];
psysetNameField(psyset) = name;
}

private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instr)
private static IEnumerable<CodeInstruction> PatchPsycastsITab(IEnumerable<CodeInstruction> instr)
{
var codeInstructions = instr as CodeInstruction[] ?? instr.ToArray();

var target = AccessTools.Method("VanillaPsycastsExpanded.Hediff_PsycastAbilities:RemovePsySet");
var replacement = AccessTools.Method(typeof(VanillaPsycastsExpanded), nameof(ReplacedRemovePsyset));

var dialogRenameType = AccessTools.TypeByName("VanillaPsycastsExpanded.UI.Dialog_RenamePsyset");
var replacedDialogRenameType = AccessTools.DeclaredConstructor(typeof(Dialog_RenamePsysetMp),
[typeof(IRenameable), typeof(Hediff)]);

var originalHediffField = AccessTools.Field("VanillaPsycastsExpanded.UI.ITab_Pawn_Psycasts:hediff");
var ourHediffField = AccessTools.Field(typeof(VanillaPsycastsExpanded), nameof(currentHediff));

for (var i = 0; i < codeInstructions.Length; i++)
foreach (var ci in instr)
{
var ci = codeInstructions[i];

if (ci.opcode == OpCodes.Callvirt && ci.operand is MethodInfo method && method == target)
{
ci.opcode = OpCodes.Call;
ci.operand = replacement;
}
else if (ci.opcode == OpCodes.Newobj && ci.operand is ConstructorInfo ctor && ctor.DeclaringType == dialogRenameType)
{
// Skip the current and next instruction
yield return ci;
yield return codeInstructions[++i];

// Get the hediff field and set our static one to it.
// We use it for syncing, as we need it for syncing PsySet,
// and it has no way to reference it directly.
// Load the current hediff onto the stack
yield return new CodeInstruction(OpCodes.Ldarg_0);
yield return new CodeInstruction(OpCodes.Ldfld, originalHediffField);
ci = new CodeInstruction(OpCodes.Stsfld, ourHediffField);

// Replace the VE rename dialog with our own
ci.operand = replacedDialogRenameType;
}

yield return ci;
}
}

private static void SyncPsysetRenameHolder(SyncWorker sync, ref PsysetRenameHolder holder)
{
if (sync.isWriting)
{
sync.Write(holder.hediff);
if (holder.hediff != null)
sync.Write(hediffPsysetsList(holder.hediff).IndexOf(holder.psyset));
}
else
{
var hediff = sync.Read<Hediff>();
IRenameable psyset = null;

if (hediff != null)
{
var index = sync.Read<int>();
var list = hediffPsysetsList(hediff);
if (index >= 0 && index < list.Count)
psyset = list[index] as IRenameable;
}

holder = new PsysetRenameHolder(psyset, hediff);
}
}

// Our syncable holder for the psyset. We need the hediff itself to sync the psyset,
// so we need a custom solution for syncing.
private class PsysetRenameHolder(IRenameable psyset, Hediff hediff) : IRenameable
{
public readonly IRenameable psyset = psyset;
public readonly Hediff hediff = hediff;

// The setter should be automatically synced by MP, since the type is syncable (#443)
public string RenamableLabel
{
get => psyset?.RenamableLabel ?? string.Empty;
set
{
if (psyset != null) psyset.RenamableLabel = value;
}
}

public string BaseLabel => psyset?.BaseLabel ?? string.Empty;
public string InspectLabel => psyset?.InspectLabel ?? string.Empty;
}

// Rename dialog for PsysetRenameHolder
private class Dialog_RenamePsysetMp(IRenameable psyset, Hediff hediff) : Dialog_Rename<PsysetRenameHolder>(new PsysetRenameHolder(psyset, hediff));

#endregion

#region Psyring
Expand Down Expand Up @@ -386,19 +424,17 @@ private static void PatchPsycasterHediff()

#region Other

private static void RegisterSyncMethods()
private static void RegisterSyncGizmos()
{
MP.RegisterSyncMethod(typeof(VanillaPsycastsExpanded), nameof(SyncPsyset));
MP.RegisterSyncMethod(typeof(VanillaPsycastsExpanded), nameof(SyncRemovePsyset));
MP.RegisterSyncMethod(typeof(VanillaPsycastsExpanded), nameof(SyncEnsurePsysetExists));
MP.RegisterSyncMethod(typeof(VanillaPsycastsExpanded), nameof(SyncRenamePsyset));

// Gizmos
MpCompat.RegisterLambdaMethod("VanillaPsycastsExpanded.CompBreakLink", "GetGizmos", 0);
MpCompat.RegisterLambdaDelegate("VanillaPsycastsExpanded.Ability_GuardianSkipBarrier", "GetGizmo", 0);

// Destroy
MpCompat.RegisterLambdaMethod("VanillaPsycastsExpanded.Skipmaster.Skipdoor", "GetDoorTeleporterGismoz", 0).SetContext(SyncContext.None);
}

private static void PatchGizmosAndFlecks()
private static void PatchMotesAndFlecks()
{
// Uses RNG after GenView.ShouldSpawnMotesAt, gonna cause desyncs
PatchingUtilities.PatchPushPopRand(new[]
Expand All @@ -408,20 +444,10 @@ private static void PatchGizmosAndFlecks()
});
}

private static void PatchCurrentMapUsage()
{
}

private static void PatchITab()
{
MpCompat.harmony.Patch(AccessTools.Method("VanillaPsycastsExpanded.UI.ITab_Pawn_Psycasts:DoPsysets"),
transpiler: new HarmonyMethod(typeof(VanillaPsycastsExpanded), nameof(Transpiler)));
}

private static void PatchSkipdoor()
{
// Destroy
MpCompat.RegisterLambdaMethod("VanillaPsycastsExpanded.Skipmaster.Skipdoor", "GetDoorTeleporterGismoz", 0).SetContext(SyncContext.None);
transpiler: new HarmonyMethod(typeof(VanillaPsycastsExpanded), nameof(PatchPsycastsITab)));
}

#endregion
Expand Down
Loading

0 comments on commit becce73

Please sign in to comment.