From 919f8cb328e33da03fc3f23120a9f3d55ae1a419 Mon Sep 17 00:00:00 2001 From: SokyranTheDragon Date: Sat, 24 Jan 2026 22:01:38 +0100 Subject: [PATCH] Sync all gizmos and a few other things Main things that are synced: - Refuelable gizmo hotkey - Trained animal attack/cancel attack gizmos - Gizmo to order all animals following their master to attack - Outfit stand gizmos (but not FloatMenuOptions) - Archonexus/Crate float menu options (they were synced through Pawn_JobTracker, but in a very specific edge case it would fail) - A ton of dev mode gizmos On top of that, I've included a handful of sync worker delegates that were needed by those (ReadingOutcomeDoer, Tile, AcceptanceReport, PsychicRitualToil). --- Source/Client/Syncing/Dict/SyncDictDlc.cs | 23 ++++++ Source/Client/Syncing/Dict/SyncDictMisc.cs | 12 +++ .../Client/Syncing/Dict/SyncDictRimWorld.cs | 44 ++++++++++ Source/Client/Syncing/Game/SyncDelegates.cs | 18 ++-- Source/Client/Syncing/Game/SyncFields.cs | 18 ++-- Source/Client/Syncing/Game/SyncMethods.cs | 82 +++++++++++++++++-- Source/Client/Syncing/Handler/SyncDelegate.cs | 12 +-- Source/Client/Syncing/SyncUtil.cs | 6 +- 8 files changed, 189 insertions(+), 26 deletions(-) diff --git a/Source/Client/Syncing/Dict/SyncDictDlc.cs b/Source/Client/Syncing/Dict/SyncDictDlc.cs index 96266ba29..7e31767fd 100644 --- a/Source/Client/Syncing/Dict/SyncDictDlc.cs +++ b/Source/Client/Syncing/Dict/SyncDictDlc.cs @@ -397,6 +397,29 @@ public static class SyncDictDlc return new PawnPsychicRitualRoleSelectionWidget(ritual, assignments.session.candidatePool, assignments); } }, + { + (ByteWriter data, PsychicRitualToil toil) => + { + data.WriteString(toil.uniqueId); + }, + (ByteReader data) => + { + var psychicRitualId = data.ReadString(); + + // Check all maps + return Find.Maps.SelectMany(map => map.lordManager.lords) + // Grab all lord toils + .Select(lord => lord.CurLordToil) + // Grab all psychic ritual toils + .OfType() + // Select all current psychic ritual toils + .Select(lordToil => lordToil.RitualData?.CurPsychicRitualToil) + // But only if not null, just as a precaution + .AllNotNull() + // Grab the first one with matching ID + .FirstOrDefault(toil => toil.uniqueId == psychicRitualId); + }, true // Implicit + }, #endregion }; diff --git a/Source/Client/Syncing/Dict/SyncDictMisc.cs b/Source/Client/Syncing/Dict/SyncDictMisc.cs index 21e346664..b8877c80c 100644 --- a/Source/Client/Syncing/Dict/SyncDictMisc.cs +++ b/Source/Client/Syncing/Dict/SyncDictMisc.cs @@ -113,6 +113,18 @@ public static class SyncDictMisc return context; } }, + { + (ByteWriter data, AcceptanceReport report) => + { + data.WriteBool(report.acceptedInt); + data.WriteString(report.reasonTextInt); + }, + (ByteReader data) => new AcceptanceReport + { + acceptedInt = data.ReadBool(), + reasonTextInt = data.ReadStringNullable() + } + }, #endregion #region Unity diff --git a/Source/Client/Syncing/Dict/SyncDictRimWorld.cs b/Source/Client/Syncing/Dict/SyncDictRimWorld.cs index 5183f07d6..a9c745137 100644 --- a/Source/Client/Syncing/Dict/SyncDictRimWorld.cs +++ b/Source/Client/Syncing/Dict/SyncDictRimWorld.cs @@ -768,6 +768,24 @@ public static class SyncDictRimWorld return (parent.gun as ThingWithComps).TryGetComp(); } }, + { + (ByteWriter writer, ReadingOutcomeDoer doer) => + { + WriteSync(writer, doer.Readable); + writer.WriteInt32(doer.Readable.doers.IndexOf(doer)); + }, + (ByteReader reader) => + { + var parent = ReadSync(reader); + var index = reader.ReadInt32(); + + // Make sure we have a valid doer + if (parent == null || index < 0 || index >= parent.doers.Count) + return null; + + return parent.doers[index]; + }, true // implicit + }, #endregion #region Things @@ -1047,6 +1065,32 @@ public static class SyncDictRimWorld return Find.WorldGrid.PlanetLayers[layerId]; }, true }, + { + (ByteWriter data, Tile tile) => + { + var map = Find.Maps.Find(m => m.pocketTileInfo == tile); + + // Handle pocket map + if (map != null) + { + data.WriteInt32(map.uniqueID); + } + // Handle normal tile + else + { + data.WriteInt32(-1); + WriteSync(data, tile.tile); + } + }, + (ByteReader data) => + { + var pocketMapId = data.ReadInt32(); + + if (pocketMapId >= 0) + return Find.Maps.Find(m => m.uniqueID == pocketMapId)?.pocketTileInfo; + return ReadSync(data).Tile; + } + }, #endregion #region Game diff --git a/Source/Client/Syncing/Game/SyncDelegates.cs b/Source/Client/Syncing/Game/SyncDelegates.cs index 33049f6ec..1ee71d206 100644 --- a/Source/Client/Syncing/Game/SyncDelegates.cs +++ b/Source/Client/Syncing/Game/SyncDelegates.cs @@ -43,6 +43,7 @@ public static void Init() SyncDelegate.Lambda(typeof(Building_PassengerShuttle), nameof(Building_PassengerShuttle.GetGizmos), 2); // Fill shuttle from cargo on map SyncMethod.Lambda(typeof(CompFlickable), nameof(CompFlickable.CompGetGizmosExtra), 1); // Toggle flick designation SyncMethod.Lambda(typeof(Pawn_PlayerSettings), nameof(Pawn_PlayerSettings.GetGizmos), 1); // Toggle release animals + SyncMethod.Lambda(typeof(Pawn_PlayerSettings), nameof(Pawn_PlayerSettings.GetGizmos), 2); // Force animals to attack specific target SyncMethod.Lambda(typeof(Building_TurretGun), nameof(Building_TurretGun.GetGizmos), 2); // Toggle turret hold fire SyncMethod.Lambda(typeof(Building_Trap), nameof(Building_Trap.GetGizmos), 1); // Toggle trap auto-rearm SyncMethod.Lambda(typeof(Building_Door), nameof(Building_Door.GetGizmos), 1); // Toggle door hold open @@ -97,7 +98,7 @@ public static void Init() SyncMethod.Lambda(typeof(CompTreeConnection), nameof(CompTreeConnection.CompGetGizmosExtra), 2).SetDebugOnly(); // Increase connection strength by 10% SyncMethod.Lambda(typeof(CompTreeConnection), nameof(CompTreeConnection.CompGetGizmosExtra), 3).SetDebugOnly(); // Decrease connection strength by 10% - SyncMethod.Lambda(typeof(CompNeuralSupercharger), nameof(CompNeuralSupercharger.CompGetGizmosExtra), 1); // Neural supercharger: allow temporary pawns to use + SyncMethod.Lambda(typeof(CompNeuralSupercharger), nameof(CompNeuralSupercharger.CompGetGizmosExtra), 1); // Neural supercharger: allow temporary pawns (guests) to use SyncMethod.Lambda(typeof(CompPilotConsole), nameof(CompPilotConsole.CompGetGizmosExtra), 1).SetDebugOnly(); // Dev launch instantly SyncMethod.Lambda(typeof(CompPilotConsole), nameof(CompPilotConsole.CompGetGizmosExtra), 2).SetDebugOnly(); // Dev reset cooldown @@ -129,10 +130,10 @@ public static void Init() SyncDelegate.Lambda(typeof(Pawn_CarryTracker), nameof(Pawn_CarryTracker.GetGizmos), 1).SetDebugOnly(); // Trigger dissolution event (CompDissolution) // CompSpawner - SyncMethod.Lambda(typeof(CompSpawner), nameof(CompSpawner.CompGetGizmosExtra), 0).SetDebugOnly(); - SyncMethod.Lambda(typeof(CompSpawnerHives), nameof(CompSpawnerHives.CompGetGizmosExtra), 0).SetDebugOnly(); - SyncMethod.Lambda(typeof(CompSpawnerItems), nameof(CompSpawnerItems.CompGetGizmosExtra), 0).SetDebugOnly(); - SyncMethod.Lambda(typeof(CompSpawnerPawn), nameof(CompSpawnerPawn.CompGetGizmosExtra), 0).SetDebugOnly(); + SyncMethod.Lambda(typeof(CompSpawner), nameof(CompSpawner.CompGetGizmosExtra), 0).SetDebugOnly(); // Spawn thing + SyncMethod.Lambda(typeof(CompSpawnerHives), nameof(CompSpawnerHives.CompGetGizmosExtra), 0).SetDebugOnly(); // Reproduce + SyncMethod.Lambda(typeof(CompSpawnerItems), nameof(CompSpawnerItems.CompGetGizmosExtra), 0).SetDebugOnly(); // Spawn items + SyncMethod.Lambda(typeof(CompSpawnerPawn), nameof(CompSpawnerPawn.CompGetGizmosExtra), 0).SetDebugOnly(); // Spawn pawn SyncMethod.Lambda(typeof(CompSendSignalOnCountdown), nameof(CompSendSignalOnCountdown.CompGetGizmosExtra), 0).SetDebugOnly(); @@ -242,6 +243,9 @@ public static void Init() SyncDelegate.Lambda(typeof(GeneResourceDrainUtility), nameof(GeneResourceDrainUtility.GetResourceDrainGizmos), 0).SetDebugOnly(); // -10% resource SyncDelegate.Lambda(typeof(GeneResourceDrainUtility), nameof(GeneResourceDrainUtility.GetResourceDrainGizmos), 1).SetDebugOnly(); // +10% resource SyncMethod.Register(typeof(CompBreakdownable), nameof(CompBreakdownable.Notify_Repaired)).SetDebugOnly(); // Dev repair breakdownable + SyncDelegate.Lambda(typeof(CompCanBeDormant), nameof(CompCanBeDormant.CompGetGizmosExtra), 1).SetDebugOnly(); // Wake up after delay + SyncDelegate.Lambda(typeof(Tile), nameof(Tile.GetGizmos), 0).SetDebugOnly(); // Generate settlement + SyncDelegate.Lambda(typeof(Tile), nameof(Tile.GetGizmos), 4).SetDebugOnly(); // Generate map // Hediffs SyncMethod.Register(typeof(Hediff_CubeInterest), nameof(Hediff_CubeInterest.StartWithdrawal)).SetDebugOnly(); @@ -272,6 +276,10 @@ public static void Init() SyncDelegate.Lambda(typeof(CompNociosphere), nameof(CompNociosphere.TargetLocation), 1); + // Animal training tracker + SyncDelegate.Lambda(typeof(Pawn_TrainingTracker), nameof(Pawn_TrainingTracker.GetGizmos), 0, fields: [SyncDelegate.DelegateThis, "master"]).SetContext(SyncContext.MapSelected).CancelIfNoSelectedMapObjects(); // Force attack target + SyncDelegate.Lambda(typeof(Pawn_TrainingTracker), nameof(Pawn_TrainingTracker.GetGizmos), 3).SetContext(SyncContext.MapSelected).CancelIfNoSelectedMapObjects(); // Cancel attacking target + InitRituals(); InitChoiceLetters(); InitDevTools(); diff --git a/Source/Client/Syncing/Game/SyncFields.cs b/Source/Client/Syncing/Game/SyncFields.cs index 667f128ff..4071f21ce 100644 --- a/Source/Client/Syncing/Game/SyncFields.cs +++ b/Source/Client/Syncing/Game/SyncFields.cs @@ -620,7 +620,7 @@ static void WatchAwaitingExecution(Pawn pawn) } [MpPrefix(typeof(Gizmo_Slider), nameof(Gizmo_Slider.GizmoOnGUI))] - static void SyncGizmoSlider(Gizmo_Slider __instance) + static void SyncGizmoSliderChanges(Gizmo_Slider __instance) { if (__instance is GeneGizmo_Resource geneGizmo) { @@ -638,12 +638,16 @@ static void SyncGizmoSlider(Gizmo_Slider __instance) SyncActivityCompTarget.Watch(comp); SyncActivityCompSuppression.Watch(comp); } - else if (__instance is Gizmo_SetFuelLevel fuelGizmo) - { - var refuelable = fuelGizmo.refuelable; - SyncCompRefuelableValue.Watch(refuelable); - SyncCompRefuelableTargetFuelLevel.Watch(refuelable); - } + } + + [MpPrefix(typeof(Gizmo_SetFuelLevel), nameof(Gizmo_SetFuelLevel.GizmoOnGUI))] + static void SyncRefuelableChanges(Gizmo_SetFuelLevel __instance) + { + // Must be separate from SyncGizmoSliderChanges, as this type overrides the base + // method and modifies the field in there (toggle the value using a hotkey). + var refuelable = __instance.refuelable; + SyncCompRefuelableValue.Watch(refuelable); + SyncCompRefuelableTargetFuelLevel.Watch(refuelable); } [MpPrefix(typeof(ITab_ContentsGenepackHolder), nameof(ITab_ContentsGenepackHolder.DoItemsLists))] diff --git a/Source/Client/Syncing/Game/SyncMethods.cs b/Source/Client/Syncing/Game/SyncMethods.cs index d03f7b666..7817c63b9 100644 --- a/Source/Client/Syncing/Game/SyncMethods.cs +++ b/Source/Client/Syncing/Game/SyncMethods.cs @@ -10,6 +10,7 @@ using RimWorld.Planet; using Verse; using Verse.AI; +using Verse.AI.Group; namespace Multiplayer.Client { @@ -126,7 +127,7 @@ public static void Init() SyncMethod.Register(typeof(CompTempControl), nameof(CompTempControl.InterfaceChangeTargetTemperature)); SyncMethod.Lambda(typeof(CompTempControl), nameof(CompTempControl.CompGetGizmosExtra), 2); // Reset temperature SyncMethod.Lambda(typeof(Comp_AtmosphericHeater), nameof(Comp_AtmosphericHeater.CompGetGizmosExtra), 0); // Reset temperature - SyncMethod.Register(typeof(CompTransporter), nameof(CompTransporter.CancelLoad), Array.Empty()); + SyncMethod.Register(typeof(CompTransporter), nameof(CompTransporter.CancelLoad), []); SyncMethod.Register(typeof(MapPortal), nameof(MapPortal.CancelLoad)); SyncMethod.Register(typeof(StorageSettings), nameof(StorageSettings.CopyFrom)).ExposeParameter(0); // Set target fuel level from Dialog_Slider. This only handles changing the fuel level for multiple buildings at once (by shift-clicking to select multiple) @@ -258,6 +259,7 @@ public static void Init() SyncMethod.Lambda(typeof(CompHackable), nameof(CompHackable.CompGetGizmosExtra), 1); // Toggle auto hack SyncMethod.Lambda(typeof(CompHackable), nameof(CompHackable.CompGetGizmosExtra), 7).SetDebugOnly(); // DEV: Hack +10% SyncMethod.Lambda(typeof(CompHackable), nameof(CompHackable.CompGetGizmosExtra), 8).SetDebugOnly(); // DEV: Complete hack + SyncMethod.Register(typeof(CompHackable), nameof(CompHackable.EndLockout)).SetDebugOnly(); // Dev: Unlock SyncMethod.Register(typeof(CompPolluteOverTime), nameof(CompPolluteOverTime.Pollute)).SetDebugOnly(); SyncMethod.Register(typeof(CompPollutionPump), nameof(CompPollutionPump.Pump)).SetDebugOnly(); SyncMethod.Lambda(typeof(CompProjectileInterceptor), nameof(CompProjectileInterceptor.CompGetGizmosExtra), 0).SetDebugOnly(); // Reset cooldown @@ -277,7 +279,8 @@ public static void Init() SyncMethod.Lambda(typeof(Pawn), nameof(Pawn.GetGizmos), 3).SetDebugOnly(); // Psychic entropy +20% SyncMethod.Lambda(typeof(Pawn), nameof(Pawn.GetGizmos), 6).SetDebugOnly(); // Reset faction permit cooldowns SyncMethod.Lambda(typeof(Pawn), nameof(Pawn.GetGizmos), 7).SetDebugOnly(); // Reset try romance cooldown - SyncMethod.Register(typeof(CompCanBeDormant), nameof(CompCanBeDormant.WakeUp)).SetDebugOnly(); + SyncMethod.Register(typeof(CompCanBeDormant), nameof(CompCanBeDormant.WakeUp)).SetDebugOnly(); // Wake up + SyncMethod.Register(typeof(CompCanBeDormant), nameof(CompCanBeDormant.ToSleep)).SetDebugOnly(); // Go to sleep SyncMethod.Lambda(typeof(Building_Bookcase), nameof(Building_Bookcase.GetGizmos), 0).SetDebugOnly(); // Fill with books SyncMethod.Lambda(typeof(Building_WorkTableAutonomous), nameof(Building_WorkTableAutonomous.GetGizmos), 0).SetDebugOnly(); // Forming cycle +25% SyncMethod.Lambda(typeof(Building_WorkTableAutonomous), nameof(Building_WorkTableAutonomous.GetGizmos), 1).SetDebugOnly(); // Complete cycle @@ -299,7 +302,7 @@ public static void Init() SyncMethod.Lambda(typeof(CompRevenant), nameof(CompRevenant.CompGetGizmosExtra), 3).SetDebugOnly(); // Find target SyncMethod.Register(typeof(CompShield), nameof(CompShield.Break)).SetDebugOnly(); SyncMethod.Lambda(typeof(CompShield), nameof(CompShield.CompGetWornGizmosExtra), 0).SetDebugOnly(); // Reset - SyncMethod.Register(typeof(CompSpawnImmortalSubplantsAround), nameof(CompSpawnImmortalSubplantsAround.RespawnCheck)).SetDebugOnly(); + SyncMethod.Register(typeof(CompSpawnImmortalSubplantsAround), nameof(CompSpawnImmortalSubplantsAround.RespawnCheck)).SetDebugOnly(); // Respawn subplant SyncMethod.Lambda(typeof(CompSpawnSubplant), nameof(CompSpawnSubplant.CompGetGizmosExtra), 0).SetDebugOnly(); // Add 100% progress SyncMethod.Lambda(typeof(CompVoidStructure), nameof(CompVoidStructure.CompGetGizmosExtra), 0).SetDebugOnly(); // Activate SyncMethod.Lambda(typeof(CompObelisk), nameof(CompObelisk.CompGetGizmosExtra), 0).SetDebugOnly(); // Trigger interaction effect @@ -330,6 +333,16 @@ public static void Init() SyncMethod.Lambda(typeof(UnnaturalCorpse), nameof(UnnaturalCorpse.GetGizmos), 3).SetDebugOnly(); // Awake SyncMethod.Lambda(typeof(UnnaturalCorpse), nameof(UnnaturalCorpse.GetGizmos), 4).SetDebugOnly(); // Unlock deactivation SyncMethod.Lambda(typeof(Thing), nameof(Thing.GetGizmos), 0).SetDebugOnly(); // Extinguish + SyncMethod.Lambda(typeof(BookOutcomeDoer_GiveQuest), nameof(BookOutcomeDoer_GiveQuest.GetGizmos), 1).SetDebugOnly(); // Give quest + SyncMethod.Register(typeof(Building_GravEngine), nameof(Building_GravEngine.Inspect)).SetDebugOnly(); // Inspect now + SyncMethod.Register(typeof(CompAncientVent), nameof(CompAncientVent.DevToggleVent)).SetDebugOnly(); // Toggle vents on map + SyncMethod.Lambda(typeof(CompEggLayer), nameof(CompEggLayer.CompGetGizmosExtra), 0); // LayEgg + SyncMethod.Register(typeof(CompOrbitalScanner), nameof(CompOrbitalScanner.ReceiveSignal)).SetDebugOnly(); // Find signal + SyncMethod.Register(typeof(CompOrbitalScanner), nameof(CompOrbitalScanner.LocateSignal)).SetDebugOnly(); // Locate signal + SyncMethod.Register(typeof(CompTerraformer), nameof(CompTerraformer.Convert)).SetDebugOnly(); // Convert + SyncMethod.Register(typeof(Crater), nameof(Crater.FillIn)).SetDebugOnly(); // Fill in + SyncMethod.Lambda(typeof(WorldObject), nameof(WorldObject.GetGizmos), 1).SetDebugOnly(); // Generate map + SyncMethod.Lambda(typeof(PsychicRitualToil_InvokeHorax), nameof(PsychicRitualToil_InvokeHorax.DebugFinishGizmo), 0).SetDebugOnly(); SyncMethod.Register(typeof(Blueprint_Build), nameof(Blueprint_Build.ChangeStyleOfAllSelected)).SetContext(SyncContext.MapSelected).CancelIfNoSelectedMapObjects(); SyncMethod.Lambda(typeof(CompTurretGun), nameof(CompTurretGun.CompGetGizmosExtra), 1); // Toggle fire at will @@ -366,7 +379,7 @@ public static void Init() SyncMethod.Lambda(typeof(Building_MechCharger), nameof(Building_MechCharger.GetGizmos), 0).SetDebugOnly(); // Waste 100% SyncMethod.Lambda(typeof(Building_MechCharger), nameof(Building_MechCharger.GetGizmos), 1).SetDebugOnly(); // Waste 25% SyncMethod.Lambda(typeof(Building_MechCharger), nameof(Building_MechCharger.GetGizmos), 2).SetDebugOnly(); // Waste 0% - SyncMethod.Register(typeof(Building_MechCharger), nameof(Building_MechCharger.GenerateWastePack)).SetDebugOnly(); // Generate waste, lambdaOrdinal: 3 + SyncMethod.Register(typeof(Building_MechCharger), nameof(Building_MechCharger.GenerateWastePack)).SetDebugOnly(); // Generate waste SyncMethod.Lambda(typeof(Building_MechCharger), nameof(Building_MechCharger.GetGizmos), 3).SetDebugOnly(); // Charge 100% // Gestator SyncMethod.Lambda(typeof(Building_MechGestator), nameof(Building_MechGestator.GetGizmos), 0).SetDebugOnly(); // Generate 5 waste @@ -381,6 +394,11 @@ public static void Init() SyncMethod.Lambda(typeof(CompMechPowerCell), nameof(CompMechPowerCell.CompGetGizmosExtra), 1).SetDebugOnly(); // Power left 100% // Repairable SyncMethod.Lambda(typeof(CompMechRepairable), nameof(CompMechRepairable.CompGetGizmosExtra), 1); // Toggle auto repair + // Ancient mech vat + SyncMethod.Register(typeof(CompMechGestatorTank), nameof(CompMechGestatorTank.State)).SetDebugOnly(); // Dev: Add mech and Dev: Remove mech + // Mech relay + SyncMethod.Register(typeof(CompMechRelay), nameof(CompMechRelay.Deactivate)).SetDebugOnly(); // Deactivate + SyncMethod.Lambda(typeof(CompMechRelay), nameof(CompMechRelay.CompGetGizmosExtra), 0).SetDebugOnly(); // Destabilize now // Atomizer SyncMethod.Lambda(typeof(CompAtomizer), nameof(CompAtomizer.CompGetGizmosExtra), 1); // Auto load @@ -425,7 +443,7 @@ public static void Init() SyncMethod.Register(typeof(HarbingerTree), nameof(HarbingerTree.AddNutrition)).SetDebugOnly(); SyncMethod.Register(typeof(HarbingerTree), nameof(HarbingerTree.SpawnNewTree)).SetDebugOnly(); SyncMethod.LocalFunc(typeof(HarbingerTree), nameof(HarbingerTree.GetGizmos), "DelayedSplatter").SetDebugOnly(); // Set blood splatters delay - SyncMethod.Lambda(typeof(CompPlantPreventCutting), nameof(CompPlantPreventCutting.CompGetGizmosExtra), 0); + SyncMethod.Register(typeof(CompPlantPreventCutting), nameof(CompPlantPreventCutting.PreventCutting)); // Entity codex SyncMethod.Register(typeof(EntityCodex), nameof(EntityCodex.SetDiscovered), [typeof(EntityCodexEntryDef), typeof(ThingDef), typeof(Thing)]); @@ -440,7 +458,6 @@ public static void Init() SyncMethod.Register(typeof(Pawn_CreepJoinerTracker), nameof(Pawn_CreepJoinerTracker.DoRejection)).SetDebugOnly(); // Pits - SyncMethod.Register(typeof(PitBurrow), nameof(PitBurrow.Collapse)).SetDebugOnly(); SyncMethod.Lambda(typeof(PitBurrow), nameof(PitBurrow.GetGizmos), 0).SetDebugOnly(); // Spawn fleshbeast SyncMethod.Register(typeof(PitGate), nameof(PitGate.TryFireIncident)).SetDebugOnly(); // Trigger incident with specific point value/with natural point value SyncMethod.Lambda(typeof(PitGate), nameof(PitGate.GetGizmos), 3).SetDebugOnly(); // End cooldown @@ -454,6 +471,50 @@ public static void Init() SyncMethod.Register(typeof(WorldComponent_GravshipController), nameof(WorldComponent_GravshipController.PlaceGravship)); SyncMethod.Register(typeof(WorldComponent_GravshipController), nameof(WorldComponent_GravshipController.AbortLanding)).SetContext(SyncContext.CurrentMap); + // Outfit stand + SyncMethod.Lambda(typeof(Building_OutfitStand), nameof(Building_OutfitStand.GetGizmos), 1); // Order a pawn to equip from a stand + SyncMethod.Register(typeof(Building_OutfitStand), nameof(Building_OutfitStand.SetAllowHauling)); // Toggle allow removing apparel + + // Archonexus core + // We can't rely on syncing through TryTakeOrderedJob with more than 1 pawn, as all pawns besides the main one + // call FloatMenuOptionProvider_DraftedMove.PawnGotoAction, which can also end the current job instead. + SyncMethod.Lambda(typeof(Building_ArchonexusCore), nameof(Building_ArchonexusCore.GetMultiSelectFloatMenuOptions), 0).SetContext(SyncContext.MapSelected).CancelIfNoSelectedMapObjects() // Activate with selected pawns. + // The method is relying on a temporary state based on the pawns that the player had selected. + // The method is using an already processed list of pawns, so we need to run it again with + // the same pawns to ensure that the list of pawns is initialized. + .SetPreInvoke((x, _) => + { + if (x is not Building_ArchonexusCore core) return; + core.GetMultiSelectFloatMenuOptions(Find.Selector.SelectedPawns).ExecuteEnumerable(); + }) + // After syncing and invoking the method, we want to restore the previous state of tmpPawnsCanReach. + // Just run the GetMultiSelectFloatMenuOptions method again so the list is populated. + .SetPostInvoke((x, _) => + { + if (x is not Building_ArchonexusCore core) return; + core.GetMultiSelectFloatMenuOptions(SyncUtil.prevSelected.OfType()).ExecuteEnumerable(); + }); + SyncMethod.Register(typeof(Building_ArchonexusCore), nameof(Building_ArchonexusCore.Activate)).SetDebugOnly(); // Activate archonexus core + + // We can't rely on syncing through TryTakeOrderedJob with more than 1 pawn, as all pawns besides the main one + // call FloatMenuOptionProvider_DraftedMove.PawnGotoAction, which can also end the current job instead. + SyncMethod.Lambda(typeof(Building_Crate), nameof(Building_Crate.GetMultiSelectFloatMenuOptions), 0).SetContext(SyncContext.MapSelected).CancelIfNoSelectedMapObjects() // Activate with selected pawns. + // The method is relying on a temporary state based on the pawns that the player had selected. + // The method is using an already processed list of pawns, so we need to run it again with + // the same pawns to ensure that the list of pawns is initialized. + .SetPreInvoke((x, _) => + { + if (x is not Building_Crate crate) return; + crate.GetMultiSelectFloatMenuOptions(Find.Selector.SelectedPawns).ExecuteEnumerable(); + }) + // After syncing and invoking the method, we want to restore the previous state of tmpPawnsCanReach. + // Just run the GetMultiSelectFloatMenuOptions method again so the list is populated. + .SetPostInvoke((x, _) => + { + if (x is not Building_Crate crate) return; + crate.GetMultiSelectFloatMenuOptions(SyncUtil.prevSelected.OfType()).ExecuteEnumerable(); + }); + // Double ExecuteWhenFinished ensures it'll load after MP Compat late patches, // so it will have registered all its sync workers already. LongEventHandler.ExecuteWhenFinished(() => LongEventHandler.ExecuteWhenFinished(() => @@ -492,6 +553,15 @@ static bool PastePawnTimetable(Pawn p) [MpTranspiler(typeof(CompPlantable), nameof(CompPlantable.BeginTargeting), lambdaOrdinal: 0)] static IEnumerable CompPlantableTranspiler(IEnumerable insts) { + // This method can do 3 things: + // - Begin targeting + // - Display a confirmation dialog, which may plant the plantable + // - Start planting immediately + // We don't want to sync the first situation. + // The second one we want to sync after confirming, which we do already. + // Syncing the last one requires either making a prefix to check if all conditions match, + // or replacing the interaction of adding a planting target with a synced one (which we do here). + foreach (var inst in insts) { // this.plantCells.Add(t.Cell) => CompPlantable_AddCell(t.Cell, this) diff --git a/Source/Client/Syncing/Handler/SyncDelegate.cs b/Source/Client/Syncing/Handler/SyncDelegate.cs index 991993fe4..baf9ae5ad 100644 --- a/Source/Client/Syncing/Handler/SyncDelegate.cs +++ b/Source/Client/Syncing/Handler/SyncDelegate.cs @@ -160,27 +160,27 @@ private void CheckFieldsExist(params string[] fields) throw new Exception($"Field with path {f} not found"); } - public new static SyncDelegate Lambda(Type parentType, string parentMethod, int lambdaOrdinal, Type[] parentArgs = null, MethodType parentMethodType = MethodType.Normal) + public new static SyncDelegate Lambda(Type parentType, string parentMethod, int lambdaOrdinal, Type[] parentArgs = null, MethodType parentMethodType = MethodType.Normal, string[] fields = null) { return Sync.RegisterSyncDelegate( MpMethodUtil.GetLambda(parentType, parentMethod, parentMethodType, parentArgs, lambdaOrdinal), - null + fields ); } - public new static SyncDelegate LambdaInGetter(Type parentType, string parentMethod, int lambdaOrdinal) + public new static SyncDelegate LambdaInGetter(Type parentType, string parentMethod, int lambdaOrdinal, string[] fields = null) { return Sync.RegisterSyncDelegate( MpMethodUtil.GetLambda(parentType, parentMethod, MethodType.Getter, null, lambdaOrdinal), - null + fields ); } - public static SyncDelegate LocalFunc(Type parentType, string parentMethod, string name, Type[] parentArgs = null) + public static SyncDelegate LocalFunc(Type parentType, string parentMethod, string name, Type[] parentArgs = null, string[] fields = null) { return Sync.RegisterSyncDelegate( MpMethodUtil.GetLocalFunc(parentType, parentMethod, MethodType.Normal, parentArgs, name), - null + fields ); } diff --git a/Source/Client/Syncing/SyncUtil.cs b/Source/Client/Syncing/SyncUtil.cs index 0de6aae56..3740b0156 100644 --- a/Source/Client/Syncing/SyncUtil.cs +++ b/Source/Client/Syncing/SyncUtil.cs @@ -14,6 +14,8 @@ namespace Multiplayer.Client { public static class SyncUtil { + public static List prevSelected = null; + public static List prevWorldSelected = null; public static bool isDialogNodeTreeOpen = false; internal static void DialogNodeTreePostfix() @@ -47,8 +49,8 @@ public static SyncHandler HandleCmd(ByteReader data) throw; } - List prevSelected = Find.Selector.selected; - List prevWorldSelected = Find.WorldSelector.selected; + prevSelected = Find.Selector.selected; + prevWorldSelected = Find.WorldSelector.selected; bool shouldQueue = false;