def wandering(self): """ Calculate the action of a monster without a specific goal in mind. """ if self.currentLevel.player in self.fov: self.state = ais.FIGHTING return self.fighting() assert self.direction is not None ordered_moves = coordinates.adjacent_coords_sorted( self.coords, self.direction) possible_moves = [ x for x in ordered_moves if self.currentLevel.canMove(self, x) ] if len(possible_moves) == 0: # You're stuck! Give up, just rest there. self.state = ais.RESTING return self.resting() else: move_coords = possible_moves[0] self.direction = coordinates.subtract(move_coords, self.coords) return action.Move(self, coordinates.subtract(move_coords, self.coords)) assert False
def closeToPlayer(self): """ Pathfind to the player, and attack him if possible. """ player_location = self.currentLevel.player.coords # If adjacent to the player, attack him. if coordinates.adjacent(player_location, self.coords): if rng.percentChance(self.specfreq): return action.SpecialMelee(self, self.currentLevel.player, self.spec) else: return action.Attack( self, self.currentLevel.player, "%(SOURCE_NAME)s attacks %(TARGET_NAME)s! (%(DAMAGE)d)") # Otherwise, pathfind toward him. else: path = pf.find_shortest_path(self.currentLevel, self.coords, player_location, False) if path != []: move_coords = coordinates.subtract(path[1], path[0]) return action.Move(self, move_coords) else: return action.Wait(self)
def traveling(self): """ Calculate the action of a monster moving to a specific location. Note that a "path" variable must exist for this to make sense. """ # self.path[0] should be the monster's current square. # self.path[1] should be the square the monster wants to move to. # self.path[-1] should be the monster's ultimate destination. assert self.path != None, "Despite the monster being in state TRAVELING, the path variable is null." if self.currentLevel.player in self.fov: self.state = ais.FIGHTING return self.fighting() else: path_is_invalid = False if len(self.path) == 0: assert False # This shouldn't happen! path_is_invalid = True elif self.coords != self.path[0]: # Something has moved the monster since its last turn. path_is_invalid = True elif len(self.path) == 1: # Since self.coords == self.path[0], the monster has reached its destination! self.state = ais.WANDERING return self.wandering() elif not self.canMove(self.path[1]): path_is_invalid = True if path_is_invalid: if len(self.path) == 0: # If the path is completely empty, something has gone wrong. assert False # Just give up and return to being stationary. self.state = ais.RESTING return self.resting() else: destination = self.path[-1] self.path = pf.find_shortest_path(self.currentLevel, self.coords, destination, True) if len(self.path) == 0: # There simply is no path to the destination! # Set self.path to only contain the destination, so that next turn, this code # attempts to find another path. self.path = [destination] return action.Wait(self) elif len(self.path) == 1: # This should not happen! assert False return action.Wait(self) if self.canMove(self.path[1]): move_direction = coordinates.subtract(self.path[1], self.coords) self.path.pop(0) return action.Move(self, move_direction) else: assert False, "The supposedly legal path contains an illegal move!" return action.Wait(self)
def fighting(self): """ Calculate the action of a monster who sees the player. """ if self.currentLevel.player not in self.fov: if self.player_last_location is not None: # The player has escaped! Find a likely square where he could have gone. adjacent_coords = coordinates.adjacent_coords( self.player_last_location) legal_coords = [ i for i in adjacent_coords if coordinates.legal(i, self.currentLevel.dimensions) ] passable_coords = [ i for i in legal_coords if self.currentLevel.isEmpty(i) ] out_of_vision_coords = \ [i for i in passable_coords if i not in self.fov] if len(out_of_vision_coords) > 0: # There is a possible escape route! Pursue! self.direction = coordinates.subtract( rng.choice(out_of_vision_coords), self.player_last_location) self.path = pf.find_shortest_path( self.currentLevel, self.coords, self.player_last_location, False) if self.path == []: # There is no route to the player's escape route. Wait, but stay in # state FIGHTING so as to take advantage of any route that opens up. return action.Wait(self) self.state = ais.TRAVELING return self.traveling() else: # There is no possible escape route; give up and rest. self.state = ais.RESTING return self.resting() else: assert False else: self.player_last_location = self.currentLevel.player.coords if self.AICode == "CLOSE": return self.closeToPlayer() elif self.AICode == "RANGEDAPPROACH": return self.rangedApproach() else: raise exc.InvalidDataWarning( "The monster %s has an unknown AICode, %s" % (self.name, self.AICode)) return action.Wait(self) assert False
def do_special_melee(attack_type, source, target): """ Have a Dude perform a special melee attack. attack_type - a string representing the type of special attack. source - the Dude attacking. target - the Dude being attacked. """ if attack_type == "CRITICAL": damage_dealt = CRIT_MULTIPLIER * \ damage(source.attack, target.defense, source.char_level, target.char_level) source.currentLevel.messages.append( "%(SOURCE_NAME)s runs %(TARGET_NAME)s all the way through! (%(DAMAGE)d)" % {"SOURCE_NAME": source.getName(), "DAMAGE": damage_dealt, "TARGET_NAME": target.getName()}) target.cur_HP -= damage_dealt target.checkDeath() elif attack_type == "KNOCK": damage_dealt = KNOCK_DAMAGE direction = coordinates.subtract(target.coords, source.coords) source.currentLevel.messages.append( "%(SOURCE_NAME)s delivers a wicked punch to %(TARGET_NAME)s! (%(DAMAGE)d)" % {"SOURCE_NAME": source.getName(), "DAMAGE": damage_dealt, "TARGET_NAME": target.getName()}) for i in range(KNOCK_DISTANCE): display.refresh_screen() destination = coordinates.add(target.coords, direction) if target.canMove(destination): target.currentLevel.moveDude(target, destination) else: break display.refresh_screen() target.cur_HP -= damage_dealt target.checkDeath() elif attack_type == "EXPLODE": explode_action = Explode(source.currentLevel, source.coords, 10) source.currentLevel.messages.append( "%(SOURCE_NAME)s explodes!" % {"SOURCE_NAME": source.getName()}) explode_action.do() elif attack_type == "STICK": damage_dealt = damage(source.attack, target.defense, source.char_level, target.char_level) / 5 source.currentLevel.messages.append( "%(SOURCE_NAME)s spins a web around %(TARGET_NAME)s! (%(DAMAGE)d)" % {"SOURCE_NAME": source.getName(), "DAMAGE": damage_dealt, "TARGET_NAME": target.getName()}) target.giveCondition(cond.Stuck(8)) target.cur_HP -= damage_dealt target.checkDeath() else: raise exc.InvalidDataWarning("%s special ability used by %s on %s." % (attack_type, str(source), str(target)))
def do(self): cur_lev = self.source.currentLevel next_loc = self.source.coords # Essentially a for loop, for i in range(self.distance). i = 0 while True: if i >= self.distance: cur_lev.messages.append("%s pounced, but caught only air." % self.source.getName()) break i += 1 next_loc = coordinates.add(self.source.coords, self.direction) display.refresh_screen() if not cur_lev.isEmpty(next_loc): cur_lev.messages.append("%s pounced at the wall." % self.source.getName()) break elif next_loc in cur_lev.dudeLayer: # The pouncer hit a dude. target = cur_lev.dudeLayer[next_loc] # Bounce off: behind the dude if possible, in front otherwise. behind = coordinates.add(next_loc, self.direction) in_front = coordinates.subtract(next_loc, self.direction) if (cur_lev.isEmpty(behind) and behind not in cur_lev.dudeLayer): cur_lev.moveDude(self.source, behind) # The pouncer should already be in front of the target, so if it is not going # through them, no motion is necessary. damage_dealt = damage(self.source.attack, target.defense, self.source.char_level, target.char_level) cur_lev.messages.append("%s pounced on %s! (%d)" % (self.source.getName(), target.getName(), damage_dealt)) target.cur_HP -= damage_dealt target.checkDeath() break else: cur_lev.moveDude(self.source, next_loc) display.refresh_screen() return self.source.speed