aboutsummaryrefslogtreecommitdiff
path: root/internal/game/cmd_prefix.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-10-03 17:36:06 -0400
committerhistoria <[not public]>2026-10-03 17:36:06 -0400
commitc6d4702c0be4d2b37e1d82ff96265c52847c3f9a (patch)
treec0a3d0638b961a9ba06e6fdb30ee5f1cd2061f2e /internal/game/cmd_prefix.go
parent0fb83bdf433b9b48240f4ac7a5d4c912f6b808cc (diff)
downloadthehouseoficarus-c6d4702c0be4d2b37e1d82ff96265c52847c3f9a.tar.gz
feat: partial commands now always work
Diffstat (limited to 'internal/game/cmd_prefix.go')
-rw-r--r--internal/game/cmd_prefix.go114
1 files changed, 114 insertions, 0 deletions
diff --git a/internal/game/cmd_prefix.go b/internal/game/cmd_prefix.go
new file mode 100644
index 0000000..da3e646
--- /dev/null
+++ b/internal/game/cmd_prefix.go
@@ -0,0 +1,114 @@
+package game
+
+import (
+ "strings"
+
+ "thehouseoficarus/internal/net"
+)
+
+// resolveCommandPrefix expands an unambiguous, currently usable command prefix
+// to its full command name. It is the general (non-casino) counterpart to
+// casinoAbbreviation: a prefix such as "inven" runs inventory, while an
+// ambiguous prefix such as "in" is left alone.
+//
+// Exact commands are never rewritten — the registry dispatch already handles
+// them, and rewriting would make the result indistinguishable from a real
+// command. When several commands share a prefix, the player's own room breaks
+// the tie: if exactly one of the candidates is advertised where they are
+// standing (an object verb, a mob action, an exit, an obstacle, a fixture, or
+// the always-runnable play gateway), that one wins. This is what lets "pl"
+// mean play even though "plant" also starts with "pl".
+func (g *Game) resolveCommandPrefix(sess *net.Session, cmd string) (string, bool) {
+ if sess == nil || sess.Player == nil || cmd == "" {
+ return "", false
+ }
+ // Exact commands and action aliases dispatch as typed.
+ if _, ok := commandRegistry[cmd]; ok {
+ return "", false
+ }
+ if _, ok := verbAliases[cmd]; ok {
+ return "", false
+ }
+ obstacleVerbsMu.RLock()
+ exactObstacle := obstacleVerbs[cmd]
+ obstacleVerbsMu.RUnlock()
+ if exactObstacle {
+ return "", false
+ }
+
+ var matches []string
+ add := func(name string) {
+ if strings.HasPrefix(name, cmd) {
+ matches = append(matches, name)
+ }
+ }
+ for name := range commandRegistry {
+ add(name)
+ }
+ for name := range verbAliases {
+ add(name)
+ }
+ obstacleVerbsMu.RLock()
+ for name := range obstacleVerbs {
+ add(name)
+ }
+ obstacleVerbsMu.RUnlock()
+
+ switch len(matches) {
+ case 0:
+ return "", false
+ case 1:
+ return matches[0], true
+ }
+
+ // Ambiguous on the whole command table: prefer a single candidate the
+ // player can actually use here.
+ usable := g.roomVerbSet(sess)
+ only := ""
+ for _, name := range matches {
+ if !usable[name] {
+ continue
+ }
+ if only != "" {
+ return "", false
+ }
+ only = name
+ }
+ if only != "" {
+ return only, true
+ }
+ return "", false
+}
+
+// roomVerbSet returns the verbs the player can use where they are: the verbs
+// advertised by the current room's objects, mobs, ground and exits, plus the
+// always-runnable play gateway. It deliberately excludes the global
+// always-available list so a context-free command cannot shadow a same-prefix
+// command that is actually offered here.
+func (g *Game) roomVerbSet(sess *net.Session) map[string]bool {
+ set := map[string]bool{
+ // play responds anywhere: at a table it opens the game, and away from
+ // one it reports that there is nothing to play. Making it usable
+ // everywhere lets "pl" name play instead of tying with plant.
+ "play": true,
+ }
+ if sess == nil || sess.Player == nil {
+ return set
+ }
+ rc := g.collectRoomCommands(sess)
+ for _, group := range [][]string{rc.objects, rc.mobs, rc.ground} {
+ for _, entry := range group {
+ if fields := strings.Fields(entry); len(fields) > 0 {
+ set[fields[0]] = true
+ }
+ }
+ }
+ for _, exit := range rc.exits {
+ for _, dir := range strings.Split(exit, ",") {
+ if dir = strings.TrimSpace(dir); dir != "" {
+ set[dir] = true
+ }
+ }
+ }
+ return set
+}