Files
rgoue/game/newlevel.go
sneak 0b56ac8019 Extract MessageLine, Player pack ops, and Level list management
Refactor step 6. MessageLine (was MsgLine) owns the msg/addmsg/endmsg
machinery, wired to its screen, pre---More-- redraw, and input via
attach(); RogueGame keeps one-line msg/addmsgf/endmsg shorthands so
the ~400 call sites are unchanged. Player gains nextPackChar and
removeFromPack (the state half of pack.c leave_pack); leavePack keeps
only the LastPick repeat-command tracking. Level gains ObjectAt
(misc.c find_obj) and AddObject/RemoveObject/AddMonster/RemoveMonster,
replacing direct attach/detach calls on the level lists. Inventory and
pickup UI flows stay on RogueGame: display and orchestration, not
state surgery. Behavior and RNG order unchanged; suite green.
2026-07-07 02:42:18 +02:00

164 lines
4.1 KiB
Go

package game
// new_level.c — dig and draw a new level.
const (
treasRoomChance = 20 // one chance in TREAS_ROOM for a treasure room
maxTreas = 10 // maximum number of treasures in a treasure room
minTreas = 2 // minimum number of treasures in a treasure room
maxTries = 10 // max number of tries to put down a monster
)
// NewLevel digs and draws a new level (new_level.c new_level).
func (g *RogueGame) NewLevel() {
p := &g.Player
p.Flags.Clear(Held) // unhold when you go down just in case
if g.Depth > g.MaxDepth {
g.MaxDepth = g.Depth
}
// Clean things off from last level
for i := range g.Level.Places {
g.Level.Places[i] = Place{Ch: ' ', Flags: FReal}
}
g.clear()
// Free up the monsters on the last level; the objects and their packs
// go with them (the garbage collector is our free_list).
g.Level.Monsters = nil
g.Level.Objects = nil
g.digRooms() // Draw rooms
g.digPassages() // Draw passages
p.NoFood++
g.putThings() // Place objects (if any)
// Place the traps
if g.rnd(10) < g.Depth {
g.Level.TrapCount = min(g.rnd(g.Depth/4)+1, MaxTraps)
for i := g.Level.TrapCount; i > 0; i-- {
// not only wouldn't it be NICE to have traps in mazes (not
// that we care about being nice), since the trap number is
// stored where the passage number is, we can't actually do it.
var stairs Coord
for {
stairs, _ = g.findFloor(false)
if g.Level.Char(stairs.Y, stairs.X) == Floor {
break
}
}
sp := g.Level.FlagsAt(stairs.Y, stairs.X)
sp.Clear(FReal)
*sp |= PlaceFlags(g.rnd(NumTrapTypes)) //nolint:gosec // G115: 0..7 fits
}
}
// Place the staircase down.
stairs, _ := g.findFloor(false)
g.Level.Stairs = stairs
g.Level.SetChar(stairs.Y, stairs.X, Stairs)
g.SeenStairs = false
for _, tp := range g.Level.Monsters {
tp.Room = g.roomIn(tp.Pos)
}
hero, _ := g.findFloor(true)
p.Pos = hero
g.enterRoom(hero)
g.mvaddch(hero.Y, hero.X, PlayerCh)
if p.On(SenseMonsters) {
g.turnSee(false)
}
if p.On(Hallucinating) {
g.visuals(0)
}
}
// randomRoom picks a room that is really there (new_level.c rnd_room).
func (g *RogueGame) randomRoom() int {
for {
rm := g.rnd(MaxRooms)
if !g.Level.Rooms[rm].Flags.Has(Gone) {
return rm
}
}
}
// putThings puts potions and scrolls on this level (new_level.c
// put_things).
func (g *RogueGame) putThings() {
// Once you have found the amulet, the only way to get new stuff is to
// go down into the dungeon.
if g.HasAmulet && g.Depth < g.MaxDepth {
return
}
// check for treasure rooms, and if so, put it in.
if g.rnd(treasRoomChance) == 0 {
g.treasureRoom()
}
// Do MAXOBJ attempts to put things on a level
for range MaxObj {
if g.rnd(100) < 36 {
// Pick a new object and link it in the list
obj := g.newThing()
g.Level.AddObject(obj)
// Put it somewhere
obj.Pos, _ = g.findFloor(false)
g.Level.SetChar(obj.Pos.Y, obj.Pos.X, obj.Kind.Glyph())
}
}
// If he is really deep in the dungeon and he hasn't found the amulet
// yet, put it somewhere on the ground
if g.Depth >= AmuletLevel && !g.HasAmulet {
obj := newObject()
g.Level.AddObject(obj)
obj.Damage = dice("0x0")
obj.HurlDmg = dice("0x0")
obj.ArmorClass = 11
obj.Kind = KindAmulet
// Put it somewhere
obj.Pos, _ = g.findFloor(false)
g.Level.SetChar(obj.Pos.Y, obj.Pos.X, Amulet)
}
}
// treasureRoom adds a treasure room (new_level.c treas_room).
func (g *RogueGame) treasureRoom() {
rp := &g.Level.Rooms[g.randomRoom()]
spots := min((rp.Max.Y-2)*(rp.Max.X-2)-minTreas, maxTreas-minTreas)
numMonst := g.rnd(spots) + minTreas
for nm := numMonst; nm > 0; nm-- {
mp, _ := g.findFloorIn(rp, 2*maxTries, false)
tp := g.newThing()
tp.Pos = mp
g.Level.AddObject(tp)
g.Level.SetChar(mp.Y, mp.X, tp.Kind.Glyph())
}
// fill up room with monsters from the next level down
nm := max(g.rnd(spots)+minTreas, numMonst+2)
spots = (rp.Max.Y - 2) * (rp.Max.X - 2)
if nm > spots {
nm = spots
}
g.Depth++
for ; nm > 0; nm-- {
if mp, ok := g.findFloorIn(rp, maxTries, true); ok {
tp := &Monster{}
g.newMonster(tp, g.randMonster(false), mp)
tp.Flags.Set(Mean) // no sloughers in THIS room
g.givePack(tp)
}
}
g.Depth--
}