Migrate. Made Exits work differently, by use of commands directly instead of an exithandler assigning commands on-the-fly. This solution is a lot cleaner and also solves an issue where @reload would kill typeclasses in situations where an exit was painting to an object whose typeclass was reloaded (same issue occured if the exit typeclass itself was reloaded). As part of these fixes I cleaned up the merging of cmdsets to now merge in strict priority order, as one would expect them to do. Many small bug-fixes and cleanups all over. Resolves issue 164. Resolves issue 163.

This commit is contained in:
Griatch 2011-05-01 18:04:15 +00:00
parent 4bcd5239b5
commit b8a13a2389
17 changed files with 323 additions and 298 deletions

View file

@ -1,104 +0,0 @@
"""
This handler creates cmdsets on the fly, by searching
an object's location for valid exit objects.
"""
from src.commands import cmdset, command
class ExitCommand(command.Command):
"Simple identifier command"
is_exit = True
locks = "cmd:all()" # should always be set to this.
destination = None
obj = None
def func(self):
"Default exit traverse if no syscommand is defined."
if self.obj.access(self.caller, 'traverse'):
# we may traverse the exit.
old_location = None
if hasattr(self.caller, "location"):
old_location = self.caller.location
# call pre/post hooks and move object.
self.obj.at_before_traverse(self.caller)
self.caller.move_to(self.destination)
self.obj.at_after_traverse(self.caller, old_location)
else:
if self.obj.db.err_traverse:
# if exit has a better error message, let's use it.
self.caller.msg(self.obj.db.err_traverse)
else:
# No shorthand error message. Call hook.
self.obj.at_fail_traverse(self.caller)
class ExitHandler(object):
"""
The exithandler auto-creates 'commands' to represent exits in the
room. It is called by cmdhandler when building its index of all
viable commands. This allows for exits to be processed along with
all other inputs the player gives to the game. The handler tries
to intelligently cache exit objects to cut down on processing.
"""
def __init__(self):
"Setup cache storage"
self.cached_exit_cmds = {}
def clear(self, exitcmd=None):
"""
Reset cache storage. If obj is given, only remove
that object from cache.
"""
if exitcmd:
# delete only a certain object from cache
try:
del self.cached_exit_cmds[exitcmd.id]
except KeyError:
pass
return
# reset entire cache
self.cached_exit_cmds = {}
def get_cmdset(self, srcobj):
"""
Search srcobjs location for valid exits, and
return objects as stored in command set
"""
# create a quick "throw away" cmdset
exit_cmdset = cmdset.CmdSet(None)
exit_cmdset.key = '_exitset'
exit_cmdset.priority = 9
exit_cmdset.duplicates = True
try:
location = srcobj.location
except Exception:
location = None
if not location:
# there can be no exits if there's no location
return exit_cmdset
# use exits to create searchable "commands" for the cmdhandler
for exi in location.exits:
if exi.id in self.cached_exit_cmds:
# retrieve from cache
exit_cmdset.add(self.cached_exit_cmds[exi.id])
else:
# not in cache, create a new exit command
cmd = ExitCommand()
cmd.key = exi.name.strip().lower()
cmd.obj = exi
if exi.aliases:
cmd.aliases = exi.aliases
cmd.destination = exi.destination
exit_cmdset.add(cmd)
self.cached_exit_cmds[exi.id] = cmd
return exit_cmdset
# The actual handler - call this to get exits
EXITHANDLER = ExitHandler()

View file

@ -274,25 +274,26 @@ class ObjectManager(TypedObjectManager):
# ObjectManager Copy method
#
def copy_object(self, original_object, new_name=None,
def copy_object(self, original_object, new_key=None,
new_location=None, new_player=None, new_home=None,
new_permissions=None, new_locks=None, new_aliases=None):
new_permissions=None, new_locks=None, new_aliases=None, new_destination=None):
"""
Create and return a new object as a copy of the source object. All will
Create and return a new object as a copy of the original object. All will
be identical to the original except for the arguments given specifically
to this method.
original_object (obj) - the object to make a copy from
new_name (str) - name the copy differently from the original.
new_key (str) - name the copy differently from the original.
new_location (obj) - if not None, change the location
new_home (obj) - if not None, change the Home
new_aliases (list of strings) - if not None, change object aliases.
new_destination (obj) - if not None, change destination
"""
# get all the object's stats
typeclass_path = original_object.typeclass_path
if not new_name:
new_name = original_object.key
if not new_key:
new_key = original_object.key
if not new_location:
new_location = original_object.location
if not new_home:
@ -305,13 +306,15 @@ class ObjectManager(TypedObjectManager):
new_locks = original_object.db_lock_storage
if not new_permissions:
new_permissions = original_object.permissions
if not new_destination:
new_destination = original_object.destination
# create new object
from src.utils import create
from src.scripts.models import ScriptDB
new_object = create.create_object(typeclass_path, new_name, new_location,
new_home, new_player, new_permissions,
new_locks, new_aliases)
new_object = create.create_object(typeclass_path, key=new_key, location=new_location,
home=new_home, player=new_player, permissions=new_permissions,
locks=new_locks, aliases=new_aliases, destination=new_destination)
if not new_object:
return None

View file

@ -449,12 +449,12 @@ class ObjectDB(TypedObject):
has_player = property(has_player_get)
#@property
def contents_get(self):
def contents_get(self, exclude=None):
"""
Returns the contents of this object, i.e. all
objects that has this object set as its location.
"""
return ObjectDB.objects.get_contents(self)
return ObjectDB.objects.get_contents(self, excludeobj=exclude)
contents = property(contents_get)
#@property
@ -748,6 +748,17 @@ class ObjectDB(TypedObject):
obj.msg(string)
obj.move_to(home)
def copy(self, new_key=None):
"""
Makes an identical copy of this object and returns
it. The copy will be named <key>_copy by default. If you
want to customize the copy by changing some settings, use
the manager method copy_object directly.
"""
if not new_key:
new_key = "%s_copy" % self.key
return ObjectDB.objects.copy_object(self, new_key=new_key)
def delete(self):
"""
Deletes this object.

View file

@ -17,8 +17,6 @@ they control by simply linking to a new object's user property.
from src.typeclasses.typeclass import TypeClass
from src.commands import cmdset, command
from src.objects.exithandler import EXITHANDLER
#
# Base class to inherit from.
@ -77,6 +75,12 @@ class Object(TypeClass):
"""
pass
def at_cache(self):
"""
Called whenever this object is cached or reloaded.
"""
pass
def at_first_login(self):
"""
Only called once, the very first
@ -401,49 +405,113 @@ class Room(Object):
class Exit(Object):
"""
This is the base exit object - it connects a location
to another. What separates it from other objects
is that it has the 'destination' property defined.
Note that property is the only identifier to
separate an exit from normal objects, so if the property
is removed, it will be treated like any other object. This
also means that any object can be made an exit by setting
the property destination to a valid location
('Quack like a duck...' and so forth).
This is the base exit object - it connects a location to
another. This is done by the exit assigning a "command" on itself
with the same name as the exit object (to do this we need to
remember to re-create the command when the object is cached since it must be
created dynamically depending on what the exit is called). This
command (which has a high priority) will thus allow us to traverse exits
simply by giving the exit-object's name on its own.
"""
# Helper classes and methods to implement the Exit. These need not
# be overloaded unless one want to change the foundation for how
# Exits work. See the end of the class for hook methods to overload.
def create_exit_cmdset(self, exidbobj):
"""
Helper function for creating an exit command set + command.
Note that exitdbobj is an ObjectDB instance. This is necessary for
handling reloads and avoid tracebacks while the typeclass system
is rebooting.
"""
class ExitCommand(command.Command):
"""
This is a command that simply cause the caller
to traverse the object it is attached to.
"""
locks = "cmd:all()" # should always be set to this.
obj = None
def func(self):
"Default exit traverse if no syscommand is defined."
if self.obj.access(self.caller, 'traverse'):
# we may traverse the exit.
old_location = None
if hasattr(self.caller, "location"):
old_location = self.caller.location
# call pre/post hooks and move object.
self.obj.at_before_traverse(self.caller)
self.caller.move_to(self.obj.destination)
self.obj.at_after_traverse(self.caller, old_location)
else:
if self.obj.db.err_traverse:
# if exit has a better error message, let's use it.
self.caller.msg(self.obj.db.err_traverse)
else:
# No shorthand error message. Call hook.
self.obj.at_failed_traverse(self.caller)
# create an exit command.
cmd = ExitCommand()
cmd.key = exidbobj.db_key.strip().lower()
cmd.obj = exidbobj
cmd.aliases = exidbobj.aliases
cmd.destination = exidbobj.db_destination
# create a cmdset
exit_cmdset = cmdset.CmdSet(None)
exit_cmdset.key = '_exitset'
exit_cmdset.priority = 9
exit_cmdset.duplicates = True
# add command to cmdset
exit_cmdset.add(cmd)
return exit_cmdset
# Command hooks
def basetype_setup(self):
"""
Setup exit-security
Don't change this, instead edit at_object_creation() to
overload the defaults (it is called after this one).
overload the default locks (it is called after this one).
"""
# the lock is open to all by default
super(Exit, self).basetype_setup()
self.locks.add("puppet:false()") # would be weird to puppet an exit ...
self.locks.add("traverse:all()") # who can pass through exit by default
self.locks.add("get:false()") # noone can pick up the exit
# this is the fundamental thing for making the Exit work:
self.cmdset.add_default(self.create_exit_cmdset(self.dbobj), permanent=False)
# an exit should have a destination (this is replaced at creation time)
if self.dbobj.location:
self.destination = self.dbobj.location
# setting default locks (overload these in at_object_creation()
self.locks.add("puppet:false()") # would be weird to puppet an exit ...
self.locks.add("traverse:all()") # who can pass through exit by default
self.locks.add("get:false()") # noone can pick up the exit
def at_object_delete(self):
"""
We have to make sure to clean the exithandler cache
when deleting the exit, or a new exit could be created
out of sync with the cache. You should do this also if
overloading this function in a child class.
"""
EXITHANDLER.clear(self.dbobj)
return True
def at_cache(self):
"Called when the typeclass is re-cached or reloaded. Should usually not be edited."
self.cmdset.add_default(self.create_exit_cmdset(self.dbobj), permanent=False)
# this and other hooks are what usually can be modified safely.
def at_object_creation(self):
"Called once, when object is first created (after basetype_setup)."
pass
def at_failed_traverse(self, traversing_object):
"""
This is called if an object fails to traverse this object for some
reason. It will not be called if the attribute "err_traverse" is defined,
that attribute will then be echoed back instead as a convenient shortcut.
(See also hooks at_before_traverse and at_after_traverse).
"""
traversing_object.msg("You cannot enter %s." % self.key)
traversing_object.msg("You cannot go there.")