aboutsummaryrefslogtreecommitdiff
path: root/internal/game
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-15 16:26:23 -0400
committerhistoria <[not public]>2026-09-15 16:26:23 -0400
commitd89504c3df74706b387b8fa829dd291e31ca01c7 (patch)
tree8924164ed1b061cca04aaa79f283d1ab0995486e /internal/game
parent0b0b25222b3131ada79ff78c28eb8bf07d977deb (diff)
downloadthehouseoficarus-d89504c3df74706b387b8fa829dd291e31ca01c7.tar.gz
fix: help topics (newplayer, title/body files), help aliases, and table width rebalancing
Diffstat (limited to 'internal/game')
-rw-r--r--internal/game/render_help.go91
-rw-r--r--internal/game/render_help_test.go81
2 files changed, 163 insertions, 9 deletions
diff --git a/internal/game/render_help.go b/internal/game/render_help.go
index e2893b5..2d3a304 100644
--- a/internal/game/render_help.go
+++ b/internal/game/render_help.go
@@ -13,9 +13,11 @@ import (
)
type HelpDef struct {
- Name string `yaml:"name"`
- Category string `yaml:"category"`
- Content string `yaml:"description"`
+ Name string `yaml:"name"`
+ Aliases []string `yaml:"aliases"`
+ Related []string `yaml:"related"`
+ Category string `yaml:"category"`
+ Content string `yaml:"description"`
}
type cmdEntry struct {
@@ -63,7 +65,7 @@ var commandList = []cmdEntry{
{"mine", "Active", "Mine rocks (Mining)"},
{"mix", "Active", "Mix potions (Pharmacy)"},
{"mods / modlist", "Instant", "List available science modules"},
- {"north / south / east / west / ne / nw / se / sw / up / down", "Active", "Move in a direction"},
+ {"north / south / east / west / up / down", "Active", "Move in a direction"},
{"option / options", "Instant", "View or change settings"},
{"prompt", "Instant", "Set custom command prompt"},
{"pull / push", "Active", "Interact with objects"},
@@ -94,16 +96,56 @@ func LoadHelp(dataDir string) ([]HelpDef, error) {
dir := filepath.Join(dataDir, "help")
var helps []HelpDef
behavior.WalkYAMLDir(dir, func(path, id string, data []byte) error {
- var h HelpDef
- if err := yaml.Unmarshal(data, &h); err != nil {
+ h := parseHelpDef(data)
+ if h == nil {
return nil
}
- helps = append(helps, h)
+ helps = append(helps, *h)
return nil
})
return helps, nil
}
+// helpRaw accepts both the canonical schema (name/description) and the
+// legacy variant (title/body) used by a few help files.
+type helpRaw struct {
+ Name string `yaml:"name"`
+ Title string `yaml:"title"`
+ Aliases []string `yaml:"aliases"`
+ Related []string `yaml:"related"`
+ Category string `yaml:"category"`
+ Description string `yaml:"description"`
+ Body string `yaml:"body"`
+}
+
+// parseHelpDef unmarshals a help definition, normalizing legacy title/body
+// files onto the canonical HelpDef. Returns nil for files that are not help
+// topics (no name at all).
+func parseHelpDef(data []byte) *HelpDef {
+ var raw helpRaw
+ if err := yaml.Unmarshal(data, &raw); err != nil {
+ return nil
+ }
+ name := raw.Name
+ if name == "" {
+ name = raw.Title
+ }
+ if name == "" {
+ return nil
+ }
+ content := raw.Description
+ if content == "" {
+ content = raw.Body
+ }
+ return &HelpDef{
+ Name: name,
+ Aliases: raw.Aliases,
+ Related: raw.Related,
+ Category: raw.Category,
+ Content: content,
+ }
+}
+
func (g *Game) doHelp(sess *net.Session, topic string) {
if topic == "" {
unicode := true
@@ -141,16 +183,47 @@ func (g *Game) doHelp(sess *net.Session, topic string) {
return
}
- topic = strings.ToLower(topic)
+ topic = strings.ToLower(strings.TrimSpace(topic))
for _, h := range helps {
- if strings.ToLower(h.Name) == topic {
+ if matchesHelpTopic(h, topic) {
sess.WriteLine(fmt.Sprintf(" %s", h.Content))
+ if len(h.Related) > 0 {
+ sess.WriteLine("")
+ sess.WriteLine(fmt.Sprintf(" Related topics: %s", strings.Join(h.Related, ", ")))
+ }
return
}
}
+
+ // No dedicated help page. If it's a known command, at least show its
+ // summary from the command table instead of a dead end.
+ for _, c := range commandList {
+ for _, name := range strings.Split(c.Name, " / ") {
+ if strings.ToLower(strings.TrimSpace(name)) == topic {
+ sess.WriteLine(fmt.Sprintf(" %s - %s", c.Name, c.Desc))
+ sess.WriteLine("")
+ sess.WriteLine(" (No detailed help page for this command yet.)")
+ return
+ }
+ }
+ }
sess.WriteLine(fmt.Sprintf("No help found for '%s'.", topic))
}
+// matchesHelpTopic reports whether the help entry answers the given topic,
+// matching its name or any declared alias (case-insensitive).
+func matchesHelpTopic(h HelpDef, topic string) bool {
+ if strings.EqualFold(h.Name, topic) {
+ return true
+ }
+ for _, a := range h.Aliases {
+ if strings.EqualFold(strings.TrimSpace(a), topic) {
+ return true
+ }
+ }
+ return false
+}
+
func firstLine(s string) string {
if idx := strings.Index(s, "\n"); idx >= 0 {
return s[:idx]
diff --git a/internal/game/render_help_test.go b/internal/game/render_help_test.go
new file mode 100644
index 0000000..b8ad614
--- /dev/null
+++ b/internal/game/render_help_test.go
@@ -0,0 +1,81 @@
+package game
+
+import (
+ "strings"
+ "testing"
+)
+
+func loadHelpOrSkip(t *testing.T) []HelpDef {
+ t.Helper()
+ helps, err := LoadHelp("../../data")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(helps) == 0 {
+ t.Fatal("no help topics loaded")
+ }
+ return helps
+}
+
+func TestLoadHelpAllFilesParse(t *testing.T) {
+ helps := loadHelpOrSkip(t)
+ seen := map[string]bool{}
+ for _, h := range helps {
+ if strings.TrimSpace(h.Name) == "" {
+ t.Errorf("help entry with empty name: %q", h.Content)
+ }
+ if seen[h.Name] {
+ t.Errorf("duplicate help topic %q", h.Name)
+ }
+ seen[h.Name] = true
+ }
+}
+
+func TestNewPlayerHelpExists(t *testing.T) {
+ helps := loadHelpOrSkip(t)
+ found := false
+ for _, h := range helps {
+ if h.Name == "newplayer" {
+ found = true
+ }
+ }
+ if !found {
+ t.Fatal("help topic 'newplayer' missing (referenced by intro room)")
+ }
+}
+
+func TestHelpAliasesResolveWithoutCollision(t *testing.T) {
+ helps := loadHelpOrSkip(t)
+ owner := map[string]string{}
+ for _, h := range helps {
+ for _, a := range h.Aliases {
+ a = strings.ToLower(strings.TrimSpace(a))
+ if a == "" {
+ continue
+ }
+ if prev, dup := owner[a]; dup {
+ t.Errorf("alias %q claimed by both %q and %q", a, prev, h.Name)
+ }
+ owner[a] = h.Name
+ }
+ }
+ for topic, want := range map[string]string{
+ "mine": "mining",
+ "chop": "woodcutting",
+ "fish": "fishing",
+ "fletch": "fletch",
+ "newplayer": "newplayer",
+ "new player": "newplayer",
+ } {
+ resolved := ""
+ for _, h := range helps {
+ if matchesHelpTopic(h, topic) {
+ resolved = h.Name
+ break
+ }
+ }
+ if resolved != want {
+ t.Errorf("help %q resolved to %q, want %q", topic, resolved, want)
+ }
+ }
+}