diff --git a/src/KitsuneCommand/Features/ChatCommandService.cs b/src/KitsuneCommand/Features/ChatCommandService.cs
index 7a252c3..1b3ac4b 100644
--- a/src/KitsuneCommand/Features/ChatCommandService.cs
+++ b/src/KitsuneCommand/Features/ChatCommandService.cs
@@ -163,6 +163,16 @@ public bool TryHandleCommand(string playerId, int entityId, string playerName, s
HandleBloodMoonVote(playerId, entityId, playerName);
return true;
+ // ── Help / discovery ─────────────────────────────────
+ // Always available regardless of feature toggles. If a server
+ // disables every group, /help still works and just reports
+ // back that nothing's enabled — which is itself useful info.
+ case "help":
+ case "commands":
+ case "?":
+ HandleHelp(entityId, settings);
+ return true;
+
default:
return false; // Not a recognized command
}
@@ -715,6 +725,67 @@ private void HandleBloodMoonVote(string playerId, int entityId, string playerNam
}
}
+ // ─── Help ──────────────────────────────────────────────────────
+
+ ///
+ /// Lists the commands available to the calling player. Only enabled
+ /// feature groups are shown — running `/help` on a server with the
+ /// store turned off shouldn't tell the player to try `/buy`.
+ ///
+ /// Each Reply() is a separate `pm` to the player's chat, so we can
+ /// safely emit one line per group without worrying about message
+ /// length truncation.
+ ///
+ private void HandleHelp(int entityId, ChatCommandSettings settings)
+ {
+ var p = settings.Prefix;
+ Reply(entityId, "Available commands:");
+
+ var anyEnabled = false;
+
+ if (settings.HomeEnabled)
+ {
+ Reply(entityId, $" HOME: {p}home [name], {p}sethome , {p}delhome , {p}homes");
+ anyEnabled = true;
+ }
+ if (settings.TeleportEnabled)
+ {
+ Reply(entityId, $" TELEPORT: {p}tp , {p}cities");
+ anyEnabled = true;
+ }
+ if (settings.PointsEnabled)
+ {
+ Reply(entityId, $" POINTS: {p}points, {p}signin");
+ anyEnabled = true;
+ }
+ if (settings.StoreEnabled)
+ {
+ Reply(entityId, $" STORE: {p}shop, {p}buy - ");
+ anyEnabled = true;
+ }
+ if (settings.VipEnabled)
+ {
+ Reply(entityId, $" VIP: {p}vip");
+ anyEnabled = true;
+ }
+ if (settings.TicketEnabled)
+ {
+ Reply(entityId, $" TICKETS: {p}ticket , {p}ticket , {p}tickets");
+ anyEnabled = true;
+ }
+
+ // Blood Moon Vote has its own enable check inside the feature
+ // (rather than a settings.BloodMoonVoteEnabled flag here), so we
+ // surface it unconditionally; the feature itself replies with a
+ // "disabled" message if a player tries to vote while it's off.
+ Reply(entityId, $" BLOOD MOON: {p}skipbm (alias: {p}voteskip)");
+
+ if (!anyEnabled)
+ {
+ Reply(entityId, "(All optional feature groups are currently disabled on this server.)");
+ }
+ }
+
// ─── Helpers ───────────────────────────────────────────────────
///