package game import ( "fmt" "sort" "strconv" "strings" "thehouseoficarus/internal/color" "thehouseoficarus/internal/net" "thehouseoficarus/internal/player" "thehouseoficarus/internal/ui" ) func (g *Game) executeAps(sess *net.Session, args []string, rawInput string) { p := sess.Player known := getKnownApsNodes(p) if len(args) == 0 { if len(known) == 0 { sess.WriteLine("You have no APS coordinates stored in your datapad. Use an APS Tower to add some.") return } displayApsNodes(g, sess, p, known) return } if len(known) == 0 { sess.WriteLine("You have no APS coordinates stored in your datapad. Use an APS Tower to add some.") return } targetID := resolveApsTarget(g, sess, p, known, args) if targetID == 0 { return } if targetID == p.RoomID { sess.WriteLine("You are already at that location.") return } path := g.findPathToRoom(p.RoomID, targetID, p.GodMode) if path == nil { room, err := g.World.LoadRoom(targetID) roomName := fmt.Sprintf("#%d", targetID) if err == nil { roomName = room.Name } sess.WriteLine(fmt.Sprintf("No path found to %s (#%d).", roomName, targetID)) return } agilityLevel := p.Level(player.Agility) if agilityLevel < 1 { agilityLevel = 1 } truncated := path if len(path) > agilityLevel { truncated = path[:agilityLevel] } room, _ := g.World.LoadRoom(targetID) roomName := fmt.Sprintf("#%d", targetID) if room != nil { roomName = room.Name } g.cancelAction(p) p.WalkSequence = truncated sess.WriteLine(fmt.Sprintf("You set off towards %s (#%d) — %d move%s queued.", roomName, targetID, len(truncated), plural(len(truncated)))) } func getKnownApsNodes(p *player.Player) []int { if p.Flags == nil { return nil } var nodes []int for k, v := range p.Flags { if !strings.HasPrefix(k, "aps_node_") { continue } if b, ok := v.(bool); !ok || !b { continue } idStr := strings.TrimPrefix(k, "aps_node_") id, err := strconv.Atoi(idStr) if err != nil { continue } nodes = append(nodes, id) } sort.Ints(nodes) return nodes } type apsNodeInfo struct { ID int Name string Distance int } func displayApsNodes(g *Game, sess *net.Session, p *player.Player, known []int) { var infos []apsNodeInfo for _, id := range known { room, err := g.World.LoadRoom(id) name := fmt.Sprintf("#%d", id) if err == nil { name = room.Name } dist := 0 if id != p.RoomID { path := g.findPathToRoom(p.RoomID, id, p.GodMode) if path != nil { dist = len(path) } } infos = append(infos, apsNodeInfo{ID: id, Name: name, Distance: dist}) } sort.Slice(infos, func(i, j int) bool { return infos[i].Distance < infos[j].Distance }) mode := g.colorMode(sess) rows := make([][]string, len(infos)) for i, info := range infos { distStr := strconv.Itoa(info.Distance) if info.Distance == 0 { distStr = color.Render(mode, color.Parse("52"), "here") } rows[i] = []string{ color.Render(mode, color.Parse("F5"), fmt.Sprintf("#%d", info.ID)), color.Render(mode, color.Parse("4B"), info.Name), distStr, } } t := &ui.Table{ Title: "APS Datapad", Columns: []string{ color.Render(mode, color.Parse("F5"), "Room"), color.Render(mode, color.Parse("4B"), "Name"), color.Render(mode, color.Parse("E6"), "Distance"), }, Rows: rows, } for _, line := range t.Render(p.OptionBool("unicode"), p.OptionInt("wrap_width")) { sess.WriteLine(line) } } func resolveApsTarget(g *Game, sess *net.Session, p *player.Player, known []int, args []string) int { input := strings.ToLower(strings.Join(args, " ")) if input == "nearest" { bestID := 0 bestDist := -1 for _, id := range known { if id == p.RoomID { return id } path := g.findPathToRoom(p.RoomID, id, p.GodMode) if path == nil { continue } if bestDist == -1 || len(path) < bestDist { bestDist = len(path) bestID = id } } if bestID == 0 { sess.WriteLine("No reachable APS nodes found.") return 0 } return bestID } if roomID, err := strconv.Atoi(input); err == nil { for _, id := range known { if id == roomID { return roomID } } sess.WriteLine(fmt.Sprintf("Room #%d is not in your APS datapad.", roomID)) return 0 } var matches []int for _, id := range known { room, err := g.World.LoadRoom(id) if err != nil { continue } nameLower := strings.ToLower(room.Name) if strings.HasPrefix(nameLower, input) || strings.HasPrefix(input, nameLower) { matches = append(matches, id) } } if len(matches) == 1 { return matches[0] } if len(matches) > 1 { sess.WriteLine("Multiple APS nodes match that name. Be more specific or use the room number.") return 0 } sess.WriteLine("That location is not in your APS datapad.") return 0 }