Made sure tickerhandler cleans itself of alrady-deleted objects. Some more bug fixes to reworked tutorialworld.

This commit is contained in:
Griatch 2015-02-22 13:40:20 +01:00
parent ca69c5aaec
commit 56104d9a1d
6 changed files with 79 additions and 54 deletions

View file

@ -342,6 +342,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
caller.ndb.last_cmd = None caller.ndb.last_cmd = None
# return result to the deferred # return result to the deferred
returnValue(ret) returnValue(ret)
except Exception: except Exception:
string = "%s\nAbove traceback is from an untrapped error." string = "%s\nAbove traceback is from an untrapped error."
string += " Please file a bug report." string += " Please file a bug report."

View file

@ -198,7 +198,7 @@ start
tickerhandler to regularly 'tick and randomly display various tickerhandler to regularly 'tick and randomly display various
weather-related messages. weather-related messages.
The room also has 'details' set on it (such as the old well), those The room also has 'details' set on it (such as the ruin in the distance), those
are snippets of text stored on the room that the custom look command are snippets of text stored on the room that the custom look command
used for all tutorial rooms can display. used for all tutorial rooms can display.
# #
@ -620,8 +620,9 @@ hole
# #
@detail hole;above = @detail hole;above =
Whereas the lower edges of the hole seem jagged and natural you can Whereas the lower edges of the hole seem jagged and natural you can
faintly make out that it turns into a man-made circular shaft higher faintly make out it turning into a man-made circular shaft higher up.
up. It looks like an old well. It looks like an old well. There must have been much more water
here once.
# #
@detail passages;dark = @detail passages;dark =
Those dark passages seem to criss-cross the cliff. No need to Those dark passages seem to criss-cross the cliff. No need to
@ -1062,7 +1063,9 @@ stairs down
the tomb of some sort of ancient heroine - it must be the goal you the tomb of some sort of ancient heroine - it must be the goal you
have been looking for! have been looking for!
# #
@dig Tomb of woman on horse @tel tut#14
#
@dig/teleport Tomb of woman on horse
: tutorial_world.rooms.TeleportRoom : tutorial_world.rooms.TeleportRoom
= Tomb with statue of riding woman;horse;riding; = Tomb with statue of riding woman;horse;riding;
# #
@ -1099,7 +1102,9 @@ stairs down
the tomb of some sort of ancient heroine - it must be the goal you the tomb of some sort of ancient heroine - it must be the goal you
have been looking for! have been looking for!
# #
@dig Tomb of the crowned queen @tel tut#14
#
@dig/teleport Tomb of the crowned queen
: tutorial_world.rooms.TeleportRoom : tutorial_world.rooms.TeleportRoom
= Tomb with statue of a crowned queen;crown;queen = Tomb with statue of a crowned queen;crown;queen
# #
@ -1136,7 +1141,9 @@ stairs down
the tomb of some sort of ancient heroine - it must be the goal you the tomb of some sort of ancient heroine - it must be the goal you
have been looking for! have been looking for!
# #
@dig Tomb of the shield @tel tut#14
#
@dig/teleport Tomb of the shield
: tutorial_world.rooms.TeleportRoom : tutorial_world.rooms.TeleportRoom
= Tomb with shield of arms;shield = Tomb with shield of arms;shield
# #
@ -1173,7 +1180,9 @@ stairs down
the tomb of some sort of ancient heroine - it must be the goal you the tomb of some sort of ancient heroine - it must be the goal you
have been looking for! have been looking for!
# #
@dig Tomb of the hero @tel tut#14
#
@dig/teleport Tomb of the hero
: tutorial_world.rooms.TeleportRoom : tutorial_world.rooms.TeleportRoom
= Tomb depicting a heroine fighting a monster;knight;hero;monster;beast = Tomb depicting a heroine fighting a monster;knight;hero;monster;beast
# #
@ -1217,10 +1226,11 @@ stairs down
# #
# The ancient tomb # The ancient tomb
# #
# This is the real tomb, the goal of the adventure. # This is the real tomb, the goal of the adventure. It is not
# directly accessible from the Antechamber but you are
# teleported here only if you solve the puzzle of the Obelisk.
# #
#------------------------------------------------------------ #------------------------------------------------------------
# Create the real tomb
# #
@dig/teleport Ancient tomb;tut#16 @dig/teleport Ancient tomb;tut#16
: tutorial_world.rooms.TutorialRoom : tutorial_world.rooms.TutorialRoom

View file

@ -113,11 +113,11 @@ class Mob(tut_objects.TutorialObject):
# chase the mob around when building. # chase the mob around when building.
self.db.patrolling = True self.db.patrolling = True
self.db.aggressive = True self.db.aggressive = True
self.db.immortal = True self.db.immortal = False
# db-store if it is dead or not # db-store if it is dead or not
self.db.is_dead = True self.db.is_dead = True
# specifies how much damage we remove from non-magic weapons # specifies how much damage we divide away from non-magic weapons
self.db.magic_resistance = 0.01 self.db.damage_resistance = 100.0
# pace (number of seconds between ticks) for # pace (number of seconds between ticks) for
# the respective modes. # the respective modes.
self.db.patrolling_pace = 6 self.db.patrolling_pace = 6
@ -388,12 +388,13 @@ class Mob(tut_objects.TutorialObject):
Someone landed a hit on us. Check our status Someone landed a hit on us. Check our status
and start attacking if not already doing so. and start attacking if not already doing so.
""" """
if not self.db.immortal: if not self.ndb.is_immortal:
if not weapon.db.magic: if not weapon.db.magic:
# not a magic weapon - scale damage with magical # not a magic weapon - divide away magic resistance
# resistance damage /= self.db.damage_resistance
damage = self.db.damage_resistance * damage
attacker.msg(self.db.weapon_ineffective_msg) attacker.msg(self.db.weapon_ineffective_msg)
else:
self.location.msg_contents(self.db.hit_msg)
self.db.health -= damage self.db.health -= damage
# analyze the result # analyze the result
@ -403,7 +404,6 @@ class Mob(tut_objects.TutorialObject):
self.set_dead() self.set_dead()
else: else:
# still alive, start attack if not already attacking # still alive, start attack if not already attacking
attacker.msg(self.db.hit_msg)
if self.db.aggressive and not self.ndb.is_attacking: if self.db.aggressive and not self.ndb.is_attacking:
self.start_attacking() self.start_attacking()

View file

@ -23,6 +23,7 @@ import random
from evennia import DefaultObject, DefaultExit, Command, CmdSet from evennia import DefaultObject, DefaultExit, Command, CmdSet
from evennia import utils from evennia import utils
from evennia.utils import search
from evennia.utils.spawner import spawn from evennia.utils.spawner import spawn
#------------------------------------------------------------ #------------------------------------------------------------
@ -276,7 +277,7 @@ class CmdLight(Command):
""" """
if self.obj.light(): if self.obj.light():
self.caller("You light %s." % self.obj.key) self.caller.msg("You light %s." % self.obj.key)
self.caller.location.msg_contents("%s lights %s!" % (self.caller, self.obj.key), exclude=[self.caller]) self.caller.location.msg_contents("%s lights %s!" % (self.caller, self.obj.key), exclude=[self.caller])
else: else:
self.caller.msg("%s is already burning." % self.obj.key) self.caller.msg("%s is already burning." % self.obj.key)
@ -285,6 +286,8 @@ class CmdLight(Command):
class CmdSetLight(CmdSet): class CmdSetLight(CmdSet):
"CmdSet for the lightsource commands" "CmdSet for the lightsource commands"
key = "lightsource_cmdset" key = "lightsource_cmdset"
# this is higher than the dark cmdset - important!
priority = 3
def at_cmdset_creation(self): def at_cmdset_creation(self):
"called at cmdset creation" "called at cmdset creation"
@ -320,24 +323,27 @@ class LightSource(TutorialObject):
# add the Light command # add the Light command
self.cmdset.add_default(CmdSetLight, permanent=True) self.cmdset.add_default(CmdSetLight, permanent=True)
def _burnout(self, ret): def _burnout(self):
""" """
This is called when this light source burns out. We make no This is called when this light source burns out. We make no
use of the return value. use of the return value.
""" """
# delete ourselves from the database
self.db.is_giving_light = False
try: try:
# our location is usually a Character (their inventory), we try
# to send to -their- location so everyone else also notices the
# light goes out.
self.location.location.msg_contents("%s's %s flickers and dies." % self.location.location.msg_contents("%s's %s flickers and dies." %
(self.location, self.key), exclude=self.location) (self.location, self.key), exclude=self.location)
self.location.msg("Your %s flickers and dies." % self.key) self.location.msg("Your %s flickers and dies." % self.key)
self.location.location.check_light_state()
except AttributeError: except AttributeError:
# we are not in someone's inventory (maybe we were dropped.) try:
self.location.msg_contents("A %s on the floor flickers and dies." % self.key) self.location.msg_contents("A %s on the floor flickers and dies." % self.key)
# delete ourselves. self.location.location.check_light_state()
except AttributeError:
pass
self.delete() self.delete()
def light(self): def light(self):
""" """
Light this object - this is called by Light command. Light this object - this is called by Light command.
@ -348,9 +354,12 @@ class LightSource(TutorialObject):
self.db.is_giving_light = True self.db.is_giving_light = True
# if we are in a dark room, trigger its light check # if we are in a dark room, trigger its light check
try: try:
self.location.location.check_light_state()
except AttributeError:
try:
# maybe we are directly in the room
self.location.check_light_state() self.location.check_light_state()
except Exception: except AttributeError:
# if we are not in a dark room, never mind
pass pass
finally: finally:
# start the burn timer. When it runs out, self._burnout # start the burn timer. When it runs out, self._burnout
@ -549,12 +558,11 @@ class CmdPressButton(Command):
self.caller.location.msg_contents(string % self.caller.key, exclude=self.caller) self.caller.location.msg_contents(string % self.caller.key, exclude=self.caller)
self.obj.open_wall() self.obj.open_wall()
self.caller.msg(string)
class CmdSetCrumblingWall(CmdSet): class CmdSetCrumblingWall(CmdSet):
"Group the commands for crumblingWall" "Group the commands for crumblingWall"
key = "crumblingwall_cmdset" key = "crumblingwall_cmdset"
priority = 2
def at_cmdset_creation(self): def at_cmdset_creation(self):
"called when object is first created." "called when object is first created."
@ -612,18 +620,18 @@ class CrumblingWall(TutorialObject, DefaultExit):
# set cmdset # set cmdset
self.cmdset.add(CmdSetCrumblingWall, permanent=True) self.cmdset.add(CmdSetCrumblingWall, permanent=True)
def open(self): def open_wall(self):
""" """
This method is called by the push button command once the puzzle This method is called by the push button command once the puzzle
is solved. It opens the wall and sets a timer for it to reset is solved. It opens the wall and sets a timer for it to reset
itself. itself.
""" """
# this will make it into a proper exit # this will make it into a proper exit (this returns a list)
eloc = self.caller.search(self.obj.db.destination, global_search=True) eloc = search.search_object(self.db.destination)
if not eloc: if not eloc:
self.caller.msg("The exit leads nowhere, there's just more stone behind it ...") self.caller.msg("The exit leads nowhere, there's just more stone behind it ...")
else: else:
self.obj.destination = eloc self.destination = eloc[0]
self.exit_open = True self.exit_open = True
# start a 45 second timer before closing again # start a 45 second timer before closing again
utils.delay(45, self.reset) utils.delay(45, self.reset)

View file

@ -111,7 +111,7 @@ class CmdTutorialSetDetail(default_cmds.MuxCommand):
if not hasattr(self.obj, "set_detail"): if not hasattr(self.obj, "set_detail"):
self.caller.msg("Details cannot be set on %s." % self.obj) self.caller.msg("Details cannot be set on %s." % self.obj)
return return
for key in self.args.split(";"): for key in self.lhs.split(";"):
# loop over all aliases, if any (if not, this will just be # loop over all aliases, if any (if not, this will just be
# the one key to loop over) # the one key to loop over)
self.obj.set_detail(key, self.rhs) self.obj.set_detail(key, self.rhs)
@ -341,7 +341,7 @@ class WeatherRoom(TutorialRoom):
# #
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
DARK_MESSAGES = ("It is pitch black. You are likely to be eaten by a grue." DARK_MESSAGES = ("It is pitch black. You are likely to be eaten by a grue.",
"It's pitch black. You fumble around but cannot find anything.", "It's pitch black. You fumble around but cannot find anything.",
"You don't see a thing. You feel around, managing to bump your fingers hard against something. Ouch!", "You don't see a thing. You feel around, managing to bump your fingers hard against something. Ouch!",
"You don't see a thing! Blindly grasping the air around you, you find nothing.", "You don't see a thing! Blindly grasping the air around you, you find nothing.",
@ -475,6 +475,12 @@ class DarkRoom(TutorialRoom):
self.db.is_lit = False self.db.is_lit = False
self.cmdsets.add(DarkCmdSet, permanent=True) self.cmdsets.add(DarkCmdSet, permanent=True)
def at_init(self):
"""
Called when room is first recached (such as after a reload)
"""
self.check_light_state()
def _carries_light(self, obj): def _carries_light(self, obj):
""" """
Checks if the given object carries anything that gives light. Checks if the given object carries anything that gives light.
@ -483,9 +489,9 @@ class DarkRoom(TutorialRoom):
but for the Attribute is_giving_light - this makes it easy to but for the Attribute is_giving_light - this makes it easy to
later add other types of light-giving items. We also accept later add other types of light-giving items. We also accept
if there is a light-giving object in the room overall (like if if there is a light-giving object in the room overall (like if
a lantern was dropped in the room) a splinter was dropped in the room)
""" """
return obj.db.is_giving_light or any(obj for obj in obj.contents if obj.db.is_giving_light) return obj.is_superuser or obj.db.is_giving_light or obj.is_superuser or any(o for o in obj.contents if o.db.is_giving_light)
def _heal(self, character): def _heal(self, character):
""" """
@ -502,8 +508,6 @@ class DarkRoom(TutorialRoom):
the room and also by the Light sources when they turn on. the room and also by the Light sources when they turn on.
""" """
if any(self._carries_light(obj) for obj in self.contents): if any(self._carries_light(obj) for obj in self.contents):
# people are carrying lights
if not self.db.is_lit:
self.cmdset.remove(DarkCmdSet) self.cmdset.remove(DarkCmdSet)
self.db.is_lit = True self.db.is_lit = True
for char in (obj for obj in self.contents if obj.has_player): for char in (obj for obj in self.contents if obj.has_player):
@ -511,7 +515,6 @@ class DarkRoom(TutorialRoom):
char.msg("The room is lit up.") char.msg("The room is lit up.")
else: else:
# noone is carrying light - darken the room # noone is carrying light - darken the room
if self.db.is_lit:
self.db.is_lit = False self.db.is_lit = False
self.cmdset.add(DarkCmdSet, permanent=True) self.cmdset.add(DarkCmdSet, permanent=True)
for char in (obj for obj in self.contents if obj.has_player): for char in (obj for obj in self.contents if obj.has_player):
@ -538,7 +541,6 @@ class DarkRoom(TutorialRoom):
teleported away. teleported away.
""" """
self.check_light_state() self.check_light_state()
obj.cmdset.delete(DarkCmdSet)
#------------------------------------------------------------ #------------------------------------------------------------
# #
@ -773,7 +775,7 @@ class CmdLookBridge(Command):
fall_exit = search_object(self.obj.db.fall_exit) fall_exit = search_object(self.obj.db.fall_exit)
if fall_exit: if fall_exit:
self.caller.msg("{r%s{n" % FALL_MESSAGE) self.caller.msg("{r%s{n" % FALL_MESSAGE)
self.caller.move_to(fall_exit, quiet=True) self.caller.move_to(fall_exit[0], quiet=True)
# inform others on the bridge # inform others on the bridge
self.obj.msg_contents("A plank gives way under %s's feet and " \ self.obj.msg_contents("A plank gives way under %s's feet and " \
"they fall from the bridge!" % self.caller.key) "they fall from the bridge!" % self.caller.key)

View file

@ -49,6 +49,7 @@ call the handler's save() and restore() methods when the server reboots.
""" """
from twisted.internet.defer import inlineCallbacks from twisted.internet.defer import inlineCallbacks
from django.core.exceptions import ObjectDoesNotExist
from evennia.scripts.scripts import ExtendedLoopingCall from evennia.scripts.scripts import ExtendedLoopingCall
from evennia.server.models import ServerConfig from evennia.server.models import ServerConfig
from evennia.utils.logger import log_trace, log_err from evennia.utils.logger import log_trace, log_err
@ -84,14 +85,17 @@ class Ticker(object):
The callback should ideally work under @inlineCallbacks so it can yield The callback should ideally work under @inlineCallbacks so it can yield
appropriately. appropriately.
""" """
for key, (obj, args, kwargs) in self.subscriptions.items(): for store_key, (obj, args, kwargs) in self.subscriptions.items():
hook_key = yield kwargs.get("_hook_key", "at_tick") hook_key = yield kwargs.get("_hook_key", "at_tick")
if not obj: if not obj:
# object was deleted between calls # object was deleted between calls
self.validate() self.remove(store_key)
continue continue
try: try:
yield _GA(obj, hook_key)(*args, **kwargs) yield _GA(obj, hook_key)(*args, **kwargs)
except ObjectDoesNotExist:
log_trace()
self.remove(store_key)
except Exception: except Exception:
log_trace() log_trace()