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
# return result to the deferred
returnValue(ret)
except Exception:
string = "%s\nAbove traceback is from an untrapped error."
string += " Please file a bug report."

View file

@ -198,7 +198,7 @@ start
tickerhandler to regularly 'tick and randomly display various
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
used for all tutorial rooms can display.
#
@ -620,8 +620,9 @@ hole
#
@detail hole;above =
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
up. It looks like an old well.
faintly make out it turning into a man-made circular shaft higher up.
It looks like an old well. There must have been much more water
here once.
#
@detail passages;dark =
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
have been looking for!
#
@dig Tomb of woman on horse
@tel tut#14
#
@dig/teleport Tomb of woman on horse
: tutorial_world.rooms.TeleportRoom
= 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
have been looking for!
#
@dig Tomb of the crowned queen
@tel tut#14
#
@dig/teleport Tomb of the crowned queen
: tutorial_world.rooms.TeleportRoom
= 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
have been looking for!
#
@dig Tomb of the shield
@tel tut#14
#
@dig/teleport Tomb of the shield
: tutorial_world.rooms.TeleportRoom
= 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
have been looking for!
#
@dig Tomb of the hero
@tel tut#14
#
@dig/teleport Tomb of the hero
: tutorial_world.rooms.TeleportRoom
= Tomb depicting a heroine fighting a monster;knight;hero;monster;beast
#
@ -1217,10 +1226,11 @@ stairs down
#
# 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
: tutorial_world.rooms.TutorialRoom

View file

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

View file

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

View file

@ -111,7 +111,7 @@ class CmdTutorialSetDetail(default_cmds.MuxCommand):
if not hasattr(self.obj, "set_detail"):
self.caller.msg("Details cannot be set on %s." % self.obj)
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
# the one key to loop over)
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.",
"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.",
@ -475,6 +475,12 @@ class DarkRoom(TutorialRoom):
self.db.is_lit = False
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):
"""
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
later add other types of light-giving items. We also accept
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):
"""
@ -502,18 +508,15 @@ class DarkRoom(TutorialRoom):
the room and also by the Light sources when they turn on.
"""
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.db.is_lit = True
for char in (obj for obj in self.contents if obj.has_player):
# this won't do anything if it is already removed
char.msg("The room is lit up.")
self.cmdset.remove(DarkCmdSet)
self.db.is_lit = True
for char in (obj for obj in self.contents if obj.has_player):
# this won't do anything if it is already removed
char.msg("The room is lit up.")
else:
# noone is carrying light - darken the room
if self.db.is_lit:
self.db.is_lit = False
self.cmdset.add(DarkCmdSet, permanent=True)
self.db.is_lit = False
self.cmdset.add(DarkCmdSet, permanent=True)
for char in (obj for obj in self.contents if obj.has_player):
if char.is_superuser:
char.msg("You are Superuser, so you are not affected by the dark state.")
@ -529,7 +532,7 @@ class DarkRoom(TutorialRoom):
# a puppeted object, that is, a Character
self._heal(obj)
# in case the new guy carries light with them
self.check_light_state()
self.check_light_state()
def at_object_leave(self, obj, target_location):
"""
@ -538,7 +541,6 @@ class DarkRoom(TutorialRoom):
teleported away.
"""
self.check_light_state()
obj.cmdset.delete(DarkCmdSet)
#------------------------------------------------------------
#
@ -773,7 +775,7 @@ class CmdLookBridge(Command):
fall_exit = search_object(self.obj.db.fall_exit)
if fall_exit:
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
self.obj.msg_contents("A plank gives way under %s's feet and " \
"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 django.core.exceptions import ObjectDoesNotExist
from evennia.scripts.scripts import ExtendedLoopingCall
from evennia.server.models import ServerConfig
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
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")
if not obj:
# object was deleted between calls
self.validate()
self.remove(store_key)
continue
try:
yield _GA(obj, hook_key)(*args, **kwargs)
except ObjectDoesNotExist:
log_trace()
self.remove(store_key)
except Exception:
log_trace()