diff options
| author | historia <[not public]> | 2026-07-06 17:41:12 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-07-06 17:41:12 -0400 |
| commit | 00554b66df9310073c3b5e1aebd125ea54d9d554 (patch) | |
| tree | 27ce9751867a00a31d22b08452ddf08a3b4e1513 /internal/admin/api_rooms.go | |
| parent | 94823b52168b44894fb5e9c960358ed02ebb122b (diff) | |
| download | thehouseoficarus-00554b66df9310073c3b5e1aebd125ea54d9d554.tar.gz | |
feat: shift+drag to create one-way links on admin map
Diffstat (limited to 'internal/admin/api_rooms.go')
| -rw-r--r-- | internal/admin/api_rooms.go | 140 |
1 files changed, 88 insertions, 52 deletions
diff --git a/internal/admin/api_rooms.go b/internal/admin/api_rooms.go index 448503d..b5353c2 100644 --- a/internal/admin/api_rooms.go +++ b/internal/admin/api_rooms.go @@ -415,19 +415,20 @@ func (s *AdminServer) handleRoomLink(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodPost: var body struct { - From int `json:"from"` - To int `json:"to"` - Dir string `json:"dir"` + From int `json:"from"` + To int `json:"to"` + Dir string `json:"dir"` + OneWay bool `json:"oneway"` } if err := readJSON(r, &body); err != nil || body.From <= 0 || body.To <= 0 { - writeJSON(w, map[string]any{"error": "invalid body, need from and to"}) + writeJSONError(w, "invalid body, need from and to", http.StatusBadRequest) return } roomA, errA := s.world.LoadRoom(body.From) roomB, errB := s.world.LoadRoom(body.To) if errA != nil || errB != nil { - writeJSON(w, map[string]any{"error": "one or both rooms not found"}) + writeJSONError(w, "one or both rooms not found", http.StatusBadRequest) return } @@ -437,7 +438,7 @@ func (s *AdminServer) handleRoomLink(w http.ResponseWriter, r *http.Request) { if opp, ok := world.OppositeExit[dir]; ok { oppDir = opp } else { - writeJSON(w, map[string]any{"error": "invalid direction: " + body.Dir}) + writeJSONError(w, "invalid direction: "+body.Dir, http.StatusBadRequest) return } } else { @@ -453,11 +454,11 @@ func (s *AdminServer) handleRoomLink(w http.ResponseWriter, r *http.Request) { cA, okA := grid.Coord[body.From] cB, okB := grid.Coord[body.To] if !okA || !okB { - writeJSON(w, map[string]any{"error": "one or both rooms are not reachable from the seed room"}) + writeJSONError(w, "one or both rooms are not reachable from the seed room", http.StatusBadRequest) return } if cA[2] != cB[2] { - writeJSON(w, map[string]any{"error": "rooms must be on same z-level"}) + writeJSONError(w, "rooms must be on same z-level", http.StatusBadRequest) return } dx, dy := cB[0]-cA[0], cB[1]-cA[1] @@ -469,7 +470,7 @@ func (s *AdminServer) handleRoomLink(w http.ResponseWriter, r *http.Request) { } } if dir == "" { - writeJSON(w, map[string]any{"error": "rooms are not adjacent"}) + writeJSONError(w, "rooms are not adjacent", http.StatusBadRequest) return } } @@ -491,6 +492,21 @@ func (s *AdminServer) handleRoomLink(w http.ResponseWriter, r *http.Request) { } } + if roomA.Exits != nil { + if existing, ok := roomA.Exits[dir]; ok && existing.Room != body.To { + writeJSONError(w, fmt.Sprintf("%s in #%d already links to #%d", dir, body.From, existing.Room), http.StatusConflict) + return + } + } + if !body.OneWay { + if roomB.Exits != nil { + if existing, ok := roomB.Exits[oppDir]; ok && existing.Room != body.From { + writeJSONError(w, fmt.Sprintf("%s in #%d already links to #%d", oppDir, body.To, existing.Room), http.StatusConflict) + return + } + } + } + conflicts := 0 testGrid := world.BuildGrid(s.cfg.StartingRoom, func(id int) (*world.Room, bool) { room, err := s.world.LoadRoom(id) @@ -513,7 +529,7 @@ func (s *AdminServer) handleRoomLink(w http.ResponseWriter, r *http.Request) { if id == body.From { roomCopy.Exits[dir] = world.ExitDef{Room: body.To} } - if id == body.To { + if id == body.To && !body.OneWay { roomCopy.Exits[oppDir] = world.ExitDef{Room: body.From} } return &roomCopy, true @@ -532,28 +548,23 @@ func (s *AdminServer) handleRoomLink(w http.ResponseWriter, r *http.Request) { } roomA.Exits[dir] = world.ExitDef{Room: body.To} - if roomB.Exits == nil { - roomB.Exits = make(map[world.ExitDir]world.ExitDef) + if !body.OneWay { + if roomB.Exits == nil { + roomB.Exits = make(map[world.ExitDir]world.ExitDef) + } + roomB.Exits[oppDir] = world.ExitDef{Room: body.From} } - roomB.Exits[oppDir] = world.ExitDef{Room: body.From} pathA, okA2 := s.world.GetRoomPath(body.From) - pathB, okB2 := s.world.GetRoomPath(body.To) - if !okA2 || !okB2 { - writeJSON(w, map[string]any{"error": "room path not found"}) + if !okA2 { + writeJSONError(w, "room path not found", http.StatusInternalServerError) return } oldA, _ := snapshotFile(pathA) - oldB, _ := snapshotFile(pathB) newA, err := writeYAMLFile(pathA, roomA) if err != nil { - writeJSON(w, map[string]any{"error": "write A: " + err.Error()}) - return - } - newB, err := writeYAMLFile(pathB, roomB) - if err != nil { - writeJSON(w, map[string]any{"error": "write B: " + err.Error()}) + writeJSONError(w, "write A: "+err.Error(), http.StatusInternalServerError) return } s.undoStack.Push(ChangeDesc{ @@ -562,24 +573,39 @@ func (s *AdminServer) handleRoomLink(w http.ResponseWriter, r *http.Request) { OldContent: oldA, NewContent: newA, }) - s.undoStack.Push(ChangeDesc{ - Description: fmt.Sprintf("link %d %s %d (reverse)", body.To, oppDir, body.From), - FilePath: pathB, - OldContent: oldB, - NewContent: newB, - }) + + if !body.OneWay { + pathB, okB2 := s.world.GetRoomPath(body.To) + if !okB2 { + writeJSONError(w, "room B path not found", http.StatusInternalServerError) + return + } + oldB, _ := snapshotFile(pathB) + newB, err := writeYAMLFile(pathB, roomB) + if err != nil { + writeJSONError(w, "write B: "+err.Error(), http.StatusInternalServerError) + return + } + s.undoStack.Push(ChangeDesc{ + Description: fmt.Sprintf("link %d %s %d (reverse)", body.To, oppDir, body.From), + FilePath: pathB, + OldContent: oldB, + NewContent: newB, + }) + } writeJSON(w, map[string]any{"ok": true}) case http.MethodDelete: var body struct { - From int `json:"from"` - To int `json:"to"` + From int `json:"from"` + To int `json:"to"` + Side string `json:"side"` } if err := readJSON(r, &body); err != nil || body.From <= 0 || body.To <= 0 { if r.Body != nil { r.Body.Close() } - writeJSON(w, map[string]any{"error": "invalid body"}) + writeJSONError(w, "invalid body", http.StatusBadRequest) return } @@ -587,73 +613,83 @@ func (s *AdminServer) handleRoomLink(w http.ResponseWriter, r *http.Request) { roomB, errB := s.world.LoadRoom(body.To) if errA != nil && errB != nil { - writeJSON(w, map[string]any{"error": "both rooms not found"}) + writeJSONError(w, "both rooms not found", http.StatusBadRequest) return } - var dir, oppDir world.ExitDir + var dirA, dirB world.ExitDir if roomA != nil { for _, d := range world.ExitOrder { if exit, ok := roomA.Exits[d]; ok && exit.Room == body.To { - dir = d - oppDir = world.OppositeExit[d] + dirA = d + dirB = world.OppositeExit[d] break } } } - if dir == "" && roomB != nil { + if dirA == "" && roomB != nil { for _, d := range world.ExitOrder { if exit, ok := roomB.Exits[d]; ok && exit.Room == body.From { - oppDir = d - dir = world.OppositeExit[d] + dirB = d + dirA = world.OppositeExit[d] break } } } - if roomA != nil && roomA.Exits != nil { - delete(roomA.Exits, dir) - } - if roomB != nil && roomB.Exits != nil { - delete(roomB.Exits, oppDir) + if body.Side == "from" { + if roomA != nil && roomA.Exits != nil { + delete(roomA.Exits, dirA) + } + } else if body.Side == "to" { + if roomB != nil && roomB.Exits != nil { + delete(roomB.Exits, dirB) + } + } else { + if roomA != nil && roomA.Exits != nil { + delete(roomA.Exits, dirA) + } + if roomB != nil && roomB.Exits != nil { + delete(roomB.Exits, dirB) + } } var pushEntries []ChangeDesc - if roomA != nil { + if roomA != nil && (body.Side == "" || body.Side == "from") { pathA, okA2 := s.world.GetRoomPath(body.From) if !okA2 { - writeJSON(w, map[string]any{"error": "room A path not found"}) + writeJSONError(w, "room A path not found", http.StatusInternalServerError) return } oldA, _ := snapshotFile(pathA) newA, err := writeYAMLFile(pathA, roomA) if err != nil { - writeJSON(w, map[string]any{"error": "write A: " + err.Error()}) + writeJSONError(w, "write A: "+err.Error(), http.StatusInternalServerError) return } pushEntries = append(pushEntries, ChangeDesc{ - Description: fmt.Sprintf("unlink %d %s %d", body.From, dir, body.To), + Description: fmt.Sprintf("unlink %d %s %d", body.From, dirA, body.To), FilePath: pathA, OldContent: oldA, NewContent: newA, }) } - if roomB != nil { + if roomB != nil && (body.Side == "" || body.Side == "to") { pathB, okB2 := s.world.GetRoomPath(body.To) if !okB2 { - writeJSON(w, map[string]any{"error": "room B path not found"}) + writeJSONError(w, "room B path not found", http.StatusInternalServerError) return } oldB, _ := snapshotFile(pathB) newB, err := writeYAMLFile(pathB, roomB) if err != nil { - writeJSON(w, map[string]any{"error": "write B: " + err.Error()}) + writeJSONError(w, "write B: "+err.Error(), http.StatusInternalServerError) return } pushEntries = append(pushEntries, ChangeDesc{ - Description: fmt.Sprintf("unlink %d %s %d (reverse)", body.To, oppDir, body.From), + Description: fmt.Sprintf("unlink %d %s %d (reverse)", body.To, dirB, body.From), FilePath: pathB, OldContent: oldB, NewContent: newB, |
