diff --git a/evennia/accounts/accounts.py b/evennia/accounts/accounts.py index b86b6eb16e..602ccc2a69 100644 --- a/evennia/accounts/accounts.py +++ b/evennia/accounts/accounts.py @@ -631,10 +631,31 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): # this will only be set if the utils.create_account # function was used to create the object. cdict = self._createdict + updates = [] + if not cdict.get("key"): + if not self.db_key: + self.db_key = "#%i" % self.dbid + updates.append("db_key") + elif self.key != cdict.get("key"): + updates.append("db_key") + self.db_key = cdict["key"] + if updates: + self.save(update_fields=updates) + if cdict.get("locks"): self.locks.add(cdict["locks"]) if cdict.get("permissions"): permissions = cdict["permissions"] + if cdict.get("tags"): + # this should be a list of tags, tuples (key, category) or (key, category, data) + self.tags.batch_add(*cdict["tags"]) + if cdict.get("attributes"): + # this should be tuples (key, val, ...) + self.attributes.batch_add(*cdict["attributes"]) + if cdict.get("nattributes"): + # this should be a dict of nattrname:value + for key, value in cdict["nattributes"]: + self.nattributes.add(key, value) del self._createdict self.permissions.batch_add(*permissions) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 037f3ff019..6cb948da03 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -1002,14 +1002,14 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): cdict["location"].at_object_receive(self, None) self.at_after_move(None) if cdict.get("tags"): - # this should be a list of tags + # this should be a list of tags, tuples (key, category) or (key, category, data) self.tags.batch_add(*cdict["tags"]) if cdict.get("attributes"): - # this should be a dict of attrname:value + # this should be tuples (key, val, ...) self.attributes.batch_add(*cdict["attributes"]) if cdict.get("nattributes"): # this should be a dict of nattrname:value - for key, value in cdict["nattributes"].items(): + for key, value in cdict["nattributes"]: self.nattributes.add(key, value) del self._createdict diff --git a/evennia/scripts/scripts.py b/evennia/scripts/scripts.py index db6e9652cb..24e25592fa 100644 --- a/evennia/scripts/scripts.py +++ b/evennia/scripts/scripts.py @@ -513,6 +513,22 @@ class DefaultScript(ScriptBase): updates.append("db_persistent") if updates: self.save(update_fields=updates) + + if cdict.get("permissions"): + self.permissions.batch_add(*cdict["permissions"]) + if cdict.get("locks"): + self.locks.add(cdict["locks"]) + if cdict.get("tags"): + # this should be a list of tags, tuples (key, category) or (key, category, data) + self.tags.batch_add(*cdict["tags"]) + if cdict.get("attributes"): + # this should be tuples (key, val, ...) + self.attributes.batch_add(*cdict["attributes"]) + if cdict.get("nattributes"): + # this should be a dict of nattrname:value + for key, value in cdict["nattributes"]: + self.nattributes.add(key, value) + if not cdict.get("autostart"): # don't auto-start the script return diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index 3f8b4cd742..03ef255093 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -530,8 +530,8 @@ class AttributeHandler(object): repeat-calling add when having many Attributes to add. Args: - indata (tuple): Tuples of varying length representing the - Attribute to add to this object. + indata (list): List of tuples of varying length representing the + Attribute to add to this object. Supported tuples are - `(key, value)` - `(key, value, category)` - `(key, value, category, lockstring)` diff --git a/evennia/utils/create.py b/evennia/utils/create.py index 1404e4caaa..c5fb6f8416 100644 --- a/evennia/utils/create.py +++ b/evennia/utils/create.py @@ -54,7 +54,8 @@ _GA = object.__getattribute__ def create_object(typeclass=None, key=None, location=None, home=None, permissions=None, locks=None, aliases=None, tags=None, - destination=None, report_to=None, nohome=False): + destination=None, report_to=None, nohome=False, attributes=None, + nattributes=None): """ Create a new in-game object. @@ -68,13 +69,18 @@ def create_object(typeclass=None, key=None, location=None, home=None, permissions (list): A list of permission strings or tuples (permstring, category). locks (str): one or more lockstrings, separated by semicolons. aliases (list): A list of alternative keys or tuples (aliasstring, category). - tags (list): List of tag keys or tuples (tagkey, category). + tags (list): List of tag keys or tuples (tagkey, category) or (tagkey, category, data). destination (Object or str): Obj or #dbref to use as an Exit's target. report_to (Object): The object to return error messages to. nohome (bool): This allows the creation of objects without a default home location; only used when creating the default location itself or during unittests. + attributes (list): Tuples on the form (key, value) or (key, value, category), + (key, value, lockstring) or (key, value, lockstring, default_access). + to set as Attributes on the new object. + nattributes (list): Non-persistent tuples on the form (key, value). Note that + adding this rarely makes sense since this data will not survive a reload. Returns: object (Object): A newly created object of the given typeclass. @@ -122,7 +128,8 @@ def create_object(typeclass=None, key=None, location=None, home=None, # store the call signature for the signal new_object._createdict = dict(key=key, location=location, destination=destination, home=home, typeclass=typeclass.path, permissions=permissions, locks=locks, - aliases=aliases, tags=tags, report_to=report_to, nohome=nohome) + aliases=aliases, tags=tags, report_to=report_to, nohome=nohome, + attributes=attributes, nattributes=nattributes) # this will trigger the save signal which in turn calls the # at_first_save hook on the typeclass, where the _createdict can be # used. @@ -139,7 +146,8 @@ object = create_object def create_script(typeclass=None, key=None, obj=None, account=None, locks=None, interval=None, start_delay=None, repeats=None, - persistent=None, autostart=True, report_to=None, desc=None): + persistent=None, autostart=True, report_to=None, desc=None, + tags=None, attributes=None): """ Create a new script. All scripts are a combination of a database object that communicates with the database, and an typeclass that diff --git a/evennia/utils/olc/__init__.py b/evennia/utils/olc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/evennia/utils/olc/olc_storage.py b/evennia/utils/olc/olc_storage.py new file mode 100644 index 0000000000..d64956f8a0 --- /dev/null +++ b/evennia/utils/olc/olc_storage.py @@ -0,0 +1,151 @@ +""" +OLC storage and sharing mechanism. + +This sets up a central storage for prototypes. The idea is to make these +available in a repository for buildiers to use. Each prototype is stored +in a Script so that it can be tagged for quick sorting/finding and locked for limiting +access. + +""" + +from evennia.scripts.scripts import DefaultScript +from evennia.utils.create import create_script +from evennia.utils.utils import make_iter +from evennia.utils.evtable import EvTable + + +class PersistentPrototype(DefaultScript): + """ + This stores a single prototype + """ + key = "persistent_prototype" + desc = "Stores a prototoype" + + +def store_prototype(caller, key, prototype, desc="", tags=None, locks="", delete=False): + """ + Store a prototype persistently. + + Args: + caller (Account or Object): Caller aiming to store prototype. At this point + the caller should have permission to 'add' new prototypes, but to edit + an existing prototype, the 'edit' lock must be passed on that prototype. + key (str): Name of prototype to store. + prototype (dict): Prototype dict. + desc (str, optional): Description of prototype, to use in listing. + tags (list, optional): Tag-strings to apply to prototype. These are always + applied with the 'persistent_prototype' category. + locks (str, optional): Locks to apply to this prototype. Used locks + are 'use' and 'edit' + delete (bool, optional): Delete an existing prototype identified by 'key'. + This requires `caller` to pass the 'edit' lock of the prototype. + Returns: + stored (StoredPrototype or None): The resulting prototype (new or edited), + or None if deleting. + Raises: + PermissionError: If edit lock was not passed by caller. + + """ + key = key.lower() + locks = locks if locks else "use:all();edit:id({}) or edit:perm(Admin)".format(caller.id) + tags = [(tag, "persistent_prototype") for tag in make_iter(tags)] + + stored_prototype = PersistentPrototype.objects.filter(db_key=key) + + if stored_prototype: + stored_prototype = stored_prototype[0] + if not stored_prototype.access(caller, 'edit'): + PermissionError("{} does not have permission to edit prototype {}".format(caller, key)) + + if delete: + stored_prototype.delete() + return + + if desc: + stored_prototype.desc = desc + if tags: + stored_prototype.tags.add(tags) + if locks: + stored_prototype.locks.add(locks) + if prototype: + stored_prototype.attributes.add("prototype", prototype) + else: + stored_prototype = create_script( + PersistentPrototype, key=key, desc=desc, persistent=True, + locks=locks, tags=tags, attributes=[("prototype", prototype)]) + return stored_prototype + + +def search_prototype(key=None, tags=None): + """ + Find prototypes based on key and/or tags. + + Kwargs: + key (str): An exact or partial key to query for. + tags (str or list): Tag key or keys to query for. These + will always be applied with the 'persistent_protototype' + tag category. + Return: + matches (queryset): All found PersistentPrototypes. This will + be all prototypes if no arguments are given. + + Note: + This will use the tags to make a subselection before attempting + to match on the key. So if key/tags don't match up nothing will + be found. + + """ + matches = PersistentPrototype.objects.all() + if tags: + # exact match on tag(s) + tags = make_iter(tags) + tag_categories = ("persistent_prototype" for _ in tags) + matches = matches.get_by_tag(tags, tag_categories) + if key: + # partial match on key + matches = matches.filter(db_key=key) or matches.filter(db_key__icontains=key) + return matches + + +def get_prototype_list(caller, key=None, tags=None, show_non_use=False, show_non_edit=True): + """ + Collate a list of found prototypes based on search criteria and access. + + Args: + caller (Account or Object): The object requesting the list. + key (str, optional): Exact or partial key to query for. + tags (str or list, optional): Tag key or keys to query for. + show_non_use (bool, optional): Show also prototypes the caller may not use. + show_non_edit (bool, optional): Show also prototypes the caller may not edit. + Returns: + table (EvTable or None): An EvTable representation of the prototypes. None + if no prototypes were found. + + """ + prototypes = search_prototype(key, tags) + + if not prototypes: + return None + + # gather access permissions as (key, desc, can_use, can_edit) + prototypes = [(prototype.key, prototype.desc, + prototype.access(caller, "use"), prototype.access(caller, "edit")) + for prototype in prototypes] + + if not show_non_use: + prototypes = [tup for tup in prototypes if tup[2]] + if not show_non_edit: + prototypes = [tup for tup in prototypes if tup[3]] + + if not prototypes: + return None + + table = [] + for i in range(len(prototypes[0])): + table.append([tup[i] for tup in prototypes]) + table = EvTable("Key", "Desc", "Use", "Edit", table, crop=True, width=78) + table.reformat_column(0, width=28) + table.reformat_column(1, width=40) + table.reformat_column(2, width=5) + table.reformat_column(3, width=5) + return table