package game // 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(Held) && tp.On(Awake) { origPos := tp.Pos wastarget := tp.On(Targeted) if g.moveMonst(tp) == -1 { continue } if tp.On(Flying) && distCp(g.Player.Pos, tp.Pos) >= 3 { if g.moveMonst(tp) == -1 { continue } } if wastarget && origPos != tp.Pos { tp.Flags.Clear(Targeted) 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(Slowed) || tp.Turn { if g.doChase(tp) == -1 { return -1 } } if tp.On(Hasted) { 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(SenseMonsters) { 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(Greedy) && 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)&FPassNum] 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(Cancelled) && 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(Targeted) { 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(Gone) { 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(Awake) } 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(Confused) && 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(Confused) } } 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.ScrollKind() == ScrollScareMonster { 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) { if tp.Pos == cp { return } sch := tp.OldCh tp.OldCh = g.mvinch(cp.Y, cp.X) if !g.Player.On(Blind) { if (sch == Floor || tp.OldCh == Floor) && tp.Room.Flags.Has(Dark) { 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(Blind) { return false } if mp.On(Invisible) && !p.On(CanSeeInvisible) { 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(Dark) } // 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(Awake) tp.Flags.Clear(Held) 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(FPassage) { return &g.Level.Passages[fp&FPassNum] } 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(Blind) { return false } if distance(y, x, p.Pos.Y, p.Pos.X) < LampDist { if g.Level.FlagsAt(y, x).Has(FPassage) { 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(Dark) } // findDest finds the proper destination for the monster (chase.c // find_dest). func (g *RogueGame) findDest(tp *Monster) *Coord { prob := g.Monsters[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.Kind == KindScroll && obj.ScrollKind() == ScrollScareMonster { 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 }