Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
d365d6a
Updated version numbers.
Reznal Jun 28, 2025
e449dc5
Cleared all build and mod loading errors for 1.6.
Reznal Jun 29, 2025
000bd7d
Fixed world not generating.
Reznal Jun 29, 2025
6818510
Init creation
Reznal Jun 29, 2025
f3f1033
Revert "Init creation"
Reznal Jun 29, 2025
3a5ed0c
Fixed LmfPtr offset values - maybe
Reznal Jun 29, 2025
c3a282a
Fixed faction repeater
Reznal Jun 29, 2025
9667573
Fixed stack tracing
Reznal Jun 30, 2025
588043f
Patch to fix dev palette being open on loading multiplayer world.
Reznal Jun 30, 2025
f7d57ba
Updated debug window to be more readable and user friendly
Reznal Jul 1, 2025
c935ca0
updated rbp for mac
Reznal Jul 6, 2025
c864bf0
- Returned InGameDebug to its original state (I think?)
Reznal Jul 9, 2025
720e7b3
Renamed IsMutant to IsSubhuman
Reznal Jul 9, 2025
7dbb387
Removed last instance of int tile
Reznal Jul 9, 2025
658e981
Correctly named function IsColonySubhumanAnyFaction
Reznal Jul 9, 2025
3897a17
Added MP log files
Reznal Jul 10, 2025
062e847
- Added Variable Tick Rate handling
Reznal Jul 10, 2025
ab1aaf3
I missed a pretty critical file heh
Reznal Jul 10, 2025
663367c
Updated VTR Synch patch
Reznal Jul 10, 2025
896efc0
Merge pull request #1 from Reznal/VTR
Reznal Jul 10, 2025
052525c
Small change to vtr
Reznal Jul 10, 2025
85210c3
Merge branch 'VTR' into 1.6-working
Reznal Jul 10, 2025
dcf043f
Updated Debug Panel
Reznal Jul 10, 2025
1a15ecb
API Update
Reznal Jul 10, 2025
3c98c0c
- Fixed for savable mp logs
Reznal Jul 11, 2025
6558192
vtr update
Reznal Jul 12, 2025
3f61638
- Vtr applied to Projectiles and WorldObjects
Reznal Jul 12, 2025
14dad1a
Merge branch 'upstream-dev' into 1.6-working
Reznal Jul 12, 2025
0e4f2f1
- Added extra error handling to SaveableMPLogs
Reznal Jul 12, 2025
d66e69f
Removed some artifacts from the merge
Reznal Jul 12, 2025
6c1c005
bringing up to upsteam dev + prs
Reznal Jul 13, 2025
ba12520
- Fixed pings
Reznal Jul 13, 2025
5f29ea5
- Added Deterministic HashCombineInt for 3,5,6,7 and 8 combinations.
Reznal Jul 13, 2025
0ca31e6
Camping integreation
Reznal Jul 13, 2025
f776396
Fixed an issue with player counting for maps
Reznal Jul 13, 2025
7798478
Added a new fix for rbp to potentially fix this running on different …
Reznal Jul 14, 2025
90bfd7e
Removed obnoxious log
Reznal Jul 14, 2025
2f1d2d4
Add PerformanceRecorder class, update debug panel
DillionLowry Jul 14, 2025
9ebc563
Merge branch 'upstream-dev' into 1.6-working
Reznal Jul 14, 2025
f0f2f69
Add PerformancCalculator for TPS calculations, update recorder and de…
DillionLowry Jul 15, 2025
d072631
Merge pull request #3 from DillionLowry/debug-additions
Reznal Jul 15, 2025
49b68f3
Plans now sync when:
Reznal Jul 15, 2025
74c4ee3
Merge branch 'upstream-dev' into 1.6-working
Reznal Jul 15, 2025
06f3eb2
Removed saveable mp logs
Reznal Jul 16, 2025
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
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,12 @@ publish/
# in these scripts will be unencrypted
PublishScripts/

# Commented out .nupkg and **/packages/* so we can use a custom package
# NuGet Packages
*.nupkg
# *.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# **/packages/*

# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
Expand Down
7 changes: 7 additions & 0 deletions Source/Client/AsyncTime/AsyncTimeComp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Multiplayer.Client.Patches;
using Multiplayer.Client.Saving;
using Multiplayer.Client.Util;
using System.Linq;

namespace Multiplayer.Client
{
Expand Down Expand Up @@ -98,6 +99,9 @@ public int GameStartAbsTick

public Queue<ScheduledCommand> cmds = new();

public int CurrentPlayerCount { get; private set; } = 0;
public int VTR => CurrentPlayerCount > 0 ? VTRSync.MinimumVtr : VTRSync.MaximumVtr;

public AsyncTimeComp(Map map, int gameStartAbsTick = 0)
{
this.map = map;
Expand Down Expand Up @@ -228,6 +232,9 @@ public void ExposeData()
Scribe_Custom.LookULong(ref randState, "randState", 1);
}

public void IncreasePlayerCount() => CurrentPlayerCount++;
public void DecreasePlayerCount() => CurrentPlayerCount = Math.Max(0, CurrentPlayerCount - 1);

public void FinalizeInit()
{
cmds = new Queue<ScheduledCommand>(
Expand Down
2 changes: 1 addition & 1 deletion Source/Client/AsyncTime/AsyncTimePatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ static class TickManagerPausedPatch
static void Postfix(ref bool __result)
{
if (Multiplayer.Client == null) return;
if (WorldRendererUtility.WorldRendered) return;
if (WorldRendererUtility.WorldSelected) return;
if (FactionCreator.generatingMap) return;

var asyncTime = Find.CurrentMap.AsyncTime();
Expand Down
13 changes: 13 additions & 0 deletions Source/Client/AsyncTime/AsyncWorldTimeComp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,19 @@ public void ExecuteCmd(ScheduledCommand cmd)
var canUseDevMode = data.ReadBool();
Multiplayer.GameComp.playerData[playerId] = new PlayerData { canUseDevMode = canUseDevMode };
}

if (cmdType == CommandType.PlayerCount)
{
int previousMapId = data.ReadInt32();
int newMapId = data.ReadInt32();
int mapCount = Find.Maps.Count;

if (0 <= previousMapId && previousMapId < mapCount)
Find.Maps[previousMapId]?.AsyncTime()?.DecreasePlayerCount();

if (0 <= newMapId && newMapId < mapCount)
Find.Maps[newMapId]?.AsyncTime()?.IncreasePlayerCount();
}
}
catch (Exception e)
{
Expand Down
1 change: 1 addition & 0 deletions Source/Client/Multiplayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public static class Multiplayer

public static string ReplaysDir => GenFilePaths.FolderUnderSaveData("MpReplays");
public static string DesyncsDir => GenFilePaths.FolderUnderSaveData("MpDesyncs");
public static string MpLogsDir => GenFilePaths.FolderUnderSaveData("MpLogs");

public static Stopwatch clock = Stopwatch.StartNew();

Expand Down
6 changes: 6 additions & 0 deletions Source/Client/OnMainThread.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Multiplayer.Client.DebugUi;
using Multiplayer.Client.Networking;
using Multiplayer.Common;
using System;
Expand Down Expand Up @@ -42,6 +43,11 @@ public void Update()
Multiplayer.session.Update();
SyncFieldUtil.UpdateSync();

if (PerformanceRecorder.IsRecording)
{
PerformanceRecorder.RecordFrame();
}

if (!Multiplayer.arbiterInstance && Application.isFocused && !TickPatch.Simulating && !Multiplayer.session.desynced)
Multiplayer.session.playerCursors.SendVisuals();

Expand Down
128 changes: 128 additions & 0 deletions Source/Client/Patches/VTRSyncPatch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
using HarmonyLib;
using Multiplayer.Client.Patches;
using Multiplayer.Client.Util;
using Multiplayer.Common;
using RimWorld.Planet;
using System;
using Verse;

namespace Multiplayer.Client.Patches
{
[HarmonyPatch(typeof(GenTicks), nameof(GenTicks.GetCameraUpdateRate))]
public static class VTRSyncPatch
{
static bool Prefix(Thing thing, ref int __result)
{
if (Multiplayer.Client == null)
return true;

__result = VTRSync.GetSynchronizedUpdateRate(thing);
return false;
}
}

[HarmonyPatch(typeof(Projectile), nameof(Projectile.UpdateRateTicks), MethodType.Getter)]
public static class VtrSyncProjectilePatch
{
static bool Prefix(ref int __result, Projectile __instance)
{
if (Multiplayer.Client == null)
return true;

__result = __instance.Spawned ? VTRSync.GetSynchronizedUpdateRate(__instance) : VTRSync.MaximumVtr;
return false;
}
}

[HarmonyPatch(typeof(WorldObject), nameof(WorldObject.UpdateRateTicks), MethodType.Getter)]
public static class VtrSyncWorldObjectPatch
{
static bool Prefix(ref int __result, WorldObject __instance)
{
if (Multiplayer.Client == null)
return true;

__result = VTRSync.MaximumVtr;
return false;
}
}

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;

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

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

[HarmonyPatch(typeof(Game), nameof(Game.CurrentMap), MethodType.Setter)]
static class MapSwitchPatch
{
static void Prefix(Map value)
{
if (Multiplayer.Client == null || Client.Multiplayer.session == null) return;

try
{
int previousMap = Find.CurrentMap?.uniqueID ?? -1;
int newMap = value?.uniqueID ?? -1;
int currentTick = Find.TickManager?.TicksGame ?? 0;

// If no change in map, do nothing
if (previousMap == newMap)
return;

// Prevent duplicate commands for the same transition, but allow retry after a tick
if (VTRSync.lastMovedToMap == newMap && currentTick == VTRSync.lastSentTick)
return;

// Send map change command to server
// Send as global command since it affects multiple maps
Multiplayer.Client.SendCommand(CommandType.PlayerCount, ScheduledCommand.Global, ByteWriter.GetBytes(previousMap, newMap));

// Track this command to prevent duplicates
VTRSync.lastMovedToMap = newMap;
VTRSync.lastSentTick = currentTick;
}
catch (Exception ex)
{
MpLog.Error($"VTR MapSwitchPatch error: {ex.Message}");
}
}
}

[HarmonyPatch(typeof(WorldRendererUtility), nameof(WorldRendererUtility.CurrentWorldRenderMode), MethodType.Getter)]
static class WorldRenderModePatch
{
private static WorldRenderMode lastRenderMode = WorldRenderMode.None;

static void Postfix(WorldRenderMode __result)
{
if (Multiplayer.Client == null) return;

try
{
// Detect transition to world map (Planet mode)
if (__result == WorldRenderMode.Planet && lastRenderMode != WorldRenderMode.Planet)
{
if (VTRSync.lastMovedToMap != -1)
{
Multiplayer.Client.SendCommand(CommandType.PlayerCount, ScheduledCommand.Global, ByteWriter.GetBytes(VTRSync.lastMovedToMap, VTRSync.WorldMapId));
}
}

lastRenderMode = __result;
}
catch (Exception ex)
{
MpLog.Error($"WorldRenderModePatch error: {ex.Message}");
}
}
}
}
2 changes: 1 addition & 1 deletion Source/Client/Persistent/CaravanFormingPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ internal static void StartFormingCaravan(Faction faction, Map map, bool reform =
// RNG shouldn't be invoked but TryAddWaypoint is quite complex and calls pathfinding
Rand.PushState();

var worldRoutePlanner = Find.WorldRoutePlanner;
WorldRoutePlanner worldRoutePlanner = Find.WorldRoutePlanner;
worldRoutePlanner.Start(dialog);
worldRoutePlanner.TryAddWaypoint(routePlannerWaypoint.Value);
}
Expand Down
20 changes: 20 additions & 0 deletions Source/Client/UI/DebugPanel/DebugLine.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using UnityEngine;

namespace Multiplayer.Client.DebugUi
{
// Data structures for organizing debug sections
internal struct DebugLine
{
public string Label;
public string Value;
public Color Color;

public DebugLine(string label, string value, Color color)
{
Label = label;
Value = value;
Color = color;
}
}

}
14 changes: 14 additions & 0 deletions Source/Client/UI/DebugPanel/DebugSection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Multiplayer.Client.DebugUi
{
internal struct DebugSection
{
public string Title;
public DebugLine[] Lines;

public DebugSection(string title, DebugLine[] lines)
{
Title = title;
Lines = lines;
}
}
}
Loading
Loading