aboutsummaryrefslogtreecommitdiff
path: root/internal/game/cmd_undig.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/cmd_undig.go')
-rw-r--r--internal/game/cmd_undig.go248
1 files changed, 248 insertions, 0 deletions
diff --git a/internal/game/cmd_undig.go b/internal/game/cmd_undig.go
new file mode 100644
index 0000000..2cfae70
--- /dev/null
+++ b/internal/game/cmd_undig.go
@@ -0,0 +1,248 @@
+package game
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "gopkg.in/yaml.v3"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
+)
+
+func (g *Game) executeUndig(sess *net.Session, args []string, rawInput string) {
+ if !g.checkAdmin(sess) {
+ sess.WriteLine("Unknown command.")
+ return
+ }
+ p := sess.Player
+ if p == nil {
+ return
+ }
+ if len(args) == 0 {
+ sess.WriteLine("Undig which direction?")
+ return
+ }
+
+ dir := g.World.ResolveExit(args[0])
+ if dir == "" {
+ sess.WriteLine("Invalid direction. Use north/south/east/west/up/down.")
+ return
+ }
+
+ curRoom, err := g.World.LoadRoom(p.RoomID)
+ if err != nil {
+ sess.WriteLine("Error loading current room.")
+ return
+ }
+
+ exitDef, ok := curRoom.Exits[dir]
+ if !ok {
+ sess.WriteLine(fmt.Sprintf("There is no exit to the %s from here.", dir))
+ return
+ }
+
+ targetID := exitDef.Room
+ targetRoom, err := g.World.LoadRoom(targetID)
+ if err != nil {
+ sess.WriteLine(fmt.Sprintf("Target room %d not found.", targetID))
+ return
+ }
+
+ sess.PendingUndigDir = string(dir)
+ sess.PendingUndigRoom = targetID
+ sess.State = net.StateUndigConfirm
+
+ sess.WriteLine(fmt.Sprintf("You are about to DELETE Room #%d (%s) and all exits leading to it.", targetID, targetRoom.Name))
+ sess.WriteLine("")
+ g.listUndigOrphans(sess, targetID)
+ sess.WriteLine("")
+ sess.WriteLine(fmt.Sprintf("Type UNDIG %s to confirm, or anything else to cancel.", strings.ToUpper(string(dir))))
+}
+
+func (g *Game) listUndigOrphans(sess *net.Session, targetID int) {
+ roomIndex := g.World.RoomIndex()
+
+ inbound := make(map[int]bool)
+ roomsDir := filepath.Join(g.DataDir, "rooms")
+ re := regexp.MustCompile(fmt.Sprintf(`\b%d\b`, targetID))
+ _ = filepath.WalkDir(roomsDir, func(path string, d os.DirEntry, err error) error {
+ if err != nil || d.IsDir() || filepath.Ext(path) != ".yaml" {
+ return nil
+ }
+ data, rerr := os.ReadFile(path)
+ if rerr != nil {
+ return nil
+ }
+ if re.MatchString(string(data)) {
+ idStr := strings.TrimSuffix(filepath.Base(path), ".yaml")
+ if rid, serr := strconv.Atoi(idStr); serr == nil && rid != targetID {
+ inbound[rid] = true
+ }
+ }
+ return nil
+ })
+
+ if len(inbound) == 0 {
+ return
+ }
+
+ var orphans []int
+ for rid := range inbound {
+ room, err := g.World.LoadRoom(rid)
+ if err != nil {
+ continue
+ }
+ hasOtherExit := false
+ for _, exitDef := range room.Exits {
+ if roomIndex[exitDef.Room] && exitDef.Room != targetID {
+ hasOtherExit = true
+ break
+ }
+ }
+ if !hasOtherExit {
+ orphans = append(orphans, rid)
+ }
+ }
+
+ if len(orphans) > 0 {
+ sess.WriteLine(g.colorize(sess, "warning",
+ fmt.Sprintf("WARNING: The following rooms will become unreachable after deletion:")))
+ for _, rid := range orphans {
+ room, err := g.World.LoadRoom(rid)
+ name := fmt.Sprintf("#%d", rid)
+ if err == nil {
+ name = fmt.Sprintf("#%d (%s)", rid, room.Name)
+ }
+ sess.WriteLine(fmt.Sprintf(" %s", name))
+ }
+ }
+}
+
+func (g *Game) handleUndigConfirm(sess *net.Session, input string) {
+ p := sess.Player
+ if p == nil {
+ sess.State = net.StateGame
+ g.writePrompt(sess)
+ return
+ }
+
+ choice := strings.TrimSpace(input)
+ dir := sess.PendingUndigDir
+ targetID := sess.PendingUndigRoom
+ sess.PendingUndigDir = ""
+ sess.PendingUndigRoom = 0
+ sess.State = net.StateGame
+
+ if dir == "" || targetID == 0 {
+ g.writePrompt(sess)
+ return
+ }
+
+ expected := "UNDIG " + strings.ToUpper(dir)
+ if strings.ToUpper(choice) != expected {
+ sess.WriteLine("Undig cancelled.")
+ g.writePrompt(sess)
+ return
+ }
+
+ g.performUndig(sess, p, targetID)
+ g.writePrompt(sess)
+}
+
+func (g *Game) performUndig(sess *net.Session, p *player.Player, targetID int) {
+ targetRoom, err := g.World.LoadRoom(targetID)
+ roomName := fmt.Sprintf("#%d", targetID)
+ if err == nil {
+ roomName = fmt.Sprintf("#%d (%s)", targetID, targetRoom.Name)
+ } else {
+ sess.WriteLine(fmt.Sprintf("Target room %d no longer exists.", targetID))
+ return
+ }
+
+ roomsDir := filepath.Join(g.DataDir, "rooms")
+ re := regexp.MustCompile(fmt.Sprintf(`\b%d\b`, targetID))
+ var cleaned int
+ _ = filepath.WalkDir(roomsDir, func(path string, d os.DirEntry, err error) error {
+ if err != nil || d.IsDir() || filepath.Ext(path) != ".yaml" {
+ return nil
+ }
+ data, rerr := os.ReadFile(path)
+ if rerr != nil {
+ return nil
+ }
+ if !re.MatchString(string(data)) {
+ return nil
+ }
+
+ idStr := strings.TrimSuffix(filepath.Base(path), ".yaml")
+ rid, serr := strconv.Atoi(idStr)
+ if serr != nil || rid == targetID {
+ return nil
+ }
+
+ var room world.Room
+ if yerr := yaml.Unmarshal(data, &room); yerr != nil {
+ return nil
+ }
+ if room.Exits == nil {
+ return nil
+ }
+
+ changed := false
+ for ed, exitDef := range room.Exits {
+ if exitDef.Room == targetID {
+ delete(room.Exits, ed)
+ changed = true
+ }
+ }
+
+ if changed {
+ newData, merr := yaml.Marshal(&room)
+ if merr != nil {
+ return nil
+ }
+ if werr := os.WriteFile(path, newData, 0644); werr != nil {
+ return werr
+ }
+ cleaned++
+ }
+ return nil
+ })
+
+ targetPath, ok := g.World.GetRoomPath(targetID)
+ if !ok {
+ sess.WriteLine(fmt.Sprintf("Room file for %d not found.", targetID))
+ return
+ }
+ if rerr := os.Remove(targetPath); rerr != nil {
+ sess.WriteLine(fmt.Sprintf("Error deleting room file: %v", rerr))
+ return
+ }
+
+ g.World.RebuildRoomIndex(g.DataDir)
+
+ if p.RoomID == targetID {
+ p.RoomID = 0
+ }
+
+ for _, other := range g.Hub.AllSessions() {
+ if other.Player != nil && other.Player.RoomID == targetID {
+ other.Player.RoomID = p.RoomID
+ if g.Hub != nil {
+ g.Hub.EnterRoom(other, p.RoomID)
+ }
+ other.WriteLine(fmt.Sprintf("The room around you dissolves. You find yourself elsewhere."))
+ g.doLook(other)
+ }
+ }
+
+ g.MobStore.RemoveMobsInRoom(targetID)
+ g.World.ClearRoomState(targetID)
+
+ sess.WriteLine(fmt.Sprintf("Deleted room %s and %d exit(s) pointing to it.", roomName, cleaned))
+}