Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
a928704
Add debug action to show mod compat when playing (#896)
mibac138 Apr 29, 2026
e743d1f
Send selected info only to other players (#902)
mibac138 Apr 30, 2026
9924e04
Show mod compat scores in the main mod list dialog (#901)
mibac138 Apr 30, 2026
c58202a
Fix/rescue pawn (#907)
Sakura-TA May 8, 2026
25eea46
Replace stacked harmony patch atttribute with TargetMethods (#909)
Sakura-TA May 9, 2026
b8bf25c
Add custom disconnection dialog when version mismatch is a likely cau…
mibac138 May 10, 2026
10552a0
Detect registering the same SyncMethod multiple times (#911)
mibac138 May 10, 2026
c915798
Fix: Bootstrap mode should be enabled when data is missing (#912)
mibac138 May 10, 2026
8e5bbf5
Bump Languages from `8bfffb7` to `a8513b0` (#916)
dependabot[bot] May 11, 2026
fc92a16
Add standalone server zip to continuous release (#915)
MhaWay May 11, 2026
0dea755
Standalone save & persistence improvements (#874)
MhaWay May 12, 2026
c6853fa
Multifaction trade (#905)
Sakura-TA May 12, 2026
ccca3fb
Multifaction spectator home check (#903)
Sakura-TA May 12, 2026
d01bfda
Typed ClientInitDataPacket.Mods (#913)
mibac138 May 12, 2026
5d273b3
package dedicated server beta artifacts for Windows and Linux (#918)
MhaWay May 14, 2026
b38a882
Clean up standalone save persistence review noise (#920)
MhaWay May 14, 2026
3bfbe12
Remove standalone bootstrap enforcement (#921)
MhaWay May 15, 2026
76eed6e
Fix bootstrap window reopening over scenario (#922)
MhaWay May 15, 2026
a331d71
Fix bootstrap world map rotation (#924)
MhaWay May 15, 2026
dd7259a
Reset bootstrap client mode after save upload (#923)
MhaWay May 15, 2026
9d938d0
Delay bootstrap reconnect until main menu (#925)
MhaWay May 15, 2026
cbf907f
Fix render burn (#926)
MhaWay May 19, 2026
1e00080
Allow Properties in RegisterSyncField (#917)
notfood May 19, 2026
fa37fdd
Use shared entry transition for bootstrap reconnect (#929)
MhaWay May 20, 2026
2f856bb
Let bootstrap shutdown exit standalone server (#930)
MhaWay May 20, 2026
f03a2ce
Add standalone server chat command help (#931)
MhaWay May 20, 2026
f4d0697
Delay bootstrap window until landing dialog clears (#928)
MhaWay May 20, 2026
823146d
Close connecting window when entering bootstrap flow (#933)
MhaWay May 20, 2026
9982be1
Bump Languages from `a8513b0` to `90a86c7` (#940)
dependabot[bot] May 29, 2026
f8702f2
Fix join point stuck state on dedicated server (#942)
Kuinox May 30, 2026
6aef95e
Fix join-point reload cache crash (#938)
NemuruYama Jun 1, 2026
821804c
Fix bootstrap config window race after map entry (#934)
MhaWay Jun 2, 2026
7dcabee
Bump Languages from `90a86c7` to `407942a`
notfood Jun 5, 2026
075ab81
Command Framework with source generation for the chat commands (#939)
NemuruYama Jun 5, 2026
39e4f87
Fix standalone join-point creation on join (#945)
MhaWay Jun 5, 2026
7899c43
Skip autosave requests while simulating (#946)
Kuinox Jun 6, 2026
f7647ca
Fix Map.IsPlayerHome spectator result patch (#947)
Kuinox Jun 7, 2026
56dac09
Update for Rimworld 1.6.4850 (#899)
still222 Jun 9, 2026
1792fc4
Fix spectator home check with null faction (#953)
Kuinox Jun 14, 2026
862eacc
Bump Languages from `407942a` to `0a6b746` (#954)
dependabot[bot] Jun 15, 2026
ca15771
Allow changing faction colors (#951)
Kuinox Jun 15, 2026
67d8f38
Synchronize colonist bar reordering (#528)
mibac138 Jun 17, 2026
a1780f7
Fix delayed world-to-map pawn timestamp conversion (#949)
Kuinox Jun 17, 2026
c9fb555
Disable desync log stack trace unwinding for non-X86/X64 arches (#950)
kirpichik Jun 17, 2026
891117e
JoinDataWindow: Fix NRE in case of a def mismatch (#962)
romangr Jul 22, 2026
a481546
Sync EndCurrentJob in FloatMenuOptionProvider_DraftedMove.PawnGotoAct…
cmlee119 Jul 22, 2026
cbe74ae
Prevent "Collection was modified"
notfood Jul 27, 2026
899e359
Filter map player home incident tags (#955)
Kuinox Jul 27, 2026
ea633b8
Isolate RNG in the gravship launch ritual's reachability check (#968)
cmlee119 Aug 1, 2026
e760d30
Add missing SetContext Map for Gravship launch (#966)
cmlee119 Aug 3, 2026
30e5ad4
Isolate RNG in PowerNet battery energy distribution
GetParanoid Sep 9, 2026
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
22 changes: 21 additions & 1 deletion .github/workflows/alpha-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,24 @@
- Linux: `~/.steam/steam/steamapps/common/RimWorld/Mods`
3. Extract the zip file into the `Mods` folder.
* You should have a `Multiplayer` folder in the `Mods` folder (`Mods/Multiplayer`)
* Make sure you do not have this directory structure: `Mods/Multiplayer-beta/Multiplayer`. If you do, move the `Multiplayer` folder to the parent directory.
* Make sure you do not have this directory structure: `Mods/Multiplayer-beta/Multiplayer`. If you do, move the `Multiplayer` folder to the parent directory.

#### Standalone server

Download `Server-beta.zip` if you want to host a dedicated standalone server for testing.

**Setup**
1. Download and extract `Server-beta.zip`.
2. Open the folder for your platform:
- `Server/Windows`
- `Server/Linux`
3. Start the server using:
- Windows: `Server.exe`
- Linux: `./Server.sh`
4. The server will start and wait for the first connection.

**First-time configuration (bootstrap)**
No manual configuration files are required.
The **first player to connect** via the Multiplayer mod client will be prompted to perform the initial setup:
they can configure the game world, scenario, and other options directly from within RimWorld.
Subsequent players connect to the already-running session.
20 changes: 19 additions & 1 deletion .github/workflows/build-beta.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,31 @@ jobs:
- name: Build Mod
run: dotnet build ${{ env.SLN_PATH }} --configuration Release --no-restore

- name: Publish Server (Windows)
run: dotnet publish ${{ env.SLN_PATH }}Server/Server.csproj --configuration Release --runtime win-x64 --self-contained false -p:UseAppHost=true -o output/Server/Windows

- name: Publish Server (Linux)
run: dotnet publish ${{ env.SLN_PATH }}Server/Server.csproj --configuration Release --runtime linux-x64 --self-contained false -p:UseAppHost=true -o output/Server/Linux

- name: Add Server Launchers
run: |
cat > output/Server/Linux/Server.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
exec dotnet "$SCRIPT_DIR/Server.dll" "$@"
EOF

chmod +x output/Server/Linux/Server.sh

- name: Package files
run: |
sed -i "s/<name>\(.*\)<\/name>\$/<name>\1 [Continuous]<\/name>/" About/About.xml
mkdir -p output/Multiplayer
mv About/ Assemblies/ AssembliesCustom/ Defs/ Languages/ Textures/ output/Multiplayer
cd output/
zip -r ../Multiplayer-beta.zip Multiplayer/
zip -r ../Server-beta.zip Server/
cd ../

- name: Upload Mod Artifacts
Expand All @@ -57,5 +75,5 @@ jobs:
- name: Upload new release
run: |
gh release create --target "${{ github.sha }}" --title "Continuous" --notes-file ".github/workflows/alpha-notes.md" --draft "continuous"
gh release upload "continuous" Multiplayer-beta.zip
gh release upload "continuous" Multiplayer-beta.zip Server-beta.zip
gh release edit "continuous" --draft=false
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ ipch/
*.opensdf
*.sdf
*.cachefile
*.lscache
*.VC.db
*.VC.VC.opendb

Expand Down
23 changes: 23 additions & 0 deletions Source/ChatCommandContracts/ChatCommandAttributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;

namespace Multiplayer.Common;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class ChatCommandAttribute(string name, params string[] aliases) : Attribute
{
public string Name { get; } = name;
public string[] Aliases { get; } = aliases;
public string Usage { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public bool RequiresHost { get; set; }
}

[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)]
public sealed class ChatArgumentAttribute(string name) : Attribute
{
public string Name { get; } = name;
public string Description { get; set; } = string.Empty;
}

[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)]
public sealed class ChatRestAttribute : Attribute;
11 changes: 11 additions & 0 deletions Source/ChatCommandContracts/ChatCommandContracts.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>12</LangVersion>
<RootNamespace>Multiplayer.Common</RootNamespace>
<AssemblyName>MultiplayerChatCommandContracts</AssemblyName>
</PropertyGroup>

</Project>
26 changes: 22 additions & 4 deletions Source/Client/AsyncTime/AsyncWorldTimeComp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ public void ExecuteCmd(ScheduledCommand cmd)

if (cmdType == CommandType.CreateJoinPoint)
{
if (Multiplayer.session?.ConnectedToStandaloneServer == true && !TickPatch.currentExecutingCmdIssuedBySelf)
return;

LongEventHandler.QueueLongEvent(CreateJoinPointAndSendIfHost, "MpCreatingJoinPoint", false, null);
}

Expand Down Expand Up @@ -273,11 +276,26 @@ public void ExecuteCmd(ScheduledCommand cmd)

private static void CreateJoinPointAndSendIfHost()
{
Multiplayer.session.dataSnapshot = SaveLoad.CreateGameDataSnapshot(SaveLoad.SaveAndReload(), Multiplayer.GameComp.multifaction);
Multiplayer.session.dataSnapshot = SaveLoad.SaveReloadAndCreateSnapshot(
Multiplayer.GameComp.multifaction,
ReloadOptimizationMode.ForJoinPointSnapshot
);

if (!TickPatch.Simulating && !Multiplayer.IsReplay &&
(Multiplayer.LocalServer != null || Multiplayer.arbiterInstance))
SaveLoad.SendGameData(Multiplayer.session.dataSnapshot, true);
if (!TickPatch.Simulating && !Multiplayer.IsReplay)
{
if (Multiplayer.session?.ConnectedToStandaloneServer == true)
{
// Standalone: every client uploads world data + individual snapshots
SaveLoad.SendGameData(Multiplayer.session.dataSnapshot, true);
SaveLoad.SendStandaloneMapSnapshots(Multiplayer.session.dataSnapshot);
SaveLoad.SendStandaloneWorldSnapshot(Multiplayer.session.dataSnapshot);
}
else if (Multiplayer.LocalServer != null || Multiplayer.arbiterInstance)
{
// Hosted: only host/arbiter uploads world data
SaveLoad.SendGameData(Multiplayer.session.dataSnapshot, true);
}
}
}

public void SetTimeEverywhere(TimeSpeed speed)
Expand Down
2 changes: 1 addition & 1 deletion Source/Client/AsyncTime/MultiplayerAsyncQuest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ public static void TickMapQuests(AsyncTimeComp mapAsyncTimeComp)
/// <param name="quests">Quests to run QuestTick() on</param>
private static void TickQuests(IEnumerable<Quest> quests)
{
foreach (var quest in quests)
foreach (var quest in quests.ToList())
{
quest.QuestTick();
}
Expand Down
9 changes: 9 additions & 0 deletions Source/Client/AsyncTime/StorytellerPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,15 @@ static IEnumerable<IncidentTargetTagDef> Postfix(IEnumerable<IncidentTargetTagDe
{
foreach (var tag in tags)
{
// Each map ticks its storyteller in that map's faction context. Do not let
// another player faction's settlement advertise itself as this map's player home.
if (tag == IncidentTargetTagDefOf.Map_PlayerHome &&
Multiplayer.Client != null &&
Multiplayer.GameComp.multifaction &&
__instance.Faction is { IsPlayer: true } faction &&
faction != Faction.OfPlayer)
continue;

// Only return Map_Misc if player's faction is (heuristically) visiting the map
// This affects multifaction where the storyteller ticks on every settlement for every faction separately
if (tag != IncidentTargetTagDefOf.Map_Misc ||
Expand Down
37 changes: 36 additions & 1 deletion Source/Client/ConstantTicker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,40 @@ private static void TickNonSimulation()

private static void TickAutosave()
{
// When connected to a remote standalone server, the client drives
// the autosave timer using the interval received at connection time
// (from the server's TOML settings via ServerProtocolOkPacket).
if (Multiplayer.session?.ConnectedToStandaloneServer == true)
{
var session = Multiplayer.session;
if (session.autosaveInterval <= 0)
return;

if (session.autosaveUnit == AutosaveUnit.Minutes)
{
session.autosaveCounter++;

if (session.autosaveCounter > session.autosaveInterval * TicksPerMinute)
{
session.autosaveCounter = 0;
Autosaving.DoAutosave();
}
}
else if (session.autosaveUnit == AutosaveUnit.Days)
{
var anyMapCounterUp =
Multiplayer.game.mapComps
.Any(m => m.autosaveCounter > session.autosaveInterval * TicksPerIngameDay);

if (anyMapCounterUp)
{
Multiplayer.game.mapComps.Do(m => m.autosaveCounter = 0);
Autosaving.DoAutosave();
}
}
return;
}

if (Multiplayer.LocalServer is not { } server) return;

if (server.settings.autosaveUnit == AutosaveUnit.Minutes)
Expand All @@ -60,7 +94,8 @@ private static void TickAutosave()
session.autosaveCounter = 0;
Autosaving.DoAutosave();
}
} else if (server.settings.autosaveUnit == AutosaveUnit.Days && server.settings.autosaveInterval > 0)
}
else if (server.settings.autosaveUnit == AutosaveUnit.Days && server.settings.autosaveInterval > 0)
{
var anyMapCounterUp =
Multiplayer.game.mapComps
Expand Down
7 changes: 7 additions & 0 deletions Source/Client/Debug/DebugActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,13 @@ public static void DumpAddrInfoTable()
}
}

[DebugAction(MultiplayerLocalCategory, "Show mod compat", allowedGameStates = AllowedGameStates.Playing)]
public static void ShowModCompatDialog()
{
var window = new ModCompatWindow(null, true, false, null);
Find.WindowStack.Add(window);
}

#if DEBUG

[DebugOutput]
Expand Down
5 changes: 5 additions & 0 deletions Source/Client/Desyncs/DeferredStackTracing.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using HarmonyLib;
using Multiplayer.Client.Patches;
using Multiplayer.Common;
Expand All @@ -25,6 +26,9 @@ static IEnumerable<MethodBase> TargetMethods()

public static int acc;

private static bool SupportsDeferredStackTracing =>
RuntimeInformation.ProcessArchitecture is Architecture.X64 or Architecture.X86;

public static void Postfix()
{
if (Native.LmfPtr == 0) return;
Expand All @@ -43,6 +47,7 @@ public static void Postfix()

public static bool ShouldAddStackTraceForDesyncLog()
{
if (!SupportsDeferredStackTracing) return false;
if (Multiplayer.Client == null) return false;
if (Multiplayer.settings.desyncTracingMode == DesyncTracingMode.None) return false;
if (Multiplayer.game == null) return false;
Expand Down
4 changes: 2 additions & 2 deletions Source/Client/Factions/Blueprints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace Multiplayer.Client
// Don't draw other factions' blueprints
// Don't link graphics of different factions' blueprints

[HarmonyPatch(typeof(GenConstruct), nameof(GenConstruct.CanPlaceBlueprintAt))]
[HarmonyPatch(typeof(GenConstruct), nameof(GenConstruct.CanPlaceBlueprintAt_NewTemp))]
static class CanPlaceBlueprintAtPatch
{
static MethodInfo CanPlaceBlueprintOver = AccessTools.Method(typeof(GenConstruct), nameof(GenConstruct.CanPlaceBlueprintOver));
Expand Down Expand Up @@ -48,7 +48,7 @@ static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> e, M
static bool ShouldIgnore1(Thing oldThing) => oldThing.def.IsBlueprint && oldThing.Faction != Faction.OfPlayer;
}

[HarmonyPatch(typeof(GenConstruct), nameof(GenConstruct.CanPlaceBlueprintAt))]
[HarmonyPatch(typeof(GenConstruct), nameof(GenConstruct.CanPlaceBlueprintAt_NewTemp))]
static class CanPlaceBlueprintAtPatch2
{
static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> e, MethodBase original)
Expand Down
10 changes: 9 additions & 1 deletion Source/Client/Factions/FactionCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ public static void SendPawn(int playerId, Pawn p)
pawnStore.GetOrAddNew(playerId).Add(p);
}

[SyncMethod]
public static void ChangeFactionColor(Faction faction, Color color)
{
if (faction is not { IsPlayer: true })
return;

faction.color = color;
}

[SyncMethod]
public static void CreateFaction(int playerId, FactionCreationData creationData)
{
Expand Down Expand Up @@ -286,4 +295,3 @@ public record FactionCreationData : ISyncSimple
public List<ThingDefCount> startingPossessions;
public bool setupNextMapFromTickZero;
}

15 changes: 15 additions & 0 deletions Source/Client/Factions/FactionsWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ void DrawFactionInLastRect(Faction faction)

using (MpStyle.Set(GameFont.Medium))
Layouter.Label(faction.Name);

var colorRect = Layouter.Rect(20f, 20f);
Widgets.DrawBoxSolid(colorRect.ContractedBy(2f), faction.Color);
Widgets.DrawBox(colorRect);
Widgets.DrawHighlightIfMouseover(colorRect);

if (Widgets.ButtonInvisible(colorRect))
{
Find.WindowStack.Add(new Dialog_ChooseFactionColor(color =>
{
FactionCreator.ChangeFactionColor(faction, color);
}, faction.Color));
}

TooltipHandler.TipRegion(colorRect, "MpChangeFactionColor".Translate());
}
Layouter.EndHorizontal();

Expand Down
Loading