mirror of
https://github.com/evennia/evennia.git
synced 2026-04-05 07:27:17 +02:00
267 lines
9.7 KiB
Python
267 lines
9.7 KiB
Python
"""
|
|
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.
|
|
|
|
This system also takes into consideration prototypes defined and stored in modules.
|
|
Such prototypes are considered 'read-only' to the system and can only be modified
|
|
in code. To replace a default prototype, add the same-name prototype in a
|
|
custom module read later in the settings.PROTOTYPE_MODULES list. To remove a default
|
|
prototype, override its name with an empty dict.
|
|
|
|
"""
|
|
|
|
from django.conf import settings
|
|
from evennia.scripts.scripts import DefaultScript
|
|
from evennia.utils.create import create_script
|
|
from evennia.utils.utils import make_iter, all_from_module
|
|
from evennia.utils.evtable import EvTable
|
|
|
|
# prepare the available prototypes defined in modules
|
|
|
|
_READONLY_PROTOTYPES = {}
|
|
_READONLY_PROTOTYPE_MODULES = {}
|
|
|
|
for mod in settings.PROTOTYPE_MODULES:
|
|
# to remove a default prototype, override it with an empty dict.
|
|
# internally we store as (key, desc, locks, tags, prototype_dict)
|
|
prots = [(key, prot) for key, prot in all_from_module(mod).items()
|
|
if prot and isinstance(prot, dict)]
|
|
_READONLY_PROTOTYPES.update(
|
|
{key.lower():
|
|
(key.lower(),
|
|
prot['prototype_desc'] if 'prototype_desc' in prot else mod,
|
|
prot['prototype_lock'] if 'prototype_lock' in prot else "use:all()",
|
|
set(make_iter(
|
|
prot['prototype_tags']) if 'prototype_tags' in prot else ["base-prototype"]),
|
|
prot)
|
|
for key, prot in prots})
|
|
_READONLY_PROTOTYPE_MODULES.update({tup[0]: mod for tup in prots})
|
|
|
|
|
|
class PersistentPrototype(DefaultScript):
|
|
"""
|
|
This stores a single prototype
|
|
"""
|
|
def at_script_creation(self):
|
|
self.key = "empty prototype"
|
|
self.desc = "A prototype"
|
|
|
|
|
|
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_orig = key
|
|
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)]
|
|
|
|
if key in _READONLY_PROTOTYPES:
|
|
mod = _READONLY_PROTOTYPE_MODULES.get(key, "N/A")
|
|
raise PermissionError("{} is a read-only prototype "
|
|
"(defined as code in {}).".format(key_orig, mod))
|
|
|
|
stored_prototype = PersistentPrototype.objects.filter(db_key=key)
|
|
|
|
if stored_prototype:
|
|
stored_prototype = stored_prototype[0]
|
|
if not stored_prototype.access(caller, 'edit'):
|
|
raise 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.batch_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_persistent_prototype(key=None, tags=None):
|
|
"""
|
|
Find persistent (database-stored) 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
|
|
|
|
Note:
|
|
This will not include read-only prototypes defined in modules.
|
|
|
|
"""
|
|
if tags:
|
|
# exact match on tag(s)
|
|
tags = make_iter(tags)
|
|
tag_categories = ["persistent_prototype" for _ in tags]
|
|
matches = PersistentPrototype.objects.get_by_tag(tags, tag_categories)
|
|
else:
|
|
matches = PersistentPrototype.objects.all()
|
|
if key:
|
|
# partial match on key
|
|
matches = matches.filter(db_key=key) or matches.filter(db_key__icontains=key)
|
|
return matches
|
|
|
|
|
|
def search_readonly_prototype(key=None, tags=None):
|
|
"""
|
|
Find read-only prototypes, defined in modules.
|
|
|
|
Kwargs:
|
|
key (str): An exact or partial key to query for.
|
|
tags (str or list): Tag key to query for.
|
|
|
|
Return:
|
|
matches (list): List of prototype tuples that includes
|
|
prototype metadata, on the form
|
|
`(key, desc, lockstring, taglist, prototypedict)`
|
|
|
|
"""
|
|
matches = []
|
|
if tags:
|
|
# use tags to limit selection
|
|
tagset = set(tags)
|
|
matches = {key: tup for key, tup in _READONLY_PROTOTYPES.items()
|
|
if tagset.intersection(tup[3])}
|
|
else:
|
|
matches = _READONLY_PROTOTYPES
|
|
|
|
if key:
|
|
if key in matches:
|
|
# exact match
|
|
return matches[key]
|
|
else:
|
|
# fuzzy matching
|
|
return [tup for pkey, tup in matches.items() if key in pkey]
|
|
return matches
|
|
|
|
|
|
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 (list): All found prototype dicts.
|
|
|
|
Note:
|
|
The available prototypes is a combination of those supplied in
|
|
PROTOTYPE_MODULES and those stored from in-game. For the latter,
|
|
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 = []
|
|
if key and key in _READONLY_PROTOTYPES:
|
|
matches.append(_READONLY_PROTOTYPES[key][3])
|
|
else:
|
|
matches.extend([prot.attributes.get("prototype")
|
|
for prot in search_persistent_prototype(key, tags)])
|
|
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.
|
|
|
|
"""
|
|
# handle read-only prototypes separately
|
|
if key and key in _READONLY_PROTOTYPES:
|
|
readonly_prototypes = _READONLY_PROTOTYPES[key]
|
|
else:
|
|
readonly_prototypes = _READONLY_PROTOTYPES.values()
|
|
|
|
# get use-permissions of readonly attributes (edit is always False)
|
|
readonly_prototypes = [
|
|
(tup[0],
|
|
tup[1],
|
|
("{}/N".format('Y'
|
|
if caller.locks.check_lockstring(caller, tup[2], access_type='use') else 'N')),
|
|
",".join(tup[3])) for tup in readonly_prototypes]
|
|
|
|
# next, handle db-stored prototypes
|
|
prototypes = search_persistent_prototype(key, tags)
|
|
|
|
if not prototypes:
|
|
return None
|
|
|
|
# gather access permissions as (key, desc, tags, can_use, can_edit)
|
|
prototypes = [(prototype.key, prototype.desc,
|
|
"{}/{}".format('Y' if prototype.access(caller, "use") else 'N',
|
|
'Y' if prototype.access(caller, "edit") else 'N'),
|
|
",".join(prototype.tags.get(category="persistent_prototype")))
|
|
for prototype in prototypes]
|
|
|
|
prototypes = prototypes + readonly_prototypes
|
|
|
|
if not show_non_use:
|
|
prototypes = [tup for tup in sorted(prototypes, key=lambda o: o[0]) if tup[2]]
|
|
if not show_non_edit:
|
|
prototypes = [tup for tup in sorted(prototypes, key=lambda o: o[0]) if tup[3]]
|
|
|
|
if not prototypes:
|
|
return None
|
|
|
|
table = []
|
|
for i in range(len(prototypes[0])):
|
|
table.append([str(tup[i]) for tup in prototypes])
|
|
table = EvTable("Key", "Desc", "Use/Edit", "Tags", table=table, crop=True, width=78)
|
|
table.reformat_column(0, width=28)
|
|
table.reformat_column(1, width=40)
|
|
table.reformat_column(2, width=11, align='r')
|
|
table.reformat_column(3, width=20)
|
|
return table
|