From 27809694d71676a9daee939e688eddd1647ee618 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 24 Apr 2011 11:26:51 +0000 Subject: [PATCH] Migrate. Added the "view" access restriction (to make objects invisible). Also changed the input of ObjectDB.objects.object_search() to not require a caller as an argument (this makes it consistent with other search methods). All default systems should have updated to the new call, but if you have custom calls, you need to change them to fit the new syntax (this is only important if explicitly use ObjectDB.objects.object_search; if you just use caller.search you should be fine) --- src/commands/cmdhandler.py | 4 +- src/commands/cmdsethandler.py | 2 +- src/commands/default/building.py | 9 +- src/commands/default/general.py | 3 + src/commands/default/system.py | 6 +- src/locks/lockfuncs.py | 59 ++++++++- src/objects/exithandler.py | 21 +++- src/objects/manager.py | 30 ++--- src/objects/migrations/0006_add_view_lock.py | 119 +++++++++++++++++++ src/objects/models.py | 6 +- src/objects/objects.py | 36 ++++++ src/players/models.py | 2 +- src/utils/search.py | 4 +- 13 files changed, 267 insertions(+), 34 deletions(-) create mode 100644 src/objects/migrations/0006_add_view_lock.py 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.