Skip to content

Commit 32a5477

Browse files
authored
Fix VTRSync player count tracking (#702)
1 parent 1f885ee commit 32a5477

File tree

5 files changed

+59
-32
lines changed

5 files changed

+59
-32
lines changed

Source/Client/Multiplayer.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using System.Threading;
1515
using Multiplayer.Client.AsyncTime;
1616
using Multiplayer.Client.Comp;
17+
using Multiplayer.Client.Patches;
1718
using Multiplayer.Client.Util;
1819

1920
namespace Multiplayer.Client
@@ -216,6 +217,7 @@ public static void StopMultiplayer()
216217
game = null;
217218

218219
TickPatch.Reset();
220+
VTRSync.Reset();
219221

220222
Find.WindowStack?.WindowOfType<ServerBrowser>()?.Cleanup(true);
221223
SyncFieldUtil.ClearAllBufferedChanges();

Source/Client/Patches/VTRSyncPatch.cs

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -56,41 +56,54 @@ static class VTRSync
5656
{
5757
// Special identifier for world map (since it doesn't have a uniqueID like regular maps)
5858
public const int WorldMapId = -2;
59-
public static int lastMovedToMap = -1;
60-
public static int lastSentTick = -1;
59+
public const int InvalidMapIndex = -1;
60+
public static int lastMovedToMap = InvalidMapIndex;
61+
public static int lastSentAtTick = -1;
6162

6263
// Vtr rates
6364
public const int MaximumVtr = 15;
6465
public const int MinimumVtr = 1;
6566

66-
public static int GetSynchronizedUpdateRate(Thing thing) => thing?.MapHeld?.AsyncTime()?.VTR ?? VTRSync.MaximumVtr;
67+
public static int GetSynchronizedUpdateRate(Thing thing) => thing?.MapHeld?.AsyncTime()?.VTR ?? MaximumVtr;
68+
69+
public static void SendViewedMapUpdate(int previous, int current)
70+
{
71+
string warn = string.Empty;
72+
if (previous != lastMovedToMap)
73+
warn = $" mismatch between expected previous map {previous} and last moved to map {lastMovedToMap}";
74+
else if (previous == current) return;
75+
int currentTick = Find.TickManager?.TicksGame ?? 0;
76+
MpLog.Debug($"VTR MapSwitchPatch: {lastMovedToMap}->{current} @ tick {currentTick}{warn}");
77+
Multiplayer.Client.SendCommand(CommandType.PlayerCount, ScheduledCommand.Global, ByteWriter.GetBytes(previous, current));
78+
lastMovedToMap = current;
79+
}
80+
81+
public static void Reset()
82+
{
83+
lastMovedToMap = InvalidMapIndex;
84+
}
6785
}
6886

6987
[HarmonyPatch(typeof(Game), nameof(Game.CurrentMap), MethodType.Setter)]
7088
static class MapSwitchPatch
7189
{
72-
const int InvalidMapIndex = -1;
73-
7490
static void Prefix(Map value)
7591
{
76-
if (Multiplayer.Client == null || Client.Multiplayer.session == null) return;
92+
if (Multiplayer.Client == null) return;
7793

7894
try
7995
{
96+
// WorldRenderModePatch will handle it
97+
if (VTRSync.lastMovedToMap == VTRSync.WorldMapId) return;
8098
int previousMap = GetPreviousMapIndex();
81-
int newMap = value?.uniqueID ?? InvalidMapIndex;
99+
int newMap = value?.uniqueID ?? VTRSync.InvalidMapIndex;
82100
int currentTick = Find.TickManager?.TicksGame ?? 0;
83101

84-
if (previousMap == newMap)
85-
return;
86-
87-
if (VTRSync.lastMovedToMap == newMap && currentTick == VTRSync.lastSentTick)
88-
return;
102+
if (previousMap == newMap) return;
103+
if (VTRSync.lastMovedToMap == newMap && currentTick == VTRSync.lastSentAtTick) return;
89104

90-
MpLog.Debug($"VTR MapSwitchPatch: Switching from map {previousMap} to {newMap} at tick {currentTick}");
91-
Multiplayer.Client.SendCommand(CommandType.PlayerCount, ScheduledCommand.Global, ByteWriter.GetBytes(previousMap, newMap));
92-
VTRSync.lastMovedToMap = newMap;
93-
VTRSync.lastSentTick = currentTick;
105+
VTRSync.SendViewedMapUpdate(previousMap, newMap);
106+
VTRSync.lastSentAtTick = currentTick;
94107
}
95108
catch (Exception ex)
96109
{
@@ -104,10 +117,10 @@ private static int GetPreviousMapIndex()
104117

105118
if (currentMapIsRemovedAndWasLatestMap)
106119
{
107-
return InvalidMapIndex;
120+
return VTRSync.InvalidMapIndex;
108121
}
109122

110-
return Find.CurrentMap?.uniqueID ?? InvalidMapIndex;
123+
return Find.CurrentMap?.uniqueID ?? VTRSync.InvalidMapIndex;
111124
}
112125
}
113126

@@ -125,9 +138,18 @@ static void Postfix(WorldRenderMode __result)
125138
// Detect transition to world map (Planet mode)
126139
if (__result == WorldRenderMode.Planet && lastRenderMode != WorldRenderMode.Planet)
127140
{
128-
if (VTRSync.lastMovedToMap != -1)
141+
if (VTRSync.lastMovedToMap != VTRSync.InvalidMapIndex && VTRSync.lastMovedToMap != VTRSync.WorldMapId)
142+
{
143+
VTRSync.SendViewedMapUpdate(VTRSync.lastMovedToMap, VTRSync.WorldMapId);
144+
}
145+
}
146+
// Detect transition back to tile map
147+
else if (__result != WorldRenderMode.Planet && lastRenderMode == WorldRenderMode.Planet)
148+
{
149+
var newMap = Find.CurrentMap?.uniqueID ?? VTRSync.InvalidMapIndex;
150+
if (newMap != VTRSync.InvalidMapIndex && VTRSync.lastMovedToMap == VTRSync.WorldMapId)
129151
{
130-
Multiplayer.Client.SendCommand(CommandType.PlayerCount, ScheduledCommand.Global, ByteWriter.GetBytes(VTRSync.lastMovedToMap, VTRSync.WorldMapId));
152+
VTRSync.SendViewedMapUpdate(VTRSync.WorldMapId, newMap);
131153
}
132154
}
133155

Source/Common/Networking/State/ServerPlayingState.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ public void HandleClientCommand(ByteReader data)
4848
int mapId = data.ReadInt32();
4949
byte[]? extra = data.ReadPrefixedBytes(65535);
5050
if (extra == null) return;
51+
if (cmd == CommandType.PlayerCount)
52+
{
53+
ByteReader reader = new ByteReader(extra);
54+
var prevMap = reader.ReadInt32();
55+
var newMap = reader.ReadInt32();
56+
if (Player.currentMap != prevMap)
57+
ServerLog.Error($"Inconsistent player {Player.Username} map. Last known map: {Player.currentMap}, " +
58+
$"however received command with transition: {prevMap} -> {newMap}");
59+
Player.currentMap = newMap;
60+
}
5161

5262
// todo check if map id is valid for the player
5363

@@ -110,9 +120,6 @@ public void HandleCursor(ByteReader data)
110120
byte seq = data.ReadByte();
111121
byte map = data.ReadByte();
112122

113-
// Track the player's current map from cursor updates
114-
Player.currentMap = map;
115-
116123
writer.WriteInt32(Player.id);
117124
writer.WriteByte(seq);
118125
writer.WriteByte(map);

Source/Common/PlayerManager.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,11 @@ public void SetDisconnected(ConnectionBase conn, MpDisconnectReason reason)
8383

8484
if (player.hasJoined)
8585
{
86-
// Handle unexpected disconnections by sending PlayerCount command
87-
if (reason == MpDisconnectReason.ClientLeft || reason == MpDisconnectReason.NetFailed)
86+
// Send PlayerCount command to remove the player from their last known map
87+
if (player.currentMap != -1)
8888
{
89-
// Send PlayerCount command to remove player from their last known map
90-
if (player.currentMap != -1)
91-
{
92-
byte[] playerCountData = ByteWriter.GetBytes(player.currentMap, -1); // previousMap: player's map, newMap: -1 (disconnected)
93-
server.commands.Send(CommandType.PlayerCount, ScheduledCommand.NoFaction, ScheduledCommand.Global, playerCountData);
94-
}
89+
byte[] playerCountData = ByteWriter.GetBytes(player.currentMap, -1); // previousMap: player's map, newMap: -1 (disconnected)
90+
server.commands.Send(CommandType.PlayerCount, ScheduledCommand.NoFaction, ScheduledCommand.Global, playerCountData);
9591
}
9692
// todo check player.IsPlaying?
9793
// todo FactionId might throw when called for not fully initialized players

Source/Common/ServerPlayer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public class ServerPlayer : IChatSource
3030
public bool frozen;
3131
public int unfrozenAt;
3232

33-
// Track which map the player is currently on (from cursor updates)
33+
// Track which map the player is currently on
3434
public int currentMap = -1;
3535

3636
public string Username => conn.username;

0 commit comments

Comments
 (0)