Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Source/Client/Debug/DebugSync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down
23 changes: 23 additions & 0 deletions Source/Client/Patches/Patches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,29 @@ static void Postfix(ref PlanetTile __result)
}
}

/// <summary>
/// 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
/// <c>WorldRendererUtility.WorldSelected ? Find.WorldSelector.SingleSelectedObject : Find.CurrentMap</c> --
/// 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.
/// </summary>
[HarmonyPatch(typeof(WorldRendererUtility), nameof(WorldRendererUtility.WorldSelected), MethodType.Getter)]
public static class WorldSelectedPatch
{
/// <summary>Non-null only while a debug command is being replayed.</summary>
public static bool? result;

@notfood notfood Aug 5, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something is wrong here, it's never assigned. Later it asks for .HasValue but it was never set.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right that this file alone does not show it. The field does get set, but in the other file.

It is set here, in DebugSync.HandleCmd:

WorldSelectedPatch.result = data.ReadBool();

It is read from the network at that spot on purpose. The node graph gets rebuilt a few lines
later, and node labels can depend on the view. So the value has to be in place first.

It is set back to null in the finally block, next to the two cursor overrides:

MouseCellPatch.result = null;
MouseTilePatch.result = null;
WorldSelectedPatch.result = null;

This is the same pattern as the two patches right above it in Patches.cs. All three are set
and cleared in the same places:

Field Declared Set Cleared
MouseCellPatch.result Patches.cs#L172 DebugSync.cs#L31 #L122
MouseTilePatch.result Patches.cs#L184 DebugSync.cs#L33 #L123
WorldSelectedPatch.result Patches.cs#L209 DebugSync.cs#L55 #L124

Still, you had to open another file to answer this. I can update it if you want to say where the value is set and cleared, so the class makes sense on its own.


static void Postfix(ref bool __result)
{
if (result.HasValue)
__result = result.Value;
}
}

[HarmonyPatch(typeof(KeyBindingDef), nameof(KeyBindingDef.IsDownEvent), MethodType.Getter)]
public static class KeyIsDownPatch
{
Expand Down