diff --git a/src/commands/cmdhandler.py b/src/commands/cmdhandler.py index 33146def18..fd2d3df803 100644 --- a/src/commands/cmdhandler.py +++ b/src/commands/cmdhandler.py @@ -115,8 +115,8 @@ def get_and_merge_cmdsets(caller): 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 - local_objlist = location.contents + caller.contents + # 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 if obj.locks.check(caller, 'call', no_superuser_bypass=True)] diff --git a/src/commands/cmdsethandler.py b/src/commands/cmdsethandler.py index 7c2cf1d840..ca75ae991f 100644 --- a/src/commands/cmdsethandler.py +++ b/src/commands/cmdsethandler.py @@ -272,7 +272,7 @@ class CmdSetHandler(object): self.permanent_paths.append("") self.update() - def add_default(self, cmdset, emit_to_obj=None, permanent=False): + def add_default(self, cmdset, emit_to_obj=None, permanent=True): """ Add a new default cmdset. If an old default existed, it is replaced. If permanent is set, a script will be created to diff --git a/src/commands/default/building.py b/src/commands/default/building.py index 1a8680e402..e55fd1e988 100644 --- a/src/commands/default/building.py +++ b/src/commands/default/building.py @@ -559,12 +559,19 @@ class CmdDestroy(MuxCommand): if obj.player and not 'override' in self.switches: string += "\nObject %s is controlled by an active player. Use /override to delete anyway." % objname continue + had_exits = hasattr(obj, "exits") and obj.exits + had_objs = hasattr(obj, "contents") and any(obj for obj in obj.contents + if not (hasattr(obj, "exits") and obj not in obj.exits)) # do the deletion okay = obj.delete() if not okay: string += "\nERROR: %s not deleted, probably because at_obj_delete() returned False." % objname else: - string += "\n%s was deleted." % objname + string += "\n%s was destroyed." % objname + if had_exits: + string += " Exits to and from %s were destroyed as well." % objname + if had_objs: + string += " Objects inside %s were moved to their homes." % objname if string: caller.msg(string.strip()) diff --git a/src/commands/default/general.py b/src/commands/default/general.py index 534311aeee..218f1508c3 100644 --- a/src/commands/default/general.py +++ b/src/commands/default/general.py @@ -71,6 +71,9 @@ class CmdLook(MuxCommand): if not hasattr(looking_at_obj, 'return_appearance'): # this is likely due to us having a player instead looking_at_obj = looking_at_obj.character + if not looking_at_obj.access(caller, "view"): + caller.msg("Could not find '%s'." % args) + return # get object's appearance caller.msg(looking_at_obj.return_appearance(caller)) # the object's at_desc() method. diff --git a/src/commands/default/system.py b/src/commands/default/system.py index efaef69bc4..6f72a11642 100644 --- a/src/commands/default/system.py +++ b/src/commands/default/system.py @@ -197,10 +197,8 @@ class CmdScripts(MuxCommand): scripts = ScriptDB.objects.get_all_scripts(key=args) if not scripts: # try to find an object instead. - objects = ObjectDB.objects.object_search(caller, - args, - global_search=True) - if objects: + objects = ObjectDB.objects.object_search(args, caller=caller, global_search=True) + if objects: scripts = [] for obj in objects: # get all scripts on the object(s) diff --git a/src/locks/lockfuncs.py b/src/locks/lockfuncs.py index 8660613baf..0de09f7239 100644 --- a/src/locks/lockfuncs.py +++ b/src/locks/lockfuncs.py @@ -226,9 +226,9 @@ def pid(accessing_obj, accessed_obj, *args, **kwargs): def attr(accessing_obj, accessed_obj, *args, **kwargs): """ Usage: - has_attr(attrname) - has_attr(attrname, value) - has_attr(attrname, value, compare=type) + attr(attrname) + attr(attrname, value) + attr(attrname, value, compare=type) where compare's type is one of (eq,gt,lt,ge,le,ne) and signifies how the value should be compared with one on accessing_obj (so @@ -288,6 +288,37 @@ def attr(accessing_obj, accessed_obj, *args, **kwargs): return True return False +def objattr(accessing_obj, accessed_obj, *args, **kwargs): + """ + Usage: + objattr(attrname) + objattr(attrname, value) + objattr(attrname, value, compare=type) + + Works like attr, except it looks for an attribute on + accessing_obj.obj, if such an entity exists. Suitable + for commands. + + """ + if hasattr(accessing_obj, "obj"): + return attr(accessing_obj.obj, accessed_obj, *args, **kwargs) + +def locattr(accessing_obj, accessed_obj, *args, **kwargs): + """ + Usage: + locattr(attrname) + locattr(attrname, value) + locattr(attrname, value, compare=type) + + Works like attr, except it looks for an attribute on + accessing_obj.location, if such an entity exists. Suitable + for commands. + + """ + if hasattr(accessing_obj, "location"): + return attr(accessing_obj.location, accessed_obj, *args, **kwargs) + + def attr_eq(accessing_obj, accessed_obj, *args, **kwargs): """ Usage: @@ -351,6 +382,28 @@ def holds(accessing_obj, accessed_obj, objid, *args, **kwargs): objid = objid.lower() return any((True for obj in contains if obj.name.lower() == objid)) +def carried(accessing_obj, accessed_obj): + """ + Usage: + carried() + + This is passed if accessed_obj is carried by accessing_obj (that is, + accessed_obj.location == accessing_obj) + """ + return hasattr(accessed_obj, "location") and accessed_obj.location == accessing_obj + +def objcarried(accessing_obj, accessed_obj): + """ + Usage: + objcarried() + + Like carried, except this lock looks for a property "obj" on the accessed_obj + and tries to determing if *this* is carried by accessing_obj. This works well + for commands and scripts. + """ + return hasattr(accessed_obj, "obj") and accessed_obj.obj and \ + hasattr(accessed_obj.obj, "location") and accessed_obj.obj.location == accessing_obj + def superuser(*args, **kwargs): """ Only accepts an accesing_obj that is superuser (e.g. user #1) diff --git a/src/objects/exithandler.py b/src/objects/exithandler.py index 3ad2cf6603..6b80bc3c77 100644 --- a/src/objects/exithandler.py +++ b/src/objects/exithandler.py @@ -11,15 +11,30 @@ class ExitCommand(command.Command): is_exit = True locks = "cmd:all()" # should always be set to this. destination = None - obj = None + obj = None def func(self): "Default exit traverse if no syscommand is defined." if self.obj.access(self.caller, 'traverse'): - self.caller.move_to(self.destination) + # 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: - self.caller.msg("You cannot enter.") + 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): """ diff --git a/src/objects/manager.py b/src/objects/manager.py index e418cf518e..80a99b1052 100644 --- a/src/objects/manager.py +++ b/src/objects/manager.py @@ -165,18 +165,18 @@ class ObjectManager(TypedObjectManager): return eval("self.filter(db_location__id=location.id)%s" % estring) @returns_typeclass_list - def object_search(self, character, ostring, + def object_search(self, ostring, caller=None, global_search=False, attribute_name=None, location=None): """ Search as an object and return results. The result is always an Object. If * is appended (player search, a Character controlled by this Player is looked for. The Character is returned, not the Player. Use player_search - to find Player objects. + to find Player objects. Always returns a list. - character: (Object) The object performing the search. ostring: (string) The string to compare names against. Can be a dbref. If name is appended by *, a player is searched for. + caller: (Object) The object performing the search. global_search: Search all objects, not just the current location/inventory attribute_name: (string) Which attribute to search in each object. If None, the default 'key' attribute is used. @@ -184,11 +184,8 @@ class ObjectManager(TypedObjectManager): """ #ostring = str(ostring).strip() - if not ostring or not character: - return None - - if not location and hasattr(character, "location"): - location = character.location + if not ostring: + return [] # Easiest case - dbref matching (always exact) dbref = self.dbref(ostring) @@ -197,14 +194,17 @@ class ObjectManager(TypedObjectManager): if dbref_match: return [dbref_match] + if not location and caller and hasattr(caller, "location"): + location = caller.location + # Test some common self-references if location and ostring == 'here': return [location] - if character and ostring in ['me', 'self']: - return [character] - if character and ostring in ['*me', '*self']: - return [character] + if caller and ostring in ['me', 'self']: + return [caller] + if caller and ostring in ['*me', '*self']: + return [caller] # Test if we are looking for an object controlled by a # specific player @@ -224,8 +224,10 @@ class ObjectManager(TypedObjectManager): or ostring.lower() in [alias.lower() for alias in location.aliases]): return [location] # otherwise, setup the locations to search in - search_locations = [character, location] - + search_locations = [location] + if caller: + search_locations.append(caller) + def local_and_global_search(ostring, exact=False): "Helper method for searching objects" matches = [] diff --git a/src/objects/migrations/0006_add_view_lock.py b/src/objects/migrations/0006_add_view_lock.py new file mode 100644 index 0000000000..a3ba1a6764 --- /dev/null +++ b/src/objects/migrations/0006_add_view_lock.py @@ -0,0 +1,119 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + "Write your forwards methods here." + + lockstring1 = 'control:id(1);get:all();edit:perm(Wizards);view:all();examine:perm(Builders);call:true();puppet:id(#4) or perm(Immortals) or pperm(Immortals);delete:id(1) or perm(Wizards)' + lockstring2 = 'control:id(#3) or perm(Immortals);get:perm(Wizards);edit:perm(Wizards);view:all();examine:perm(Builders);call:false();puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals);delete:perm(Wizards)' + + try: + for obj in orm.ObjectDB.objects.all().exclude(db_player__isnull=False): + obj.db_lock_storage = lockstring1 + obj.save() + for obj in orm.ObjectDB.objects.filter(db_player__isnull=False): + obj.db_lock_storage = lockstring2 % (obj.id, obj.db_player.id) + obj.save() + + except utils.DatabaseError: + # running from scatch. In this case we just ignore this. + pass + + def backwards(self, orm): + "Write your backwards methods here." + raise RuntimeError("You cannot reverse this migration.") + + 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.alias': { + 'Meta': {'object_name': 'Alias'}, + 'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['objects.ObjectDB']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'objects.objattribute': { + 'Meta': {'object_name': 'ObjAttribute'}, + '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']"}), + 'db_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + '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'}) + }, + 'objects.objectnick': { + 'Meta': {'unique_together': "(('db_nick', 'db_type', 'db_obj'),)", 'object_name': 'ObjectNick'}, + 'db_nick': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['objects.ObjectDB']"}), + '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'}) + }, + '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'}) + } + } + + complete_apps = ['objects'] diff --git a/src/objects/models.py b/src/objects/models.py index bcaac75789..1dfabb2ce1 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -527,7 +527,7 @@ class ObjectDB(TypedObject): else: results = PlayerDB.objects.player_search(ostring.lstrip('*')) else: - results = ObjectDB.objects.object_search(self, ostring, + results = ObjectDB.objects.object_search(ostring, caller=self, global_search=global_search, attribute_name=attribute_name, location=location) @@ -658,8 +658,8 @@ class ObjectDB(TypedObject): except Exception: emit_to_obj.msg(errtxt % "at_announce_move()" ) logger.log_trace() - return False - + return False + # Perform move try: self.location = destination diff --git a/src/objects/objects.py b/src/objects/objects.py index f5fe331e19..a2585ecc9b 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -62,6 +62,7 @@ class Object(TypeClass): self.locks.add("control:id(%s) or perm(Immortals)" % dbref) # edit locks/permissions, delete self.locks.add("examine:perm(Builders)") # examine properties + self.locks.add("view:all()") # look at object (visibility) self.locks.add("edit:perm(Wizards)") # edit properties/attributes self.locks.add("delete:perm(Wizards)") # delete object self.locks.add("get:all()") # pick up object @@ -185,7 +186,33 @@ class Object(TypeClass): source_location - where moved_object came from. """ pass + + + def at_before_traverse(self, traversing_object): + """ + Called just before an object uses this object to + traverse to another object (i.e. this object is a type of Exit) + The target location should normally be available as self.destination. + """ + pass + + def at_after_traverse(self, traversing_object, source_location): + """ + Called just after an object successfully used this object to + traverse to another object (i.e. this object is a type of Exit) + + The target location should normally be available as self.destination. + """ + 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. + """ + pass # hooks called by the default cmdset. @@ -414,3 +441,12 @@ class Exit(Object): """ EXITHANDLER.clear(self.dbobj) return True + + 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. + """ + traversing_object.msg("You cannot enter %s." % self.key) + diff --git a/src/players/models.py b/src/players/models.py index a5ca57d6f3..6177c471af 100644 --- a/src/players/models.py +++ b/src/players/models.py @@ -377,7 +377,7 @@ class PlayerDB(TypedObject): else: # more limited player-only search. Still returns an Object. ObjectDB = ContentType.objects.get(app_label="objects", model="objectdb").model_class() - matches = ObjectDB.objects.object_search(self, ostring, global_search=global_search) + matches = ObjectDB.objects.object_search(ostring, caller=self, global_search=global_search) # deal with results matches = AT_SEARCH_RESULT(self, ostring, matches, global_search=global_search) return matches diff --git a/src/utils/search.py b/src/utils/search.py index e53ea08427..ded9bb47bd 100644 --- a/src/utils/search.py +++ b/src/utils/search.py @@ -44,15 +44,15 @@ HelpEntry = ContentType.objects.get(app_label="help", model="helpentry").model_c # is reachable from within each command class # by using self.caller.search()! # -# def object_search(self, character, ostring, +# def object_search(self, ostring, caller=None, # global_search=False, # attribute_name=None): # """ # Search as an object and return results. # -# character: (Object) The object performing the search. # ostring: (string) The string to compare names against. # Can be a dbref. If name is appended by *, a player is searched for. +# caller: (Object) The object performing the search. # global_search: Search all objects, not just the current location/inventory # attribute_name: (string) Which attribute to search in each object. # If None, the default 'name' attribute is used.