Hello Mod
Code mod tour: lifecycle logs, console commands hello / hello.ping / hello.beacon / hello.stats, host-player messages, a network prefab, game events and world data.
Code mod tour: lifecycle logs, console commands hello / hello.ping / hello.beacon / hello.stats, host-player messages, a network prefab, game events and world data.
Mod id example.hello, side both.
A code mod: build it with dotnet build (see Making mods) and put the output folder in the game's Mods folder.
mod.json
{
"id": "example.hello",
"name": "Hello Mod",
"version": "1.2",
"author": "Seeds & Circuits",
"side": "both",
"description": "Code mod tour: lifecycle logs, console commands hello / hello.ping / hello.beacon / hello.stats, host-player messages, a network prefab, game events and world data."
}
HelloMod.cs
using SeedsAndCircuits;
using SeedsAndCircuits.DebugConsole;
using SeedsAndCircuits.Inventory;
using SeedsAndCircuits.Mods;
using UnityEngine;
namespace HelloMod
{
/// <summary>
/// The smallest tour of the mod API: lifecycle logs, a console command, messages between host and players, and a
/// network prefab. Side "both": the host and every player run it (joiners download it from the host).
/// </summary>
public sealed class HelloMod : Mod
{
static HelloMod s_Instance;
public override void OnLoad()
{
s_Instance = this;
Log($"Loaded from {Folder}. {ItemCatalog.All.Count} items known so far.");
// Messages: the same mod on another machine sends these (names are per mod).
OnMessage("greet", msg => Log($"The host says: {msg.Text}"));
OnMessage("ping", msg => SendToClients("pong", $"Pong from the host to everyone (client {msg.Sender} pinged)."));
OnMessage("pong", msg => Log(msg.Text));
// A network prefab built in code: a glowing post the host can spawn for everyone.
var beacon = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
beacon.transform.localScale = new Vector3(0.3f, 2f, 0.3f);
beacon.GetComponent<Renderer>().material.color = new Color(1f, 0.8f, 0.2f);
Object.Destroy(beacon.GetComponent<Collider>());
RegisterNetworkPrefab("beacon", beacon);
// Game events (they fire on the host): count what happens in this world.
GameEvents.TreeFelled += OnTreeFelled;
GameEvents.ItemCrafted += OnItemCrafted;
GameEvents.PlayerDied += OnPlayerDied;
}
// ------------------------------------------------------------------ world stats (game events + world data)
// Saved with the world (WorldData): each world keeps its own counts.
[System.Serializable]
sealed class Stats
{
public int treesFelled;
public int itemsCrafted;
public int deaths;
}
Stats _stats = new Stats();
void OnTreeFelled(string kind, Vector3 position) => _stats.treesFelled++;
void OnItemCrafted(GameObject player, string station, string itemId, int count) => _stats.itemsCrafted += count;
void OnPlayerDied(GameObject player, string cause) => _stats.deaths++;
// Host: written into the world save just before it's saved.
public override void OnSavingWorld() => WorldData.Set("stats", _stats);
public override void OnWorldLoaded()
{
if (IsServer)
_stats = WorldData.Get("stats", new Stats());
Log($"World loaded with {ItemCatalog.All.Count} items. Host: {IsServer}.");
}
public override void OnWorldUnloaded() => Log("World closed.");
public override void OnUnload()
{
GameEvents.TreeFelled -= OnTreeFelled;
GameEvents.ItemCrafted -= OnItemCrafted;
GameEvents.PlayerDied -= OnPlayerDied;
Log("Turned off.");
s_Instance = null;
}
// Host only: welcome every player who joins.
public override void OnPlayerJoined(ulong clientId)
{
if (clientId != LocalClientId)
SendToClient(clientId, "greet", $"Welcome, client {clientId}!");
}
public override void OnPlayerLeft(ulong clientId) => Log($"Client {clientId} left.");
// Any static [ConsoleCommand] in a mod's assembly becomes a console command while the mod is on (written out as
// ConsoleCommandAttribute here: outside the console's namespace the short name clashes with its ConsoleCommand class).
[ConsoleCommandAttribute("hello", "Says hello from the Hello Mod example.", Local = true)]
static string Hello() => "Hello from a mod!";
// Not Local: runs on the host, which has the stats.
[ConsoleCommandAttribute("hello.stats", "This world's counts (saved with it): trees felled, items crafted, deaths.")]
static string StatsCommand()
{
if (s_Instance == null || !s_Instance.IsServer)
return "Only the host keeps the stats.";
Stats s = s_Instance._stats;
return $"Trees felled: {s.treesFelled}, items crafted: {s.itemsCrafted}, deaths: {s.deaths}";
}
[ConsoleCommandAttribute("hello.ping", "Pings the host; it answers every player.", Local = true)]
static string Ping()
{
if (s_Instance == null || !s_Instance.IsClient)
return "Not in a game.";
s_Instance.SendToServer("ping", "ping");
return "Ping sent.";
}
// Not Local: runs on the host (for the host, or for a player the host gave the console with op).
[ConsoleCommandAttribute("hello.beacon", "Puts a beacon at your feet for everyone (host).")]
static string Beacon()
{
if (s_Instance == null || !s_Instance.IsServer)
return "Only the host can spawn beacons.";
Transform player = DebugConsole.FindPlayer();
if (player == null)
return "No player.";
s_Instance.SpawnNetworkPrefab("beacon", player.position + Vector3.up, Quaternion.identity);
return "Beacon placed.";
}
}
}
HelloMod.csproj
<Project Sdk="Microsoft.NET.Sdk">
<!-- Build settings and game references: ../Mod.props. Output: bin/Hello Mod/ -->
<PropertyGroup>
<AssemblyName>HelloMod</AssemblyName>
<ModFolder>Hello Mod</ModFolder>
</PropertyGroup>
<Import Project="../Mod.props" />
</Project>