Stolen Realm
Install with App

Details

Latest version
0.2.0
Last Updated
First Uploaded
Downloads
26
Likes
0
Size
28KB
Dependants

Categories

ModNet

Lets mods send messages to the other modded players in a multiplayer game, through the game's own network. Other mods use it to share what only they know about, such as a player's emote or custom model, and to check that everyone has the same mod version.

ModNet is a library. On its own it does nothing you can see in play. Every player who wants a mod's shared features needs ModNet and that mod.

  • Hello handshake. Each modded player announces their installed mods and versions when they join.
  • Join check. The host's Steam lobby lists the host's mods that use ModNet. If you lack one of them, joining that lobby is refused and a popup lists what to install. A version that differs from the host's only shows a warning once you're in. Direct-IP games and hosts without ModNet 0.2+ aren't checked.
  • Players card. With Mods Menu installed, Options → Mods has a ModNet card listing who runs which mods, with version differences highlighted.

A game where some players don't have ModNet hasn't been tested yet.

Settings

Setting Default
Enabled on Exchange messages with other modded players. Off: mods behave as if nobody else had them
Check Mods On Join on Refuse to join a Steam lobby whose host runs ModNet mods you don't have; warn about version differences
Log Traffic off Log every message sent and received (turn on when reporting a multiplayer problem)
Test Tools off Ping and payload-size test buttons on the ModNet card

The file is BepInEx\config\com.helvetica.modnet.cfg.

How it works

Each message is a string passed to the game's replicated Root.SetAnimationTrigger(Character, string). The host relays it to every client. On a modded player, a Harmony prefix catches the string before it reaches the Animator.

For mod developers

  1. Add a soft dependency, so your mod still loads without ModNet. It also makes your mod one that players need in order to join a host who runs it (join check):
    [BepInDependency("com.helvetica.modnet", BepInDependency.DependencyFlags.SoftDependency)]
    
  2. Reference ModNet.dll with Private="false", so it isn't copied next to your mod.
  3. Put every ModNet call in one nested class, and only call into it after checking that ModNet is installed: the runtime loads ModNet's types only when that class's code runs.
    internal static class MySync
    {
        static bool _available;
        internal static void Init() { _available = true; Bridge.Hook(); }        // from OnModLoaded, if installed
        internal static void Tell(Character c, string what) { if (_available) Bridge.Send(c, what); }
    
        static class Bridge
        {
            internal static void Send(Character c, string p) => ModNetApi.Send("mymod", p, c);
            internal static void Hook()
            {
                ModNetApi.Register("mymod", m => Handle(m.Character, m.Payload));
                ModNetApi.PlayerHello += _ => ResendState();                      // late joiners
            }
        }
    }
    // OnModLoaded: if (Chainloader.PluginInfos.ContainsKey("com.helvetica.modnet")) MySync.Init();
    

API (ModNetApi)

Member
Send(channel, payload, subject) To every other modded player, not to yourself. subject = one of your characters; null = any. Returns false if not sent (true when queued)
Register(channel, handler) NetMessage: Channel, Payload, Character (the subject), SenderOwnerId, Sender
Ready, IsMultiplayer, MyOwnerId Sending starts about 3 s after your characters appear in the world
BecameReady Raised once per session when sending starts
PlayerHello Raised when a modded player is first heard from in a session. Re-send your state here
PlayerLeft Raised when a player leaves
Players, GetPlayer(ownerId) PlayerInfo: Name, IsMe, HasModNet, SteamId, Mods (GUID → version), Has(guid, version)
EveryoneHas(guid, version) Gate for features that need every player to have the mod

Rules

  • Events only. Send events, never per-frame data.
  • Size and rate. Payloads up to 1000 characters. Sending is throttled to 10 per second, with bursts of 20.
  • Sending from a handler is fine. A message sent while another one is being received (from a handler, or from PlayerHello) is queued and goes out on the next frame.
  • Separator. Use | inside payloads. Channel names can't contain it.
  • Don't trust the sender. A message's Character is the sender's own character. Ignore messages that would change your own characters.
  • Protocol version. ModNetApi.Protocol is part of the wire marker. Players on another protocol version ignore each other.
Thunderstore development is made possible with ads. Please consider making an exception to your adblock.