diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index 5636b3f6b3..eb150f64a7 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -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." diff --git a/evennia/contrib/tutorial_world/build.ev b/evennia/contrib/tutorial_world/build.ev index 4e116ead23..587beb9ed6 100644 --- a/evennia/contrib/tutorial_world/build.ev +++ b/evennia/contrib/tutorial_world/build.ev @@ -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 diff --git a/evennia/contrib/tutorial_world/mob.py b/evennia/contrib/tutorial_world/mob.py index 6882365aba..cc3af9fe81 100644 --- a/evennia/contrib/tutorial_world/mob.py +++ b/evennia/contrib/tutorial_world/mob.py @@ -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() diff --git a/evennia/contrib/tutorial_world/objects.py b/evennia/contrib/tutorial_world/objects.py index bf6c4447d7..ac08d50a10 100644 --- a/evennia/contrib/tutorial_world/objects.py +++ b/evennia/contrib/tutorial_world/objects.py @@ -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) diff --git a/evennia/contrib/tutorial_world/rooms.py b/evennia/contrib/tutorial_world/rooms.py index 0aab853b9f..8690282237 100644 --- a/evennia/contrib/tutorial_world/rooms.py +++ b/evennia/contrib/tutorial_world/rooms.py @@ -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) diff --git a/evennia/scripts/tickerhandler.py b/evennia/scripts/tickerhandler.py index 5b13cf54a1..c4a469eb10 100644 --- a/evennia/scripts/tickerhandler.py +++ b/evennia/scripts/tickerhandler.py @@ -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()