package game import ( "strings" "thehouseoficarus/internal/behavior" "thehouseoficarus/internal/casino" "thehouseoficarus/internal/net" "thehouseoficarus/internal/world" ) var alwaysAvailable = []string{ "look", "say", "global", "who", "exits", "map", "walk", "score", "stats", "inventory", "equip", "remove", "style", "tech", "option", "color", "colortable", "prompt", "alias", "unalias", "help", "queued", "stop", "aps", "autotrigger", "trigger", "sneak", "mods", "identify", "eat", "drink", "fletch", "clean", "burn", "stoke", "search", "get", "drop", } // roomCommandSections holds the interactable commands advertised for the // player's current room, before rendering. It is shared by the verbs display // and by prefix resolution, so an abbreviation can only beat a same-prefix // rival when the player can actually use it here. type roomCommandSections struct { objects []string mobs []string exits []string ground []string } // collectRoomCommands builds the room's object, mob, exit and ground command // listings without writing anything to the session. func (g *Game) collectRoomCommands(sess *net.Session) roomCommandSections { p := sess.Player var rc roomCommandSections seen := make(map[string]bool) for _, st := range g.World.AllObjInstances(p.RoomID) { def, _, err := g.resolveObjectDef(p.RoomID, st.DefID) if err != nil || (def.Hidden && !p.GodMode) { 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(&rc.objects, &seen, verb+" "+name) } } } if bt == "talk" { addUnique(&rc.objects, &seen, "talk "+name) } for _, ui := range def.OnUse { if ui.ItemID == "" && (ui.Condition == nil || g.checkCondition(sess, ui.Condition)) { addUnique(&rc.objects, &seen, "use "+name) break } } recipes := g.stationRecipes(p, def.ID) if len(recipes) > 0 { addUnique(&rc.objects, &seen, "use "+name) for _, r := range recipes { c := r.FirstCraft() key := c.Subtype if key == "" { key = c.Type } if info, ok := productionTypes[key]; ok { if info.ActionType != behavior.TypeCombine && info.ActionType != behavior.TypeFletch && info.ActionType != behavior.TypeMix { addUnique(&rc.objects, &seen, string(info.ActionType)) } } for _, c := range r.FirstCraft().Ingredients { for _, itemID := range c.Items { if itemDef, err := g.ItemStore.Load(itemID); err == nil { addUnique(&rc.objects, &seen, "use "+itemDef.Name+" on "+name) } } } } } for _, ui := range def.OnUse { if ui.ItemID != "" && (ui.Condition == nil || g.checkCondition(sess, ui.Condition)) { if itemDef, err := g.ItemStore.Load(ui.ItemID); err == nil { addUnique(&rc.objects, &seen, "use "+itemDef.Name+" on "+name) } } } if def.Steal != nil && len(def.Steal.Drops) > 0 { addUnique(&rc.objects, &seen, "steal "+name) } addUnique(&rc.objects, &seen, "look "+name) } for _, m := range g.MobStore.MobsInRoom(p.RoomID) { mName := strings.ToLower(m.Name) if !m.Protected { addUnique(&rc.mobs, &seen, "attack "+mName) } if m.IsTalkable() { addUnique(&rc.mobs, &seen, "talk "+mName) } if len(m.StealDrops) > 0 { addUnique(&rc.mobs, &seen, "steal "+mName) } addUnique(&rc.mobs, &seen, "look "+mName) } if room, err := g.World.LoadRoom(p.RoomID); err == nil { var exitDirs []string for _, dir := range world.ExitOrder { ed, ok := room.Exits[dir] if !ok { continue } if !g.exitDisplayState(sess, p.RoomID, dir, ed).Visible { continue } exitDirs = append(exitDirs, string(dir)) } if len(exitDirs) > 0 { rc.exits = append(rc.exits, 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(&rc.ground, &seen, "get "+def.Name) } } addUnique(&rc.ground, &seen, "get all") } if g.CourseStore != nil { if info := g.CourseStore.GetObstacle(p.RoomID); info != nil { addUnique(&rc.ground, &seen, info.Verb) } } if g.canUseBank(p) { addUnique(&rc.objects, &seen, "bank") } if g.findTerminalInRoom(p.RoomID) != "" { addUnique(&rc.objects, &seen, "jack") } return rc } func (g *Game) executeVerbs(sess *net.Session, args []string, rawInput string) { p := sess.Player if sess.CryptoActive && g.atCryptoCounter(p) { g.showCasinoHints(sess, g.cryptoCommands(sess)) return } if sess.PredictionActive && g.atPredictionCounter(p) { g.showCasinoHints(sess, g.predictionCommands(sess)) return } // A seated casino player has no room verbs: the only valid commands are // the game's live hints, so what/verbs shows those instead of exits and // objects that cannot be used mid-hand. if g.Casino != nil { if table, playing := g.Casino.Participation(p.Name); playing { if table != nil { g.showCasinoHints(sess, table.Hints(p.Name)) } else if machine, ok := g.Casino.MachineFor(p.Name); ok { g.showCasinoHints(sess, machine.Hints()) } return } } showAll := len(args) == 1 && strings.ToLower(args[0]) == "all" rc := g.collectRoomCommands(sess) hasRoomCmds := len(rc.objects) > 0 || len(rc.mobs) > 0 || len(rc.exits) > 0 || len(rc.ground) > 0 if hasRoomCmds { if len(rc.objects) > 0 { sess.WriteLine("Objects:") writeCmdLines(sess, rc.objects) } if len(rc.mobs) > 0 { sess.WriteLine("Mobs:") writeCmdLines(sess, rc.mobs) } if len(rc.exits) > 0 { sess.WriteLine("Exits:") writeCmdLines(sess, rc.exits) } if len(rc.ground) > 0 { sess.WriteLine("Ground:") writeCmdLines(sess, rc.ground) } } 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.`) } } // showCasinoHints prints the live command hints for a seated player. It is // the casino branch of the what/verbs command, so a casino never lists room // exits that are not valid options while seated. func (g *Game) showCasinoHints(sess *net.Session, commands []casino.MenuCommand) { if len(commands) == 0 { sess.WriteLine(g.colorize(sess, "casino_action", "No commands are available right now.")) return } sess.WriteLine(g.casinoMenuCommands(sess, 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) { width := 70 if sess.Player != nil { if w := sess.Player.OptionInt("wrap_width"); w > 0 { width = w } } 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) } }