Skip to content

Feature Kit Creator

Zaldaryon edited this page Sep 4, 2026 · 1 revision

This feature lets staff snapshot a live inventory into a reusable, role-scoped kit, and lets players redeem kits they are assigned. No custom client is possible (server-only, stock-client compatible), so every write path snapshots a real ItemStack off a connected player instead of taking typed item codes.

Config Surface

StratumCommandsConfig (sources/VintagestoryLib/Vintagestory.Server/StratumConfig.cs) has no dedicated Kits/KitEdit section in a checked-in default JSON file; both are plain StratumCommandAccessConfig entries with code defaults:

public StratumCommandAccessConfig Kits { get; set; } = StratumCommandAccessConfig.ForPrivilege("stratum.kits");

public StratumCommandAccessConfig KitEdit { get; set; } = StratumCommandAccessConfig.ForPrivilege("stratum.kitedit");

StratumCommandAccessConfig itself defaults to Enabled=true, CooldownSeconds=0, CooldownBypassForStaff=true, with Privilege set by ForPrivilege. Neither stratum.kits nor stratum.kitedit is granted to any default role (including admin) — an operator has to grant it explicitly, e.g. /roles grant <role> stratum.kitedit.

Runtime Flow

Implementation class: CmdStratumKits (sources/VintagestoryLib/Vintagestory.Server/CmdStratumKits.cs). Storage: StratumKitStore (stratum-kits.json). Delivery: StratumKitGiver.

1. Registration

Both commands are registered from the constructor, gated by StratumCommandRegistration.ShouldRegister against the config above:

server.api.commandapi.Create("kitedit")
	.WithDescription("Create and manage kits")
	.WithAdditionalInformation("Actions: list, create <name>, additem <name>, ...")
	.WithArgs(
		parsers.WordRange("action", KitEditActions),
		parsers.OptionalWord("name"),
		parsers.OptionalWord("value"))
	.RequiresPrivilege(Privilege.chat)
	.HandleWith(HandleKitEdit);

.RequiresPrivilege(Privilege.chat) is the loosest base gate Vintage Story's command API requires to consider a registration complete; the real, configurable gate is CheckAccess inside the handler, checked against Commands.KitEdit via StratumCommandAccessCatalog. Every Stratum command registration needs that base call — /kitedit shipped without it until #293/#294 and threw Programming error: Incomplete command - no name or required privilege has been set on every invocation as a result.

2. Argument validation

HandleKitEdit checks the action's exact arity before dispatching, via a lookup table:

private static (int ArgCount, string Usage) ActionShape(string action)
{
	return action switch
	{
		"list" => (0, "/kitedit list"),
		"create" => (1, "/kitedit create &lt;name&gt;"),
		...
	};
}

Too few arguments returns the usage line; too many returns /kitedit <action> takes N argument(s). Usage: .... Usage text is written with &lt;/&gt; deliberately: command results render as VTML in chat, and a raw <name> is parsed as an unknown tag and silently dropped by the client.

3. Snapshot scope

create and additem read from the caller's live inventory. create's scope is a per-kit setting (StratumKitDefinition.Scope, "all" or "hotbar", set with /kitedit setscope <name> all|hotbar, added in #294/#295):

private static List<StratumKitItem> SnapshotInventory(IPlayer player, string scope)
{
	bool hotbarOnly = string.Equals(scope, "hotbar", StringComparison.OrdinalIgnoreCase);
	...
	bool included = inventory.ClassName == GlobalConstants.hotBarInvClassName
		|| (!hotbarOnly && inventory.ClassName == GlobalConstants.characterInvClassName);

all (default) walks hotbar and character inventory together, so worn armor and a worn backpack are captured — this was the only behavior before #295 and remains the default. hotbar walks only the hotbar, which already includes the offhand slot, capturing nothing worn or equipped. The character inventory's own backpackInvClassName sub-inventory is never walked directly either way: a worn backpack's contents already round-trip inside the bag's own ItemStack attributes, so walking it too would double them.

Scope is applied on the next create, not retroactively — re-running create after setscope is required to actually change an existing kit's item list.

4. Delivery

StratumKitGiver.Give turns stored items back into real stacks. Armor is placed directly into a compatible, empty character slot (ItemSlotCharacter's own CanHold enforces dress-type compatibility); everything else goes through Entity.TryGiveItemStack, the same path /giveitem uses. Anything that doesn't fit is dropped at the recipient's feet rather than discarded.

5. Respawn and cooldowns

Kits flagged GiveOnRespawn are handed out after OnPlayerRespawn, polling for the entity to actually be alive first (vanilla's own respawn handler starts an async teleport before reviving). OnePerLife and per-kit CooldownSeconds are tracked the same way /kit's and /kitedit's own command-level cooldowns are (StratumCommandCooldowns.TryUse), including the CooldownBypassForStaff rule for staff holding Commands.StaffChat.

What The Defaults Mean

  • Enabled=true on both Kits and KitEdit: both commands register out of the box, but nobody can use them until a role is granted the privilege.
  • CooldownSeconds=0 on both: no command-level cooldown by default; per-kit cooldowns (setcooldown) are independent and also default to 0.
  • CooldownBypassForStaff=true: staff with Commands.StaffChat access skip both the command-level and per-kit cooldowns.
  • A kit's own Scope defaults to "all": existing kits and newly created ones behave exactly as before #295 unless an operator explicitly opts into "hotbar".

Related Code

  • sources/VintagestoryLib/Vintagestory.Server/CmdStratumKits.cs
  • sources/VintagestoryLib/Vintagestory.Server/StratumKitStore.cs (StratumKitDefinition, StratumKitItem)
  • sources/VintagestoryLib/Vintagestory.Server/StratumKitGiver.cs
  • sources/VintagestoryLib/Vintagestory.Server/StratumConfig.cs (StratumCommandsConfig.Kits / .KitEdit)
  • docs/commands/kits.md in the repo (player-facing command reference: syntax, permissions, troubleshooting)
  • Issues/PRs: #210 (original request), #269 (original implementation), #293/#295/#294 (registration fix, argument validation, snapshot scope)

Related References

Clone this wiki locally