Skip to content
Merged
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
2 changes: 2 additions & 0 deletions Source/Client/Multiplayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using System.Threading;
using Multiplayer.Client.AsyncTime;
using Multiplayer.Client.Comp;
using Multiplayer.Client.Patches;
using Multiplayer.Client.Util;

namespace Multiplayer.Client
Expand Down Expand Up @@ -216,6 +217,7 @@ public static void StopMultiplayer()
game = null;

TickPatch.Reset();
VTRSync.Reset();

Find.WindowStack?.WindowOfType<ServerBrowser>()?.Cleanup(true);
SyncFieldUtil.ClearAllBufferedChanges();
Expand Down
62 changes: 42 additions & 20 deletions Source/Client/Patches/VTRSyncPatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,41 +56,54 @@ static class VTRSync
{
// Special identifier for world map (since it doesn't have a uniqueID like regular maps)
public const int WorldMapId = -2;
public static int lastMovedToMap = -1;
public static int lastSentTick = -1;
public const int InvalidMapIndex = -1;
public static int lastMovedToMap = InvalidMapIndex;
public static int lastSentAtTick = -1;

// Vtr rates
public const int MaximumVtr = 15;
public const int MinimumVtr = 1;

public static int GetSynchronizedUpdateRate(Thing thing) => thing?.MapHeld?.AsyncTime()?.VTR ?? VTRSync.MaximumVtr;
public static int GetSynchronizedUpdateRate(Thing thing) => thing?.MapHeld?.AsyncTime()?.VTR ?? MaximumVtr;

public static void SendViewedMapUpdate(int previous, int current)
{
string warn = string.Empty;
if (previous != lastMovedToMap)
warn = $" mismatch between expected previous map {previous} and last moved to map {lastMovedToMap}";
else if (previous == current) return;
int currentTick = Find.TickManager?.TicksGame ?? 0;
MpLog.Debug($"VTR MapSwitchPatch: {lastMovedToMap}->{current} @ tick {currentTick}{warn}");
Multiplayer.Client.SendCommand(CommandType.PlayerCount, ScheduledCommand.Global, ByteWriter.GetBytes(previous, current));
lastMovedToMap = current;
}

public static void Reset()
{
lastMovedToMap = InvalidMapIndex;
}
}

[HarmonyPatch(typeof(Game), nameof(Game.CurrentMap), MethodType.Setter)]
static class MapSwitchPatch
{
const int InvalidMapIndex = -1;

static void Prefix(Map value)
{
if (Multiplayer.Client == null || Client.Multiplayer.session == null) return;
if (Multiplayer.Client == null) return;

try
{
// WorldRenderModePatch will handle it
if (VTRSync.lastMovedToMap == VTRSync.WorldMapId) return;
int previousMap = GetPreviousMapIndex();
int newMap = value?.uniqueID ?? InvalidMapIndex;
int newMap = value?.uniqueID ?? VTRSync.InvalidMapIndex;
int currentTick = Find.TickManager?.TicksGame ?? 0;

if (previousMap == newMap)
return;

if (VTRSync.lastMovedToMap == newMap && currentTick == VTRSync.lastSentTick)
return;
if (previousMap == newMap) return;
if (VTRSync.lastMovedToMap == newMap && currentTick == VTRSync.lastSentAtTick) return;

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

if (currentMapIsRemovedAndWasLatestMap)
{
return InvalidMapIndex;
return VTRSync.InvalidMapIndex;
}

return Find.CurrentMap?.uniqueID ?? InvalidMapIndex;
return Find.CurrentMap?.uniqueID ?? VTRSync.InvalidMapIndex;
}
}

Expand All @@ -125,9 +138,18 @@ static void Postfix(WorldRenderMode __result)
// Detect transition to world map (Planet mode)
if (__result == WorldRenderMode.Planet && lastRenderMode != WorldRenderMode.Planet)
{
if (VTRSync.lastMovedToMap != -1)
if (VTRSync.lastMovedToMap != VTRSync.InvalidMapIndex && VTRSync.lastMovedToMap != VTRSync.WorldMapId)
{
VTRSync.SendViewedMapUpdate(VTRSync.lastMovedToMap, VTRSync.WorldMapId);
}
}
// Detect transition back to tile map
else if (__result != WorldRenderMode.Planet && lastRenderMode == WorldRenderMode.Planet)
{
var newMap = Find.CurrentMap?.uniqueID ?? VTRSync.InvalidMapIndex;
if (newMap != VTRSync.InvalidMapIndex && VTRSync.lastMovedToMap == VTRSync.WorldMapId)
{
Multiplayer.Client.SendCommand(CommandType.PlayerCount, ScheduledCommand.Global, ByteWriter.GetBytes(VTRSync.lastMovedToMap, VTRSync.WorldMapId));
VTRSync.SendViewedMapUpdate(VTRSync.WorldMapId, newMap);
}
}

Expand Down
13 changes: 10 additions & 3 deletions Source/Common/Networking/State/ServerPlayingState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
{
var playerId = data.ReadInt32();
var traces = data.ReadPrefixedBytes();
Server.GetPlayer(playerId)?.SendPacket(Packets.Server_Traces, new object[] { TracesPacket.Transfer, traces });

Check warning on line 40 in Source/Common/Networking/State/ServerPlayingState.cs

View workflow job for this annotation

GitHub Actions / Builds

Possible null reference assignment.
}
}

Expand All @@ -48,6 +48,16 @@
int mapId = data.ReadInt32();
byte[]? extra = data.ReadPrefixedBytes(65535);
if (extra == null) return;
if (cmd == CommandType.PlayerCount)
{
ByteReader reader = new ByteReader(extra);
var prevMap = reader.ReadInt32();
var newMap = reader.ReadInt32();
if (Player.currentMap != prevMap)
ServerLog.Error($"Inconsistent player {Player.Username} map. Last known map: {Player.currentMap}, " +
$"however received command with transition: {prevMap} -> {newMap}");
Player.currentMap = newMap;
}

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

Expand Down Expand Up @@ -110,9 +120,6 @@
byte seq = data.ReadByte();
byte map = data.ReadByte();

// Track the player's current map from cursor updates
Player.currentMap = map;

writer.WriteInt32(Player.id);
writer.WriteByte(seq);
writer.WriteByte(map);
Expand Down
12 changes: 4 additions & 8 deletions Source/Common/PlayerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,11 @@ public void SetDisconnected(ConnectionBase conn, MpDisconnectReason reason)

if (player.hasJoined)
{
// Handle unexpected disconnections by sending PlayerCount command
if (reason == MpDisconnectReason.ClientLeft || reason == MpDisconnectReason.NetFailed)
// Send PlayerCount command to remove the player from their last known map
if (player.currentMap != -1)
{
// Send PlayerCount command to remove player from their last known map
if (player.currentMap != -1)
{
byte[] playerCountData = ByteWriter.GetBytes(player.currentMap, -1); // previousMap: player's map, newMap: -1 (disconnected)
server.commands.Send(CommandType.PlayerCount, ScheduledCommand.NoFaction, ScheduledCommand.Global, playerCountData);
}
byte[] playerCountData = ByteWriter.GetBytes(player.currentMap, -1); // previousMap: player's map, newMap: -1 (disconnected)
server.commands.Send(CommandType.PlayerCount, ScheduledCommand.NoFaction, ScheduledCommand.Global, playerCountData);
}
// todo check player.IsPlaying?
// todo FactionId might throw when called for not fully initialized players
Expand Down
2 changes: 1 addition & 1 deletion Source/Common/ServerPlayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@
public bool frozen;
public int unfrozenAt;

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

public string Username => conn.username;

Check warning on line 36 in Source/Common/ServerPlayer.cs

View workflow job for this annotation

GitHub Actions / Builds

Possible null reference return.
public int Latency => conn.Latency;
public int FactionId { get; set; }
public bool HasJoined => conn.State is ConnectionStateEnum.ServerLoading or ConnectionStateEnum.ServerPlaying;
Expand Down
Loading