""" This module defines the database models for all in-game objects, that is, all objects that has an actual existence in-game. Each database object is 'decorated' with a 'typeclass', a normal python class that implements all the various logics needed by the game in question. Objects created of this class transparently communicate with its related database object for storing all attributes. The admin should usually not have to deal directly with this database object layer. Attributes are separate objects that store values persistently onto the database object. Like everything else, they can be accessed transparently through the decorating TypeClass. """ import traceback from django.db import models from django.conf import settings from src.typeclasses.models import TypedObject, TagHandler, NickHandler, AliasHandler, AttributeHandler from src.server.caches import get_field_cache, set_field_cache, del_field_cache from src.server.caches import get_prop_cache, set_prop_cache from src.typeclasses.typeclass import TypeClass from src.objects.manager import ObjectManager from src.players.models import PlayerDB from src.commands.cmdsethandler import CmdSetHandler from src.commands import cmdhandler from src.scripts.scripthandler import ScriptHandler from src.utils import logger from src.utils.utils import make_iter, to_unicode, variable_from_module, inherits_from from django.utils.translation import ugettext as _ #__all__ = ("ObjectDB", ) _ScriptDB = None _AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) _SESSIONS = None _GA = object.__getattribute__ _SA = object.__setattr__ _DA = object.__delattr__ _ME = _("me") _SELF = _("self") _HERE = _("here") #------------------------------------------------------------ # # ObjectDB # #------------------------------------------------------------ class ObjectDB(TypedObject): """ All objects in the game use the ObjectDB model to store data in the database. This is handled transparently through the typeclass system. Note that the base objectdb is very simple, with few defined fields. Use attributes to extend your type class with new database-stored variables. The TypedObject supplies the following (inherited) properties: key - main name name - alias for key typeclass_path - the path to the decorating typeclass typeclass - auto-linked typeclass date_created - time stamp of object creation permissions - perm strings locks - lock definitions (handler) dbref - #id of object db - persistent attribute storage ndb - non-persistent attribute storage The ObjectDB adds the following properties: player - optional connected player (always together with sessid) sessid - optional connection session id (always together with player) location - in-game location of object home - safety location for object (handler) scripts - scripts assigned to object (handler from typeclass) cmdset - active cmdset on object (handler from typeclass) aliases - aliases for this object (property) nicks - nicknames for *other* things in Evennia (handler) sessions - sessions connected to this object (see also player) has_player - bool if an active player is currently connected contents - other objects having this object as location exits - exits from this object """ # # ObjectDB Database model setup # # # inherited fields (from TypedObject): # db_key (also 'name' works), db_typeclass_path, db_date_created, # db_permissions # # These databse fields (including the inherited ones) should normally be set # using their corresponding wrapper properties, named same as the field, but without # the db_* prefix (e.g. the db_key field is set with self.key instead). The wrappers # will automatically save and cache the data more efficiently. # If this is a character object, the player is connected here. db_player = models.ForeignKey("players.PlayerDB", blank=True, null=True, verbose_name='player', help_text='a Player connected to this object, if any.') # the session id associated with this player, if any db_sessid = models.IntegerField(null=True, verbose_name="session id", help_text="unique session id of connected Player, if any.") # The location in the game world. Since this one is likely # to change often, we set this with the 'location' property # to transparently handle Typeclassing. db_location = models.ForeignKey('self', related_name="locations_set",db_index=True, blank=True, null=True, verbose_name='game location') # a safety location, this usually don't change much. db_home = models.ForeignKey('self', related_name="homes_set", blank=True, null=True, verbose_name='home location') # destination of this object - primarily used by exits. db_destination = models.ForeignKey('self', related_name="destinations_set", db_index=True, blank=True, null=True, verbose_name='destination', help_text='a destination, used only by exit objects.') # database storage of persistant cmdsets. db_cmdset_storage = models.CharField('cmdset', max_length=255, null=True, blank=True, help_text="optional python path to a cmdset class.") # Database manager objects = ObjectManager() # caches for quick lookups of typeclass loading. _typeclass_paths = settings.OBJECT_TYPECLASS_PATHS _default_typeclass_path = settings.BASE_OBJECT_TYPECLASS or "src.objects.objects.Object" # Add the object-specific handlers def __init__(self, *args, **kwargs): "Parent must be initialized first." TypedObject.__init__(self, *args, **kwargs) # handlers _SA(self, "cmdset", CmdSetHandler(self)) _GA(self, "cmdset").update(init_mode=True) _SA(self, "scripts", ScriptHandler(self)) _SA(self, "attributes", AttributeHandler(self)) _SA(self, "tags", TagHandler(self)) _SA(self, "aliases", AliasHandler(self)) _SA(self, "nicks", NickHandler(self)) # make sure to sync the contents cache when initializing self.contents_update() # Wrapper properties to easily set database fields. These are # @property decorators that allows to access these fields using # normal python operations (without having to remember to save() # etc). So e.g. a property 'attr' has a get/set/del decorator # defined that allows the user to do self.attr = value, # value = self.attr and del self.attr respectively (where self # is the object in question). #TODO - make player-handler # player property (wraps db_player) #@property def __player_get(self): """ Getter. Allows for value = self.player. We have to be careful here since Player is also a TypedObject, so as to not create a loop. """ player = get_field_cache(self, "player") if player: try: return player.typeclass except Exception,e: print "player_get:", e return player #@player.setter def __player_set(self, player): "Setter. Allows for self.player = value" if inherits_from(player, TypeClass): player = player.dbobj set_field_cache(self, "player", player) # we must set this here or superusers won't be able to # bypass lockchecks unless they start the game connected # to the character in question. self.locks.cache_lock_bypass(self) #@player.deleter def __player_del(self): "Deleter. Allows for del self.player" del_field_cache(self, "player") player = property(__player_get, __player_set, __player_del) # sessid property (wraps db_sessid) #@property #def __sessid_get(self): # """ # Getter. Allows for value = self.sessid. Since sessid # is directly related to self.player, we cannot have # a sessid without a player being connected (but the # opposite could be true). # """ # if not get_field_cache(self, "sessid"): # del_field_cache(self, "sessid") # return get_field_cache(self, "sessid") ##@sessid.setter #def __sessid_set(self, sessid): # "Setter. Allows for self.player = value" # set_field_cache(self, "sessid", sessid) ##@sessid.deleter #def __sessid_del(self): # "Deleter. Allows for del self.player" # del_field_cache(self, "sessid") #sessid = property(__sessid_get, __sessid_set, __sessid_del) def _at_db_location_save(self, new_value, old_value=None): "This is called automatically just before a new location is saved." #print "db_location_handler:", loc, old_value loc = new_value try: old_loc = old_value # new_value can be dbref, typeclass or dbmodel if ObjectDB.objects.dbref(loc, reqhash=False): loc = ObjectDB.objects.dbref_search(loc) if loc and type(loc) != ObjectDB: # this should not fail if new_value is valid. loc = _GA(loc, "dbobj") # recursive location check def is_loc_loop(loc, depth=0): "Recursively traverse the target location to make sure we are not in it." if depth > 10: return elif loc == self: raise RuntimeError elif loc == None: raise RuntimeWarning # just to quickly get out return is_loc_loop(_GA(loc, "db_location"), depth+1) # check so we don't create a location loop - if so, RuntimeError will be raised. try: is_loc_loop(loc) except RuntimeWarning: pass #print "db_location_handler2:", _GA(loc, "db_key") if loc else loc, type(loc) # update the contents of each location if old_loc: _GA(_GA(old_loc, "dbobj"), "contents_update")(self, remove=True) if loc: _GA(loc, "contents_update")(self) return loc except RuntimeError: string = "Cannot set location, " string += "%s.location = %s would create a location-loop." % (self.key, loc) _GA(self, "msg")(_(string)) logger.log_trace(string) raise RuntimeError(string) except Exception, e: string = "Cannot set location (%s): " % str(e) string += "%s is not a valid location." % loc _GA(self, "msg")(_(string)) logger.log_trace(string) raise Exception(string) ## location property (wraps db_location) ##@property #def __location_get(self): # "Getter. Allows for value = self.location." # loc = get_field_cache(self, "location") # if loc: # return _GA(loc, "typeclass") # return None ##@location.setter #def __location_set(self, location): # "Setter. Allows for self.location = location" # try: # old_loc = _GA(self, "location") # if ObjectDB.objects.dbref(location): # # dbref search # loc = ObjectDB.objects.dbref_search(location) # loc = loc and _GA(loc, "dbobj") # elif location and type(location) != ObjectDB: # loc = _GA(location, "dbobj") # else: # loc = location # # recursive location check # def is_loc_loop(loc, depth=0): # "Recursively traverse the target location to make sure we are not in it." # if depth > 10: return # elif loc == self: raise RuntimeError # elif loc == None: raise RuntimeWarning # just to quickly get out # return is_loc_loop(_GA(loc, "db_location"), depth+1) # # check so we don't create a location loop - if so, RuntimeError will be raised. # try: is_loc_loop(loc) # except RuntimeWarning: pass # # set the location # set_field_cache(self, "location", loc) # # update the contents of each location # if old_loc: # _GA(_GA(old_loc, "dbobj"), "contents_update")() # if loc: # _GA(loc, "contents_update")() # except RuntimeError: # string = "Cannot set location, " # string += "%s.location = %s would create a location-loop." % (self.key, loc) # _GA(self, "msg")(_(string)) # logger.log_trace(string) # raise RuntimeError(string) # except Exception, e: # string = "Cannot set location (%s): " % str(e) # string += "%s is not a valid location." % location # _GA(self, "msg")(_(string)) # logger.log_trace(string) # raise Exception(string) ##@location.deleter #def __location_del(self): # "Deleter. Allows for del self.location" # _GA(self, "location").contents_update() # _SA(self, "db_location", None) # _GA(self, "save")() # del_field_cache(self, "location") #location = property(__location_get, __location_set, __location_del) # home property (wraps db_home) #@property def __home_get(self): "Getter. Allows for value = self.home" home = get_field_cache(self, "home") if home: return _GA(home, "typeclass") return None #@home.setter def __home_set(self, home): "Setter. Allows for self.home = value" try: if home == None or type(home) == ObjectDB: hom = home elif ObjectDB.objects.dbref(home): hom = ObjectDB.objects.dbref_search(home) if hom and hasattr(hom,'dbobj'): hom = _GA(hom, "dbobj") else: hom = _GA(home, "dbobj") else: hom = _GA(home, "dbobj") set_field_cache(self, "home", hom) except Exception: string = "Cannot set home: " string += "%s is not a valid home." _GA(self, "msg")(_(string) % home) logger.log_trace(string) #raise #@home.deleter def __home_del(self): "Deleter. Allows for del self.home." _SA(self, "db_home", None) _GA(self, "save")() del_field_cache(self, "home") home = property(__home_get, __home_set, __home_del) # destination property (wraps db_destination) #@property def __destination_get(self): "Getter. Allows for value = self.destination." dest = get_field_cache(self, "destination") if dest: return _GA(dest, "typeclass") return None #@destination.setter def __destination_set(self, destination): "Setter. Allows for self.destination = destination" try: if destination == None or type(destination) == ObjectDB: # destination is None or a valid object dest = destination elif ObjectDB.objects.dbref(destination): # destination is a dbref; search dest = ObjectDB.objects.dbref_search(destination) if dest and _GA(self, "_hasattr")(dest,'dbobj'): dest = _GA(dest, "dbobj") else: dest = _GA(destination, "dbobj") else: dest = destination.dbobj set_field_cache(self, "destination", dest) except Exception: string = "Cannot set destination: " string += "%s is not a valid destination." % destination _GA(self, "msg")(string) logger.log_trace(string) raise #@destination.deleter def __destination_del(self): "Deleter. Allows for del self.destination" _SA(self, "db_destination", None) _GA(self, "save")() del_field_cache(self, "destination") destination = property(__destination_get, __destination_set, __destination_del) # cmdset_storage property. # This seems very sensitive to caching, so leaving it be for now. /Griatch #@property def __cmdset_storage_get(self): "Getter. Allows for value = self.name. Returns a list of cmdset_storage." if _GA(self, "db_cmdset_storage"): return [path.strip() for path in _GA(self, "db_cmdset_storage").split(',')] return [] #@cmdset_storage.setter def __cmdset_storage_set(self, value): "Setter. Allows for self.name = value. Stores as a comma-separated string." value = ",".join(str(val).strip() for val in make_iter(value)) _SA(self, "db_cmdset_storage", value) _GA(self, "save")() #@cmdset_storage.deleter def __cmdset_storage_del(self): "Deleter. Allows for del self.name" _SA(self, "db_cmdset_storage", "") _GA(self, "save")() cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set, __cmdset_storage_del) class Meta: "Define Django meta options" verbose_name = "Object" verbose_name_plural = "Objects" # # ObjectDB class access methods/properties # #@property def __sessions_get(self): """ Retrieve sessions connected to this object. """ # if the player is not connected, this will simply be an empty list. if _GA(self, "db_player"): return _GA(_GA(self, "db_player"), "get_all_sessions")() return [] sessions = property(__sessions_get) #@property def __has_player_get(self): """ Convenience function for checking if an active player is currently connected to this object """ return any(_GA(self, "sessions")) has_player = property(__has_player_get) is_player = property(__has_player_get) #@property def __is_superuser_get(self): "Check if user has a player, and if so, if it is a superuser." return (_GA(self, "db_player") and _GA(_GA(self, "db_player"), "is_superuser") and not _GA(_GA(self, "db_player"), "attributes").get("_quell")) is_superuser = property(__is_superuser_get) # contents #@property def contents_get(self, exclude=None): """ Returns the contents of this object, i.e. all objects that has this object set as its location. This should be publically available. exclude is one or more objects to not return """ cont = get_prop_cache(self, "_contents") exclude = make_iter(exclude) if cont == None: cont = _GA(self, "contents_update")() return [obj for obj in cont.values() if obj not in exclude] contents = property(contents_get) def contents_update(self, obj=None, remove=False): """ Updates the contents property of the object add - object to add to content list remove object to remove from content list """ cont = get_prop_cache(self, "_contents") if not cont: cont = {} if obj: if remove: cont.pop(self.dbid, None) else: cont[self.dbid] = obj else: cont = dict((o.dbid, o) for o in ObjectDB.objects.get_contents(self)) set_prop_cache(self, "_contents", cont) return cont #@property def __exits_get(self): """ Returns all exits from this object, i.e. all objects at this location having the property destination != None. """ return [exi for exi in _GA(self, "contents") if exi.destination] exits = property(__exits_get) # # Main Search method # def search(self, searchdata, global_search=False, use_nicks=False, typeclass=None, location=None, attribute_name=None, quiet=False, exact=False): """ Returns the typeclass of an Object matching a search string/condition Perform a standard object search in the database, handling multiple results and lack thereof gracefully. By default, only objects in self's current location or inventory is searched. Note: to find Players, use eg. ev.player_search. Inputs: searchdata (str or obj): Primary search criterion. Will be matched against object.key (with object.aliases second) unless the keyword attribute_name specifies otherwise. Special strings: # - search by unique dbref. This is always a global search. me,self - self-reference to this object - - can be used to differentiate between multiple same-named matches global_search (bool): Search all objects globally. This is overruled by "location" keyword. use_nicks (bool): Use nickname-replace (nicktype "object") on the search string typeclass (str or Typeclass, or list of either): Limit search only to Objects with this typeclass. May be a list of typeclasses for a broader search. location (Object): Specify a location to search, if different from the self's given location plus its contents. This can also be a list of locations. attribute_name (str): Define which property to search. If set, no key+alias search will be performed. This can be used to search database fields (db_ will be automatically appended), and if that fails, it will try to return objects having Attributes with this name and value equal to searchdata. A special use is to search for "key" here if you want to do a key-search without including aliases. quiet (bool) - don't display default error messages - return multiple matches as a list and no matches as None. If not set (default), will echo error messages and return None. exact (bool) - if unset (default) - prefers to match to beginning of string rather than not matching at all. If set, requires exact mathing of entire string. Returns: quiet=False (default): no match or multimatch: auto-echoes errors to self.msg, then returns None (results are handled by modules set by settings.SEARCH_AT_RESULT and settings.SEARCH_AT_MULTIMATCH_INPUT) match: a unique object match quiet=True: no match or multimatch: returns None or list of multi-matches match: a unique object match """ is_string = isinstance(searchdata, basestring) # handle some common self-references: if searchdata == _HERE: return self.location if searchdata in (_ME, _SELF): return self.typeclass if use_nicks: nicktype = "object" # get all valid nicks to search nicks = self.nicks.all(category="object_nick_%s" % nicktype) if self.has_player: pnicks = self.nicks.all(category="player_nick_%s" % nicktype) nicks = nicks + pnicks for nick in nicks: if searchdata == nick.db_key: searchdata = nick.db_data break candidates=None if global_search or (is_string and searchdata.startswith("#") and len(searchdata) > 1 and searchdata[1:].isdigit()): # only allow exact matching if searching the entire database or unique #dbrefs exact = True elif location: # location(s) were given candidates = [] for obj in make_iter(location): candidates.extend([o.dbobj for o in obj.contents]) else: # local search. Candidates are self.contents, self.location and self.location.contents location = self.location candidates = self.contents if location: candidates = candidates + [location] + location.contents else: candidates.append(self) # normally we are included in location.contents # db manager expects database objects candidates = [obj.dbobj for obj in candidates] results = ObjectDB.objects.object_search(searchdata, attribute_name=attribute_name, typeclass=typeclass, candidates=candidates, exact=exact) if quiet: return results return _AT_SEARCH_RESULT(self, searchdata, results, global_search) def search_player(self, searchdata, quiet=False): """ Simple wrapper of the player search also handling me, self """ if searchdata in (_ME, _SELF) and _GA(self, "db_player"): return _GA(self, "db_player") results = PlayerDB.objects.player_search(searchdata) if quiet: return results return _AT_SEARCH_RESULT(self, searchdata, results, True) # # Execution/action methods # def execute_cmd(self, raw_string, sessid=None): """ Do something as this object. This command transparently lets its typeclass execute the command. Evennia also calls this method whenever the player sends a command on the command line. Argument: raw_string (string) - raw command input Returns Deferred - this is an asynchronous Twisted object that will not fire until the command has actually finished executing. To overload this one needs to attach callback functions to it, with addCallback(function). This function will be called with an eventual return value from the command execution. This return is not used at all by Evennia by default, but might be useful for coders intending to implement some sort of nested command structure. """ # nick replacement - we require full-word matching. # do text encoding conversion raw_string = to_unicode(raw_string) raw_list = raw_string.split(None) raw_list = [" ".join(raw_list[:i+1]) for i in range(len(raw_list)) if raw_list[:i+1]] # fetch the nick data efficiently nicks = self.db_attributes.filter(db_category__in=("nick_inputline", "nick_channel")) if self.has_player: pnicks = self.player.db_attributes.filter(db_category__in=("nick_inputline", "nick_channel")) nicks = list(nicks) + list(pnicks) for nick in nicks: if nick.db_key in raw_list: raw_string = raw_string.replace(nick.db_key, nick.db_strvalue, 1) break return cmdhandler.cmdhandler(_GA(self, "typeclass"), raw_string, callertype="object", sessid=sessid) def msg(self, text=None, from_obj=None, sessid=0, **kwargs): """ Emits something to a session attached to the object. message (str): The message to send from_obj (obj): object that is sending. data (object): an optional data object that may or may not be used by the protocol. sessid (int): sessid to relay to, if any. If set to 0 (default), use either from_obj.sessid (if set) or self.sessid automatically If None, echo to all connected sessions """ global _SESSIONS if not _SESSIONS: from src.server.sessionhandler import SESSIONS as _SESSIONS text = utils.to_str(text, force_string=True) if text else "" if "data" in kwargs: # deprecation warning from src.utils import logger logger.log_depmsg("ObjectDB.msg(): 'data'-dict keyword is deprecated. Use **kwargs instead.") data = kwargs.pop("data") if isinstance(data, dict): kwargs.update(data) if from_obj: # call hook try: _GA(from_obj, "at_msg_send")(text=text, to_obj=self, **kwargs) except Exception: pass session = _SESSIONS.session_from_sessid(sessid if sessid else _GA(self, "sessid")) if session: session.msg(text=text, **kwargs) def msg_contents(self, message, exclude=None, from_obj=None, **kwargs): """ Emits something to all objects inside an object. exclude is a list of objects not to send to. See self.msg() for more info. """ contents = _GA(self, "contents") if exclude: exclude = make_iter(exclude) contents = [obj for obj in contents if (obj not in exclude and obj not in exclude)] for obj in contents: obj.msg(message, from_obj=from_obj, **kwargs) def move_to(self, destination, quiet=False, emit_to_obj=None, use_destination=True, to_none=False): """ Moves this object to a new location. Moves this object to a new location. Note that if is an exit object (i.e. it has "destination"!=None), the move_to will happen to this destination and -not- into the exit object itself, unless use_destination=False. Note that no lock checks are done by this function, such things are assumed to have been handled before calling move_to. destination: (Object) Reference to the object to move to. This can also be an exit object, in which case the destination property is used as destination. quiet: (bool) If true, don't emit left/arrived messages. emit_to_obj: (Object) object to receive error messages use_destination (bool): Default is for objects to use the "destination" property of destinations as the target to move to. Turning off this keyword allows objects to move "inside" exit objects. to_none - allow destination to be None. Note that no hooks are run when moving to a None location. If you want to run hooks, run them manually. Returns True/False depending on if there were problems with the move. This method may also return various error messages to the emit_to_obj. """ def logerr(string=""): trc = traceback.format_exc() errstring = "%s%s" % (trc, string) logger.log_trace() _GA(self, "msg")(errstring) errtxt = _("Couldn't perform move ('%s'). Contact an admin.") if not emit_to_obj: emit_to_obj = self if not destination: if to_none: # immediately move to None. There can be no hooks called since # there is no destination to call them with. self.location = None return True emit_to_obj.msg(_("The destination doesn't exist.")) return if destination.destination and use_destination: # traverse exits destination = destination.destination # Before the move, call eventual pre-commands. try: if not self.at_before_move(destination): return except Exception: logerr(errtxt % "at_before_move()") #emit_to_obj.msg(errtxt % "at_before_move()") #logger.log_trace() return False # Save the old location source_location = _GA(self, "location") if not source_location: # there was some error in placing this room. # we have to set one or we won't be able to continue if _GA(self, "home"): source_location = _GA(self, "home") else: default_home = ObjectDB.objects.get_id(settings.CHARACTER_DEFAULT_HOME) source_location = default_home # Call hook on source location try: source_location.at_object_leave(self, destination) except Exception: logerr(errtxt % "at_object_leave()") #emit_to_obj.msg(errtxt % "at_object_leave()") #logger.log_trace() return False if not quiet: #tell the old room we are leaving try: self.announce_move_from(destination) except Exception: logerr(errtxt % "at_announce_move()") #emit_to_obj.msg(errtxt % "at_announce_move()" ) #logger.log_trace() return False # Perform move try: #print "move_to location:", destination _SA(self, "location", destination) except Exception: emit_to_obj.msg(errtxt % "location change") logger.log_trace() return False if not quiet: # Tell the new room we are there. try: self.announce_move_to(source_location) except Exception: logerr(errtxt % "announce_move_to()") #emit_to_obj.msg(errtxt % "announce_move_to()") #logger.log_trace() return False # Perform eventual extra commands on the receiving location # (the object has already arrived at this point) try: destination.at_object_receive(self, source_location) except Exception: logerr(errtxt % "at_object_receive()") #emit_to_obj.msg(errtxt % "at_object_receive()") #logger.log_trace() return False # Execute eventual extra commands on this object after moving it # (usually calling 'look') try: self.at_after_move(source_location) except Exception: logerr(errtxt % "at_after_move") #emit_to_obj.msg(errtxt % "at_after_move()") #logger.log_trace() return False return True # # Object Swap, Delete and Cleanup methods # def clear_exits(self): """ Destroys all of the exits and any exits pointing to this object as a destination. """ for out_exit in [exi for exi in ObjectDB.objects.get_contents(self) if exi.db_destination]: out_exit.delete() for in_exit in ObjectDB.objects.filter(db_destination=self): in_exit.delete() def clear_contents(self): """ Moves all objects (players/things) to their home location or to default home. """ # Gather up everything that thinks this is its location. objs = ObjectDB.objects.filter(db_location=self) default_home_id = int(settings.CHARACTER_DEFAULT_HOME.lstrip("#")) try: default_home = ObjectDB.objects.get(id=default_home_id) if default_home.dbid == _GA(self, "dbid"): # we are deleting default home! default_home = None except Exception: string = _("Could not find default home '(#%d)'.") logger.log_errmsg(string % default_home_id) default_home = None for obj in objs: home = obj.home # Obviously, we can't send it back to here. if not home or (home and home.dbid == _GA(self, "dbid")): obj.home = default_home # If for some reason it's still None... if not obj.home: string = "Missing default home, '%s(#%d)' " string += "now has a null location." obj.location = None obj.msg(_("Something went wrong! You are dumped into nowhere. Contact an admin.")) logger.log_errmsg(string % (obj.name, obj.dbid)) return if obj.has_player: if home: string = "Your current location has ceased to exist," string += " moving you to %s(#%d)." obj.msg(_(string) % (home.name, home.dbid)) else: # Famous last words: The player should never see this. string = "This place should not exist ... contact an admin." obj.msg(_(string)) obj.move_to(home) def copy(self, new_key=None): """ Makes an identical copy of this object. If you want to customize the copy by changing some settings, use ObjectDB.object.copy_object() directly. new_key (string) - new key/name of copied object. If new_key is not specified, the copy will be named _copy by default. Returns: Object (copy of this one) """ def find_clone_key(): """ Append 01, 02 etc to obj.key. Checks next higher number in the same location, then adds the next number available returns the new clone name on the form keyXX """ key = _GA(self, "key") num = 1 for obj in (obj for obj in self.location.contents if obj.key.startswith(key) and obj.key.lstrip(key).isdigit()): num += 1 return "%s%03i" % (key, num) new_key = new_key or find_clone_key() return ObjectDB.objects.copy_object(self, new_key=new_key) delete_iter = 0 def delete(self): """ Deletes this object. Before deletion, this method makes sure to move all contained objects to their respective home locations, as well as clean up all exits to/from the object. """ global _ScriptDB if not _ScriptDB: from src.scripts.models import ScriptDB as _ScriptDB if _GA(self, "delete_iter") > 0: # make sure to only call delete once on this object # (avoid recursive loops) return False if not self.at_object_delete(): # this is an extra pre-check # run before deletion mechanism # is kicked into gear. _SA(self, "delete_iter", 0) return False self.delete_iter += 1 # See if we need to kick the player off. for session in _GA(self, "sessions"): session.msg(_("Your character %s has been destroyed.") % _GA(self, "key")) # no need to disconnect, Player just jumps to OOC mode. # sever the connection (important!) if _GA(self, 'player'): _SA(_GA(self, "player"), "character", None) _SA(self, "player", None) for script in _ScriptDB.objects.get_all_scripts_on_obj(self): script.stop() #for script in _GA(self, "scripts").all(): # script.stop() # if self.player: # self.player.user.is_active = False # self.player.user.save( # Destroy any exits to and from this room, if any _GA(self, "clear_exits")() # Clear out any non-exit objects located within the object _GA(self, "clear_contents")() old_loc = _GA(self, "location") # Perform the deletion of the object super(ObjectDB, self).delete() # clear object's old location's content cache of this object if old_loc: old_loc.contents_update() return True