mirror of
https://github.com/evennia/evennia.git
synced 2026-03-25 01:06:32 +01:00
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:
parent
4bcd5239b5
commit
b8a13a2389
17 changed files with 323 additions and 298 deletions
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue