aboutsummaryrefslogtreecommitdiff
path: root/internal/admin/api_rooms.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-06 17:41:12 -0400
committerhistoria <[not public]>2026-07-06 17:41:12 -0400
commit00554b66df9310073c3b5e1aebd125ea54d9d554 (patch)
tree27ce9751867a00a31d22b08452ddf08a3b4e1513 /internal/admin/api_rooms.go
parent94823b52168b44894fb5e9c960358ed02ebb122b (diff)
downloadthehouseoficarus-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.go140
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,