package game import ( "fmt" "os" "path/filepath" "strconv" "strings" "gopkg.in/yaml.v3" "thehouseoficarus/internal/behavior" "thehouseoficarus/internal/net" "thehouseoficarus/internal/world" ) func (g *Game) roomInsert(sess *net.Session, args []string) { p := sess.Player if p == nil { return } if len(args) == 0 { sess.WriteLine("Usage: room insert [name]") return } dir := g.World.ResolveExit(args[0]) if dir == "" { sess.WriteLine("Invalid direction. Use n/s/e/w/ne/nw/se/sw/u/d.") return } var name string if len(args) > 1 { name = strings.Join(args[1:], " ") } else { name = "New Room" } curPath, ok := g.World.GetRoomPath(p.RoomID) if !ok { sess.WriteLine("Error: can't find current room file.") return } curRoom, err := g.World.LoadRoom(p.RoomID) if err != nil { sess.WriteLine("Error loading current room.") return } exitDef, hasExit := curRoom.Exits[dir] if !hasExit { 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 } targetName := fmt.Sprintf("#%d (%s)", targetID, targetRoom.Name) oppositeDir := world.OppositeExit[dir] newID, err := findNextRoomID(curPath) if err != nil { sess.WriteLine("Error scanning room directory.") return } conflicts := world.InsertGridConflicts(p.RoomID, dir, targetID, newID, func(id int) (*world.Room, bool) { r, err := g.World.LoadRoom(id) if err != nil { return nil, false } return r, true }) if len(conflicts) > 0 { for _, c := range conflicts { switch c.Kind { case "twist": room, _ := g.World.LoadRoom(c.Target) conflictName := fmt.Sprintf("#%d", c.Target) if room != nil { conflictName = fmt.Sprintf("#%d (%s)", c.Target, room.Name) } sess.WriteLine(fmt.Sprintf( "Cannot insert: room %s would have an ambiguous grid position after insertion (reachable via an alternate path from here).", conflictName)) case "overlap": occRoom, _ := g.World.LoadRoom(c.Occupier) occName := fmt.Sprintf("#%d", c.Occupier) if occRoom != nil { occName = fmt.Sprintf("#%d (%s)", c.Occupier, occRoom.Name) } sess.WriteLine(fmt.Sprintf( "Cannot insert: pushing %s would cause grid collision with %s.", targetName, occName)) } } return } dirPath := filepath.Dir(curPath) newPath := filepath.Join(dirPath, strconv.Itoa(newID)+".yaml") newRoom := &world.Room{ Name: name, Description: behavior.DescList{ {Text: "A featureless room."}, }, Exits: map[world.ExitDir]world.ExitDef{ dir: {Room: targetID}, oppositeDir: {Room: p.RoomID}, }, } data, err := yaml.Marshal(newRoom) if err != nil { sess.WriteLine("Error creating room YAML.") return } if err := os.MkdirAll(dirPath, 0755); err != nil { sess.WriteLine("Error creating directory.") return } if err := os.WriteFile(newPath, data, 0644); err != nil { sess.WriteLine("Error writing room file.") return } g.World.AddRoomPath(newID, newPath) curRoom.Exits[dir] = world.ExitDef{ Room: newID, Condition: exitDef.Condition, BlockedMessage: exitDef.BlockedMessage, SetFlags: exitDef.SetFlags, SetPlayerFlags: exitDef.SetPlayerFlags, Hidden: exitDef.Hidden, AlwaysBlocked: exitDef.AlwaysBlocked, } curData, err := yaml.Marshal(curRoom) if err != nil { sess.WriteLine("Error updating current room YAML.") return } if err := os.WriteFile(curPath, curData, 0644); err != nil { sess.WriteLine("Error writing current room file.") return } if targetExit, tok := targetRoom.Exits[oppositeDir]; tok && targetExit.Room == p.RoomID { targetRoom.Exits[oppositeDir] = world.ExitDef{ Room: newID, Condition: targetExit.Condition, BlockedMessage: targetExit.BlockedMessage, SetFlags: targetExit.SetFlags, SetPlayerFlags: targetExit.SetPlayerFlags, Hidden: targetExit.Hidden, AlwaysBlocked: targetExit.AlwaysBlocked, } } targetPath, tok2 := g.World.GetRoomPath(targetID) if tok2 { targetData, terr := yaml.Marshal(targetRoom) if terr == nil { os.WriteFile(targetPath, targetData, 0644) } } oldRoom := p.RoomID p.ClearMoveState() if g.Combat.Get(p.Name) != nil { g.stopCombat(p.Name) } p.Action = nil g.cancelRest(p.Name) g.cancelEnterSeq(p.Name) if p.EnterSeqRoom != 0 && p.EnterSeqRoom == oldRoom { p.EnterSeqRoom = 0 } if ss, ok := g.safespot.Get(p.Name); ok { g.forceLeaveSafespot(sess, p, &ss, "") } p.RoomID = newID p.HazardTimer = 0 p.Stats.RecordRoomVisit(newID) g.AccountStore.SaveCharacter(p) g.World.SeedGroundItems(newID) g.seedRoomMobs(newID) g.seedRoomObjects(newID) if g.Hub != nil { for _, other := range g.Hub.PlayersInRoom(oldRoom) { if other != sess { other.WriteLine(fmt.Sprintf("%s vanishes into the tunnel.", p.Name)) } } g.Hub.EnterRoom(sess, newID) for _, other := range g.Hub.PlayersInRoom(newID) { if other != sess { other.WriteLine(fmt.Sprintf("%s appears.", p.Name)) } } } g.World.RebuildRoomIndex(g.DataDir) sess.WriteLine(fmt.Sprintf("You insert a room to the %s and create Room #%d (%s).", dir, newID, name)) g.doLook(sess) g.runEnterSteps(sess, newID) g.checkAggro(sess) }