diff --git a/game/gamesrc/objects/baseobjects.py b/game/gamesrc/objects/baseobjects.py index 2157c9ddb2..0781d64822 100644 --- a/game/gamesrc/objects/baseobjects.py +++ b/game/gamesrc/objects/baseobjects.py @@ -133,18 +133,18 @@ class Room(BaseRoom): class Exit(BaseExit): """ - Exits are connectors between rooms. They are identified by the - engine by having an attribute "_destination" defined on themselves, - pointing to a valid room object. That is usually defined when - the exit is created (in, say, @dig or @link-type commands), not - hard-coded in their typeclass. Exits do have to make sure they - clean up a bit after themselves though, easiest accomplished - by letting by_object_delete() call the object's parent. - - Note that exits need to do cache-cleanups after they are - deleted, so if you re-implement at_object_delete() for some - reason, make sure to call the parent class-method too! + Exits are connectors between rooms. Exits defines the + 'destination' property and sets up a command on itself with the + same name as the Exit object - this command allows the player to + traverse the exit to the destination just by writing the name of + the object on the command line. + Relevant hooks: + at_before_traverse(traveller) - called just before traversing + at_after_traverse(traveller, source_loc) - called just after traversing + at_failed_traverse(traveller) - called if traversal failed for some reason. Will + not be called if the attribute 'err_traverse' is + defined, in which case that will simply be echoed. """ pass diff --git a/src/commands/cmdhandler.py b/src/commands/cmdhandler.py index 57437d86eb..b723d90993 100644 --- a/src/commands/cmdhandler.py +++ b/src/commands/cmdhandler.py @@ -53,7 +53,6 @@ from traceback import format_exc from django.conf import settings from src.comms.channelhandler import CHANNELHANDLER from src.commands.cmdsethandler import import_cmdset -from src.objects.exithandler import EXITHANDLER from src.utils import logger, utils #This switches the command parser to a user-defined one. @@ -70,7 +69,6 @@ CMD_NOMATCH = "__nomatch_command" CMD_MULTIMATCH = "__multimatch_command" CMD_NOPERM = "__noperm_command" CMD_CHANNEL = "__send_to_channel" -CMD_EXIT = "__move_to_exit" class NoCmdSets(Exception): "No cmdsets found. Critical error." @@ -92,63 +90,45 @@ def get_and_merge_cmdsets(caller): caller_cmdset = caller.cmdset.current except AttributeError: caller_cmdset = None - - # All surrounding cmdsets + + # Create cmdset for all player's available channels channel_cmdset = None - exit_cmdset = None - local_objects_cmdsets = [None] - - # Player object's commandsets - try: - player_cmdset = caller.player.cmdset.current - except AttributeError: - player_cmdset = None - if not caller_cmdset.no_channels: - # Make cmdsets out of all valid channels channel_cmdset = CHANNELHANDLER.get_cmdset(caller) - if not caller_cmdset.no_exits: - # Make cmdsets out of all valid exits in the room - exit_cmdset = EXITHANDLER.get_cmdset(caller) + + # Gather cmdsets from location, objects in location or carried + local_objects_cmdsets = [None] location = None if hasattr(caller, "location"): location = caller.location if location and not caller_cmdset.no_objs: # Gather all cmdsets stored on objects in the room and # also in the caller's inventory and the location itself - local_objlist = location.contents + caller.contents + [location] - local_objects_cmdsets = [obj.cmdset.current - for obj in local_objlist + local_objlist = location.contents_get(exclude=caller.dbobj) + caller.contents + [location] + local_objects_cmdsets = [obj.cmdset.current for obj in local_objlist if obj.locks.check(caller, 'call', no_superuser_bypass=True)] - # Merge all command sets into one - # (the order matters, the higher-prio cmdsets are merged last) - cmdset = caller_cmdset - for obj_cmdset in [obj_cmdset for obj_cmdset in local_objects_cmdsets if obj_cmdset]: - # Here only, object cmdsets are merged with duplicates=True - # (or we would never be able to differentiate between same-prio objects) - try: - old_duplicate_flag = obj_cmdset.duplicates - obj_cmdset.duplicates = True - cmdset = obj_cmdset + cmdset - obj_cmdset.duplicates = old_duplicate_flag - except TypeError: - pass - # Exits and channels automatically has duplicates=True. - try: - cmdset = exit_cmdset + cmdset - except TypeError: - pass - try: - cmdset = channel_cmdset + cmdset - except TypeError: - pass - # finally merge on the player cmdset. This should have a low priority - try: - cmdset = player_cmdset + cmdset - except TypeError: - pass - return cmdset + # Player object's commandsets + try: + player_cmdset = caller.player.cmdset.current + except AttributeError: + player_cmdset = None + + cmdsets = [caller_cmdset] + [player_cmdset] + [channel_cmdset] + local_objects_cmdsets + # weed out all non-found sets + cmdsets = [cmdset for cmdset in cmdsets if cmdset] + # sort cmdsets after reverse priority (highest prio are merged in last) + cmdsets = sorted(cmdsets, key=lambda x: x.priority) + if cmdsets: + # Merge all command sets into one, beginning with the lowest-prio one + cmdset = cmdsets.pop(0) + for merging_cmdset in cmdsets: + #print "<%s(%s,%s)> onto <%s(%s,%s)>" % (merging_cmdset.key, merging_cmdset.priority, merging_cmdset.mergetype, + # cmdset.key, cmdset.priority, cmdset.mergetype) + cmdset = merging_cmdset + cmdset + else: + cmdset = None + return cmdset def match_command(cmd_candidates, cmdset, logged_caller=None): """ @@ -365,17 +345,6 @@ def cmdhandler(caller, raw_string, unloggedin=False, testing=False): cmd_candidate.args) raise ExecSystemCommand(cmd, sysarg) - # Check if this is an Exit match. - if hasattr(cmd, 'is_exit') and cmd.is_exit: - # even if a user-defined syscmd is not defined, the - # found cmd is already a system command in its own right. - syscmd = cmdset.get(CMD_EXIT) - if syscmd: - # replace system command with custom version - cmd = syscmd - sysarg = raw_string - raise ExecSystemCommand(cmd, sysarg) - # A normal command. # Assign useful variables to the instance diff --git a/src/commands/cmdset.py b/src/commands/cmdset.py index 47b334d9d9..dd8dff5648 100644 --- a/src/commands/cmdset.py +++ b/src/commands/cmdset.py @@ -237,7 +237,6 @@ class CmdSet(object): cmds = [instantiate(cmd)] else: cmds = instantiate(cmd) - for cmd in cmds: # add all commands if not hasattr(cmd, 'obj'): @@ -247,8 +246,9 @@ class CmdSet(object): self.commands[ic] = cmd # replace except ValueError: self.commands.append(cmd) - # extra run to make sure to avoid doublets + # extra run to make sure to avoid doublets self.commands = list(set(self.commands)) + #print "In cmdset.add(cmd):", self.key, cmd def remove(self, cmd): """ diff --git a/src/commands/default/building.py b/src/commands/default/building.py index 40953f0dbe..f5762dceb6 100644 --- a/src/commands/default/building.py +++ b/src/commands/default/building.py @@ -136,6 +136,8 @@ class CmdSetObjAlias(MuxCommand): aliases = list(set(old_aliases)) # save back to object. obj.aliases = aliases + # we treat this as a re-caching (relevant for exits to re-build their exit commands with the correct aliases) + obj.at_cache() caller.msg("Aliases for '%s' are now set to %s." % (obj.key, ", ".join(obj.aliases))) class CmdCopy(ObjManipCommand): @@ -175,7 +177,7 @@ class CmdCopy(ObjManipCommand): return to_obj_name = "%s_copy" % from_obj_name to_obj_aliases = ["%s_copy" % alias for alias in from_obj.aliases] - copiedobj = ObjectDB.objects.copy_object(from_obj, new_name=to_obj_name, + copiedobj = ObjectDB.objects.copy_object(from_obj, new_key=to_obj_name, new_aliases=to_obj_aliases) if copiedobj: string = "Identical copy of %s, named '%s' was created." % (from_obj_name, to_obj_name) @@ -633,6 +635,8 @@ class CmdDig(ObjManipCommand): typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH, typeclass) + # create room + lockstring = "control:id(%s) or perm(Immortal); delete:id(%s) or perm(Wizard); edit:id(%s) or perm(Wizard)" lockstring = lockstring % (caller.dbref, caller.dbref, caller.dbref) @@ -644,6 +648,9 @@ class CmdDig(ObjManipCommand): alias_string = " (%s)" % ", ".join(new_room.aliases) room_string = "Created room %s(%s)%s of type %s." % (new_room, new_room.dbref, alias_string, typeclass) + + # create exit to room + exit_to_string = "" exit_back_string = "" @@ -667,17 +674,17 @@ class CmdDig(ObjManipCommand): typeclass.startswith('game.')): typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH, typeclass) - new_to_exit = create.create_object(typeclass, to_exit["name"], - location, - aliases=to_exit["aliases"]) - new_to_exit.destination = new_room - new_to_exit.locks.add(lockstring) + new_to_exit = create.create_object(typeclass, to_exit["name"], location, + aliases=to_exit["aliases"], + locks=lockstring, destination=new_room) alias_string = "" if new_to_exit.aliases: alias_string = " (%s)" % ", ".join(new_to_exit.aliases) exit_to_string = "\nCreated Exit from %s to %s: %s(%s)%s." exit_to_string = exit_to_string % (location.name, new_room.name, new_to_exit, new_to_exit.dbref, alias_string) + + # Create exit back from new room if len(self.rhs_objs) > 1: # Building the exit back to the current room @@ -700,10 +707,8 @@ class CmdDig(ObjManipCommand): typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH, typeclass) new_back_exit = create.create_object(typeclass, back_exit["name"], - new_room, - aliases=back_exit["aliases"]) - new_back_exit.destination = location - new_back_exit.locks.add(lockstring) + new_room, aliases=back_exit["aliases"], + locks=lockstring, destination=location) alias_string = "" if new_back_exit.aliases: alias_string = " (%s)" % ", ".join(new_back_exit.aliases) @@ -1534,7 +1539,7 @@ class CmdExamine(ObjManipCommand): perms = [""] elif not perms: perms = [""] - string += headers["perms"] % (", ".join(perms)) + string += headers["playerperms"] % (", ".join(perms)) string += headers["typeclass"] % (obj.typeclass, obj.typeclass_path) if hasattr(obj, "location"): diff --git a/src/commands/default/cmdset_unloggedin.py b/src/commands/default/cmdset_unloggedin.py index 0a128da33c..1e189ed460 100644 --- a/src/commands/default/cmdset_unloggedin.py +++ b/src/commands/default/cmdset_unloggedin.py @@ -11,6 +11,7 @@ class UnloggedinCmdSet(CmdSet): Sets up the unlogged cmdset. """ key = "Unloggedin" + priority = 0 def at_cmdset_creation(self): "Populate the cmdset" diff --git a/src/commands/default/general.py b/src/commands/default/general.py index 218f1508c3..85d7996ebe 100644 --- a/src/commands/default/general.py +++ b/src/commands/default/general.py @@ -613,13 +613,15 @@ class CmdOOCLook(CmdLook): help_cateogory = "General" def func(self): - "implement the command" + "implement the ooc look command" - self.character = None + self.character = None if utils.inherits_from(self.caller, "src.objects.objects.Object"): # An object of some type is calling. Convert to player. + print self.caller, self.caller.__class__ self.character = self.caller - self.caller = self.caller.player + if hasattr(self.caller, "player"): + self.caller = self.caller.player if not self.character: string = "You are out-of-character (OOC). " @@ -627,7 +629,7 @@ class CmdOOCLook(CmdLook): self.caller.msg(string) else: self.caller = self.character # we have to put this back for normal look to work. - super(CmdLook, self).func() + super(CmdOOCLook, self).func() class CmdIC(MuxCommand): """ diff --git a/src/commands/default/muxcommand.py b/src/commands/default/muxcommand.py index 2210d66f41..d1787eb1f9 100644 --- a/src/commands/default/muxcommand.py +++ b/src/commands/default/muxcommand.py @@ -136,7 +136,7 @@ class MuxCommand(Command): string += "-" * 50 string += "\nname of cmd (self.key): {w%s{n\n" % self.key string += "cmd aliases (self.aliases): {w%s{n\n" % self.aliases - string += "cmd perms (self.permissions): {w%s{n\n" % self.permissions + string += "cmd locks (self.locks): {w%s{n\n" % self.locks string += "help category (self.help_category): {w%s{n\n" % self.help_category string += "object calling (self.caller): {w%s{n\n" % self.caller string += "object storing cmdset (self.obj): {w%s{n\n" % self.obj diff --git a/src/commands/default/syscommands.py b/src/commands/default/syscommands.py index f41dbd8113..84b05dc493 100644 --- a/src/commands/default/syscommands.py +++ b/src/commands/default/syscommands.py @@ -28,7 +28,6 @@ from src.commands.cmdhandler import CMD_NOMATCH from src.commands.cmdhandler import CMD_MULTIMATCH from src.commands.cmdhandler import CMD_NOPERM from src.commands.cmdhandler import CMD_CHANNEL -from src.commands.cmdhandler import CMD_EXIT from src.commands.default.muxcommand import MuxCommand @@ -188,37 +187,3 @@ class SystemSendToChannel(MuxCommand): msg = "[%s] %s: %s" % (channel.key, caller.name, msg) msgobj = create.create_message(caller, msg, channels=[channel]) channel.msg(msgobj) - -# -# Command called when the system recognizes the command given -# as matching an exit from the room. E.g. if there is an exit called 'door' -# and the user gives the command -# > door -# the exit 'door' should be traversed to its destination. - -class SystemUseExit(MuxCommand): - """ - Handles what happens when user gives a valid exit - as a command. It receives the raw string as input. - """ - key = CMD_EXIT - locks = "cmd:all()" - - def func(self): - """ - Handle traversing an exit - """ - caller = self.caller - if not self.args: - return - exit_name = self.args - exi = caller.search(exit_name) - if not exi: - return - destination = exi.destination - if not destination: - return - if exit.access(caller, 'traverse'): - caller.move_to(destination) - else: - caller.msg("You cannot enter") diff --git a/src/objects/exithandler.py b/src/objects/exithandler.py deleted file mode 100644 index f4b2c8eaf2..0000000000 --- a/src/objects/exithandler.py +++ /dev/null @@ -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() diff --git a/src/objects/manager.py b/src/objects/manager.py index 80a99b1052..40badec3e6 100644 --- a/src/objects/manager.py +++ b/src/objects/manager.py @@ -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 diff --git a/src/objects/models.py b/src/objects/models.py index 1352118deb..2ee90ae2ef 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -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 _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. diff --git a/src/objects/objects.py b/src/objects/objects.py index f057dc27c2..2adee59951 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -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.") diff --git a/src/players/migrations/0005_adding_player_cmdset.py b/src/players/migrations/0005_adding_player_cmdset.py new file mode 100644 index 0000000000..b01490847d --- /dev/null +++ b/src/players/migrations/0005_adding_player_cmdset.py @@ -0,0 +1,108 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models, utils +from django.conf import settings + + +class Migration(DataMigration): + + def forwards(self, orm): + "Write your forwards methods here." + try: + for player in orm.PlayerDB.objects.all(): + if not player.db_cmdset_storage: + player.db_cmdset_storage = settings.CMDSET_OOC + player.save() + except utils.DatabaseError: + # this will happen if we start db from scratch (ignore in that case) + pass + + def backwards(self, orm): + "Write your backwards methods here." + raise RuntimeError("This migration cannot be reverted.") + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'objects.objectdb': { + 'Meta': {'object_name': 'ObjectDB'}, + 'db_cmdset_storage': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'db_destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destinations_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}), + 'db_home': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'homes_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}), + 'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'db_location': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locations_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}), + 'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'db_permissions': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}), + 'db_player': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['players.PlayerDB']", 'null': 'True', 'blank': 'True'}), + 'db_typeclass_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'players.playerattribute': { + 'Meta': {'object_name': 'PlayerAttribute'}, + 'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['players.PlayerDB']"}), + 'db_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'players.playerdb': { + 'Meta': {'object_name': 'PlayerDB'}, + 'db_cmdset_storage': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['objects.ObjectDB']", 'null': 'True'}), + 'db_permissions': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}), + 'db_typeclass_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'players.playernick': { + 'Meta': {'unique_together': "(('db_nick', 'db_type', 'db_obj'),)", 'object_name': 'PlayerNick'}, + 'db_nick': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['players.PlayerDB']"}), + 'db_real': ('django.db.models.fields.TextField', [], {}), + 'db_type': ('django.db.models.fields.CharField', [], {'default': "'inputline'", 'max_length': '16', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + } + } + + complete_apps = ['players'] diff --git a/src/server/initial_setup.py b/src/server/initial_setup.py index 39af49a0f6..79041ca0e3 100644 --- a/src/server/initial_setup.py +++ b/src/server/initial_setup.py @@ -57,7 +57,7 @@ def create_objects(): user=god_user) god_character.id = 1 god_character.db.desc = 'This is User #1.' - god_character.locks.add("examine:perm(Immortals);edit:false();delete:false();boot:false();msg:all()") + god_character.locks.add("examine:perm(Immortals);edit:false();delete:false();boot:false();msg:all();puppet:false()") god_character.save() diff --git a/src/utils/create.py b/src/utils/create.py index 29b0a756b6..2a51461f0e 100644 --- a/src/utils/create.py +++ b/src/utils/create.py @@ -29,7 +29,8 @@ from src.utils.utils import is_iter, has_parent # def create_object(typeclass, key=None, location=None, - home=None, player=None, permissions=None, locks=None, aliases=None): + home=None, player=None, permissions=None, locks=None, + aliases=None, destination=None): """ Create a new in-game object. Any game object is a combination of a database object that stores data persistently to @@ -89,6 +90,8 @@ def create_object(typeclass, key=None, location=None, # link a player and the object together new_object.player = player player.obj = new_object + + new_object.destination = destination # call the hook method. This is where all at_creation # customization happens as the typeclass stores custom diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index 7e00291846..2c363db5aa 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -101,6 +101,8 @@ class SharedMemoryModel(Model): """ if instance._get_pk_val() is not None: cls.__instance_cache__[instance._get_pk_val()] = instance + if hasattr(instance, 'at_cache') and callable(instance.at_cache): + instance.at_cache() #key = "%s-%s" % (cls, instance.pk) #TCACHE[key] = instance #print "cached: %s (%s: %s) (total cached: %s)" % (instance, cls.__name__, len(cls.__instance_cache__), len(TCACHE)) diff --git a/src/utils/reloads.py b/src/utils/reloads.py index 464c15f308..bd0db5312a 100644 --- a/src/utils/reloads.py +++ b/src/utils/reloads.py @@ -17,7 +17,6 @@ from src.comms.models import Channel, Msg from src.help.models import HelpEntry from src.typeclasses import models as typeclassmodels -from src.objects import exithandler from src.comms import channelhandler from src.comms.models import Channel from src.utils import reimport, utils, logger @@ -34,12 +33,7 @@ def start_reload_loop(): "" cemit_info('-'*50) cemit_info(" Starting asynchronous server reload.") - reload_modules() # this must be given time to finish - - wait_time = 5 - cemit_info(" Waiting %ss to give modules time to fully re-cache ..." % wait_time) - time.sleep(wait_time) - + reload_modules() reload_scripts() reload_commands() reset_loop() @@ -49,7 +43,7 @@ def start_reload_loop(): cemit_info(" Asynchronous server reload finished.\n" + '-'*50) def at_err(e): "error callback" - string = "Reload: Asynchronous reset exited with an error:\n{r%s{n" % e.getErrorMessage() + string = " Reload: Asynchronous reset exited with an error:\n {r%s{n" % e.getErrorMessage() cemit_info(string) utils.run_async(run_loop, at_return, at_err) @@ -89,13 +83,13 @@ def reload_modules(): "Check so modpath is not in an unsafe module" return not any(mpath.startswith(modpath) for mpath in unsafe_modules) - cemit_info(" Cleaning module caches ...") + #cemit_info(" Cleaning module caches ...") # clean as much of the caches as we can cache = AppCache() cache.app_store = SortedDict() #cache.app_models = SortedDict() # cannot clean this, it resets ContentTypes! - cache.app_errors = {} + cache.app_errors = {} cache.handled = {} cache.loaded = False @@ -108,29 +102,28 @@ def reload_modules(): string = "" if unsafe_dir_modified or unsafe_mod_modified: - if unsafe_dir_modified: - string += "\n-{rThe following changed module(s) can only be reloaded{n" - string += "\n {rby a server reboot:{n\n %s\n" - string = string % unsafe_dir_modified + if unsafe_mod_modified: - string += "\n-{rThe following modules contains at least one Script class with a timer{n" - string += "\n {rcomponent and has already spawned instances - these cannot be{n " - string += "\n {rsafely cleaned from memory on the fly. Stop all the affected scripts{n " - string += "\n {ror restart the server to safely reload:{n\n %s\n" - string = string % unsafe_mod_modified + string += "\n {rModules containing Script classes with a timer component" + string += "\n and which has already spawned instances cannot be reloaded safely.{n" + string += "\n {rThese module(s) can only be reloaded by server reboot:{n\n %s\n" + string = string % ", ".join(unsafe_dir_modified + unsafe_mod_modified) + if string: cemit_info(string) if safe_modified: - cemit_info(" Reloading module(s):\n %s ..." % safe_modified) + cemit_info(" Reloading safe module(s):{n\n %s" % "\n ".join(safe_modified)) reimport.reimport(*safe_modified) + wait_time = 5 + cemit_info(" Waiting %s secs to give modules time to re-cache ..." % wait_time) + time.sleep(wait_time) cemit_info(" ... all safe modules reloaded.") else: - cemit_info(" ... no modules could be (or needed to be) reloaded.") + cemit_info(" No modules reloaded.") # clean out cache dictionary of typeclasses, exits and channels - typeclassmodels.reset() - exithandler.EXITHANDLER.clear() + typeclassmodels.reset() channelhandler.CHANNELHANDLER.update() # run through all objects in database, forcing re-caching. @@ -161,23 +154,22 @@ def reload_scripts(scripts=None, obj=None, key=None, def reload_commands(): from src.commands import cmdsethandler cmdsethandler.CACHED_CMDSETS = {} - cemit_info(" Cleaned cmdset cache.") + #cemit_info(" Cleaned cmdset cache.") def reset_loop(): "Reload and restart all entities that can be reloaded." # run the reset loop on all objects - cemit_info(" Running resets on database entities ...") + cemit_info(" Resetting all cached database entities ...") t1 = time.time() - [h.locks.reset() for h in HelpEntry.objects.all()] [m.locks.reset() for m in Msg.objects.all()] [c.locks.reset() for c in Channel.objects.all()] [s.locks.reset() for s in ScriptDB.objects.all()] - [(o.typeclass(o), o.cmdset.reset(), o.locks.reset()) for o in ObjectDB.get_all_cached_instances()] + [(o.typeclass(o), o.cmdset.reset(), o.locks.reset(), o.at_cache()) for o in ObjectDB.get_all_cached_instances()] [(p.typeclass(p), p.cmdset.reset(), p.locks.reset()) for p in PlayerDB.get_all_cached_instances()] t2 = time.time() - cemit_info(" ... Loop finished in %g seconds." % (t2-t1)) + cemit_info(" ... Reset finished in %g seconds." % (t2-t1)) def cemit_info(message): """