owned this note
owned this note
Published
Linked with GitHub
# Rimworld MP Wiki
Welcome to the Rimworld Multiplayer Wiki.
- [Discord](https://discord.gg/S4bxXpv)
- [Github](https://github.com/rwmt/Multiplayer/releases)
- [Website](https://rimworldmultiplayer.com)
- [Steam Workshop](https://steamcommunity.com/sharedfiles/filedetails/?id=1752864297)
- [What is this wiki?](https://hackmd.io/Gd_gueokTNui_fqzOSG2Tg?view#What-is-this-wiki)
### Installation
---
The easiest way is from the [Steam Workshop](https://steamcommunity.com/sharedfiles/filedetails/?id=1752864297). You can also install it manually by just grabbing the latest release from [here](https://github.com/rwmt/Multiplayer/releases) and unpacking it in your mod folder.
### Hosting and Joining
---
#### Steam
The easiest way to connect is through Steam. Just host a world with the Steam option checked, and your friends should be able to shift-tab join on you.
#### Direct Connect or LAN
For direct portforward UDP 30502 on your local address and give out your public ip to your friends to connect. When hosting the server, just type 0.0.0.0 in the host address box.
LAN connections will show up in the LAN tab or the Multiplayer window.
### FAQ
---
**Is the mod dead?**
No. There will be a formal announcement if the mod ever dies. No-progress does not mean the death of the mod. The mod is developed by the community, who all have lives outside of the mod. If you want something in the mod so badly, code it yourself! We accept any constructive PRs.
**What happened to Zetrith?**
Shot dead by 8623 Krekka
**How can players connect?**
Players can connect through Steam, LAN, or Direct. To use direct connect, portforward 30502 through UDP or use a software like Hamachi.
**Is there PVP? Can I make a seperate faction?**
Currently everyone is cooperatively playing exactly the same game, with same maps, in the same faction. You can create multiple colonies.
**Can mods be used in Multiplayer?**
Yes, but not all mods will work in multiplayer. Check out the spreadsheet in #welcome or use the #mod-check channel to find compatible mods. When playing a "modpack", all players need to use the same load order and also the host has to share their Mod_ configs with the other players. (See bottom of this for tutorial)
**What do the numbers on the mod spreadsheet/website mean?**
0 means untested, 1 means unplayable, 2 is major issues, 3 is minor issues, and 4 is no problems.
**What is a save replay?**
A multiplayer save game.
**Can I change my ingame username?**
Yes, your name can be changed under options, mod settings, Multiplayer - Show Settings.
**Can I play singleplayer with the mod enabled?**
Yes, this should not cause any problems even with a lot of other mods enabled even if they are marked as incompatible.
**Which RimWorld version should I be using?**
Current Version, or anything above 1.0
**What is the maximum amount of players that can be online?**
We recommend 8 players max, but there is no limit.
**Why does simulating take so long?**
Simulations takes place from the last autosave made, so if you do /autosave in chat before saving a replay or someone connects to your game it will speed up the simulation time. The same applies to resyncing, so if the host forces an autosave before someone resyncs it will also speed up their simulation time.
**Can I donate to the project?**
We don’t accept donations as a project anymore but if you’re feeling generous then these are people who have contributed to the mod’s development and upkeep.
Zetrith - (Creator, Core, Support) - https://ko-fi.com/zetrith
NotFood - (Core, Mod Compatiblity, Compatibility Commissions) - https://ko-fi.com/notfood
Sokyran - (Core, Mod Compatiblity, Compatibility Commissions) - https://ko-fi.com/sokyran
Nebual - (Core) - https://ko-fi.com/Nebual
Thomas107500 - (Mod Compatiblity) - https://ko-fi.com/thomas107500
Luz - (Support/Admin) - https://ko-fi.com/llavorre
Mistress Mia - (Support/Admin) - https://ko-fi.com/miaamakiir
Swept - (Support/Admin/Website) - https://ko-fi.com/swept
**How do I convert a Multiplayer game back to Singleplayer?**
On the main menu click Multiplayer, select the save you want to convert and click watch. After watching it, select the time you want and click menu then "convert to singleplayer". It should show up in your singleplayer folder after that.
**How to clean a dirty core folder?**
This method can help you if you're having trouble loading Rimworld or in resolving Desync issues. Go to your Rimworld install folder > Mods > Core and delete the Core folder. Then, verify Rimworld's game files in Steam.
**How do I share config files?**
If you are running modpacks with Rimworld Multiplayer, you will need to share the **host's** config files among the people who are connecting to the host. To do this, navigate to Rimworld's config folder, and copy `ModsConfig.xml` and any files that look like this -> `Mod_XXX_XXX.xml` . Make the people you are playing with put these files in their config folder, **REPLACING THEM** when it asks them too.
Windows Rimworld Config Dir : `C:/Users/%USERPROFILE%/AppData/LocalLow/Ludeon Studios/RimWorld by Ludeon Studios/Config/`
Mac Rimworld Config Dir : `~/Library/Application\ Support/RimWorld/Config/`
Linux Rimworld Config Dir : `~/.config/unity3d/Ludeon Studios/RimWorld by Ludeon Studios/Config`
### Common Desync Reasons
---
• **Incompatible Mods**
Not all mods are compatible with Rimworld Multiplayer. If you use ones that aren't compatible, you **will** desync. Before starting any kind of playthrough, use #modcheck, the spreadsheet link in #welcome, or the modlist checking website to make sure whatever mods you are using are compatible. There's also handy premade modlists in #modlist if you just wanna jump in.
• **Not sharing ModConfig files**
If you are using any kind of complex mods, they will usually generate a settings file on your PC. These files *NEED* to be shared between the people you are playing with, otherwise you will desync. There's a tutorial for how to do this at the bottom of the #faq channel.
• **Different language setting**
If not all players using the same language settings some desyncs can occure.
• **Dirty "Core" folder**
Sometimes when installing/uninstalling mods, your Core folder might get something wrong with it. This is a rare issue but could be causing desyncs. Instructions on how to fix it are listed in #faq.
• **Bad Connection, Slow PC, Shit Internet.**
Just like any other online game, if you have a poor connection, you will desync/lag a lot more. Same is true for Rimworld Multiplayer. Another cause of desyncs is low FPS. If you are running the game at 30fps or lower, you might have connections problem. I generally advise that the most powerful PC "hosts" when playing Rimworld Multiplayer.
• **Some desyncs are just chance**
Sometimes, desyncs are unavoidable. If you're playing a 200+ modpack in Multiplayer and get a couple desyncs every 3-4 hours, you have a *goodass* modpack.
• **"Wrong Protocol Version"**
You or your friend is using an outdated version of the multiplayer mod. Check #patch-notes for the latest version.
# Dev MP Wiki
### Compiling
1. Clone the repo into RimWorld's Mods folder (otherwise reference paths will need fixing)
2. Run the Multiplayer.csproj file found in the source folder with an IDE and you can just press start to compile it.
3. Now you can just copy the About, Assemblies, Defs and Languages folders into another folder in the mods folder.
#### Trouble Compiling?
Ionic.Zip.Reduced might cause problems for some. The proper Nuget package is [here](https://www.nuget.org/packages/DotNetZip.Reduced/).
### API
[Precompiled 0MultiplayerAPI.dll](https://www.nuget.org/packages/RimWorld.MultiplayerAPI/)
#### A Small Example
In this section, we're going to focus on the basics of the api. Specifically, we're going to enable a mod for Multiplayer.
The boilerplate
By the time RimWorld reaches your mod the API is ready to use, you can call it wherever you see fit. See basic C# mod tutorials. The easiest place is [StaticConstructorOnStartup]
The following is the most basic boilerplate code.
```csharp=
using Multiplayer.API;
using Verse;
namespace MyExampleMod
{
[StaticConstructorOnStartup]
public static class MyExampleModCompat
{
static MyExampleModCompat()
{
if (!MP.enabled) return;
// This is where the magic happens and your attributes
// auto register, similar to Harmony's PatchAll.
MP.RegisterAll();
// You can choose to not auto register and do it manually
// with the MP.Register* methods.
// Use MP.IsInMultiplayer to act upon it in other places
// user can have it enabled and not be in session
}
}
}
```
That's all! You are done.
#### More Examples
The [Multiplayer Compatibility Project](https://github.com/notfood/RimWorld-Multiplayer-Compatibility)
#### Fields
Well... sometimes you aren't that lucky. Sometimes you want to change a specific Field. For those scenarios there is the MP.Watch* methods. Basically, at the beginning of the operation you take a snapshot of the Field value and then compare it later. MPAPI checks them if they need to broadcast it and then spreads the change accordingly.
```csharp=
...
[HarmonyPatch(typeof(RimWorldWindowClass), nameof(DoWindowContents))]
static class RimWorldWindowClass_DoWindowContents_Patch
{
// Draw a 160x16 slider in RimWorldWindowClass
static void Postfix(Rect inRect)
{
if (MP.IsInMultiplayer) {
MP.WatchBegin(); // Let's being watching
// This is here to set the variable if it changed on other clients
// It will update the variable and the logic will stay the same.
MP.Watch(instance, nameof(instance.weight));
}
Rect sliderRect = new Rect(inRect.x, inRect.y, 160f, 16f);
instance.weight = GUI.HorizontalSlider(sliderRect, instance.weight, -10f, 10f);
if (MP.IsInMultiplayer) {
MP.WatchEnd(); // We are done watching!
}
}
static MyObject instance = new MyObject();
class MyObject {
[SyncField]
public float weight;
}
}
...
```
That's all! So easy! Is it...?
#### Gizmos
Oh dear! Oh deity no no--- NO- Pray you aren't dealing with them with Reflection. See the Examples for dark magic. But if it's your own code, just move the Action delegate to a method anywhere and tag it with [SyncMethod].
#### Sync Workers
Sometimes, when you use [SyncMethod], you will encounter... `Error writing type: MyType, ... Multiplayer.Client.SerializationException: No writer for type MyType`
MPAPI needs to know how to reference your type to send it to the other clients. Basically if you have...
```csharp=
[SyncMethod]
static MyReturnType MyMethod(MyArgumentType arg1, MyArgumentType2 arg2, int arg3, float arg4) { ... }
```
You need to teach MPApi to handle your types, so they can send them over the wire to the other players. For our example, we will need to write a SyncWorker for MyReturnType, MyArgumentType and MyArgumentType2, most of RimWorld types are already handled so don't be afraid to use them.
Assuming MyReturnType is made of aString, anInt and aFloat, here is how you write a SyncWorker for it:
```csharp=
[SyncWorker(shouldConstruct = true)]
static void SyncMyReturnType(SyncWorker sync, ref MyReturnType type) {
sync.Bind(type.aString);
sync.Bind(type.anInt);
sync.Bind(type.aFloat);
}
```
Bind does the writing and reading for you, shouldConstruct makes it so type is constructed before being passed to the SyncWorker (not needed for structs). But if you need more complex situations, you can use Write and Read.
Assuming MyArgumentType requires an argument Pawn to construct, you'd write it this way:
```csharp=
[SyncWorker]
static void SyncMyArgumentType(SyncWorker sync, ref MyArgumentType type) {
if (sync.isWriting) {
sync.Write(type.Pawn);
} else {
Pawn pawn = sync.Read<Pawn>();
type = new MyArgumentType(pawn);
}
}
```
*Todo migrate the rest of the API doc
https://github.com/Parexy/Multiplayer/wiki/API#mp
### General Documentation
#### Debugging Rimworld/Mods
1. Make a backup of the original file `steamapps/common/Rimworld/MonoBleedingEdge/EmbedRuntime/mono-2.0-bdwgc.dll`
2. Replace it with a patched version from https://github.com/0xd4d/dnSpy/releases - at time of writing, Rimworld needed 2019.2.17, which was only available on https://github.com/0xd4d/dnSpy/issues/1392#issuecomment-602254675 - mirror: https://cdn.discordapp.com/attachments/308960765908615170/691385539710025789/mono-2.0-bdwgc.dll from the [Harmony Discord Server](https://github.com/pardeike/Harmony#documentation)
3. Download dnSpy
4. Inside dnSpy, choose `Debug` -> `Start Debugging...` -> `Debug Engine=Unity, Executable=RimWorldWin64.exe` and let it start the game in debug mode.
5. Load Assembly-CSharp.dll before and set breakpoints to your liking
#### Introduction
The networking for Rimworld, like networking for many RTS games is done via **deterministic lockstep**, if you are not familiar with this I suggest looking in [here](https://ruoyusun.com/2019/03/28/game-networking-1.html).
#### Entry Point
The class *HostWindow:89* contains the entry point of the mod. It is responsible for creating the Multiplayer menu item, which allows the player to either join an existing game, or host a new game.
#### Init of MP Menu Item
1. Create listener to add or update LAN server connections
2. Create NetManager on port 5100
#### Chat Commands
It looks like there are 2 commands, which are hard-coded. Chat command loading might need to be made dynamic, preferably by registering chat commands defined in a file. MultiplayerServer:77
#### ClientUtil
Contains method to start hosting a server, good starting point see how the server works.
#### Arbiter
When hosting a server, the Arbiter is started here: ClientUtil:201
#### MultiplayerServer
Main server update loop: MultiplayerServer:109 The server ticks at a rate of 16.66... ms per tick. This tick rate is variable though, since the sleep timing is set to 10ms. Can the sleep time be calculated, so that the tick rate is going to be as close to 16.66...ms as possible?
#### Client connections to server
Steam connection
SteamIntegration:114
### "Handshake" process
#### Foreword
Firstly, I’d just like to clarify that by “handshake” I mean the veritable hailstorm of packets sent between the two instances of Rimworld - which I’ll refer to as the “client” and “server” for the joining player and the hosting player respectively, despite this not being entirely accurate - during the first few seconds of a connection, during which def databases are checked for mismatches, protocol versions are checked, and world data is exchanged.
#### Initiating the Connection
The client initiates the connection by opening a socket to the server’s port and sending a *ClientProtocol packet (ID 0)*, along with the current network protocol the client is using. The server receives this and checks it against its own protocol version. If the two don’t match, then the client is disconnected with the reason *Protocol (1)*. Otherwise, the server sends back a *ServerModList (16) packet* with its RimWorld version as a length-prefixed string, and the mods the server is running, in order, as a length-prefixed array of length-prefixed strings.
#### Defs Check
The client does not do any validation on either of these fields, assuming both the rimworld version and modlist is fine - though it does keep track of them - and in response sends the data needed to validate that the def databases match. This consists of a *ClientDefs (1)* packet, and along with this it sends the number of DefDatabases registered as well as, for each of these databases, what type it contains (e.g. ThingDef) as a length-prefixed string, followed by a 32-bit integer containing the number of Defs in that database, followed by a 32-bit integer containing the hash of the database.
If the client sends more than 512 database types, it’s disconnected with reason *Generic (0)*
The server checks each of these databases against its own and appends the result of each check to an array of statuses. If a database doesn’t exist on the server, but does on the client, then the status *NotFound (1)* is used. If the count of defs mismatches, then the status *CountDiff (2)* is used. If the hash mismatches, then *HashDiff (3)* is used. Otherwise, *OK (0)* is used. If any of these statuses is NOT Ok then the client is disconnected with reason *Defs (2)* and the status array.
Otherwise, the server sends *ServerDefsOK (17)* along with the game name and the client’s player id.
#### World Download
The client, upon receiving this packet, sets its internal status to *Downloading (1)*, and sends a *ClientUsername (2)* packet along with its chosen username. The server validates that this is not null or empty (in which case the packet is completely ignored) and then checks that it is between 3 and 15 characters long (inclusive) or kicks the client with reason *UsernameLength (3)*. A handful of other checks are also performed, firstly that the client’s name is alphanumeric (without spaces) - if this check if failed the client is dropped with reason *UsernameChars (4)* - and that it’s not taken - if this is the case, then it’s kicked with reason *UsernameAlreadyOnline (5)*.
At this point the server broadcasts a notification to each client, stating that a player has joined, and providing their username, and it also sends an updated player list.
Next, the server sets the player’s faction (currently only to the MP faction), and if the player is the only person on their faction online, then a notification is sent out that their faction is now online.
The server then sends the client a chunked *ServerWorldData (18) packet*, consisting of:
The player’s faction ID as a 32-bit int Another 32-bit int for the current tick A length-prefixed byte array of the gzip-compressed world data A length-prefixed array of map commands (consisting each of a map id, followed by a length-prefixed array of commands) And finally a length-prefixed array of gzip-compressed map data (consisting of a map id, then a length-prefixed byte array of the data itself).
It also sends a *ServerPlayerList (19)* packet consisting of the *action id (List/0)* as a byte, then a length-prefixed array of serialized player data.
The client decompresses this data and then invokes the standard rimworld load methods to load the save, followed by simulating the world at high-speed until the tick catches up with the current server-side one.
Once this is done, the client sends a *ClientWorldReady (3) packet*, at which point the server sets the player’s state to Playing, and the handshake is complete.
### Localization
We are using [this](https://github.com/rwmt/Multiplayer/issues/15) Github issue to track the languages we have left to localize for. If you know any of these languages **well**, you can contribute by downloading [this file](https://cdn.discordapp.com/attachments/277256016045932544/685180024352931850/Multiplayer.xml) and translating every english phrase between > and < into your language.
When you're done, either make a Github PR for the language or send the translated file to one of the admins and they will review it.
## Detailed Dev Documentation
The following is more detailed documentation.
<table>
<tbody>
<tr>
<td><a href="#mp">MP</a></td>
<td><a href="#isynccall">ISyncCall</a></td>
</tr>
<tr>
<td><a href="#syncmethodattribute">SyncMethodAttribute</a></td>
<td><a href="#syncfieldattribute">SyncFieldAttribute</a></td>
</tr>
<tr>
<td><a href="#syncworkerattribute">SyncWorkerAttribute</a></td>
<td><a href="#syncworkerdelegate\">SyncWorkerDelegate</a></td>
</tr>
<tr>
<td><a href="#isyncfield">ISyncField</a></td>
<td><a href="#isynchronizable">ISynchronizable</a></td>
</tr>
<tr>
<td><a href="#isyncmethod">ISyncMethod</a></td>
<td><a href="#isyncdelegate">ISyncDelegate</a></td>
</tr>
<tr>
<td><a href="#syncworker">SyncWorker</a></td>
<td><a href="#synccontext">SyncContext</a></td>
</tr>
</tbody>
</table>
### MP
The primary static class that contains methods used to interface with the multiplayer mod.
#### API
Contains the API version
#### enabled
##### Value
Returns if API is initialized.
#### IsHosting
##### Value
Returns if currently running on a host.
#### IsInMultiplayer
##### Value
Returns if currently running in a multiplayer session (both on client and host).
#### PlayerName
##### Value
Returns local player's name.
#### RegisterSyncDelegate(inType, nestedType, methodName, fields, args)
Registers the syncDelegate. Handles anonymous nested types, you will have to figure out the name of your target by decompiling.
##### Returns
The sync delegate.
| Name | Description |
| ---- | ----------- |
| inType | *System.Type*<br>In type. |
| nestedType | *System.String*<br>Nested type. |
| methodName | *System.String*<br>Method name. |
| fields | *System.String[]*<br>Fields. |
| args | *System.Type[]*<br>Arguments. |
#### RegisterSyncDelegate(type, nestedType, method)
Registers the syncDelegate. Handles anonymous nested types, you will have to figure out the name of your target by decompiling.
##### Returns
The sync delegate.
| Name | Description |
| ---- | ----------- |
| type | *System.Type*<br>Type. |
| nestedType | *System.String*<br>Nested type. |
| method | *System.String*<br>Method. |
#### RegisterSyncField(field)
Registers a field for syncing and returns it's <a href="#isyncfield">ISyncField</a>.
##### Remarks
It's recommended to use <a href="#syncfieldattribute">SyncFieldAttribute</a> instead, unless you have to otherwise.
They must be Watched between MP.WatchBegin and MP.WatchEnd with the MP.Watch* methods
| Name | Description |
| ---- | ----------- |
| field | *System.Reflection.FieldInfo*<br>FieldInfo of a field to register |
##### Returns
A new registered <a href="#isyncfield">ISyncField</a>
#### RegisterSyncField(targetType, memberPath)
Registers a field for syncing and returns it's <a href="#isyncfield">ISyncField</a>.
##### Remarks
It's recommended to use <a href="#syncfieldattribute">SyncFieldAttribute</a> instead, unless you have to otherwise.
They must be Watched between MP.WatchBegin and MP.WatchEnd with the MP.Watch* methods
| Name | Description |
| ---- | ----------- |
| targetType | *System.Type*<br>
Type of the target class that contains the specified member
if null, memberPath will point at field from the global namespace in the "Type/fieldName" format.
|
| memberPath | *System.String*<br>Path to a member. If the member is to be indexed, it has to end with /[] eg. `"myArray/[]"` |
##### Returns
A new registered <a href="#isyncfield">ISyncField</a>
#### RegisterSyncMethod(method, argTypes)
Registers a method for syncing and returns its <a href="#isyncmethod">ISyncMethod</a>.
| Name | Description |
| ---- | ----------- |
| method | *System.Reflection.MethodInfo*<br>MethodInfo of a method to register |
| argTypes | *Multiplayer.API.SyncType[]*<br>Method's parameter types |
##### Remarks
It's recommended to use <a href="#syncmethodattribute">SyncMethodAttribute</a> instead, unless you have to otherwise.
##### Returns
A new registered <a href="#isyncmethod">ISyncMethod</a>
##### Example
Register a method for syncing using reflection and set it to debug only.
```cs
RegisterSyncMethod(typeof(MyType).GetMethod(nameof(MyType.MyMethod))).SetDebugOnly();
```
#### RegisterSyncMethod(type, methodOrPropertyName, argTypes)
Registers a method for syncing and returns its <a href="#isyncmethod">ISyncMethod</a>.
##### Remarks
It's recommended to use <a href="#syncmethodattribute">SyncMethodAttribute</a> instead, unless you have to otherwise.
| Name | Description |
| ---- | ----------- |
| type | *System.Type*<br>Type that contains the method |
| methodOrPropertyName | *System.String*<br>Name of the method |
| argTypes | *Multiplayer.API.SyncType[]*<br>Method's parameter types |
##### Returns
A new registered <a href="#isyncmethod">ISyncMethod</a>
#### RegisterSyncWorker\<T\>(syncWorkerDelegate, targetType, isImplicit, shouldConstruct)
Registers the SyncWorker based on SyncWorkerDelegate.
##### Remarks
It's recommended to use <a href="#syncworkerattribute">SyncWorkerAttribute</a> instead, unless you have to otherwise.
| Name | Description |
| ---- | ----------- |
| syncWorkerDelegate | *Multiplayer.API.SyncWorkerDelegate{\`\`0}*<br>Sync worker delegate. |
| targetType | *System.Type*<br>Type to handle. |
| isImplicit | *System.Boolean*<br>If set to `true` the SyncWorker will handle the type and all the derivate Types. |
| shouldConstruct | *System.Boolean*<br>If set to `true` the SyncWorker will be provided with an instance created with no arguments. |
##### Type Parameters
- T - Type to handle.
#### Watch(target, fieldName, index)
Helper method for <a href="#isyncfield.watch(system.object,system.object)">ISyncField.Watch(System.Object,System.Object)</a> given an instance.
| Name | Description |
| ---- | ----------- |
| target | *System.Object*<br>An object of type set in the <a href="#isyncfield">ISyncField</a> to watch |
| fieldName | *System.String*<br><a href="#isyncfield">ISyncField</a> name of the field to watch for changes |
| index | *System.Object*<br>Index in the field path set in <a href="#isyncfield">ISyncField</a> |
#### Watch(memberPath, target, index)
Helper method for <a href="#isyncfield.watch(system.object,system.object)">ISyncField.Watch(System.Object,System.Object)</a> given an instance.
| Name | Description |
| ---- | ----------- |
| memberPath | *System.String*<br><a href="#isyncfield">ISyncField</a> the memberPath of the ISyncField |
| target | *System.Object*<br>An object of type set in the <a href="#isyncfield">ISyncField</a> to watch, null for static |
| index | *System.Object*<br>Index in the field path set in <a href="#isyncfield">ISyncField</a> |
#### Watch(type, fieldName, index)
Helper method for <a href="#isyncfield.watch(system.object,system.object)">ISyncField.Watch(System.Object,System.Object)</a> given a type.
| Name | Description |
| ---- | ----------- |
| type | *System.Type*<br>An object of type set in the <a href="#isyncfield">ISyncField</a> to watch, for static types |
| fieldName | *System.String*<br><a href="#isyncfield">ISyncField</a> name of the field to watch for changes |
| index | *System.Object*<br>Index in the field path set in <a href="#isyncfield">ISyncField</a> |
#### WatchBegin
Starts a new synchronization stack.
##### Remarks
Has to be called before invoking Watch methods.
See also <a href="#isyncfield.watch(system.object,system.object)">ISyncField.Watch(System.Object,System.Object)</a>.
#### WatchEnd
Ends the current synchronization stack and executes it.
##### Remarks
Has to be called after invoking Watch methods.
See also <a href="#isyncfield.watch(system.object,system.object)">ISyncField.Watch(System.Object,System.Object)</a>.
### SyncContext
Context flags which are sent along with a command
#### CurrentMap
Send current map context
#### MapMouseCell
Send mouse cell context (emulates mouse position)
#### MapSelected
Send map selected context (object selected on the map)
#### None
Default value. (no context)
#### QueueOrder_Down
Send order queue context (emulates pressing KeyBindingDefOf.QueueOrder)
#### WorldSelected
Send world selected context (object selected on the world map)
### SyncFieldAttribute
An attribute that is used to mark fields for syncing. It will be Watched for changes by the MPApi when instructed.
#### Example
An example showing how to mark a field for syncing.
```cs
public class MyClass
{
[SyncField]
bool myField;
...
}
```
### Constructor(context)
| Name | Description |
| ---- | ----------- |
| context | *Multiplayer.API.SyncContext*<br>Context |
#### bufferChanges
Instructs SyncField to use a buffer instead of syncing instantly (when <a href="#mp.watchend">MP.WatchEnd</a> is called).
#### cancelIfValueNull
Instructs SyncField to cancel synchronization if the value of the member it's pointing at is null.
#### debugOnly
Instructs SyncField to synchronize only in debug mode.
#### hostOnly
Instructs SyncField to synchronize only if it's invoked by the host.
#### inGameLoop
Instructs SyncField to sync in game loop.
#### version
### SyncMethodAttribute
An attribute that is used to mark methods for syncing. The call will be replicated by the MPApi on all clients automatically.
#### Example
An example showing how to mark a method for syncing.
```cs
[SyncMethod]
public void MyMethod(...)
{
...
}
```
### Constructor(context)
| Name | Description |
| ---- | ----------- |
| context | *Multiplayer.API.SyncContext*<br>Context |
#### cancelIfAnyArgNull
Instructs SyncMethod to cancel synchronization if any arg is null (see <a href="#isyncmethod.cancelifanyargnull">ISyncMethod.CancelIfAnyArgNull</a>).
#### cancelIfNoSelectedMapObjects
Instructs SyncMethod to cancel synchronization if no map objects were selected during the call (see <a href="#isyncmethod.cancelifnoselectedmapobjects">ISyncMethod.CancelIfNoSelectedMapObjects</a>).
#### cancelIfNoSelectedWorldObjects
Instructs SyncMethod to cancel synchronization if no world objects were selected during call replication(see <a href="#isyncmethod.cancelifnoselectedworldobjects">ISyncMethod.CancelIfNoSelectedWorldObjects</a>).
#### debugOnly
Instructs SyncMethod to synchronize only in debug mode (see <a href="#isyncmethod.setdebugonly">ISyncMethod.SetDebugOnly</a>).
#### exposeParameters
A list of types to expose (see <a href="#isyncmethod.exposeparameter(system.int32)">ISyncMethod.ExposeParameter(System.Int32)</a>)
### SyncWorker
An abstract class that can be both a reader and a writer depending on implementation.
#### Remarks
See <a href="#isynchronizable">ISynchronizable</a> and <a href="#syncworkerattribute">SyncWorkerAttribute</a> for usage examples.
### Bind(obj)
Reads or writes an object inheriting <a href="#isynchronizable">ISynchronizable</a> interface.
#### Remarks
Does not create a new object.
| Name | Description |
| ---- | ----------- |
| obj | *Multiplayer.API.ISynchronizable@*<br>object to bind |
### Bind(obj)
Reads or writes an object referenced by obj.
| Name | Description |
| ---- | ----------- |
| obj | *System.Boolean@*<br>object to bind |
### Bind(obj)
Reads or writes an object referenced by obj.
| Name | Description |
| ---- | ----------- |
| obj | *System.Byte@*<br>object to bind |
### Bind(obj)
Reads or writes an object referenced by obj.
| Name | Description |
| ---- | ----------- |
| obj | *System.Double@*<br>object to bind |
### Bind(obj)
Reads or writes an object referenced by obj.
| Name | Description |
| ---- | ----------- |
| obj | *System.Int16@*<br>object to bind |
### Bind(obj)
Reads or writes an object referenced by obj.
| Name | Description |
| ---- | ----------- |
| obj | *System.Int32@*<br>object to bind |
### Bind(obj)
Reads or writes an object referenced by obj.
| Name | Description |
| ---- | ----------- |
| obj | *System.Int64@*<br>object to bind |
### Bind(obj, name)
Uses reflection to bind a field or property
| Name | Description |
| ---- | ----------- |
| obj | *System.Object*<br>
object where the field or property can be found
if null, name will point at field from the global namespace
|
| name | *System.String*<br>path to the field or property |
### Bind(obj)
Reads or writes an object referenced by obj.
| Name | Description |
| ---- | ----------- |
| obj | *System.SByte@*<br>object to bind |
### Bind(obj)
Reads or writes an object referenced by obj.
| Name | Description |
| ---- | ----------- |
| obj | *System.Single@*<br>object to bind |
### Bind(obj)
Reads or writes an object referenced by obj.
| Name | Description |
| ---- | ----------- |
| obj | *System.String@*<br>object to bind |
### Bind(obj)
Reads or writes an object referenced by obj.
| Name | Description |
| ---- | ----------- |
| obj | *System.UInt16@*<br>object to bind |
### Bind(obj)
Reads or writes an object referenced by obj.
| Name | Description |
| ---- | ----------- |
| obj | *System.UInt32@*<br>object to bind |
### Bind(obj)
Reads or writes an object referenced by obj.
| Name | Description |
| ---- | ----------- |
| obj | *System.UInt64@*<br>object to bind |
### Bind\<T\>(obj)
Reads or writes an object referenced by obj
#### Remarks
Can read/write types using user defined syncers, <a href="#isynchronizable">ISynchronizable</a>s and readers/writers implemented by the multiplayer mod.
#### Type Parameters
- T - type of the object to bind
| Name | Description |
| ---- | ----------- |
| obj | *\`\`0@*<br>object to bind |
### BindType\<T\>(type)
Reads or writes a <a href="#system.type">System.Type</a> referenced by type.
#### Type Parameters
- T - Base type that type derives from.
| Name | Description |
| ---- | ----------- |
| type | *System.Type@*<br>type to bind |
### isWriting
if is currently writing.
### Read\<T\>
Read the specified Type from the memory stream, only active during reading.
#### Returns
The requested Type object. Null if writing.
#### Type Parameters
- T - The Type to read.
### Write\<T\>(obj)
Write the specified obj, only active during writing.
| Name | Description |
| ---- | ----------- |
| obj | *\`\`0*<br>Object to write. |
#### Type Parameters
- T - Type to write.
## SyncWorkerAttribute
An attribute that marks a method as a SyncWorker for a type specified in its second parameter.
#### Remarks
Method with this attribute has to be static.
#### Example
An implementation that manually constructs an object.
```cs
[SyncWorkerAttribute]
public static void MySyncWorker(SyncWorker sync, ref MyClass inst)
{
if(!sync.isWriting)
inst = new MyClass("hello");
sync.bind(ref inst.myField);
}
```
An implementation that instead of creating a new object, references its existing one which resides in MyThingComp that inherits ThingComp class.
Subclasses of ThingComp are sent as a reference by the multiplayer mod itself.
```cs
[SyncWorkerAttribute]
public static void MySyncWorker(SyncWorker sync, ref MyClass inst)
{
if(!sync.isWriting)
MyThingComp parent = null;
sync.Bind(ref parent); // Receive its parent
inst = new MyClass(parent);
else
sync.Bind(ref inst.parent); // Send its parent
sync.bind(ref inst.myField);
}
```
### isImplicit
Decides if the type specified in the second parameter should also be used as a syncer for all of its subclasses.
### shouldConstruct
Decides if the method should get an already constructed object in case of reading data.
## SyncWorkerDelegate\`1
SyncWorker signature for adding new Types.
| Name | Description |
| ---- | ----------- |
| obj | *\`\`0*<br>Target Type |
#### Remarks
<a href="#syncworkerattribute">SyncWorkerAttribute</a> for usage examples.
## UninitializedAPI
An exception that is thrown if you try to use the API without avaiable host.
## ISyncCall
ISyncCall interface.
#### Remarks
Used internally
### DoSync(target, args)
Manually calls the synced method.
| Name | Description |
| ---- | ----------- |
| target | *System.Object*<br>Object currently bound to that method. Null if the method is static. |
| args | *System.Object[]*<br>Parameters to call the method with. |
#### Returns
if the original call should be canceled.
## ISyncDelegate
Sync delegate.
#### Remarks
See <a href="#mp.registersyncdelegate(system.type,system.string,system.string)">MP.RegisterSyncDelegate(System.Type,System.String,System.String)</a> and <a href="#mp.registersyncdelegate(system.type,system.string,system.string,system.string[],system.type[])">MP.RegisterSyncDelegate(System.Type,System.String,System.String,System.String[],System.Type[])</a> to see how to use it.
### CancelIfNoSelectedObjects
Cancels if no selected objects.
#### Returns
self
### RemoveNullsFromLists(listFields)
Removes the nulls from lists.
#### Returns
self
| Name | Description |
| ---- | ----------- |
| listFields | *System.String[]*<br>List fields. |
### SetContext(context)
Sets the context.
#### Returns
self
| Name | Description |
| ---- | ----------- |
| context | *Multiplayer.API.SyncContext*<br>Context. |
### SetDebugOnly
Sets the debug only.
#### Returns
self
## ISyncField
SyncField interface.
#### Example
Creates and registers a SyncField that points to `myField` in object of type `MyType` and enables its change buffer.
```cs
MPApi.SyncField(typeof(MyType), "myField").SetBufferChanges();
```
Creates and registers a SyncField that points to `myField` which resides in `MyStaticClass`.
```cs
MPApi.SyncField(null, "MyAssemblyNamespace.MyStaticClass.myField");
```
Creates and registers a SyncField that points to `myField` that resides in an object stored by myEnumberable defined in an object of type `MyType`.
To watch this one you have to supply an index in <a href="#isyncfield.watch(system.object,system.object)">ISyncField.Watch(System.Object,System.Object)</a>.
```cs
MPApi.SyncField(typeof(MyType), "myEnumerable/[]/myField");
```
### CancelIfValueNull
Instructs SyncField to cancel synchronization if the value of the member it's pointing at is null.
#### Returns
self
### DoSync(target, value, index)
Manually syncs a field.
| Name | Description |
| ---- | ----------- |
| target | *System.Object*<br>An object of type set in the <a href="#isyncfield">ISyncField</a>. Set to null if you're watching a static field. |
| value | *System.Object*<br>Value to apply to the synced field. |
| index | *System.Object*<br>Index in the field path set in <a href="#isyncfield">ISyncField</a> |
#### Returns
if the change should be canceled.
### InGameLoop
Instructs SyncField to sync in game loop.
#### Returns
self
### PostApply(action)
Adds an Action that runs after a field is synchronized.
| Name | Description |
| ---- | ----------- |
| action | *System.Action{System.Object,System.Object}*<br>An action ran after a field is synchronized. Called with target and value. |
#### Returns
self
### PreApply(action)
Adds an Action that runs before a field is synchronized.
| Name | Description |
| ---- | ----------- |
| action | *System.Action{System.Object,System.Object}*<br>An action ran before a field is synchronized. Called with target and value. |
#### Returns
self
### SetBufferChanges
Instructs SyncField to use a buffer instead of syncing instantly (when <a href="#mp.watchend">MP.WatchEnd</a> is called).
#### Returns
self
### SetDebugOnly
Instructs SyncField to synchronize only in debug mode.
#### Returns
self
### SetHostOnly
Instructs SyncField to synchronize only if it's invoked by the host.
#### Returns
self
### SetVersion(System.Int32)
#### Returns
self
### Watch(target, index)
| Name | Description |
| ---- | ----------- |
| target | *System.Object*<br>An object of type set in the <a href="#isyncfield">ISyncField</a>. Set to null if you're watching a static field. |
| index | *System.Object*<br>Index in the field path set in <a href="#isyncfield">ISyncField</a>. |
#### Returns
self
## ISynchronizable
An interface that allows syncing objects that inherit it.
### Sync(sync)
An entry point that is used when object is to be read/written.
#### Remarks
Requires a default constructor that takes no parameters.
Check <a href="#syncworkerattribute">SyncWorkerAttribute</a> to see how to make a syncer that allows for a manual object construction.
| Name | Description |
| ---- | ----------- |
| sync | *Multiplayer.API.SyncWorker*<br>A SyncWorker that will read/write data bound with Bind methods. |
#### Example
A simple implementation that binds object's fields x, y, z for reading/writing.
```cs
public void Sync(SyncWorker sync)
{
sync.Bind(ref this.x);
sync.Bind(ref this.y);
sync.Bind(ref this.z);
}
```
An implementation that sends field a, but saves it back into field b when it's received.
```cs
public void Sync(SyncWorker sync)
{
if(sync.isWriting)
sync.Bind(ref this.a);
else
sync.Bind(ref this.b);
}
```
### ISyncMethod
SyncMethod interface.
##### Remarks
See <a href="#syncmethodattribute">SyncMethodAttribute</a>, <a href="#mp.registersyncmethod(system.reflection.methodinfo,multiplayer.api.synctype[])">MP.RegisterSyncMethod(System.Reflection.MethodInfo,Multiplayer.API.SyncType[])</a> and <a href="#mp.registersyncmethod(system.type,system.string,multiplayer.api.synctype[])">MP.RegisterSyncMethod(System.Type,System.String,Multiplayer.API.SyncType[])</a> to see how to use it.
#### CancelIfAnyArgNull
Instructs SyncMethod to cancel synchronization if any arg is null.
##### Returns
self
#### CancelIfNoSelectedMapObjects
Instructs SyncMethod to cancel synchronization if no map objects were selected during call replication.
##### Returns
self
#### CancelIfNoSelectedWorldObjects
Instructs SyncMethod to cancel synchronization if no world objects were selected during call replication.
##### Returns
self
#### ExposeParameter(index)
Use parameter's type's IExposable interface to transfer its data to other clients.
##### Remarks
IExposable is the interface used for saving data to the save which means it utilizes IExposable.ExposeData() method.
| Name | Description |
| ---- | ----------- |
| index | *System.Int32*<br>Index at which parameter is to be marked to expose |
##### Returns
self
#### MinTime(time)
Currently unused in the Multiplayer mod.
| Name | Description |
| ---- | ----------- |
| time | *System.Int32*<br>Milliseconds between resends |
##### Returns
self
#### SetContext(context)
Instructs method to send context along with the call.
##### Remarks
Context is restored after method is called.
| Name | Description |
| ---- | ----------- |
| context | *Multiplayer.API.SyncContext*<br>One or more context flags |
##### Returns
self
#### SetDebugOnly
Instructs SyncMethod to synchronize only in debug mode.
##### Returns
self
#### SetPostInvoke(action)
Adds an Action that runs after a call is replicated on client.
| Name | Description |
| ---- | ----------- |
| action | *System.Action{System.Object,System.Object[]}*<br>An action ran after a call is replicated on client. Called with target and value. |
##### Returns
self
#### SetPreInvoke(action)
Adds an Action that runs before a call is replicated on client.
| Name | Description |
| ---- | ----------- |
| action | *System.Action{System.Object,System.Object[]}*<br>An action ran before a call is replicated on client. Called with target and value. |
#### Returns
self
### SetVersion(version)
| Name | Description |
| ---- | ----------- |
| version | *System.Int32*<br>Handler version |
#### Returns
self
---
## What is this wiki?
This wiki is in the form of a hackmd book. It is a live edited, community contributable that is somewhere between a discord conversation and a github wiki. To contribute, you must make an account with hackmd, but you can link your github account and that will work fine.
Now you might think that community editing is a good way to get the entire wiki griefed, but hackmd has a pretty robust backup system that should prevent anyones damage from being anything but a click to undo.
Anyway, be respectful of other's notes. Don't remove anything unless it's outright wrong or stupid.