- fight.c in full: fight/attack with all eight special monster attacks (aquator rust, ice freeze, rattlesnake poison, wraith/vampire drains, flytrap hold, leprechaun/nymph theft), swing, roll_em dice parsing (with a C-semantics atoi — Go's strconv rejects '1x4/...'), hit/miss message variants, killed, remove_mon - chase.c completed: runners, move_monst, relocate, do_chase with dragon breath, chase target selection (C's dead oroom comparison in relocate is preserved as-is) - move.c in full: do_move with passgo turning, all eight traps, rndmove, rust_armor - sticks.c completed: do_zap (all 14 sticks), drain, fire_bolt with wall bounces; weapons.c completed: missile/do_motion/fall/wield - rip.c: death tombstone, total_winner, killname; C exit() becomes a gameEnd panic that Run will recover - score.go: top-ten scoreboard as a gob file with lock-file protocol replacing the XOR-encrypted C format - wizard.c: whatis/set_know/teleport; daemons.c stomach - monster bestiary moved to per-game state (C mutates the flytrap damage string during play) Tests: combat math, kill/removal bookkeeping, monster chase pursuit, death unwinding, scripted-input headless terminal.
194 lines
4.6 KiB
Go
194 lines
4.6 KiB
Go
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 := &g.Monsters[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) < g.Monsters[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)
|
|
}
|