rooms.c, passages.c, new_level.c ported in full: room/maze layout, corridor spanning tree with extra connections, passage numbering flood fill, trap/stairs/object placement, treasure rooms. Careful RNG-call ordering throughout keeps generation seed-faithful to C. Supporting subsystems this depends on, also ported: - screen.go: curses replaced by in-memory Window cell buffers behind a Terminal interface (tcell arrives with the UI phase; tests run headless) - io.go: msg/addmsg/endmsg message-line protocol, status line, step_ok - pack.c in full (add_pack sorting/stacking walk, get_item, inventory) - things.c in full (inv_name, new_thing, discovery lists, pagination) - monsters.c in full; chase.c navigation half (roomin/cansee/see_monst/ find_dest/runto/set_oldch); rings.c, armor.c in full - weapons.c/sticks.c creation half (init_weapon, fix_stick, num) - misc.c look() display update, eat, level-up, direction input - daemons.c callbacks except stomach (needs death()) and the runners chase driver (combat phase) - init.c init_player with the starting kit Tests: level invariants across seeds, determinism, 30-depth sweep.
139 lines
3.5 KiB
Go
139 lines
3.5 KiB
Go
package game
|
|
|
|
// chase.c — the navigation and visibility half. The chase driver (runners,
|
|
// move_monst, do_chase, chase) arrives with the combat phase, since it
|
|
// attacks the player and fires dragon bolts.
|
|
|
|
// setOldch sets the oldch character for the monster (chase.c set_oldch).
|
|
func (g *RogueGame) setOldch(tp *Monster, cp Coord) {
|
|
if tp.Pos == cp {
|
|
return
|
|
}
|
|
sch := tp.OldCh
|
|
tp.OldCh = g.mvinch(cp.Y, cp.X)
|
|
if !g.Player.On(IsBlind) {
|
|
if (sch == Floor || tp.OldCh == Floor) && tp.Room.Flags.Has(IsDark) {
|
|
tp.OldCh = ' '
|
|
} else if distCp(cp, g.Player.Pos) <= LampDist && g.Options.SeeFloor {
|
|
tp.OldCh = g.Level.Char(cp.Y, cp.X)
|
|
}
|
|
}
|
|
}
|
|
|
|
// seeMonst returns true if the hero can see the monster (chase.c
|
|
// see_monst).
|
|
func (g *RogueGame) seeMonst(mp *Monster) bool {
|
|
p := &g.Player
|
|
if p.On(IsBlind) {
|
|
return false
|
|
}
|
|
if mp.On(IsInvis) && !p.On(CanSee) {
|
|
return false
|
|
}
|
|
y, x := mp.Pos.Y, mp.Pos.X
|
|
if distance(y, x, p.Pos.Y, p.Pos.X) < LampDist {
|
|
if y != p.Pos.Y && x != p.Pos.X &&
|
|
!stepOk(g.Level.Char(y, p.Pos.X)) && !stepOk(g.Level.Char(p.Pos.Y, x)) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
if mp.Room != p.Room {
|
|
return false
|
|
}
|
|
return !mp.Room.Flags.Has(IsDark)
|
|
}
|
|
|
|
// runto sets a monster running after the hero (chase.c runto).
|
|
func (g *RogueGame) runto(runner Coord) {
|
|
tp := g.Level.MonsterAt(runner.Y, runner.X)
|
|
if tp == nil {
|
|
return
|
|
}
|
|
// Start the beastie running
|
|
tp.Flags.Set(IsRun)
|
|
tp.Flags.Clear(IsHeld)
|
|
tp.Dest = g.findDest(tp)
|
|
}
|
|
|
|
// roomin finds what room some coordinates are in; nil means they aren't in
|
|
// any room (chase.c roomin).
|
|
func (g *RogueGame) roomin(cp Coord) *Room {
|
|
fp := *g.Level.FlagsAt(cp.Y, cp.X)
|
|
if fp.Has(FPass) {
|
|
return &g.Level.Passages[fp&FPNum]
|
|
}
|
|
|
|
for i := range g.Level.Rooms {
|
|
rp := &g.Level.Rooms[i]
|
|
if cp.X <= rp.Pos.X+rp.Max.X && rp.Pos.X <= cp.X &&
|
|
cp.Y <= rp.Pos.Y+rp.Max.Y && rp.Pos.Y <= cp.Y {
|
|
return rp
|
|
}
|
|
}
|
|
|
|
g.msg("in some bizarre place (%d, %d)", cp.Y, cp.X)
|
|
return nil
|
|
}
|
|
|
|
// diagOk checks to see if a diagonal move is legal (chase.c diag_ok).
|
|
func (g *RogueGame) diagOk(sp, ep Coord) bool {
|
|
if ep.X < 0 || ep.X >= NumCols || ep.Y <= 0 || ep.Y >= NumLines-1 {
|
|
return false
|
|
}
|
|
if ep.X == sp.X || ep.Y == sp.Y {
|
|
return true
|
|
}
|
|
return stepOk(g.Level.Char(ep.Y, sp.X)) && stepOk(g.Level.Char(sp.Y, ep.X))
|
|
}
|
|
|
|
// cansee returns true if the hero can see a certain coordinate (chase.c
|
|
// cansee).
|
|
func (g *RogueGame) cansee(y, x int) bool {
|
|
p := &g.Player
|
|
if p.On(IsBlind) {
|
|
return false
|
|
}
|
|
if distance(y, x, p.Pos.Y, p.Pos.X) < LampDist {
|
|
if g.Level.FlagsAt(y, x).Has(FPass) {
|
|
if y != p.Pos.Y && x != p.Pos.X &&
|
|
!stepOk(g.Level.Char(y, p.Pos.X)) &&
|
|
!stepOk(g.Level.Char(p.Pos.Y, x)) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
// We can only see if the hero is in the same room as the coordinate
|
|
// and the room is lit, or if it is close.
|
|
rer := g.roomin(Coord{X: x, Y: y})
|
|
return rer == p.Room && !rer.Flags.Has(IsDark)
|
|
}
|
|
|
|
// findDest finds the proper destination for the monster (chase.c
|
|
// find_dest).
|
|
func (g *RogueGame) findDest(tp *Monster) *Coord {
|
|
prob := monsterTable[tp.Type-'A'].Carry
|
|
if prob <= 0 || tp.Room == g.Player.Room || g.seeMonst(tp) {
|
|
return &g.Player.Pos
|
|
}
|
|
for _, obj := range g.Level.Objects {
|
|
if obj.Type == Scroll && obj.Which == SScare {
|
|
continue
|
|
}
|
|
if g.roomin(obj.Pos) == tp.Room && g.rnd(100) < prob {
|
|
claimed := false
|
|
for _, other := range g.Level.Monsters {
|
|
if other.Dest == &obj.Pos {
|
|
claimed = true
|
|
break
|
|
}
|
|
}
|
|
if !claimed {
|
|
return &obj.Pos
|
|
}
|
|
}
|
|
}
|
|
return &g.Player.Pos
|
|
}
|