aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/help/aps.yaml27
-rw-r--r--data/help/verbs.yaml15
-rw-r--r--data/recipes/smelting/smelt_bronze.yaml (renamed from data/recipes/smelting/bronze.yaml)0
-rw-r--r--data/recipes/smelting/smelt_iron.yaml (renamed from data/recipes/smelting/iron.yaml)0
-rw-r--r--data/recipes/smelting/smelt_mithril.yaml (renamed from data/recipes/smelting/mithril.yaml)0
-rw-r--r--data/recipes/smelting/smelt_steel.yaml (renamed from data/recipes/smelting/steel.yaml)0
-rw-r--r--data/rooms/town/1.yaml2
-rw-r--r--internal/game/action.go24
-rw-r--r--internal/game/cmd_registry.go8
-rw-r--r--internal/game/cmd_verbs.go234
10 files changed, 286 insertions, 24 deletions
diff --git a/data/help/aps.yaml b/data/help/aps.yaml
new file mode 100644
index 0000000..7d01934
--- /dev/null
+++ b/data/help/aps.yaml
@@ -0,0 +1,27 @@
+name: "aps"
+category: "Exploration"
+description: |
+ The Asteroid Positioning System routes you toward synced APS Towers.
+
+ Usage: aps
+ aps nearest
+ aps <room-number>
+ aps <room-name>
+
+ Before you can navigate to a location, you must first sync your
+ datapad at an APS Tower in that room. Type "use aps tower" to
+ register coordinates for your current location. The room name
+ targeting (e.g. "aps town square") only works for rooms you have
+ previously synced at — it will not find arbitrary rooms.
+
+ Limits:
+ APS queues up to your Agility level in movement steps per use.
+ Unlike "walk", it will not reject targets further away than your
+ Agility level — it simply queues as many steps as it can. Re-issue
+ the command to continue from your new position.
+
+ Commands:
+ aps List all synced nodes with name, room #, and distance
+ aps nearest Walk toward the closest synced node
+ aps <#> Walk toward synced room by number
+ aps <name> Walk toward synced room by name prefix
diff --git a/data/help/verbs.yaml b/data/help/verbs.yaml
new file mode 100644
index 0000000..f8d37d6
--- /dev/null
+++ b/data/help/verbs.yaml
@@ -0,0 +1,15 @@
+name: "verbs"
+category: "Exploration"
+description: |
+ Shows every possible command you can use on non-hidden objects, mobs,
+ and exits in the current room. Also called "what", "syntax", or "commands".
+
+ Usage: verbs
+ verbs all
+
+ The base command shows only room-specific interactions — objects you
+ can mine, mobs you can talk to, stations you can use, exits you can
+ take, and ground items you can pick up. General commands like "look"
+ and "say" are omitted since they work in every room.
+
+ Add "all" to include the full list of always-available commands.
diff --git a/data/recipes/smelting/bronze.yaml b/data/recipes/smelting/smelt_bronze.yaml
index af28ca0..af28ca0 100644
--- a/data/recipes/smelting/bronze.yaml
+++ b/data/recipes/smelting/smelt_bronze.yaml
diff --git a/data/recipes/smelting/iron.yaml b/data/recipes/smelting/smelt_iron.yaml
index fa210d6..fa210d6 100644
--- a/data/recipes/smelting/iron.yaml
+++ b/data/recipes/smelting/smelt_iron.yaml
diff --git a/data/recipes/smelting/mithril.yaml b/data/recipes/smelting/smelt_mithril.yaml
index 8e11e61..8e11e61 100644
--- a/data/recipes/smelting/mithril.yaml
+++ b/data/recipes/smelting/smelt_mithril.yaml
diff --git a/data/recipes/smelting/steel.yaml b/data/recipes/smelting/smelt_steel.yaml
index 86ee366..86ee366 100644
--- a/data/recipes/smelting/steel.yaml
+++ b/data/recipes/smelting/smelt_steel.yaml
diff --git a/data/rooms/town/1.yaml b/data/rooms/town/1.yaml
index fcd7d82..f9d50c5 100644
--- a/data/rooms/town/1.yaml
+++ b/data/rooms/town/1.yaml
@@ -37,6 +37,6 @@ item_spawns:
- id: graceful_cape
quantity: 1
respawn_ticks: 60
- - id: deck
+ - id: basic_deck
quantity: 1
respawn_ticks: 60
diff --git a/internal/game/action.go b/internal/game/action.go
index 2f5dbaf..d6ef1bc 100644
--- a/internal/game/action.go
+++ b/internal/game/action.go
@@ -75,6 +75,7 @@ func (g *Game) startAction(sess *net.Session, verb, target string) {
var obj *object.ObjectDef
var mob *world.MobInstance
+ var chosen *world.ObjState
if len(instances) > 0 {
uniqueDefs := make(map[string]bool)
@@ -86,7 +87,6 @@ func (g *Game) startAction(sess *net.Session, verb, target string) {
return
}
- var chosen *world.ObjState
if instanceIdx >= 0 && instanceIdx < len(instances) {
chosen = &instances[instanceIdx]
} else {
@@ -163,29 +163,11 @@ func (g *Game) startAction(sess *net.Session, verb, target string) {
switch bhType {
case "gather":
- if obj == nil {
+ if obj == nil || chosen == nil {
sess.WriteLine(fmt.Sprintf("You can't %s that.", verb))
return
}
- chosenObj := g.World.FindObjInstances(p.RoomID, obj.ID)
- sort.Slice(chosenObj, func(i, j int) bool {
- return chosenObj[i].Index < chosenObj[j].Index
- })
- var st *world.ObjState
- for i := range chosenObj {
- if !chosenObj[i].Depleted {
- st = &chosenObj[i]
- break
- }
- }
- if st == nil && len(chosenObj) > 0 {
- st = &chosenObj[0]
- }
- if st == nil {
- sess.WriteLine(fmt.Sprintf("There's nothing here to %s.", verb))
- return
- }
- g.startGather(sess, p, obj, st)
+ g.startGather(sess, p, obj, chosen)
case "talk":
if mob != nil {
g.startMobTalk(sess, p, mob)
diff --git a/internal/game/cmd_registry.go b/internal/game/cmd_registry.go
index db1ec81..5073b02 100644
--- a/internal/game/cmd_registry.go
+++ b/internal/game/cmd_registry.go
@@ -117,7 +117,11 @@ var commandRegistry = map[string]commandDef{
"remove": {(*Game).executeRemove, ClassFree},
"unwear": {(*Game).executeRemove, ClassFree},
"unwield": {(*Game).executeRemove, ClassFree},
- "aps": {(*Game).executeAps, ClassActive},
+ "aps": {(*Game).executeAps, ClassActive},
+ "verbs": {(*Game).executeVerbs, ClassInstant},
+ "what": {(*Game).executeVerbs, ClassInstant},
+ "syntax": {(*Game).executeVerbs, ClassInstant},
+ "commands": {(*Game).executeVerbs, ClassInstant},
}
func classifyCommand(cmd string) CommandClass {
@@ -173,7 +177,7 @@ func (g *Game) executeCommand(sess *net.Session, cmd string, args []string, rawI
}
func (g *Game) executeGather(sess *net.Session, args []string, rawInput string) {
- verb := strings.TrimSpace(rawInput)
+ verb := strings.Fields(rawInput)[0]
p := sess.Player
g.cancelAction(p)
if len(args) == 0 {
diff --git a/internal/game/cmd_verbs.go b/internal/game/cmd_verbs.go
new file mode 100644
index 0000000..1ecd138
--- /dev/null
+++ b/internal/game/cmd_verbs.go
@@ -0,0 +1,234 @@
+package game
+
+import (
+ "strings"
+
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/world"
+)
+
+var alwaysAvailable = []string{
+ "look", "l",
+ "say",
+ "exits",
+ "map",
+ "walk",
+ "score", "stats",
+ "inventory", "inv", "i",
+ "equipment", "eq", "equip", "wear", "wield",
+ "remove", "unwear", "unwield",
+ "style",
+ "tech", "t",
+ "option", "options",
+ "color", "colors", "colortable",
+ "prompt",
+ "alias", "unalias",
+ "help",
+ "inspect",
+ "queued",
+ "aps",
+ "autotrigger", "auto",
+ "trigger",
+ "sneak",
+ "mods", "modules", "modlist", "mod", "module",
+ "id", "identify",
+ "eat", "drink",
+ "fletch",
+ "clean",
+ "burn", "stoke",
+ "search",
+ "get", "take", "grab", "pick",
+ "drop",
+}
+
+func (g *Game) executeVerbs(sess *net.Session, args []string, rawInput string) {
+ p := sess.Player
+ showAll := len(args) == 1 && strings.ToLower(args[0]) == "all"
+
+ seen := make(map[string]bool)
+ var sectionObjs []string
+ var sectionMobs []string
+ var sectionExits []string
+ var sectionGround []string
+
+ for _, st := range g.World.AllObjInstances(p.RoomID) {
+ def, err := g.ObjectStore.Load(st.DefID)
+ if err != nil || def.Hidden {
+ continue
+ }
+ name := strings.ToLower(def.Name)
+
+ bt := def.BehaviorType()
+
+ if bt == "gather" && def.Gather != nil {
+ for verb, skill := range verbSkill {
+ if strings.EqualFold(def.Gather.Skill, skill) {
+ addUnique(&sectionObjs, &seen, verb+" "+name)
+ }
+ }
+ }
+
+ if bt == "talk" {
+ addUnique(&sectionObjs, &seen, "talk "+name)
+ }
+
+ if bt == "use" {
+ addUnique(&sectionObjs, &seen, "use "+name)
+ }
+
+ for _, ui := range def.UseInteractions {
+ if ui.Item == "" && (ui.Condition == nil || g.checkCondition(sess, ui.Condition)) {
+ addUnique(&sectionObjs, &seen, "use "+name)
+ break
+ }
+ }
+
+ recipes := g.stationRecipes(p, def.ID)
+ if len(recipes) > 0 {
+ addUnique(&sectionObjs, &seen, "use "+name)
+ for _, r := range recipes {
+ if info, ok := productionTypes[r.Type]; ok {
+ if info.ActionType != "combine" && info.ActionType != "fletch" && info.ActionType != "mix" {
+ addUnique(&sectionObjs, &seen, info.ActionType)
+ }
+ }
+ for _, c := range r.Consume {
+ for _, itemID := range c.Items {
+ if itemDef, err := g.ItemStore.Load(itemID); err == nil {
+ addUnique(&sectionObjs, &seen, "use "+itemDef.Name+" on "+name)
+ }
+ }
+ }
+ }
+ }
+
+ for _, ui := range def.UseInteractions {
+ if ui.Item != "" && (ui.Condition == nil || g.checkCondition(sess, ui.Condition)) {
+ if itemDef, err := g.ItemStore.Load(ui.Item); err == nil {
+ addUnique(&sectionObjs, &seen, "use "+itemDef.Name+" on "+name)
+ }
+ }
+ }
+
+ if def.StealTable != "" {
+ addUnique(&sectionObjs, &seen, "steal "+name)
+ }
+
+ addUnique(&sectionObjs, &seen, "look "+name)
+ }
+
+ for _, m := range g.MobStore.MobsInRoom(p.RoomID) {
+ mName := strings.ToLower(m.Name)
+
+ if !m.Protected {
+ addUnique(&sectionMobs, &seen, "attack "+mName)
+ }
+ if m.IsTalkable() {
+ addUnique(&sectionMobs, &seen, "talk "+mName)
+ }
+ if m.StealTable != "" {
+ addUnique(&sectionMobs, &seen, "steal "+mName)
+ }
+ addUnique(&sectionMobs, &seen, "look "+mName)
+ }
+
+ if room, err := g.World.LoadRoom(p.RoomID); err == nil {
+ var exitDirs []string
+ for _, dir := range world.ExitOrder {
+ if _, ok := room.Exits[dir]; ok {
+ exitDirs = append(exitDirs, string(dir))
+ }
+ }
+ if len(exitDirs) > 0 {
+ sectionExits = append(sectionExits, strings.Join(exitDirs, ", "))
+ }
+ }
+
+ ground := g.World.GroundItems(p.RoomID)
+ if len(ground) > 0 {
+ for itemID := range ground {
+ if def, err := g.ItemStore.Load(itemID); err == nil {
+ addUnique(&sectionGround, &seen, "get "+def.Name)
+ }
+ }
+ addUnique(&sectionGround, &seen, "get all")
+ }
+
+ if info := g.CourseStore.GetObstacle(p.RoomID); info != nil {
+ addUnique(&sectionGround, &seen, info.Verb)
+ }
+
+ if g.canUseBank(p) {
+ addUnique(&sectionObjs, &seen, "bank")
+ }
+
+ if g.findTerminalInRoom(p.RoomID) != "" {
+ addUnique(&sectionObjs, &seen, "jack")
+ }
+
+ hasRoomCmds := len(sectionObjs) > 0 || len(sectionMobs) > 0 || len(sectionExits) > 0 || len(sectionGround) > 0
+
+ if hasRoomCmds {
+ if len(sectionObjs) > 0 {
+ sess.WriteLine("Objects:")
+ writeCmdLines(sess, sectionObjs)
+ }
+ if len(sectionMobs) > 0 {
+ sess.WriteLine("Mobs:")
+ writeCmdLines(sess, sectionMobs)
+ }
+ if len(sectionExits) > 0 {
+ sess.WriteLine("Exits:")
+ writeCmdLines(sess, sectionExits)
+ }
+ if len(sectionGround) > 0 {
+ sess.WriteLine("Ground:")
+ writeCmdLines(sess, sectionGround)
+ }
+ } else {
+ sess.WriteLine("There is nothing notable to interact with here.")
+ }
+
+ if showAll {
+ sess.WriteLine("")
+ sess.WriteLine("Always available:")
+ writeCmdLineStrings(sess, alwaysAvailable)
+ } else {
+ sess.WriteLine("")
+ sess.WriteLine(`Type "verbs all" to see all available commands.`)
+ }
+}
+
+func addUnique(list *[]string, seen *map[string]bool, cmd string) {
+ if (*seen)[cmd] {
+ return
+ }
+ (*seen)[cmd] = true
+ *list = append(*list, cmd)
+}
+
+func writeCmdLines(sess *net.Session, cmds []string) {
+ wrapCmdLines(sess, cmds, " ")
+}
+
+func writeCmdLineStrings(sess *net.Session, cmds []string) {
+ wrapCmdLines(sess, cmds, " ")
+}
+
+func wrapCmdLines(sess *net.Session, cmds []string, prefix string) {
+ const width = 70
+ var current string
+ for _, cmd := range cmds {
+ if current == "" {
+ current = prefix + cmd
+ } else if len(current)+len(cmd)+2 <= width {
+ current += ", " + cmd
+ } else {
+ sess.WriteLine(current)
+ current = prefix + cmd
+ }
+ }
+ if current != "" {
+ sess.WriteLine(current)
+ }
+}