735 lines
30 KiB
Python
735 lines
30 KiB
Python
"""
|
|
This module implements a simple mobile object with
|
|
a very rudimentary AI as well as an aggressive enemy
|
|
object based on that mobile class.
|
|
|
|
"""
|
|
|
|
import random
|
|
|
|
from evennia import TICKER_HANDLER
|
|
from evennia import search_object
|
|
from evennia.contrib.tutorial_world import objects as tut_objects
|
|
|
|
|
|
class Mob(tut_objects.TutorialObject):
|
|
"""
|
|
This is a state-machine AI mobile. It has several states which
|
|
are controlled from setting various Attributes:
|
|
|
|
patrolling: if set, the mob will move randomly
|
|
from room to room, but preferring to not return
|
|
the way it came. If unset, the mob will remain
|
|
stationary (idling) until attacked.
|
|
aggressive: if set, will attack Characters in
|
|
the same room using whatever Weapon it
|
|
carries (see tutorial_world.objects.Weapon).
|
|
if unset, the mob will never engage in combat
|
|
no matter what.
|
|
hunting: if set, the mob will pursue enemies trying
|
|
to flee from it, so it can enter combat. If unset,
|
|
it will return to patrolling/idling if fled from.
|
|
immortal: If set, the mob cannot take any damage.
|
|
It also has several states,
|
|
is_patrolling - set when the mob is patrolling.
|
|
is_attacking - set when the mob is in combat
|
|
is_hunting - set when the mob is pursuing an enemy.
|
|
is_immortal - is currently immortal
|
|
is_dead: if set, the Mob is set to immortal, non-patrolling
|
|
and non-aggressive mode. Its description is
|
|
turned into that of a corpse-description.
|
|
Other important properties:
|
|
home - the home location should set to someplace inside
|
|
the patrolling area. The mob will use this if it should
|
|
happen to roam into a room with no exits.
|
|
|
|
"""
|
|
def __init__(self):
|
|
"""
|
|
When initialized from cache (after a server reboot), set up
|
|
the AI state.
|
|
"""
|
|
# The AI state machine (not persistent).
|
|
self.ndb.is_patrolling = self.db.patrolling and not self.db.is_dead
|
|
self.ndb.is_attacking = False
|
|
self.ndb.is_hunting = False
|
|
self.ndb.is_immortal = self.db.immortal or self.db.is_dead
|
|
|
|
def at_object_creation(self):
|
|
"""
|
|
Called the first time the object is created.
|
|
We set up the base properties and flags here.
|
|
"""
|
|
# Main AI flags. We start in dead mode so we don't have to
|
|
# chase the mob around when building.
|
|
self.db.patrolling = False
|
|
self.db.aggressive = False
|
|
self.db.immortal = True
|
|
# db-store if it is dead or not
|
|
self.db.is_dead = True
|
|
# specifies how much damage we remove from non-magic weapons
|
|
self.db.magic_resistance = 0.01
|
|
# pace (number of seconds between ticks) for
|
|
# the respective modes.
|
|
self.db.patrolling_pace = 6
|
|
self.db.aggressive_pace = 2
|
|
self.db.hunting_pace = 1
|
|
self.db.death_pace = 100 # stay dead for 100 seconds
|
|
|
|
# we store the call to the tickerhandler
|
|
# so we can easily deactivate the last
|
|
# ticker subscription when we switch.
|
|
# since we will use the same idstring
|
|
# throughout we only need to save the
|
|
# previous interval we used.
|
|
self.db.last_ticker_interval = None
|
|
|
|
# store two separate descriptions, one for alive and
|
|
# one for dead (corpse)
|
|
self.db.desc_alive = "This is a moving object."
|
|
self.db.desc_dead = "A dead body."
|
|
|
|
# health stats
|
|
self.db.full_health = 20
|
|
self.db.health = 20
|
|
|
|
# when this mob defeats someone, we move the character off to
|
|
# some other place (Dark Cell in the tutorial).
|
|
self.db.send_defeated_to = "dark cell"
|
|
# text to echo to the defeated foe.
|
|
self.db.defeat_msg = "You fall to the ground."
|
|
self.db.defeat_msg_room = "%s falls to the ground."
|
|
self.db.weapon_ineffective_text = "Your weapon just passes through your enemy, causing almost no effect!"
|
|
|
|
self.db.death_msg = "After the last hit %s evaporates." % self.key
|
|
self.db.hit_msg = "%s wails, shudders and writhes." % self.key
|
|
|
|
self.db.tutorial_info = "This is an object with simple state AI, using a ticker to move."
|
|
|
|
def _set_ticker(self, interval, hook_key, stop=False):
|
|
"""
|
|
Set how often the given hook key should
|
|
be "ticked".
|
|
|
|
Args:
|
|
interval (int): The number of seconds
|
|
between ticks
|
|
hook_key (str): The name of the method
|
|
(on this mob) to call every interval
|
|
seconds.
|
|
stop (bool, optional): Just stop the
|
|
last ticker without starting a new one.
|
|
With this set, the interval and hook_key
|
|
arguments are unused.
|
|
|
|
In order to only have one ticker
|
|
running at a time, we make sure to store the
|
|
previous ticker subscription so that we can
|
|
easily find and stop it before setting a
|
|
new one. The tickerhandler is persistent so
|
|
we need to remmeber this across reloads.
|
|
|
|
"""
|
|
idstring = "tutorial_mob" # this doesn't change
|
|
last_interval = self.db.last_ticker_interval
|
|
if last_interval:
|
|
# we have a previous subscription, kill this first.
|
|
TICKER_HANDLER.remove(self, last_interval, idstring)
|
|
if not stop:
|
|
# set the new ticker
|
|
TICKER_HANDLER.add(self, interval, idstring, hook_key)
|
|
|
|
def _find_target(self, location):
|
|
"""
|
|
Scan the given location for suitable targets (this is defined
|
|
as Characters) to attack. Will ignore superusers.
|
|
|
|
Args:
|
|
location (Object): the room to scan.
|
|
|
|
Returns:
|
|
The first suitable target found.
|
|
|
|
"""
|
|
targets = [obj for obj in location.contents_get(exclude=self)
|
|
if obj.has_player and not obj.is_superuser]
|
|
return targets[0] if targets else None
|
|
|
|
def set_alive(self):
|
|
"""
|
|
Set the mob to "alive" mode. This effectively
|
|
resurrects it from the dead state.
|
|
"""
|
|
self.db.health = self.db.full_health
|
|
self.db.is_dead = False
|
|
self.db.desc = self.db.desc_alive
|
|
self.ndb.is_immortal = self.db.immortal
|
|
self.ndb.is_patrolling = self.db.patrolling
|
|
if not self.location:
|
|
self.move_to(self.home)
|
|
if self.db.patrolling:
|
|
self.start_patrolling()
|
|
|
|
def set_dead(self):
|
|
"""
|
|
Set the mob to "dead" mode. This turns it off
|
|
and makes sure it can take no more damage.
|
|
It also starts a ticker for when it will return.
|
|
"""
|
|
self.db.is_dead = True
|
|
self.location = None
|
|
self.ndb.is_patrolling = False
|
|
self.ndb.is_attacking = False
|
|
self.ndb.is_hunting = False
|
|
self.ndb.is_immortal = True
|
|
# we shall return after some time
|
|
self._set_ticker(self.db.death_pace, "set_alive")
|
|
|
|
def start_idle(self):
|
|
"""
|
|
Starts just standing around. This will kill
|
|
the ticker and do nothing more.
|
|
"""
|
|
self._set_ticker(None, None, stop=True)
|
|
|
|
def start_patrolling(self):
|
|
"""
|
|
Start the patrolling state by
|
|
registering us with the ticker-handler
|
|
at a leasurely pace.
|
|
"""
|
|
if not self.db.patrolling:
|
|
self.start_idle()
|
|
return
|
|
self._set_ticker(self.db.patrolling_pace, "do_patrol")
|
|
self.ndb.is_patrolling = True
|
|
self.ndb.is_hunting = False
|
|
self.ndb.is_attacking = False
|
|
# for the tutorial, we also heal the mob in this mode
|
|
self.db.health = self.db.full_health
|
|
|
|
def start_hunting(self):
|
|
"""
|
|
Start the hunting state
|
|
"""
|
|
if not self.db.hunting:
|
|
self.start_patrolling()
|
|
return
|
|
self._set_ticker(self.db.hunting_pace, "do_hunt")
|
|
self.ndb.is_patrolling = False
|
|
self.ndb.is_hunting = True
|
|
self.ndb.is_attacking = False
|
|
|
|
def start_attacking(self):
|
|
"""
|
|
Start the attacking state
|
|
"""
|
|
if not self.db.aggressive:
|
|
self.start_hunting()
|
|
return
|
|
self._set_ticker(self.db.attacking_pace, "do_attack")
|
|
self.ndb.is_patrolling = False
|
|
self.ndb.is_hunting = False
|
|
self.ndb.is_attacking = True
|
|
|
|
def do_patrol(self):
|
|
"""
|
|
Called repeatedly during patrolling mode. In this mode, the
|
|
mob scans its surroundings and randomly chooses a viable exit.
|
|
One should lock exits with the traverse:has_player() lock in
|
|
order to block the mob from moving outside its area while
|
|
allowing player-controlled characters to move normally.
|
|
"""
|
|
if self.db.aggressive:
|
|
# first check if there are any targets in the room.
|
|
target = self._find_target(self.location)
|
|
if target:
|
|
self.start_attacking()
|
|
return
|
|
# no target found, look for an exit.
|
|
exits = [exi for exi in self.location.exits
|
|
if exi.access(self, "traverse")]
|
|
last_location = self.ndb.last_location
|
|
if exits:
|
|
# randomly pick an exit
|
|
exit = random.choice(self.location.exits)
|
|
if len(exits) > 1 and exit.destination == last_location:
|
|
# don't go back the same way we came if we
|
|
# can avoid it.
|
|
return
|
|
# move there.
|
|
self.move_to(exit.destination)
|
|
else:
|
|
# no exits! teleport to home to get away.
|
|
self.move_to(self.home)
|
|
|
|
def do_hunting(self):
|
|
"""
|
|
Called regularly when in hunting mode. In hunting mode the mob
|
|
scans adjacent rooms for enemies and moves towards them to
|
|
attack if possible.
|
|
"""
|
|
if self.db.aggressive:
|
|
# first check if there are any targets in the room.
|
|
target = self._find_target(self.location)
|
|
if target:
|
|
self.start_attacking()
|
|
return
|
|
# no targets found, scan surrounding rooms
|
|
exits = [exi for exi in self.location.exits
|
|
if exi.access(self, "traverse")]
|
|
if exits:
|
|
# scan the exits destination for targets
|
|
for exit in exits:
|
|
target = self._find_target(exit.destination)
|
|
if target:
|
|
# a target found. Move there.
|
|
self.move_to(exit.destination)
|
|
return
|
|
# if we get to this point we lost our
|
|
# prey. Resume patrolling.
|
|
self.start_patrolling()
|
|
else:
|
|
# no exits! teleport to home to get away.
|
|
self.move_to(self.home)
|
|
|
|
def do_attacking(self):
|
|
"""
|
|
Called regularly when in attacking mode. In attacking mode
|
|
the mob will bring its weapons to bear on any targets
|
|
in the room.
|
|
"""
|
|
# first make sure we have a target
|
|
target = self._find_target(self.location)
|
|
if not target:
|
|
# no target, start looking for one
|
|
self.start_hunting()
|
|
return
|
|
|
|
# we use the same attack commands as defined in
|
|
# tutorial_world.objects.Weapon, assuming that
|
|
# the mob is given a Weapon to attack with.
|
|
attack_cmd = random.choice(("thrust", "pierce", "stab", "slash", "chop"))
|
|
self.execute_cmd("%s %s" % (attack_cmd, target))
|
|
|
|
# analyze the current state
|
|
if target.db.health <= 0:
|
|
# we reduced the target to <= 0 health. Move them to the
|
|
# defeated room
|
|
target.msg(self.db.defeat_msg)
|
|
self.location.msg_contents(self.db.defeat_msg_room % target.key, exclude=target)
|
|
defeat_location = search_object(self.db.defeat_location)
|
|
if defeat_location:
|
|
target.move_to(defeat_location, quiet=True)
|
|
|
|
# response methods - called by other objects
|
|
|
|
def at_hit(self, weapon, attacker, damage):
|
|
"""
|
|
Someone landed a hit on us. Check our status
|
|
and start attacking if not already doing so.
|
|
"""
|
|
if not self.db.immortal:
|
|
if not weapon.db.magic:
|
|
# not a magic weapon - scale damage with magical
|
|
# resistance
|
|
damage = self.db.damage_resistance * damage
|
|
attacker.msg(self.db.weapon_ineffective_msg)
|
|
self.db.health -= damage
|
|
|
|
# analyze the result
|
|
if self.db.health <= 0:
|
|
# we are dead!
|
|
attacker.msg(self.db.death_msg)
|
|
self.set_dead()
|
|
else:
|
|
# still alive, start attack if not already attacking
|
|
attacker.msg(self.db.hit_msg)
|
|
if self.db.aggressive and not self.ndb.is_attacking:
|
|
self.start_attacking()
|
|
|
|
def at_new_arrival(self, new_character):
|
|
"""
|
|
This is triggered whenever a new character enters the room.
|
|
This is called by the TutorialRoom the mob stands in and
|
|
allows it to be aware of changes immediately without needing
|
|
to poll for them all the time. For example, the mob can react
|
|
right away, also when patrolling on a very slow ticker.
|
|
"""
|
|
# the room actually already checked all we need, so
|
|
# we know it is a valid target.
|
|
if self.db.aggressive and not self.ndb.is_attacking:
|
|
self.start_attacking()
|
|
|
|
#
|
|
##------------------------------------------------------------
|
|
##
|
|
## Mob - mobile object
|
|
##
|
|
## This object utilizes exits and moves about randomly from
|
|
## room to room.
|
|
##
|
|
##------------------------------------------------------------
|
|
#
|
|
#class Mob(tut_objects.TutorialObject):
|
|
# """
|
|
# This type of mobile will roam from exit to exit at
|
|
# random intervals. Simply lock exits against the is_mob attribute
|
|
# to block them from the mob (lockstring = "traverse:not attr(is_mob)").
|
|
# """
|
|
# def at_object_creation(self):
|
|
# "This is called when the object is first created."
|
|
# self.db.tutorial_info = "This is a moving object. It moves randomly from room to room."
|
|
#
|
|
# self.scripts.add(tut_scripts.IrregularEvent)
|
|
# # this is a good attribute for exits to look for, to block
|
|
# # a mob from entering certain exits.
|
|
# self.db.is_mob = True
|
|
# self.db.last_location = None
|
|
# # only when True will the mob move.
|
|
# self.db.roam_mode = True
|
|
# #
|
|
# self.db.move_from
|
|
# self.location.msg_contents("With a cold breeze, %s drifts in the direction of %s." % (self.key, destination.key))
|
|
#
|
|
# def announce_move_from(self, destination):
|
|
# "Called just before moving"
|
|
# self.location.msg_contents("With a cold breeze, %s drifts in the direction of %s." % (self.key, destination.key))
|
|
#
|
|
# def announce_move_to(self, source_location):
|
|
# "Called just after arriving"
|
|
# self.location.msg_contents("With a wailing sound, %s appears from the %s." % (self.key, source_location.key))
|
|
#
|
|
# def update_irregular(self):
|
|
# "Called at irregular intervals. Moves the mob."
|
|
# if self.roam_mode:
|
|
# exits = [ex for ex in self.location.exits
|
|
# if ex.access(self, "traverse")]
|
|
# if exits:
|
|
# # Try to make it so the mob doesn't backtrack.
|
|
# new_exits = [ex for ex in exits
|
|
# if ex.destination != self.db.last_location]
|
|
# if new_exits:
|
|
# exits = new_exits
|
|
# self.db.last_location = self.location
|
|
# # execute_cmd() allows the mob to respect exit and
|
|
# # exit-command locks, but may pose a problem if there is more
|
|
# # than one exit with the same name.
|
|
# # - see Enemy example for another way to move
|
|
# self.execute_cmd("%s" % exits[random.randint(0, len(exits) - 1)].key)
|
|
#
|
|
#
|
|
#
|
|
##------------------------------------------------------------
|
|
##
|
|
## Enemy - mobile attacking object
|
|
##
|
|
## An enemy is a mobile that is aggressive against players
|
|
## in its vicinity. An enemy will try to attack characters
|
|
## in the same location. It will also pursue enemies through
|
|
## exits if possible.
|
|
##
|
|
## An enemy needs to have a Weapon object in order to
|
|
## attack.
|
|
##
|
|
## This particular tutorial enemy is a ghostly apparition that can only
|
|
## be hurt by magical weapons. It will also not truly "die", but only
|
|
## teleport to another room. Players defeated by the apparition will
|
|
## conversely just be teleported to a holding room.
|
|
##
|
|
##------------------------------------------------------------
|
|
#
|
|
#class AttackTimer(DefaultScript):
|
|
# """
|
|
# This script is what makes an eneny "tick".
|
|
# """
|
|
# def at_script_creation(self):
|
|
# "This sets up the script"
|
|
# self.key = "AttackTimer"
|
|
# self.desc = "Drives an Enemy's combat."
|
|
# self.interval = random.randint(2, 3) # how fast the Enemy acts
|
|
# self.start_delay = True # wait self.interval before first call
|
|
# self.persistent = True
|
|
#
|
|
# def at_repeat(self):
|
|
# "Called every self.interval seconds."
|
|
# if self.obj.db.inactive:
|
|
# return
|
|
# # id(self.ndb.twisted_task)
|
|
# if self.obj.db.roam_mode:
|
|
# self.obj.roam()
|
|
# #return
|
|
# elif self.obj.db.battle_mode:
|
|
# #print "attack"
|
|
# self.obj.attack()
|
|
# return
|
|
# elif self.obj.db.pursue_mode:
|
|
# #print "pursue"
|
|
# self.obj.pursue()
|
|
# #return
|
|
# else:
|
|
# #dead mode. Wait for respawn.
|
|
# if not self.obj.db.dead_at:
|
|
# self.obj.db.dead_at = time.time()
|
|
# if (time.time() - self.obj.db.dead_at) > self.obj.db.dead_timer:
|
|
# self.obj.reset()
|
|
#
|
|
#
|
|
#class Enemy(Mob):
|
|
# """
|
|
# This is a ghostly enemy with health (hit points). Their chance to hit,
|
|
# damage etc is determined by the weapon they are wielding, same as
|
|
# characters.
|
|
#
|
|
# An enemy can be in four modes:
|
|
# roam (inherited from Mob) - where it just moves around randomly
|
|
# battle - where it stands in one place and attacks players
|
|
# pursue - where it follows a player, trying to enter combat again
|
|
# dead - passive and invisible until it is respawned
|
|
#
|
|
# Upon creation, the following attributes describe the enemy's actions
|
|
# desc - description
|
|
# full_health - integer number > 0
|
|
# defeat_location - unique name or #dbref to the location the player is
|
|
# taken when defeated. If not given, will remain in room.
|
|
# defeat_text - text to show player when they are defeated (just before
|
|
# being whisped away to defeat_location)
|
|
# defeat_text_room - text to show other players in room when a player
|
|
# is defeated
|
|
# win_text - text to show player when defeating the enemy
|
|
# win_text_room - text to show room when a player defeates the enemy
|
|
# respawn_text - text to echo to room when the mob is reset/respawn in
|
|
# that room.
|
|
#
|
|
# """
|
|
# def at_object_creation(self):
|
|
# "Called at object creation."
|
|
# super(Enemy, self).at_object_creation()
|
|
#
|
|
# self.db.tutorial_info = "This moving object will attack players in the same room."
|
|
#
|
|
# # state machine modes
|
|
# self.db.roam_mode = True
|
|
# self.db.battle_mode = False
|
|
# self.db.pursue_mode = False
|
|
# self.db.dead_mode = False
|
|
# # health (change this at creation time)
|
|
# self.db.full_health = 20
|
|
# self.db.health = 20
|
|
# self.db.dead_at = time.time()
|
|
# self.db.dead_timer = 100 # how long to stay dead
|
|
# # this is used during creation to make sure the mob doesn't move away
|
|
# self.db.inactive = True
|
|
# # store the last player to hit
|
|
# self.db.last_attacker = None
|
|
# # where to take defeated enemies
|
|
# self.db.defeat_location = "darkcell"
|
|
# self.scripts.add(AttackTimer)
|
|
#
|
|
# def update_irregular(self):
|
|
# "the irregular event is inherited from Mob class"
|
|
# strings = self.db.irregular_echoes
|
|
# if strings:
|
|
# self.location.msg_contents(strings[random.randint(0, len(strings) - 1)])
|
|
#
|
|
# def roam(self):
|
|
# "Called by Attack timer. Will move randomly as long as exits are open."
|
|
#
|
|
# # in this mode, the mob is healed.
|
|
# self.db.health = self.db.full_health
|
|
# players = [obj for obj in self.location.contents
|
|
# if utils.inherits_from(obj, BASE_CHARACTER_TYPECLASS) and not obj.is_superuser]
|
|
# if players:
|
|
# # we found players in the room. Attack.
|
|
# self.db.roam_mode = False
|
|
# self.db.pursue_mode = False
|
|
# self.db.battle_mode = True
|
|
#
|
|
# elif random.random() < 0.2:
|
|
# # no players to attack, move about randomly.
|
|
# exits = [ex.destination for ex in self.location.exits
|
|
# if ex.access(self, "traverse")]
|
|
# if exits:
|
|
# # Try to make it so the mob doesn't backtrack.
|
|
# new_exits = [ex for ex in exits
|
|
# if ex.destination != self.db.last_location]
|
|
# if new_exits:
|
|
# exits = new_exits
|
|
# self.db.last_location = self.location
|
|
# # locks should be checked here
|
|
# self.move_to(exits[random.randint(0, len(exits) - 1)])
|
|
# else:
|
|
# # no exits - a dead end room. Respawn back to start.
|
|
# self.move_to(self.home)
|
|
#
|
|
# def attack(self):
|
|
# """
|
|
# This is the main mode of combat. It will try to hit players in
|
|
# the location. If players are defeated, it will whisp them off
|
|
# to the defeat location.
|
|
# """
|
|
# last_attacker = self.db.last_attacker
|
|
# players = [obj for obj in self.location.contents
|
|
# if utils.inherits_from(obj, BASE_CHARACTER_TYPECLASS) and not obj.is_superuser]
|
|
# if players:
|
|
#
|
|
# # find a target
|
|
# if last_attacker in players:
|
|
# # prefer to attack the player last attacking.
|
|
# target = last_attacker
|
|
# else:
|
|
# # otherwise attack a random player in location
|
|
# target = players[random.randint(0, len(players) - 1)]
|
|
#
|
|
# # try to use the weapon in hand
|
|
# attack_cmds = ("thrust", "pierce", "stab", "slash", "chop")
|
|
# cmd = attack_cmds[random.randint(0, len(attack_cmds) - 1)]
|
|
# self.execute_cmd("%s %s" % (cmd, target))
|
|
#
|
|
# # analyze result.
|
|
# if target.db.health <= 0:
|
|
# # we reduced enemy to 0 health. Whisp them off to
|
|
# # the prison room.
|
|
# tloc = search_object(self.db.defeat_location)
|
|
# tstring = self.db.defeat_text
|
|
# if not tstring:
|
|
# tstring = "You feel your conciousness slip away ... you fall to the ground as "
|
|
# tstring += "the misty apparition envelopes you ...\n The world goes black ...\n"
|
|
# target.msg(tstring)
|
|
# ostring = self.db.defeat_text_room
|
|
# if tloc:
|
|
# if not ostring:
|
|
# ostring = "\n%s envelops the fallen ... and then their body is suddenly gone!" % self.key
|
|
# # silently move the player to defeat location
|
|
# # (we need to call hook manually)
|
|
# target.location = tloc[0]
|
|
# tloc[0].at_object_receive(target, self.location)
|
|
# elif not ostring:
|
|
# ostring = "%s falls to the ground!" % target.key
|
|
# self.location.msg_contents(ostring, exclude=[target])
|
|
# # Pursue any stragglers after the battle
|
|
# self.battle_mode = False
|
|
# self.roam_mode = False
|
|
# self.pursue_mode = True
|
|
# else:
|
|
# # no players found, this could mean they have fled.
|
|
# # Switch to pursue mode.
|
|
# self.battle_mode = False
|
|
# self.roam_mode = False
|
|
# self.pursue_mode = True
|
|
#
|
|
# def pursue(self):
|
|
# """
|
|
# In pursue mode, the enemy tries to find players in adjoining rooms, preferably
|
|
# those that previously attacked it.
|
|
# """
|
|
# last_attacker = self.db.last_attacker
|
|
# players = [obj for obj in self.location.contents if utils.inherits_from(obj, BASE_CHARACTER_TYPECLASS) and not obj.is_superuser]
|
|
# if players:
|
|
# # we found players in the room. Maybe we caught up with some,
|
|
# # or some walked in on us before we had time to pursue them.
|
|
# # Switch to battle mode.
|
|
# self.battle_mode = True
|
|
# self.roam_mode = False
|
|
# self.pursue_mode = False
|
|
# else:
|
|
# # find all possible destinations.
|
|
# destinations = [ex.destination for ex in self.location.exits
|
|
# if ex.access(self, "traverse")]
|
|
# # find all players in the possible destinations. OBS-we cannot
|
|
# # just use the player's current position to move the Enemy; this
|
|
# # might have changed when the move is performed, causing the enemy
|
|
# # to teleport out of bounds.
|
|
# players = {}
|
|
# for dest in destinations:
|
|
# for obj in [o for o in dest.contents
|
|
# if utils.inherits_from(o, BASE_CHARACTER_TYPECLASS)]:
|
|
# players[obj] = dest
|
|
# if players:
|
|
# # we found targets. Move to intercept.
|
|
# if last_attacker in players:
|
|
# # preferably the one that last attacked us
|
|
# self.move_to(players[last_attacker])
|
|
# else:
|
|
# # otherwise randomly.
|
|
# key = players.keys()[random.randint(0, len(players) - 1)]
|
|
# self.move_to(players[key])
|
|
# else:
|
|
# # we found no players nearby. Return to roam mode.
|
|
# self.battle_mode = False
|
|
# self.roam_mode = True
|
|
# self.pursue_mode = False
|
|
#
|
|
# def at_hit(self, weapon, attacker, damage):
|
|
# """
|
|
# Called when this object is hit by an enemy's weapon
|
|
# Should return True if enemy is defeated, False otherwise.
|
|
#
|
|
# In the case of players attacking, we handle all the events
|
|
# and information from here, so the return value is not used.
|
|
# """
|
|
#
|
|
# self.db.last_attacker = attacker
|
|
# if not self.db.battle_mode:
|
|
# # we were attacked, so switch to battle mode.
|
|
# self.db.roam_mode = False
|
|
# self.db.pursue_mode = False
|
|
# self.db.battle_mode = True
|
|
# #self.scripts.add(AttackTimer)
|
|
#
|
|
# if not weapon.db.magic:
|
|
# # In the tutorial, the enemy is a ghostly apparition, so
|
|
# # only magical weapons can harm it.
|
|
# string = self.db.weapon_ineffective_text
|
|
# if not string:
|
|
# string = "Your weapon just passes through your enemy, causing no effect!"
|
|
# attacker.msg(string)
|
|
# return
|
|
# else:
|
|
# # an actual hit
|
|
# health = float(self.db.health)
|
|
# health -= damage
|
|
# self.db.health = health
|
|
# if health <= 0:
|
|
# string = self.db.win_text
|
|
# if not string:
|
|
# string = "After your last hit, %s folds in on itself, it seems to fade away into nothingness. " % self.key
|
|
# string += "In a moment there is nothing left but the echoes of its screams. But you have a "
|
|
# string += "feeling it is only temporarily weakened. "
|
|
# string += "You fear it's only a matter of time before it materializes somewhere again."
|
|
# attacker.msg(string)
|
|
# string = self.db.win_text_room
|
|
# if not string:
|
|
# string = "After %s's last hit, %s folds in on itself, it seems to fade away into nothingness. " % (attacker.name, self.key)
|
|
# string += "In a moment there is nothing left but the echoes of its screams. But you have a "
|
|
# string += "feeling it is only temporarily weakened. "
|
|
# string += "You fear it's only a matter of time before it materializes somewhere again."
|
|
# self.location.msg_contents(string, exclude=[attacker])
|
|
#
|
|
# # put mob in dead mode and hide it from view.
|
|
# # AttackTimer will bring it back later.
|
|
# self.db.dead_at = time.time()
|
|
# self.db.roam_mode = False
|
|
# self.db.pursue_mode = False
|
|
# self.db.battle_mode = False
|
|
# self.db.dead_mode = True
|
|
# self.location = None
|
|
# else:
|
|
# self.location.msg_contents("%s wails, shudders and writhes." % self.key)
|
|
# return False
|
|
#
|
|
# def reset(self):
|
|
# """
|
|
# If the mob was 'dead', respawn it to its home position and reset
|
|
# all modes and damage."""
|
|
# if self.db.dead_mode:
|
|
# self.db.health = self.db.full_health
|
|
# self.db.roam_mode = True
|
|
# self.db.pursue_mode = False
|
|
# self.db.battle_mode = False
|
|
# self.db.dead_mode = False
|
|
# self.location = self.home
|
|
# string = self.db.respawn_text
|
|
# if not string:
|
|
# string = "%s fades into existence from out of thin air. It's looking pissed." % self.key
|
|
# self.location.msg_contents(string)
|