go: port dungeon generation, items base, pack, and monster creation
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.
This commit is contained in:
193
game/monsters.go
Normal file
193
game/monsters.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package game
|
||||
|
||||
// monsters.c — monster creation and saving throws.
|
||||
|
||||
// lvlMons and wandMons list monsters in rough order of vorpalness; zero
|
||||
// entries in wandMons never wander (monsters.c).
|
||||
var lvlMons = [26]byte{
|
||||
'K', 'E', 'B', 'S', 'H', 'I', 'R', 'O', 'Z', 'L', 'C', 'Q', 'A',
|
||||
'N', 'Y', 'F', 'T', 'W', 'P', 'X', 'U', 'M', 'V', 'G', 'J', 'D',
|
||||
}
|
||||
|
||||
var wandMons = [26]byte{
|
||||
'K', 'E', 'B', 'S', 'H', 0, 'R', 'O', 'Z', 0, 'C', 'Q', 'A',
|
||||
0, 'Y', 0, 'T', 'W', 'P', 0, 'U', 'M', 'V', 'G', 'J', 0,
|
||||
}
|
||||
|
||||
// randMonster picks a monster to show up; the lower the level, the meaner
|
||||
// the monster (monsters.c randmonster).
|
||||
func (g *RogueGame) randMonster(wander bool) byte {
|
||||
mons := &lvlMons
|
||||
if wander {
|
||||
mons = &wandMons
|
||||
}
|
||||
for {
|
||||
d := g.Depth + (g.rnd(10) - 6)
|
||||
if d < 0 {
|
||||
d = g.rnd(5)
|
||||
}
|
||||
if d > 25 {
|
||||
d = g.rnd(5) + 21
|
||||
}
|
||||
if mons[d] != 0 {
|
||||
return mons[d]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// newMonster picks a new monster and adds it to the list (monsters.c
|
||||
// new_monster).
|
||||
func (g *RogueGame) newMonster(tp *Monster, typ byte, cp Coord) {
|
||||
levAdd := g.Depth - AmuletLevel
|
||||
if levAdd < 0 {
|
||||
levAdd = 0
|
||||
}
|
||||
attachMon(&g.Level.Monsters, tp)
|
||||
tp.Type = typ
|
||||
tp.Disguise = typ
|
||||
tp.Pos = cp
|
||||
g.move(cp.Y, cp.X)
|
||||
tp.OldCh = g.inch()
|
||||
tp.Room = g.roomin(cp)
|
||||
g.Level.SetMonsterAt(cp.Y, cp.X, tp)
|
||||
mp := &monsterTable[tp.Type-'A']
|
||||
tp.Stats.Lvl = mp.Stats.Lvl + levAdd
|
||||
tp.Stats.MaxHP = g.roll(tp.Stats.Lvl, 8)
|
||||
tp.Stats.HP = tp.Stats.MaxHP
|
||||
tp.Stats.Arm = mp.Stats.Arm - levAdd
|
||||
tp.Stats.Dmg = mp.Stats.Dmg
|
||||
tp.Stats.Str = mp.Stats.Str
|
||||
tp.Stats.Exp = mp.Stats.Exp + levAdd*10 + expAdd(tp)
|
||||
tp.Flags = mp.Flags
|
||||
if g.Depth > 29 {
|
||||
tp.Flags.Set(IsHaste)
|
||||
}
|
||||
tp.Turn = true
|
||||
tp.Pack = nil
|
||||
if g.Player.IsWearing(RAggr) {
|
||||
g.runto(cp)
|
||||
}
|
||||
if typ == 'X' {
|
||||
tp.Disguise = g.rndThing()
|
||||
}
|
||||
}
|
||||
|
||||
// expAdd is the experience to add for this monster's level/hit points
|
||||
// (monsters.c exp_add).
|
||||
func expAdd(tp *Monster) int {
|
||||
var mod int
|
||||
if tp.Stats.Lvl == 1 {
|
||||
mod = tp.Stats.MaxHP / 8
|
||||
} else {
|
||||
mod = tp.Stats.MaxHP / 6
|
||||
}
|
||||
if tp.Stats.Lvl > 9 {
|
||||
mod *= 20
|
||||
} else if tp.Stats.Lvl > 6 {
|
||||
mod *= 4
|
||||
}
|
||||
return mod
|
||||
}
|
||||
|
||||
// wanderer creates a new wandering monster and aims it at the player
|
||||
// (monsters.c wanderer).
|
||||
func (g *RogueGame) wanderer() {
|
||||
tp := &Monster{}
|
||||
var cp Coord
|
||||
for {
|
||||
cp, _ = g.findFloor(nil, 0, true)
|
||||
if g.roomin(cp) != g.Player.Room {
|
||||
break
|
||||
}
|
||||
}
|
||||
g.newMonster(tp, g.randMonster(true), cp)
|
||||
if g.Player.On(SeeMonst) {
|
||||
g.standout()
|
||||
if !g.Player.On(IsHalu) {
|
||||
g.addch(tp.Type)
|
||||
} else {
|
||||
g.addch(byte(g.rnd(26) + 'A'))
|
||||
}
|
||||
g.standend()
|
||||
}
|
||||
g.runto(tp.Pos)
|
||||
}
|
||||
|
||||
// wakeMonster is what to do when the hero steps next to a monster
|
||||
// (monsters.c wake_monster).
|
||||
func (g *RogueGame) wakeMonster(y, x int) *Monster {
|
||||
p := &g.Player
|
||||
tp := g.Level.MonsterAt(y, x)
|
||||
if tp == nil {
|
||||
panic("can't find monster in wake_monster")
|
||||
}
|
||||
ch := tp.Type
|
||||
// Every time he sees a mean monster, it might start chasing him
|
||||
if !tp.On(IsRun) && g.rnd(3) != 0 && tp.On(IsMean) && !tp.On(IsHeld) &&
|
||||
!p.IsWearing(RStealth) && !p.On(IsLevit) {
|
||||
tp.Dest = &p.Pos
|
||||
tp.Flags.Set(IsRun)
|
||||
}
|
||||
if ch == 'M' && !p.On(IsBlind) && !p.On(IsHalu) &&
|
||||
!tp.On(IsFound) && !tp.On(IsCanc) && tp.On(IsRun) {
|
||||
rp := p.Room
|
||||
if (rp != nil && !rp.Flags.Has(IsDark)) ||
|
||||
distance(y, x, p.Pos.Y, p.Pos.X) < LampDist {
|
||||
tp.Flags.Set(IsFound)
|
||||
if !g.save(VsMagic) {
|
||||
if p.On(IsHuh) {
|
||||
g.Lengthen(DUnconfuse, g.spread(HuhDuration))
|
||||
} else {
|
||||
g.Fuse(DUnconfuse, 0, g.spread(HuhDuration), After)
|
||||
}
|
||||
p.Flags.Set(IsHuh)
|
||||
mname := g.setMname(tp)
|
||||
g.addmsg("%s", mname)
|
||||
if mname != "it" {
|
||||
g.addmsg("'")
|
||||
}
|
||||
g.msg("s gaze has confused you")
|
||||
}
|
||||
}
|
||||
}
|
||||
// Let greedy ones guard gold
|
||||
if tp.On(IsGreed) && !tp.On(IsRun) {
|
||||
tp.Flags.Set(IsRun)
|
||||
if p.Room.GoldVal != 0 {
|
||||
tp.Dest = &p.Room.Gold
|
||||
} else {
|
||||
tp.Dest = &p.Pos
|
||||
}
|
||||
}
|
||||
return tp
|
||||
}
|
||||
|
||||
// givePack gives a pack to a monster if it deserves one (monsters.c
|
||||
// give_pack).
|
||||
func (g *RogueGame) givePack(tp *Monster) {
|
||||
if g.Depth >= g.MaxDepth && g.rnd(100) < monsterTable[tp.Type-'A'].Carry {
|
||||
attachObj(&tp.Pack, g.newThing())
|
||||
}
|
||||
}
|
||||
|
||||
// saveThrow sees if a creature saves against something (monsters.c
|
||||
// save_throw).
|
||||
func (g *RogueGame) saveThrow(which int, st *Stats) bool {
|
||||
need := 14 + which - st.Lvl/2
|
||||
return g.roll(1, 20) >= need
|
||||
}
|
||||
|
||||
// save sees if the hero saves against various nasty things (monsters.c
|
||||
// save).
|
||||
func (g *RogueGame) save(which int) bool {
|
||||
p := &g.Player
|
||||
if which == VsMagic {
|
||||
if p.IsRing(Left, RProtect) {
|
||||
which -= p.CurRing[Left].Arm
|
||||
}
|
||||
if p.IsRing(Right, RProtect) {
|
||||
which -= p.CurRing[Right].Arm
|
||||
}
|
||||
}
|
||||
return g.saveThrow(which, &p.Stats)
|
||||
}
|
||||
Reference in New Issue
Block a user