go: port combat, chase driver, traps, zapping, death and scores
- 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.
This commit is contained in:
265
game/chase.go
265
game/chase.go
@@ -1,8 +1,265 @@
|
||||
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.
|
||||
// chase.c — code for one creature to chase another.
|
||||
|
||||
// dragonShot: one chance in DRAGONSHOT that a dragon will flame.
|
||||
const dragonShot = 5
|
||||
|
||||
// runners makes all the running monsters move (chase.c runners).
|
||||
func (g *RogueGame) runners(int) {
|
||||
list := append([]*Monster(nil), g.Level.Monsters...)
|
||||
for _, tp := range list {
|
||||
if !tp.On(IsHeld) && tp.On(IsRun) {
|
||||
origPos := tp.Pos
|
||||
wastarget := tp.On(IsTarget)
|
||||
if g.moveMonst(tp) == -1 {
|
||||
continue
|
||||
}
|
||||
if tp.On(IsFly) && distCp(g.Player.Pos, tp.Pos) >= 3 {
|
||||
if g.moveMonst(tp) == -1 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if wastarget && origPos != tp.Pos {
|
||||
tp.Flags.Clear(IsTarget)
|
||||
g.ToDeath = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if g.HasHit {
|
||||
g.endmsg()
|
||||
g.HasHit = false
|
||||
}
|
||||
}
|
||||
|
||||
// moveMonst executes a single turn of running for a monster (chase.c
|
||||
// move_monst). Returns -1 if the monster died or left the level.
|
||||
func (g *RogueGame) moveMonst(tp *Monster) int {
|
||||
if !tp.On(IsSlow) || tp.Turn {
|
||||
if g.doChase(tp) == -1 {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
if tp.On(IsHaste) {
|
||||
if g.doChase(tp) == -1 {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
tp.Turn = !tp.Turn
|
||||
return 0
|
||||
}
|
||||
|
||||
// relocate makes the monster's new location be the specified one, updating
|
||||
// all the relevant state (chase.c relocate).
|
||||
func (g *RogueGame) relocate(th *Monster, newLoc Coord) {
|
||||
if newLoc != th.Pos {
|
||||
g.mvaddch(th.Pos.Y, th.Pos.X, th.OldCh)
|
||||
th.Room = g.roomin(newLoc)
|
||||
g.setOldch(th, newLoc)
|
||||
oroom := th.Room
|
||||
g.Level.SetMonsterAt(th.Pos.Y, th.Pos.X, nil)
|
||||
|
||||
if oroom != th.Room {
|
||||
th.Dest = g.findDest(th)
|
||||
}
|
||||
th.Pos = newLoc
|
||||
g.Level.SetMonsterAt(newLoc.Y, newLoc.X, th)
|
||||
}
|
||||
g.move(newLoc.Y, newLoc.X)
|
||||
if g.seeMonst(th) {
|
||||
g.addch(th.Disguise)
|
||||
} else if g.Player.On(SeeMonst) {
|
||||
g.standout()
|
||||
g.addch(th.Type)
|
||||
g.standend()
|
||||
}
|
||||
}
|
||||
|
||||
// doChase makes one thing chase another (chase.c do_chase). Returns -1 if
|
||||
// the chaser died in the attempt.
|
||||
func (g *RogueGame) doChase(th *Monster) int {
|
||||
p := &g.Player
|
||||
stoprun := false // true means we are there
|
||||
mindist := 32767
|
||||
|
||||
rer := th.Room // find room of chaser
|
||||
if th.On(IsGreed) && rer.GoldVal == 0 {
|
||||
th.Dest = &p.Pos // if gold has been taken, run after hero
|
||||
}
|
||||
var ree *Room // find room of chasee
|
||||
if th.Dest == &p.Pos {
|
||||
ree = p.Room
|
||||
} else {
|
||||
ree = g.roomin(*th.Dest)
|
||||
}
|
||||
// We don't count doors as inside rooms for this routine
|
||||
door := g.Level.Char(th.Pos.Y, th.Pos.X) == Door
|
||||
var this Coord
|
||||
|
||||
over:
|
||||
// If the object of our desire is in a different room, and we are not
|
||||
// in a corridor, run to the door nearest to our goal.
|
||||
if rer != ree {
|
||||
for i := range rer.Exits {
|
||||
curdist := distCp(*th.Dest, rer.Exits[i])
|
||||
if curdist < mindist {
|
||||
this = rer.Exits[i]
|
||||
mindist = curdist
|
||||
}
|
||||
}
|
||||
if door {
|
||||
rer = &g.Level.Passages[*g.Level.FlagsAt(th.Pos.Y, th.Pos.X)&FPNum]
|
||||
door = false
|
||||
goto over
|
||||
}
|
||||
} else {
|
||||
this = *th.Dest
|
||||
// For dragons check and see if (a) the hero is on a straight line
|
||||
// from it, and (b) that it is within shooting distance, but
|
||||
// outside of striking range.
|
||||
if th.Type == 'D' && (th.Pos.Y == p.Pos.Y || th.Pos.X == p.Pos.X ||
|
||||
abs(th.Pos.Y-p.Pos.Y) == abs(th.Pos.X-p.Pos.X)) &&
|
||||
distCp(th.Pos, p.Pos) <= BoltLength*BoltLength &&
|
||||
!th.On(IsCanc) && g.rnd(dragonShot) == 0 {
|
||||
g.Delta.Y = sign(p.Pos.Y - th.Pos.Y)
|
||||
g.Delta.X = sign(p.Pos.X - th.Pos.X)
|
||||
if g.HasHit {
|
||||
g.endmsg()
|
||||
}
|
||||
g.fireBolt(th.Pos, &g.Delta, "flame")
|
||||
g.Running = false
|
||||
g.Count = 0
|
||||
g.Quiet = 0
|
||||
if g.ToDeath && !th.On(IsTarget) {
|
||||
g.ToDeath = false
|
||||
g.Kamikaze = false
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
// This now contains what we want to run to this time so we run to it.
|
||||
// If we hit it we either want to fight it or stop running
|
||||
if !g.chase(th, this) {
|
||||
if this == p.Pos {
|
||||
return g.attack(th)
|
||||
} else if this == *th.Dest {
|
||||
for _, obj := range g.Level.Objects {
|
||||
if th.Dest == &obj.Pos {
|
||||
detachObj(&g.Level.Objects, obj)
|
||||
attachObj(&th.Pack, obj)
|
||||
if th.Room.Flags.Has(IsGone) {
|
||||
g.Level.SetChar(obj.Pos.Y, obj.Pos.X, Passage)
|
||||
} else {
|
||||
g.Level.SetChar(obj.Pos.Y, obj.Pos.X, Floor)
|
||||
}
|
||||
th.Dest = g.findDest(th)
|
||||
break
|
||||
}
|
||||
}
|
||||
if th.Type != 'F' {
|
||||
stoprun = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if th.Type == 'F' {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
g.relocate(th, g.chRet)
|
||||
// And stop running if need be
|
||||
if stoprun && th.Pos == *th.Dest {
|
||||
th.Flags.Clear(IsRun)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// chase finds the spot for the chaser to move closer to the chasee
|
||||
// (chase.c chase). Returns true if we want to keep on chasing later, false
|
||||
// if we reach the goal. The chosen spot lands in g.chRet.
|
||||
func (g *RogueGame) chase(tp *Monster, ee Coord) bool {
|
||||
p := &g.Player
|
||||
er := tp.Pos
|
||||
plcnt := 1
|
||||
var curdist int
|
||||
|
||||
// If the thing is confused, let it move randomly. Invisible Stalkers
|
||||
// are slightly confused all of the time, and bats are quite confused
|
||||
// all the time
|
||||
if (tp.On(IsHuh) && g.rnd(5) != 0) || (tp.Type == 'P' && g.rnd(5) == 0) ||
|
||||
(tp.Type == 'B' && g.rnd(2) == 0) {
|
||||
// get a valid random move
|
||||
g.chRet = g.rndmove(&tp.Creature)
|
||||
curdist = distCp(g.chRet, ee)
|
||||
// Small chance that it will become un-confused
|
||||
if g.rnd(20) == 0 {
|
||||
tp.Flags.Clear(IsHuh)
|
||||
}
|
||||
} else {
|
||||
// Otherwise, find the empty spot next to the chaser that is
|
||||
// closest to the chasee. This will eventually hold where we move
|
||||
// to get closer. If we can't find an empty spot, we stay where we
|
||||
// are.
|
||||
curdist = distCp(er, ee)
|
||||
g.chRet = er
|
||||
|
||||
ey := er.Y + 1
|
||||
if ey >= NumLines-1 {
|
||||
ey = NumLines - 2
|
||||
}
|
||||
ex := er.X + 1
|
||||
if ex >= NumCols {
|
||||
ex = NumCols - 1
|
||||
}
|
||||
|
||||
for x := er.X - 1; x <= ex; x++ {
|
||||
if x < 0 {
|
||||
continue
|
||||
}
|
||||
for y := er.Y - 1; y <= ey; y++ {
|
||||
tryp := Coord{X: x, Y: y}
|
||||
if !g.diagOk(er, tryp) {
|
||||
continue
|
||||
}
|
||||
ch := g.Level.VisibleChar(y, x)
|
||||
if stepOk(ch) {
|
||||
// If it is a scroll, it might be a scare monster
|
||||
// scroll so we need to look it up to see what type
|
||||
// it is.
|
||||
if ch == Scroll {
|
||||
var found *Object
|
||||
for _, obj := range g.Level.Objects {
|
||||
if y == obj.Pos.Y && x == obj.Pos.X {
|
||||
found = obj
|
||||
break
|
||||
}
|
||||
}
|
||||
if found != nil && found.Which == SScare {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// It can also be a Xeroc, which we shouldn't step on
|
||||
if m := g.Level.MonsterAt(y, x); m != nil && m.Type == 'X' {
|
||||
continue
|
||||
}
|
||||
// If we didn't find any scrolls at this place or it
|
||||
// wasn't a scare scroll, then this place counts
|
||||
thisdist := distance(y, x, ee.Y, ee.X)
|
||||
if thisdist < curdist {
|
||||
plcnt = 1
|
||||
g.chRet = tryp
|
||||
curdist = thisdist
|
||||
} else if thisdist == curdist {
|
||||
if plcnt++; g.rnd(plcnt) == 0 {
|
||||
g.chRet = tryp
|
||||
curdist = thisdist
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return curdist != 0 && g.chRet != p.Pos
|
||||
}
|
||||
|
||||
// setOldch sets the oldch character for the monster (chase.c set_oldch).
|
||||
func (g *RogueGame) setOldch(tp *Monster, cp Coord) {
|
||||
@@ -113,7 +370,7 @@ func (g *RogueGame) cansee(y, x int) bool {
|
||||
// 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
|
||||
prob := g.Monsters[tp.Type-'A'].Carry
|
||||
if prob <= 0 || tp.Room == g.Player.Room || g.seeMonst(tp) {
|
||||
return &g.Player.Pos
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user