diff --git a/Source/Client/Debug/DebugSync.cs b/Source/Client/Debug/DebugSync.cs index 4b8609d8c..aaea73357 100644 --- a/Source/Client/Debug/DebugSync.cs +++ b/Source/Client/Debug/DebugSync.cs @@ -48,6 +48,12 @@ public static void HandleCmd(ByteReader data) int selectedId = data.ReadInt32(); + // Replay under the acting player's view, not the local one. Read before the node graph is + // rebuilt below, because node labels are allowed to depend on it -- the incident action's label + // embeds its target's name, so a wrong view here produces a path that matches nothing and the + // command silently does not run on this client. + WorldSelectedPatch.result = data.ReadBool(); + if (Multiplayer.MapContext != null) { var thing = Multiplayer.ThingsById.GetValueSafe(selectedId); @@ -115,6 +121,7 @@ public static void HandleCmd(ByteReader data) MouseCellPatch.result = null; MouseTilePatch.result = null; + WorldSelectedPatch.result = null; Find.Selector.selected = prevSelected; FieldRefs.worldSelected(Find.WorldSelector) = prevWorldSelected; @@ -156,6 +163,11 @@ public static void SendCmd(DebugSource source, int hash, string path, Map map, I else writer.WriteInt32(Find.WorldSelector.SingleSelectedObject?.ID ?? -1); + // Which view the acting player had open. Debug actions may legitimately read it -- vanilla's + // incident action derives its entire target from it -- so replaying one faithfully means + // reproducing it, exactly as the cursor and selection above are reproduced. + writer.WriteBool(WorldRendererUtility.WorldSelected); + Multiplayer.WriterLog.AddCurrentNode(writer); int mapId = map?.uniqueID ?? ScheduledCommand.Global; diff --git a/Source/Client/Patches/Patches.cs b/Source/Client/Patches/Patches.cs index 344833379..d6127fe52 100644 --- a/Source/Client/Patches/Patches.cs +++ b/Source/Client/Patches/Patches.cs @@ -192,6 +192,29 @@ static void Postfix(ref PlanetTile __result) } } + /// + /// Overrides whether the planet view is showing, while a debug command is being replayed. + /// + /// Companion to the cursor overrides above. Debug actions are allowed to read the interface -- vanilla's + /// incident action picks its target with + /// WorldRendererUtility.WorldSelected ? Find.WorldSelector.SingleSelectedObject : Find.CurrentMap -- + /// so replaying one faithfully means reproducing what the acting player could see, not just where their + /// cursor was. Without this, the same command targets a caravan on a player looking at the planet and a + /// colony on a player looking at a map. + /// + [HarmonyPatch(typeof(WorldRendererUtility), nameof(WorldRendererUtility.WorldSelected), MethodType.Getter)] + public static class WorldSelectedPatch + { + /// Non-null only while a debug command is being replayed. + public static bool? result; + + static void Postfix(ref bool __result) + { + if (result.HasValue) + __result = result.Value; + } + } + [HarmonyPatch(typeof(KeyBindingDef), nameof(KeyBindingDef.IsDownEvent), MethodType.Getter)] public static class KeyIsDownPatch {