From dde563e6810124a73de5ef12e92774c540f9d62e Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Sat, 20 Jun 2026 03:51:23 -0400 Subject: feat: aps system implemented --- internal/game/cmd_aps.go | 213 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 internal/game/cmd_aps.go (limited to 'internal/game/cmd_aps.go') diff --git a/internal/game/cmd_aps.go b/internal/game/cmd_aps.go new file mode 100644 index 0000000..8e20c6c --- /dev/null +++ b/internal/game/cmd_aps.go @@ -0,0 +1,213 @@ +package game + +import ( + "fmt" + "sort" + "strconv" + "strings" + + "thehouseoficarus/internal/color" + "thehouseoficarus/internal/net" + "thehouseoficarus/internal/player" +) + +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) + 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) + 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("82"), "here") + } + rows[i] = []string{ + color.Render(mode, color.Parse("245"), fmt.Sprintf("#%d", info.ID)), + color.Render(mode, color.Parse("75"), info.Name), + distStr, + } + } + + t := &Table{ + Title: "APS Datapad", + Columns: []string{ + color.Render(mode, color.Parse("245"), "Room"), + color.Render(mode, color.Parse("75"), "Name"), + color.Render(mode, color.Parse("230"), "Distance"), + }, + Rows: rows, + } + + for _, line := range t.Render(p.OptionBool("unicode")) { + 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) + 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 +} -- cgit v1.2.3