(Feat) Multiplayer - akarnokd/ThePlanetCrafterMods GitHub Wiki
This page documents the (Feat) Multiplayer mod.
This mod adds (some level of) multiplayer support to The Planet Crafter.
Currently, this means the ability to play a world (new or existing save) by two or more players (host, clients).
-
Install BepInEx.
-
đŊ Download the
akarnokd-FeatMultiplayer.zip
for both the host and client.- From NexusMods: https://www.nexusmods.com/planetcrafter/mods/29
- From releases: https://github.com/akarnokd/ThePlanetCrafterMods/releases/latest
-
Extract the zip into the
BepInEx\plugins
directory.- Please keep the folder inside the zip so you'll end up with
BepInEx\plugins\akarnokd - (Feat) Multiplayer
- Please keep the folder inside the zip so you'll end up with
-
Ask the host for its IP address.
- If running on the same network (both computers behind NAT or a router), have the host start the game, then on the right side, read off the IP address (without the port) on the main screen.
-
On the client, open
akarnokd.theplanetcraftermods.featmultiplayer.cfg
, find[Client]
, and setHostAddress
to the host's IP address. Example:[Client] ## The IP address where the Host can be located from the client. # Setting type: String # Default value: HostAddress = 192.168.0.100
The game and this mod creates log files in the game's save directory:
%USERPROFILE%\AppData\LocalLow\MijuGames\Planet Crafter
-
Player.log
(both sides) -
Player_Client_[name].log
(client side only) -
Player_Host.log
(host side only)
When reporting issues, please post these logs and if possible, post the save of the Host Survival-x.json
as well.
The configuration file is in BepInEx\config\akarnokd.theplanetcraftermods.featmultiplayer.cfg
The following shortcuts work only when the player is not in an UI window. Some keys also require holding the Accessibility Key (default CTRL, vanilla settings):
Shortcut | Description |
---|---|
G | Show Emote wheel. |
H | Toggle showing the location info of other players in an overlay. |
CTRL+I | Spawn chests with all sorts of items on the host. |
CTRL+P | Trigger all kinds of meteor events on the host (one after the other). |
CTRL+H | Toggle health/food/water drain (both sides). |
- Host a co-op world by using one of the (existing or new) saves.
- Join a co-op world (up to 25 players, limited to 8 by default).
- The host world remembers the backpack and equipment of the client.
- See each other on the screen as Astronaut Avatar.
- See each other move around.
- Emote to each other.
- Change coloring of the host and client avatars via config.
- Sync up basic Terraformation status; i.e., grass, water shows for both players.
- Place buildings and items, such as machines, containers, furniture, etc.
- Place doors/windows on buildings
- Crafting
- Mine resources
- Grab items dropped to the ground
- Add and remove items to/from player-built containers
- Items dropped animate on the client side
- Doors open for the other player
- Unlock blueprint microchips; both players receive unlocks
- Set text on containers, labels
- Set colors on beacons
- Interacting with pre-placed chests (blue/golden)
- Terraformation speed
- UPnP port mapping
- Launching rockets by both parties
- See meteor shower and impacts on both sides
- Accessing machine inventories
- Asteroid resources syncing
- Grabbing vegetables or algae
- Seeing the same trees grow
- Sync of day-night cycle
- Sync environmental effects (storms, rains, etc)
- Equipping and unequipping equipment (client)
- Deconstructing on the client
- Game mode sync (chill, standard, intense, etc)
- Dying and death chests (standard only)
- Story elements (messages)
- See each other's flashlights
- Terrain greening upon joining the host
- Shredding items
- Recycling items
- DNA Manipulator
- Picking up larvae
- Story events (should trigger meteor events, but never got that far in testing).
- Spawning of special larvae
- The new machines of 0.5.005 (I can't even name them, sorry).
- No jetpack effects
- Asteroid rocks not in sync or not visible
- Sky backdrop sync (moons can look out of phase)
đ See also: known issues.
Depending on what other mods do to the game, some of them may or may not work properly, at all, or even break this mod. See the sections below for working, non-working and unknown mods.
âšī¸ All sorts of mods can be found here:
- https://planet-crafter.fandom.com/wiki/Modding#Current_Mods
- https://www.nexusmods.com/planetcrafter/mods/
Legend:
- đĩ works, but some action needs to be taken
- đ works, can be on either or both host and client
- đģ
ghost only
Mod name | Author | Version | Link | Status |
---|---|---|---|---|
(Cheat) Asteroid Landing Position Override | đ | latest | File | đ 1 |
(Cheat) Auto Consume | đ | latest | File | đ |
(Cheat) Auto Launch Rockets | đ | latest | File | đ 1 |
(Cheat) Auto Sequence DNA | đ | latest | File | đ 1 |
(Cheat) Inventory Stacking | đ | latest | File | đĩ 2 |
(Cheat) Highlight Nearby Resources | đ | latest | File | đ |
(Cheat) Teleport To Nearest Minable | đ | latest | File | đĩ 1 |
(Fix) International Loading | đ | latest | File | đ |
(Feat) Command Console | đ | latest | File | đĩ 3 |
(Feat) Space Cows | đ | latest | File | đĩ 2 |
(Misc) Plugin Update Checker | đ | latest | File | đ |
(Save) Automatic Save | đ | latest | File | đĩ 1 |
(UI) Continue | đ | latest | File | đ |
(UI) Custom Inventory Sort All | đ | latest | File | đĩ 2 |
(UI) Hungarian Translation | đ | latest | File | đ |
(UI) Italian Translation | đ | latest | File | đ |
(UI) Menu Shortcut Keys | đ | latest | File | đ |
(UI) Overview Panel | đ | latest | File | đ |
(UI) Show Consumable Counts | đ | latest | File | đ |
(UI) Show MultiTool Mode | đ | latest | File | đ |
(UI) Show Player Inventory Counts | đ | latest | File | đ |
(UI) Show Rocket Counts | đ | latest | File | đ |
(UI) Sort Saves | đ | latest | File | đ |
Asteroid Tweaks | Lathrey | 2.1.0 | Lathrey-AsteroidTweaks.dll | đĩ 2 |
Auto Move | Lathrey | 2.5.0 | Lathrey-AutoMove.dll | đ |
Disable Build Constraints | Lathrey | 2.5.0 | Lathrey-DisableBuildConstraints.dll | đ |
Improve Performance | Lathrey | 2.5.0 | Lathrey-ImprovePerformance.dll | đ |
Toggle Fog | Lathrey | 2.5.0 | Lathrey-ToggleFog.dll | đ |
Better Jetpack | aedenthorn | 0.1.1 | Nexus | đ |
Custom Flashlight | aedenthorn | 0.2.0 | Nexus | đĩ 2 |
Drill Sound | aedenthorn | 0.1.0 | Nexus | đ |
Remote Storage Access | aedenthron | 0.2.1 | Nexus | đ |
Quick Rotate | aedenthorn | 0.1.0 | Nexus | đ |
Quick Store | aedenthron | 0.3.6 | Nexus | đ |
Show Next Unlockables | aedenthron | 0.1.0 | Nexus | đ |
Fix Shallow Water | CarlKenner | 0.0.1 | Link | đ |
Fix Units | CarlKenner | 0.0.1 | Link | đ |
Discord Presence | Ludeo | 1.0.0 | Discord.Presence.zip | đ |
Terraformation Details Overlay | Ludeo | 1.0.0 | Terraformation.Details.Overlay.zip | đ |
Craft The Planet! | doublestop | 0.0.2 | CraftThePlanet-0.0.2.zip | đ |
Compass Always Visible | doublestop | 0.0.1 | CompassAlwaysVisible-0.0.1.zip | đ |
Custom Progress Speed | MikePdog | 1.0.1.1 | mikeypdog-CustomProgressSpeed.zip | đĩ 2 |
No Fall Damage | cisox | 1.0.0.0 | Nexus | đ |
Toggle Beacons | cisox | 1.0.0.0 | Nexus | đ |
- 1 Detects the multiplayer mod and disables itself on the client in multiplayer games.
- 2 Install on both the host and the client. Ensure their configs match on all sides.
- 3 Spawning items doesn't last on the client because the host will delete such objects.
Legend:
- đĩ doesn't work on its own but there might be a workaround
- â doesn't work at all and can't be fixed from this end
Mod name | Author | Version | Link | Status |
---|---|---|---|---|
OreExtractorTweaks | Lathrey | <= 2.1.0 | Lathrey-OreExtractorTweaks.dll | đĩ 1 |
Day Night Cycle Tweaks | Lathrey | <= 2.1.0 | Lathrey-DayNightCycleTweaks.dll | â 2 |
Creative Mode | skrwoor | <= 0.0.0.3 | Nexus | đĩ 3 |
Mobile Crafter | skrwoor | <= 0.0.0.1 | Nexus | â 4 |
Auto Mine | aedenthorn | <= 0.2.2 | Nexus | â 5 |
Better Meteorites | aedenthorn | <= 0.1.1 | Nexus | â 6 |
Craft from Containers | aedenthorn | <= 0.3.2 | Nexus | â 7 |
Spawn Objects | aedenthron | <= 0.3.0 | Nexus | â 8 |
Storage Customization | aedenthorn | <= 0.3.2 | Nexus | â 9 |
Shortcuts | doublestop | 0.0.5 | Shortcuts-0.0.5.zip | đĩ 10 |
Always Deconstruct | cisox | 1.0.0.3 | Nexus | â 11 |
Keep items on death | cisox | 1.0.0.0 | Nexus | â 11 |
Set respawn point | cisox | 1.0.0.0 | Nexus | â 11 |
- 1 May work when the (Cheat) Machine Remote Deposit mod of mine is also installed.
- 2 Runs with its own state and bypasses the vanilla day-night cycle.
- 3 Dying on the client may not work properly, free crafting might not work.
-
4 Conflicting overrides (
TryToCraftInWorld
,AddItemInEquipment
,RemoveItemFromEquipment
). -
5 Creates objects on the client (
CheckForNearbyMinables
). -
6 Very likely conflicting overrides (
Asteroid_CreateImpact_Patch
). - 7 Conflicting overrides; the host has to deduce materials.
-
8 Creates objects on the client (
WorldObjectsHandler.CreateXXX
use). - 9 Modifies inventory size on the client, patches equipment change callbacks (not used on client).
- 10 Quickload borks the gamestate and network state on both host or client.
- 11 Conflicting overrides.
Anything not listed in the previous two sections not yet tested with this mod.
Mod name | Author | Version | Link | Status |
---|---|---|---|---|
* | đ | latest | File |
I haven't decided about these yet.
- Ingame chat.
- Expose message sending and receiving via API to mods.
Yes.
It does as of version 0.2.0.0
We don't have a full player model and I couldn't get anything other than these flat-images to work.
There could be a problem reaching your nearby router or NAT device, it doesn't support UPnP, or you are not behind NAT/router. If your router does support UPnP, you may need to manually add port mapping in the device's setup.
The mod specifies a public API that can be used for
- detecting the current mode (singleplayer or multiplayer),
- creating and processing custom messages,
- sending standard objects as messages,
- retrieving client backpack and equipment inventories.
They are defined as delegate fields on the FeatMultiplayer.Plugin
class to avoid paying the cost of reflective method calls for softly-depending mods.
Also the delegates use standard types only so that other mods don't have to depend on the binary/dll of the multiplayer mod for custom types.
First of all, a third party mod should define a (soft) dependency on this mod via the BepInDepencency
annotation:
I recommend taking the class by source and using it in your project so it doesn't need a hard dependency on the multiplayer mod's dll.
FeatMultiplayerApi mApi = FeatMultiplayerApi.Create();
if (mApi.IsAvailable()) {
Logger.LogInfo("Multiplayer mod found!");
Logger.LogInfo("Current state: " + mApi.GetState());
}
using BepInEx;
[BepInDependency(modFeatMultiplayerGuid, BepInDependency.DependencyFlags.SoftDependency)]
class SomePlugin : BaseUnityPlugin {
const string modFeatMultiplayerGuid
= "akarnokd.theplanetcraftermods.featmultiplayer";
}
(I recommend using a const string
for this as we'll need the mod's Guid next.)
Next, in preferably the Awake
method, locate the the multiplayer mod's plugin object, and read off the fields starting with api
.
using BepInEx.Bootstrap;
private void Awake()
{
if (Chainloader.PluginInfos.TryGetValue(
modFeatMultiplayerGuid, out BepInEx.PluginInfo pi))
{
Func<string> apiGetMultiplayerMode = GetApi(pi, "apiGetMultiplayerMode");
Func<string, int> apiCountByGroupId = GetApi(pi, "apiCountByGroupId");
Action<Func<int, string, ConcurrentQueue<object>, bool>> apiAddMessageParser
= GetApi(pi, "apiAddMessageParser");
Action<Func<object, bool>> apiAddMessageReceiver
= GetApi(pi, "apiAddMessageReceiver");
Action<int, object> apiSend = GetApi(pi, "apiSend");
Action<int> apiSignal = GetApi(pi, "apiSignal");
Action<int, WorldObject> apiSendWorldObject = GetApi(pi, "apiSendWorldObject");
Action<object, Action<object>> apiSuppressInventoryChangeWhile
= GetApi(pi, "apiSuppressInventoryChangeWhile");
Func<int, Inventory> apiGetClientBackpack
= GetApi(pi, "apiGetClientBackpack");
Func<int, Inventory> apiGetClientEquipment
= GetApi(pi, "apiGetClientEquipment");
}
// For convenience
static T GetApi<T>(BepInEx.PluginInfo pi, string name)
{
return (T)AccessTools.Field(pi.Instance.GetType(), name).GetValue(null);
}
}
Of course, put the values into a (static) field inside your plugin.
Returns the current mode in string format: MainMenu
, SinglePlayer
, CoopHost
or CoopClient
.
Usage suggestion: call it in patched methods to check if multiplayer-specific behavior is necessary.
switch (apiGetMultiplayerMode())
{
case "CoopHost":
{
// Custom host behavior
break;
}
case "CoopClient":
{
// Custom client behavior
break;
}
}
Returns the count of existing WorldObject
under a specific group identifier string.
This is more effective than counting them via WorldObjectsHandler.GetAllWorldObjects()
as
the multiplayer mod already tracks these counts.
int biomassRockets = apiCountByGroupId("RocketBiomass1");
Registers a Func
object that will be called when a non-standard message is parsed for a client on the background thread.
This function will receive the client identifier, the raw message string and a queue to talk to the UI thread. The function
should return true
if it successfully identified and parsed the message so no other parsers need to be tried.
đĢ Multi-client is currently not supported so the clientId parameter is ignored.
// register the method
apiAddMessageParser(ParseMyMessage);
// the custom object parsed
class MyMessage {
int clientId;
string content;
}
// the parser
static bool ParseMyMessage(int clientId, string str, ConcurrentQueue<object> queue) {
if (str.StartsWith("MyMessage="))
{
queue.Enqueue(new MyMessage()
{
clientId = clientId,
content = str.Substring(10)
});
return true;
}
return false;
}
Registers a Func
that will receive the non-standard parsed message object and perform the necessary actions on the UI thread.
This function will receive the parsed message object and should return true
if it successfully processed the message.
// register the method
apiAddMessageReceiver(ReceiveMyMessage);
// the receiver
static bool ReceiveMyMessage(object obj)
{
if (obj is MyMessage myMessage) {
MijuTools.Managers.GetManager<BaseHudHandler>()
.DisplayCursorText("", 3f, myMessage.content);
return true;
}
return false;
}
Queues up an object to be sent to a particular or all clients.
The object should implement ToString
in a way that can be identified by a parser on the other side.
ToString
does not accidentally
contain the newline \n
character as it is used to identify the boundaries of messages.
âšī¸ In CoopClient
mode, the clientId
parameter should be zero, indicating the target is
the host. In CoopHost
mode, a clientId
of zero means broadcast to all clients; non-zero targets a specific
client.
đĢ Multi-client is currently not supported so the clientId parameter is ignored.
âšī¸ The string conversion will happen on the background thread.
âšī¸ The message is only queued and may not be sent immediately. Use the apiSignal
to
send it out over the network immediately.
âšī¸ To send a WorldObject
, use apiSendWorldObject
.
int clientId = 0; // all clients or to the host.
apiSend(clientId, new MyMessageToSend() { content = "Hello world!" });
class MyMessageToSend {
string content;
public override string ToString()
{
return "MyMessage=" + content + "\n";
}
}
You can also create the string upfront and send it directly:
apiSend(0, "MyMessage=Hello World!\n");
Signals the network stack to send out any queued-up messages immediately.
Usually this should be called right after an apiSend
call.
đĢ Multi-client is currently not supported so the clientId parameter is ignored.
apiSend(0, "MyMessage=Hello World!\n");
apiSignal(0);
Sends a WorldObject
out immediately to the target client.
đĢ Multi-client is currently not supported so the clientId parameter is ignored.
âšī¸ there is no need to call apiSignal
after this method.
using SpaceCraft;
Group group = GroupsHandler.GetGroupViaId("Iron");
WorldObject wo = WorldObjectsHandler.CreateNewWorldObject(group, 0);
apiSendWorldObject(wo);
Allows performing an action and not send out the related inventory change messages.
It takes an object and an action, which will receive the former object when executing.
Inventory inventory = InventoriesHandler.GetInventoryById(1);
apiSuppressInventoryChangeWhile(inventory, AddToInventory);
static void AddToInventory(object inv)
{
var inventory = inv as Inventory;
Group group = GroupsHandler.GetGroupViaId("Iron");
WorldObject wo = WorldObjectsHandler.CreateNewWorldObject(group, 0);
apiSendWorldObject(wo);
inventory.AddItem(wo);
}
On the host, it returns the given client's shadow backpack inventory, or null
if not present.
Use it to put items into the client's inventory.
đĢ Multi-client is currently not supported so the clientId parameter is ignored.
Inventory inv = apiGetClientBackpack(1);
if (inv != null)
{
apiSuppressInventoryChangeWhile(inventory, AddToInventory);
}
On the host, it returns the given client's shadow equipment inventory, or null
if not present.
Use it to put equipment into the client's equipment slots.
đĢ Multi-client is currently not supported so the clientId parameter is ignored.
Inventory inv = apiGetClientEquipment(1);
if (inv != null)
{
Group group = GroupsHandler.GetGroupViaId("Backpack1");
WorldObject wo = WorldObjectsHandler.CreateNewWorldObject(group, 0);
apiSendWorldObject(wo);
inv.AddItem(wo);
}
Host only. Sends the entire inventory object to the client, including its size and content.
âšī¸ The inventory gets automatically created on the client if it didn't exist.
đĢ Multi-client is currently not supported so the clientId parameter is ignored.
âšī¸ The message is only queued and may not be sent immediately. Use the apiSignal
to
send it out over the network immediately.
Inventory inv = apiGetClientEquipment(1);
inv.GetInsideWorldObjects().Clear();
inv.SetSize(6);
apiSendInventory(1, inv);
apiSignal(1);