diff options
| -rw-r--r-- | data/help/aps.yaml | 27 | ||||
| -rw-r--r-- | data/help/verbs.yaml | 15 | ||||
| -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.yaml | 2 | ||||
| -rw-r--r-- | internal/game/action.go | 24 | ||||
| -rw-r--r-- | internal/game/cmd_registry.go | 8 | ||||
| -rw-r--r-- | internal/game/cmd_verbs.go | 234 |
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(§ionObjs, &seen, verb+" "+name) + } + } + } + + if bt == "talk" { + addUnique(§ionObjs, &seen, "talk "+name) + } + + if bt == "use" { + addUnique(§ionObjs, &seen, "use "+name) + } + + for _, ui := range def.UseInteractions { + if ui.Item == "" && (ui.Condition == nil || g.checkCondition(sess, ui.Condition)) { + addUnique(§ionObjs, &seen, "use "+name) + break + } + } + + recipes := g.stationRecipes(p, def.ID) + if len(recipes) > 0 { + addUnique(§ionObjs, &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(§ionObjs, &seen, info.ActionType) + } + } + for _, c := range r.Consume { + for _, itemID := range c.Items { + if itemDef, err := g.ItemStore.Load(itemID); err == nil { + addUnique(§ionObjs, &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(§ionObjs, &seen, "use "+itemDef.Name+" on "+name) + } + } + } + + if def.StealTable != "" { + addUnique(§ionObjs, &seen, "steal "+name) + } + + addUnique(§ionObjs, &seen, "look "+name) + } + + for _, m := range g.MobStore.MobsInRoom(p.RoomID) { + mName := strings.ToLower(m.Name) + + if !m.Protected { + addUnique(§ionMobs, &seen, "attack "+mName) + } + if m.IsTalkable() { + addUnique(§ionMobs, &seen, "talk "+mName) + } + if m.StealTable != "" { + addUnique(§ionMobs, &seen, "steal "+mName) + } + addUnique(§ionMobs, &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(§ionGround, &seen, "get "+def.Name) + } + } + addUnique(§ionGround, &seen, "get all") + } + + if info := g.CourseStore.GetObstacle(p.RoomID); info != nil { + addUnique(§ionGround, &seen, info.Verb) + } + + if g.canUseBank(p) { + addUnique(§ionObjs, &seen, "bank") + } + + if g.findTerminalInRoom(p.RoomID) != "" { + addUnique(§ionObjs, &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) + } +} |
