diff options
| author | historia <[not public]> | 2026-06-28 02:40:30 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-06-28 02:40:30 -0400 |
| commit | a1b6613c6c6a2f8d6e1ecd47e766bd40f92ac295 (patch) | |
| tree | 4ff8fefb029c93f2aa3db7361658b0b4e9805b66 /internal/game/cmd_undig.go | |
| parent | 87dacfbb3dd16e7f55a82361eace98f9a39d75c8 (diff) | |
| download | thehouseoficarus-a1b6613c6c6a2f8d6e1ecd47e766bd40f92ac295.tar.gz | |
feat: admin accounts, god mode, OLC commands like dig/room, 'inline' objects changed to 'local' objects
Diffstat (limited to 'internal/game/cmd_undig.go')
| -rw-r--r-- | internal/game/cmd_undig.go | 248 |
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)) +} |
