package admin import ( "net/http" "os" "path/filepath" "strconv" "thehouseoficarus/internal/world" ) func (s *AdminServer) handleMap(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, `{"error":"method not allowed"}`, http.StatusMethodNotAllowed) return } zStr := r.URL.Query().Get("z") z, err := strconv.Atoi(zStr) if err != nil { z = 0 } dir := r.URL.Query().Get("dir") seed := s.cfg.StartingRoom if dir != "" { base := filepath.Join(s.dataDir, "rooms", dir) if low, ok := findLowestRoom(base); ok { seed = low } } g := world.BuildGrid(seed, func(id int) (*world.Room, bool) { room, err := s.world.LoadRoom(id) if err != nil { return nil, false } return room, true }, nil, nil) type RoomEntry struct { ID int `json:"id"` X int `json:"x"` Y int `json:"y"` Name string `json:"name"` Color string `json:"color"` } type LinkEntry struct { From int `json:"from"` To int `json:"to"` Dir string `json:"dir"` Bidirectional bool `json:"bidirectional"` } type UDLink struct { From int `json:"from"` To int `json:"to"` } roomData := make(map[int]*world.Room) var rooms []RoomEntry minX, maxX := 0, 0 minY, maxY := 0, 0 first := true for rid, coord := range g.Coord { if coord[2] != z { continue } r, err := s.world.LoadRoom(rid) if err != nil { continue } roomData[rid] = r rooms = append(rooms, RoomEntry{ ID: rid, X: coord[0], Y: coord[1], Name: r.Name, Color: r.Color, }) if first { minX, maxX = coord[0], coord[0] minY, maxY = coord[1], coord[1] first = false } else { if coord[0] < minX { minX = coord[0] } if coord[0] > maxX { maxX = coord[0] } if coord[1] < minY { minY = coord[1] } if coord[1] > maxY { maxY = coord[1] } } } var links []LinkEntry var upLinks []UDLink var downLinks []UDLink seenLinks := make(map[string]bool) for rid, room := range roomData { c := g.Coord[rid] for dir, exit := range room.Exits { target := exit.Room if target <= 0 { continue } targetCoord, ok := g.Coord[target] if !ok { continue } if targetCoord[2] > c[2] { upLinks = append(upLinks, UDLink{From: rid, To: target}) continue } if targetCoord[2] < c[2] { downLinks = append(downLinks, UDLink{From: rid, To: target}) continue } if dir == world.Up || dir == world.Down { continue } if _, onZ := roomData[target]; !onZ { continue } bidirectional := false if targetRoom, ok := roomData[target]; ok { if oppExit, ok := targetRoom.Exits[world.OppositeExit[dir]]; ok && oppExit.Room == rid { bidirectional = true } } key := linkKey(rid, target) if bidirectional && seenLinks[key] { continue } seenLinks[key] = true links = append(links, LinkEntry{ From: rid, To: target, Dir: string(dir), Bidirectional: bidirectional, }) } } writeJSON(w, map[string]any{ "rooms": rooms, "links": links, "upLinks": upLinks, "downLinks": downLinks, "dir": getRoomDir(s, seed), "seed": seed, "disconnected": findDisconnectedRooms(s, dir, seed, g), "bounds": map[string]int{ "minX": minX, "maxX": maxX, "minY": minY, "maxY": maxY, }, }) } func getRoomDir(s *AdminServer, roomID int) string { path, ok := s.world.GetRoomPath(roomID) if !ok { return "" } rel, err := filepath.Rel(filepath.Join(s.dataDir, "rooms"), filepath.Dir(path)) if err != nil || rel == "." { return "" } return rel } func linkKey(a, b int) string { if a < b { return strconv.Itoa(a) + "-" + strconv.Itoa(b) } return strconv.Itoa(b) + "-" + strconv.Itoa(a) } func findLowestRoom(dir string) (int, bool) { entries, err := os.ReadDir(dir) if err != nil { return 0, false } lowest := 0 found := false for _, e := range entries { if e.IsDir() { continue } name := e.Name() if filepath.Ext(name) != ".yaml" { continue } id, err := strconv.Atoi(name[:len(name)-5]) if err != nil || id <= 0 { continue } if !found || id < lowest { lowest = id found = true } } return lowest, found } func findDisconnectedRooms(s *AdminServer, dir string, seed int, g world.RoomGrid) []map[string]any { var result []map[string]any scanDir := dir if scanDir == "" { scanDir = getRoomDir(s, seed) } allIDs := listRoomIDsInDir(s, scanDir) for _, id := range allIDs { if _, inGrid := g.Coord[id]; inGrid { continue } room, err := s.world.LoadRoom(id) if err != nil { continue } result = append(result, map[string]any{ "id": id, "name": room.Name, }) } return result } func listRoomIDsInDir(s *AdminServer, dir string) []int { base := filepath.Join(s.dataDir, "rooms") walkRoot := base if dir != "" { walkRoot = filepath.Join(base, dir) } var ids []int filepath.WalkDir(walkRoot, func(path string, d os.DirEntry, err error) error { if err != nil || d.IsDir() || filepath.Ext(d.Name()) != ".yaml" { return nil } name := d.Name() id, convErr := strconv.Atoi(name[:len(name)-5]) if convErr == nil && id > 0 { ids = append(ids, id) } return nil }) return ids } func (s *AdminServer) handleNextRoomID(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, `{"error":"method not allowed"}`, http.StatusMethodNotAllowed) return } fromStr := r.URL.Query().Get("from") fromID := 0 if fromStr != "" { fromID, _ = strconv.Atoi(fromStr) } id, _, err := nextRoomIDInDir(s.dataDir, fromID) if err != nil { writeJSON(w, map[string]any{"error": err.Error()}) return } writeJSON(w, map[string]any{"id": id}) }