-
Notifications
You must be signed in to change notification settings - Fork 0
API Reference
For mods that want to appear as a gamemode in Side Hustle's main-menu list. Register early (in your
OnInitializeMelon); registration is load-order independent, so it does not matter whether Side Hustle or
your mod loads first.
Reference SideHustle.dll and declare it as an optional dependency, so the hub loads first and your mod
still loads cleanly if Side Hustle is absent:
[assembly: MelonOptionalDependencies("SideHustle")]Reference these from a normal modded install:
-
Mods/SideHustle.dll- the API -
MelonLoader/net6/MelonLoader.dll,Il2CppInterop.Runtime.dll -
UnityEngine.CoreModule.dlletc. only if your own gamemode needs them - For Thunderstore, list
DooDesch-SideHustleas a dependency.
namespace SideHustle
{
public static class API
{
// Register (or replace, by Id) a gamemode.
public static void Register(GamemodeDescriptor descriptor);
// Remove a previously registered gamemode. Returns true if one was removed.
public static bool Unregister(string id);
// Host-only: kick a connected player from the current session by their Steam id (id64). A no-op on a
// client or if no such player is connected, and never kicks the host. Returns true if a player was kicked.
public static bool KickPlayer(ulong steamId64, string reason = "Kicked by host");
// True once Side Hustle's own OnInitializeMelon has run.
public static bool IsReady { get; }
// The currently registered gamemodes.
public static IReadOnlyList<GamemodeDescriptor> Registered { get; }
}
}Registering with an existing Id replaces it, so re-registering (hot reload, double init) is safe.
| Field | Meaning |
|---|---|
Id |
Stable, unique id (e.g. "you.yourmode"). Required. Used for de-dup and lobby filtering. |
DisplayName |
Shown in the list (falls back to Id). |
Description |
One-line description under the name. |
Author |
Shown in the list (optional). |
Icon / IconTex
|
Optional Sprite / Texture2D for the list row. If Icon is null, the hub builds one from IconTex. |
Support |
Singleplayer | Multiplayer | Hybrid - decides whether selecting launches straight into SP or shows Singleplayer / Host / Join. |
Surface |
MenuSpace (overlay on the menu, no save) | World (Side Hustle boots a throwaway save first). |
OnLaunchSingleplayer |
Action<LaunchContext> - start singleplayer. Required for Singleplayer / Hybrid. |
OnHostMultiplayer |
Optional - start hosting a multiplayer session. |
OnJoinMultiplayer |
Optional - join a multiplayer session. |
OnExitToHub |
Optional - called when Side Hustle tears the gamemode down (the player backs out). |
Helper properties: AllowsSingleplayer and AllowsMultiplayer derive from Support.
Passed to your launch callbacks.
| Member | Meaning |
|---|---|
Descriptor |
The descriptor being launched. |
IsHost |
true = host, false = client, null = singleplayer. |
LobbyId |
Steam lobby id for multiplayer; 0 for singleplayer. |
IsSingleplayer |
true when this is a singleplayer launch (IsHost == null). |
PlayerCount |
Players currently in the lobby (1 for singleplayer). |
HostName |
Display name of the host - the custom name they set on the Host / Join screen, else their Steam persona (null for singleplayer). |
HasPassword |
true if the lobby was opened with a password. |
Multiplayer |
The full multiplayer payload (max players, gamemode name, host name, and a free-form ConfigBlob the host published). Null for singleplayer. |
ReturnToHub() |
Call when your gamemode finishes; the hub restores the menu and re-shows the list. Safe to call once. |
public enum GamemodeSupport { Singleplayer, Multiplayer, Hybrid }
public enum GamemodeSurface { MenuSpace, World }- MenuSpace gamemodes build their own overlay on top of the main menu and never load a save (e.g. an in-game editor).
- World gamemodes need the loaded game world, so Side Hustle boots a throwaway save before handing over.
Register early - in your OnInitializeMelon. The menu list is built from the live registry each time the
menu scene loads, so a gamemode registered before the first menu shows up immediately. Registering later is
fine too; it appears the next time the menu is opened.
using SideHustle;
using MelonLoader;
[assembly: MelonOptionalDependencies("SideHustle")]
public sealed class Core : MelonMod
{
public override void OnInitializeMelon()
{
SideHustle.API.Register(new GamemodeDescriptor
{
Id = "you.yourmode",
DisplayName = "Your Mode",
Description = "What your gamemode does.",
Author = "You",
Support = GamemodeSupport.Singleplayer,
Surface = GamemodeSurface.MenuSpace,
OnLaunchSingleplayer = ctx =>
{
// build your overlay / start your mode
// when finished: ctx.ReturnToHub();
}
});
}
}Set Support to Multiplayer or Hybrid and add OnHostMultiplayer / OnJoinMultiplayer. When the player
picks your gamemode, Side Hustle shows a Singleplayer / Host / Join choice (and, for Host, a player-count
picker), then handles the lobby for you:
-
Host - Side Hustle creates a public, joinable Steam lobby, tags it so the server browser can find it,
and (for
Worldgamemodes) boots a throwaway world. It then callsOnHostMultiplayer(ctx)withIsHost = true, theLobbyId, the player count and the host options. -
Join - the player picks a session in the built-in server browser (filtered to your gamemode). Side
Hustle joins the lobby; the game streams the host's world in, then it calls
OnJoinMultiplayer(ctx)withIsHost = falseand the lobby info read back from the host.
Your job is to run the gamemode once the callback fires; networking (lobby + the game's co-op session) is
already up. Call ctx.ReturnToHub() to end the session - Side Hustle leaves the lobby and returns to the menu.
SideHustle.API.Register(new GamemodeDescriptor
{
Id = "you.yourmode",
DisplayName = "Your Mode",
Support = GamemodeSupport.Multiplayer, // or Hybrid to also allow solo
Surface = GamemodeSurface.World, // most multiplayer gamemodes need the world
OnHostMultiplayer = ctx => { /* start the round - you are the host (ctx.LobbyId) */ },
OnJoinMultiplayer = ctx => { /* sync into the host's session (ctx.HostName, ctx.PlayerCount) */ },
OnExitToHub = ctx => { /* tear down when the session ends */ }
});Bigger lobbies (more than the vanilla 4 players) work when the player also has BiggerLobbies installed.
While you host a Side Hustle session, the framework already handles a few multiplayer niceties for you:
- Anyone can join - the game's normal "kick players who aren't the host's Steam friends" behaviour is lifted while a Side Hustle gamemode is hosting, so public (and password) lobbies work with non-friends too.
- Everyone can invite - the pause-menu Steam friend-invite (+) button is shown to every lobby member, not just the host.
- Custom display name - each gamemode has a "Your name" field on its Host / Join screen. A player who sets one appears under that name to everyone else (the in-game nametag, your gamemode's scoreboard/roster, and the server browser) instead of their Steam name, for that session only. It overrides the replicated player name, so your gamemode reads it for free - no extra work. Per gamemode, and never saved.
-
Kick - call
API.KickPlayer(steamId64, reason)on the host to remove a player (resolve the target'ssteamId64however your gamemode tracks players). It is host-authoritative: a no-op on clients and never kicks the host.
A gamemode can declare which other mods may stay loaded while it runs, so unrelated mods do not interfere. Set
GamemodeDescriptor.Policy:
public sealed class ModPolicy
{
public string[] AllowedMods; // extra mods allowed to stay loaded (mod name OR DLL file name)
public string[] RequiredMods; // mods this gamemode needs (enabled if disabled; warned if not installed)
}When the player selects your gamemode, Side Hustle shows a confirmation listing exactly which mods it will pause and enable, then - on confirmation - restarts the game into a temporary, isolated profile that loads only the allowed mods, and continues into your gamemode. The player's installed mods are never disabled, renamed or moved (the profile is a throwaway sandbox of hardlinks), so mod managers stay in sync and a normal launch always loads everything; leaving your gamemode restarts back to the full set. You never list the essentials - MelonLoader, S1API, Side Hustle, your own mod, the Mod Manager & Phone App, and (for multiplayer gamemodes) the multiplayer libraries are always kept, as is anything a kept mod depends on. Match mods by their MelonLoader display name or their DLL file name (case-insensitive). A required mod that is not installed at all is reported so the player can add it via their mod manager.
SideHustle.API.Register(new GamemodeDescriptor
{
Id = "you.yourmode",
DisplayName = "Your Mode",
// ... callbacks ...
Policy = new ModPolicy
{
AllowedMods = new[] { "Some Compatible Mod" }, // everything else loaded is paused for the session
RequiredMods = new[] { "A Mod You Depend On" } // enabled if present-but-disabled
}
});A gamemode with no Policy (the default) never changes which mods are loaded.
By default the Host form pre-selects "Current installed mods" (no changes). Set
GamemodeDescriptor.DefaultRequiredModsOnly = true to pre-select "Required mods only" instead - the host can
still switch it before starting. Useful for a multiplayer gamemode that wants everyone on an identical, clean set.
When a host does pick "Required mods only", Side Hustle shows a short countdown (Restart now / Cancel) before it
restarts the game into the isolated profile, so the restart is never a surprise.