From becce73486149ffae1159dcc2641f3159655aca7 Mon Sep 17 00:00:00 2001 From: SokyranTheDragon <36712560+SokyranTheDragon@users.noreply.github.com> Date: Fri, 24 May 2024 04:46:12 +0200 Subject: [PATCH] Fix and update Vanilla Expanded mods for 1.5 (#445) 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 (https://github.com/Vanilla-Expanded/VanillaFactionsExpanded-Empire/pull/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 https://github.com/rwmt/Multiplayer/pull/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 --- Source/Mods/VanillaEventsExpanded.cs | 7 +- Source/Mods/VanillaExpandedFramework.cs | 33 ++-- .../Mods/VanillaFurnitureExpandedSecurity.cs | 9 +- Source/Mods/VanillaPsycastsExpanded.cs | 180 ++++++++++-------- Source_Referenced/VanillaFactionsEmpire.cs | 37 ++++ 5 files changed, 164 insertions(+), 102 deletions(-) diff --git a/Source/Mods/VanillaEventsExpanded.cs b/Source/Mods/VanillaEventsExpanded.cs index 192a6c35..dc585530 100644 --- a/Source/Mods/VanillaEventsExpanded.cs +++ b/Source/Mods/VanillaEventsExpanded.cs @@ -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"); diff --git a/Source/Mods/VanillaExpandedFramework.cs b/Source/Mods/VanillaExpandedFramework.cs index 62b25071..a5edd9da 100644 --- a/Source/Mods/VanillaExpandedFramework.cs +++ b/Source/Mods/VanillaExpandedFramework.cs @@ -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), @@ -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()); + } } } } diff --git a/Source/Mods/VanillaFurnitureExpandedSecurity.cs b/Source/Mods/VanillaFurnitureExpandedSecurity.cs index 9da85b6f..a5d13a42 100644 --- a/Source/Mods/VanillaFurnitureExpandedSecurity.cs +++ b/Source/Mods/VanillaFurnitureExpandedSecurity.cs @@ -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"); } } diff --git a/Source/Mods/VanillaPsycastsExpanded.cs b/Source/Mods/VanillaPsycastsExpanded.cs index 1c766764..9a10e921 100644 --- a/Source/Mods/VanillaPsycastsExpanded.cs +++ b/Source/Mods/VanillaPsycastsExpanded.cs @@ -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 @@ -45,7 +66,6 @@ private static void LatePatch() private static ISyncField syncPsychicEntropyLimit; private static ISyncField syncPsychicEntropyTargetFocus; private static AccessTools.FieldRef psychicEntropyGetter; - private static Hediff currentHediff; // Hediff_PsycastAbilities private static FastInvokeHandler removePsysetMethod; @@ -110,11 +130,15 @@ private static void PostPsyfocusTarget() // HashSet 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(SyncPsysetRenameHolder); + var abilityDefType = AccessTools.TypeByName("VFECore.Abilities.AbilityDef"); abilityDefHashSetType = typeof(HashSet<>).MakeGenericType(abilityDefType); abilityDefHashSetAddMethod = MethodInvoker.GetHandler(AccessTools.Method(abilityDefHashSetType, "Add")); @@ -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) @@ -257,46 +277,19 @@ 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 Transpiler(IEnumerable instr) + private static IEnumerable PatchPsycastsITab(IEnumerable 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; @@ -304,22 +297,67 @@ private static IEnumerable Transpiler(IEnumerable(); + IRenameable psyset = null; + + if (hediff != null) + { + var index = sync.Read(); + 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(new PsysetRenameHolder(psyset, hediff)); + #endregion #region Psyring @@ -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[] @@ -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 diff --git a/Source_Referenced/VanillaFactionsEmpire.cs b/Source_Referenced/VanillaFactionsEmpire.cs index fd6ecd66..5d1aab14 100644 --- a/Source_Referenced/VanillaFactionsEmpire.cs +++ b/Source_Referenced/VanillaFactionsEmpire.cs @@ -4,6 +4,7 @@ using Multiplayer.API; using RimWorld; using RimWorld.Planet; +using RimWorld.QuestGen; using Verse; using Verse.AI.Group; using VFEEmpire; @@ -117,6 +118,26 @@ public VanillaFactionsEmpire(ModContentPack mod) // Causes the order to change, which could cause issues before the method is synced. PatchingUtilities.PatchCancelInInterface(AccessTools.DeclaredMethod(typeof(ColonistTitleCache.RoyaltyTracker), nameof(ColonistTitleCache.RoyaltyTracker.Postfix))); } + + // Quest gen error + { + var types = new[] + { + typeof(Questnode_Root_ArtExhibit), + typeof(QuestNode_Root_DeserterHideout), + typeof(QuestNode_Root_GrandBall), + typeof(QuestNode_Root_NobleVisit), + }; + + foreach (var type in types) + { + var method = AccessTools.DeclaredMethod(type, nameof(QuestNode.TestRunInt)); + if (method == null) + Log.Error($"Failed patching {nameof(QuestNode)}.{nameof(QuestNode.TestRunInt)} for type {type}"); + else + MpCompat.harmony.Patch(method, prefix: new HarmonyMethod(typeof(VanillaFactionsEmpire), nameof(PreTestRun))); + } + } } #endregion @@ -340,5 +361,21 @@ private static void SyncSlingBeam(RoyalTitlePermitWorker_Slicing permitWorker, L } #endregion + + #region Quest error fix + + private static bool PreTestRun(ref bool __result) + { + if (!MP.IsInMultiplayer) + return true; + + if (QuestGen_Get.GetMap() != null) + return true; + + __result = false; + return false; + } + + #endregion } } \ No newline at end of file