Add functionality for object-update menu node, untested
This commit is contained in:
parent
c004c6678b
commit
ec9813c256
5 changed files with 265 additions and 64 deletions
40
CHANGELOG.md
40
CHANGELOG.md
|
|
@ -1,7 +1,29 @@
|
||||||
# Evennia Changelog
|
# Changelog
|
||||||
|
|
||||||
# Sept 2017:
|
## Evennia 0.8 (2018)
|
||||||
Release of Evennia 0.7; upgrade to Django 1.11, change 'Player' to
|
|
||||||
|
### Prototype changes
|
||||||
|
|
||||||
|
- A new form of prototype - database-stored prototypes, editable from in-game. The old,
|
||||||
|
module-created prototypes remain as read-only prototypes.
|
||||||
|
- All prototypes must have a key `prototype_key` identifying the prototype in listings. This is
|
||||||
|
checked to be server-unique. Prototypes created in a module will use the global variable name they
|
||||||
|
are assigned to if no `prototype_key` is given.
|
||||||
|
- Prototype field `prototype` was renamed to `prototype_parent` to avoid mixing terms.
|
||||||
|
- All prototypes must either have `typeclass` or `prototype_parent` defined. If using
|
||||||
|
`prototype_parent`, `typeclass` must be defined somewhere in the inheritance chain. This is a
|
||||||
|
change from Evennia 0.7 which allowed 'mixin' prototypes without `typeclass`/`prototype_key`. To
|
||||||
|
make a mixin now, give it a default typeclass, like `evennia.objects.objects.DefaultObject` and just
|
||||||
|
override in the child as needed.
|
||||||
|
- The spawn command was extended to accept a full prototype on one line.
|
||||||
|
- The spawn command got the /save switch to save the defined prototype and its key.
|
||||||
|
- The command spawn/menu will now start an OLC (OnLine Creation) menu to load/save/edit/spawn prototypes.
|
||||||
|
|
||||||
|
|
||||||
|
# Overviews
|
||||||
|
|
||||||
|
## Sept 2017:
|
||||||
|
Release of Evennia 0.7; upgrade to Django 1.11, change 'Player' to
|
||||||
'Account', rework the website template and a slew of other updates.
|
'Account', rework the website template and a slew of other updates.
|
||||||
Info on what changed and how to migrate is found here:
|
Info on what changed and how to migrate is found here:
|
||||||
https://groups.google.com/forum/#!msg/evennia/0JYYNGY-NfE/cDFaIwmPBAAJ
|
https://groups.google.com/forum/#!msg/evennia/0JYYNGY-NfE/cDFaIwmPBAAJ
|
||||||
|
|
@ -14,9 +36,9 @@ Lots of bugfixes and considerable uptick in contributors. Unittest coverage
|
||||||
and PEP8 adoption and refactoring.
|
and PEP8 adoption and refactoring.
|
||||||
|
|
||||||
## May 2016:
|
## May 2016:
|
||||||
Evennia 0.6 with completely reworked Out-of-band system, making
|
Evennia 0.6 with completely reworked Out-of-band system, making
|
||||||
the message path completely flexible and built around input/outputfuncs.
|
the message path completely flexible and built around input/outputfuncs.
|
||||||
A completely new webclient, split into the evennia.js library and a
|
A completely new webclient, split into the evennia.js library and a
|
||||||
gui library, making it easier to customize.
|
gui library, making it easier to customize.
|
||||||
|
|
||||||
## Feb 2016:
|
## Feb 2016:
|
||||||
|
|
@ -33,15 +55,15 @@ library format with a stand-alone launcher, in preparation for making
|
||||||
an 'evennia' pypy package and using versioning. The version we will
|
an 'evennia' pypy package and using versioning. The version we will
|
||||||
merge with will likely be 0.5. There is also work with an expanded
|
merge with will likely be 0.5. There is also work with an expanded
|
||||||
testing structure and the use of threading for saves. We also now
|
testing structure and the use of threading for saves. We also now
|
||||||
use Travis for automatic build checking.
|
use Travis for automatic build checking.
|
||||||
|
|
||||||
## Sept 2014:
|
## Sept 2014:
|
||||||
Updated to Django 1.7+ which means South dependency was dropped and
|
Updated to Django 1.7+ which means South dependency was dropped and
|
||||||
minimum Python version upped to 2.7. MULTISESSION_MODE=3 was added
|
minimum Python version upped to 2.7. MULTISESSION_MODE=3 was added
|
||||||
and the web customization system was overhauled using the latest
|
and the web customization system was overhauled using the latest
|
||||||
functionality of django. Otherwise, mostly bug-fixes and
|
functionality of django. Otherwise, mostly bug-fixes and
|
||||||
implementation of various smaller feature requests as we got used
|
implementation of various smaller feature requests as we got used
|
||||||
to github. Many new users have appeared.
|
to github. Many new users have appeared.
|
||||||
|
|
||||||
## Jan 2014:
|
## Jan 2014:
|
||||||
Moved Evennia project from Google Code to github.com/evennia/evennia.
|
Moved Evennia project from Google Code to github.com/evennia/evennia.
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ OLC Prototype menu nodes
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
from random import choice
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia.utils.evmenu import EvMenu, list_node
|
from evennia.utils.evmenu import EvMenu, list_node
|
||||||
from evennia.utils.ansi import strip_ansi
|
from evennia.utils.ansi import strip_ansi
|
||||||
|
|
@ -469,7 +470,7 @@ def _caller_attrs(caller):
|
||||||
|
|
||||||
def _display_attribute(attr_tuple):
|
def _display_attribute(attr_tuple):
|
||||||
"""Pretty-print attribute tuple"""
|
"""Pretty-print attribute tuple"""
|
||||||
attrkey, value, category, locks, default_access = attr_tuple
|
attrkey, value, category, locks = attr_tuple
|
||||||
value = protlib.protfunc_parser(value)
|
value = protlib.protfunc_parser(value)
|
||||||
typ = type(value)
|
typ = type(value)
|
||||||
out = ("Attribute key: '{attrkey}' (category: {category}, "
|
out = ("Attribute key: '{attrkey}' (category: {category}, "
|
||||||
|
|
@ -503,7 +504,7 @@ def _add_attr(caller, attr_string, **kwargs):
|
||||||
attrname, category = nameparts
|
attrname, category = nameparts
|
||||||
elif nparts > 2:
|
elif nparts > 2:
|
||||||
attrname, category, locks = nameparts
|
attrname, category, locks = nameparts
|
||||||
attr_tuple = (attrname, category, locks)
|
attr_tuple = (attrname, value, category, locks)
|
||||||
|
|
||||||
if attrname:
|
if attrname:
|
||||||
prot = _get_menu_prototype(caller)
|
prot = _get_menu_prototype(caller)
|
||||||
|
|
@ -513,7 +514,7 @@ def _add_attr(caller, attr_string, **kwargs):
|
||||||
# replace existing attribute with the same name in the prototype
|
# replace existing attribute with the same name in the prototype
|
||||||
ind = [tup[0] for tup in attrs].index(attrname)
|
ind = [tup[0] for tup in attrs].index(attrname)
|
||||||
attrs[ind] = attr_tuple
|
attrs[ind] = attr_tuple
|
||||||
except IndexError:
|
except ValueError:
|
||||||
attrs.append(attr_tuple)
|
attrs.append(attr_tuple)
|
||||||
|
|
||||||
_set_prototype_value(caller, "attrs", attrs)
|
_set_prototype_value(caller, "attrs", attrs)
|
||||||
|
|
@ -541,7 +542,8 @@ def _edit_attr(caller, attrname, new_value, **kwargs):
|
||||||
|
|
||||||
def _examine_attr(caller, selection):
|
def _examine_attr(caller, selection):
|
||||||
prot = _get_menu_prototype(caller)
|
prot = _get_menu_prototype(caller)
|
||||||
attr_tuple = prot['attrs'][selection]
|
ind = [part[0] for part in prot['attrs']].index(selection)
|
||||||
|
attr_tuple = prot['attrs'][ind]
|
||||||
return _display_attribute(attr_tuple)
|
return _display_attribute(attr_tuple)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -572,15 +574,15 @@ def node_attrs(caller):
|
||||||
|
|
||||||
def _caller_tags(caller):
|
def _caller_tags(caller):
|
||||||
prototype = _get_menu_prototype(caller)
|
prototype = _get_menu_prototype(caller)
|
||||||
tags = prototype.get("tags")
|
tags = prototype.get("tags", [])
|
||||||
return tags
|
return tags
|
||||||
|
|
||||||
|
|
||||||
def _display_tag(tag_tuple):
|
def _display_tag(tag_tuple):
|
||||||
"""Pretty-print attribute tuple"""
|
"""Pretty-print attribute tuple"""
|
||||||
tagkey, category, data = tag_tuple
|
tagkey, category, data = tag_tuple
|
||||||
out = ("Tag: '{tagkey}' (category: {category}{})".format(
|
out = ("Tag: '{tagkey}' (category: {category}{dat})".format(
|
||||||
tagkey=tagkey, category=category, data=", data: {}".format(data) if data else ""))
|
tagkey=tagkey, category=category, dat=", data: {}".format(data) if data else ""))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -613,16 +615,21 @@ def _add_tag(caller, tag, **kwargs):
|
||||||
|
|
||||||
old_tag = kwargs.get("edit", None)
|
old_tag = kwargs.get("edit", None)
|
||||||
|
|
||||||
if old_tag:
|
if not old_tag:
|
||||||
# editing a tag means removing the old and replacing with new
|
# a fresh, new tag
|
||||||
|
tags.append(tag_tuple)
|
||||||
|
else:
|
||||||
|
# old tag exists; editing a tag means removing the old and replacing with new
|
||||||
try:
|
try:
|
||||||
ind = [tup[0] for tup in tags].index(old_tag)
|
ind = [tup[0] for tup in tags].index(old_tag)
|
||||||
del tags[ind]
|
del tags[ind]
|
||||||
|
if tags:
|
||||||
|
tags.insert(ind, tag_tuple)
|
||||||
|
else:
|
||||||
|
tags = [tag_tuple]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
tags.append(tag_tuple)
|
|
||||||
|
|
||||||
_set_prototype_value(caller, "tags", tags)
|
_set_prototype_value(caller, "tags", tags)
|
||||||
|
|
||||||
text = kwargs.get('text')
|
text = kwargs.get('text')
|
||||||
|
|
@ -814,18 +821,121 @@ def node_prototype_locks(caller):
|
||||||
return text, options
|
return text, options
|
||||||
|
|
||||||
|
|
||||||
|
def _update_spawned(caller, **kwargs):
|
||||||
|
"""update existing objects"""
|
||||||
|
prototype = kwargs['prototype']
|
||||||
|
objects = kwargs['objects']
|
||||||
|
back_node = kwargs['back_key']
|
||||||
|
num_changed = spawner.batch_update_objects_with_prototype(prototype, objects=objects)
|
||||||
|
caller.msg("|g{num} objects were updated successfully.|n".format(num=num_changed))
|
||||||
|
return back_key
|
||||||
|
|
||||||
|
|
||||||
|
def _keep_diff(caller, **kwargs):
|
||||||
|
key = kwargs['key']
|
||||||
|
diff = kwargs['diff']
|
||||||
|
diff[key] = "KEEP"
|
||||||
|
|
||||||
|
|
||||||
|
def node_update_objects(caller, **kwargs):
|
||||||
|
"""Offer options for updating objects"""
|
||||||
|
|
||||||
|
def _keep_option(keyname, prototype, obj, obj_prototype, diff, objects, back_node):
|
||||||
|
"""helper returning an option dict"""
|
||||||
|
options = {"desc": "Keep {} as-is".format(keyname),
|
||||||
|
"goto": (_keep_diff,
|
||||||
|
{"key": keyname, "prototype": prototype,
|
||||||
|
"obj": obj, "obj_prototype": obj_prototype,
|
||||||
|
"diff": diff, "objects": objects, "back_node": back_node})}
|
||||||
|
return options
|
||||||
|
|
||||||
|
prototype = kwargs.get("prototype", None)
|
||||||
|
update_objects = kwargs.get("objects", None)
|
||||||
|
back_node = kwargs.get("back_node", "node_index")
|
||||||
|
obj_prototype = kwargs.get("obj_prototype", None)
|
||||||
|
diff = kwargs.get("diff", None)
|
||||||
|
|
||||||
|
if not update_objects:
|
||||||
|
text = "There are no existing objects to update."
|
||||||
|
options = {"key": "_default",
|
||||||
|
"goto": back_node}
|
||||||
|
return text, options
|
||||||
|
|
||||||
|
if not diff:
|
||||||
|
# use one random object as a reference to calculate a diff
|
||||||
|
obj = choice(update_objects)
|
||||||
|
diff, obj_prototype = spawner.prototype_diff_from_object(prototype, obj)
|
||||||
|
|
||||||
|
text = ["Suggested changes to {} objects".format(len(update_objects)),
|
||||||
|
"Showing random example obj to change: {name} (#{dbref}))\n".format(obj.key, obj.dbref)]
|
||||||
|
options = []
|
||||||
|
io = 0
|
||||||
|
for (key, inst) in sorted(((key, val) for key, val in diff.items()), key=lambda tup: tup[0]):
|
||||||
|
line = "{iopt} |w{key}|n: {old}{sep}{new} {change}"
|
||||||
|
old_val = utils.crop(str(obj_prototype[key]), width=20)
|
||||||
|
|
||||||
|
if inst == "KEEP":
|
||||||
|
text.append(line.format(iopt='', key=key, old=old_val, sep=" ", new='', change=inst))
|
||||||
|
continue
|
||||||
|
|
||||||
|
new_val = utils.crop(str(spawner.init_spawn_value(prototype[key])), width=20)
|
||||||
|
io += 1
|
||||||
|
if inst in ("UPDATE", "REPLACE"):
|
||||||
|
text.append(line.format(iopt=io, key=key, old=old_val,
|
||||||
|
sep=" |y->|n ", new=new_val, change=inst))
|
||||||
|
options.append(_keep_option(key, prototype,
|
||||||
|
obj, obj_prototype, diff, objects, back_node))
|
||||||
|
elif inst == "REMOVE":
|
||||||
|
text.append(line.format(iopt=io, key=key, old=old_val,
|
||||||
|
sep=" |r->|n ", new='', change=inst))
|
||||||
|
options.append(_keep_option(key, prototype,
|
||||||
|
obj, obj_prototype, diff, objects, back_node))
|
||||||
|
options.extend(
|
||||||
|
[{"key": ("|wu|r update {} objects".format(len(update_objects)), "update", "u"),
|
||||||
|
"goto": (_update_spawned, {"prototype": prototype, "objects": objects,
|
||||||
|
"back_node": back_node, "diff": diff})},
|
||||||
|
{"key": ("|wr|neset changes", "reset", "r"),
|
||||||
|
"goto": ("node_update_objects", {"prototype": prototype, "back_node": back_node,
|
||||||
|
"objects": update_objects})},
|
||||||
|
{"key": "|wb|rack ({})".format(back_node[5:], 'b'),
|
||||||
|
"goto": back_node}])
|
||||||
|
|
||||||
|
return text, options
|
||||||
|
|
||||||
|
|
||||||
def node_prototype_save(caller, **kwargs):
|
def node_prototype_save(caller, **kwargs):
|
||||||
"""Save prototype to disk """
|
"""Save prototype to disk """
|
||||||
# these are only set if we selected 'yes' to save on a previous pass
|
# these are only set if we selected 'yes' to save on a previous pass
|
||||||
accept_save = kwargs.get("accept", False)
|
|
||||||
prototype = kwargs.get("prototype", None)
|
prototype = kwargs.get("prototype", None)
|
||||||
|
accept_save = kwargs.get("accept_save", False)
|
||||||
|
|
||||||
if accept_save and prototype:
|
if accept_save and prototype:
|
||||||
# we already validated and accepted the save, so this node acts as a goto callback and
|
# we already validated and accepted the save, so this node acts as a goto callback and
|
||||||
# should now only return the next node
|
# should now only return the next node
|
||||||
|
prototype_key = prototype.get("prototype_key")
|
||||||
protlib.save_prototype(**prototype)
|
protlib.save_prototype(**prototype)
|
||||||
caller.msg("|gPrototype saved.|n")
|
|
||||||
return "node_spawn"
|
spawned_objects = protlib.search_objects_with_prototype(prototype_key)
|
||||||
|
nspawned = spawned_objects.count()
|
||||||
|
|
||||||
|
if nspawned:
|
||||||
|
text = ("Do you want to update {} object(s) "
|
||||||
|
"already using this prototype?".format(nspawned))
|
||||||
|
options = (
|
||||||
|
{"key": ("|wY|Wes|n", "yes", "y"),
|
||||||
|
"goto": ("node_update_objects",
|
||||||
|
{"accept_update": True, "objects": spawned_objects,
|
||||||
|
"prototype": prototype, "back_node": "node_prototype_save"})},
|
||||||
|
{"key": ("[|wN|Wo|n]", "n"),
|
||||||
|
"goto": "node_spawn"},
|
||||||
|
{"key": "_default",
|
||||||
|
"goto": "node_spawn"})
|
||||||
|
else:
|
||||||
|
text = "|gPrototype saved.|n"
|
||||||
|
options = {"key": "_default",
|
||||||
|
"goto": "node_spawn"}
|
||||||
|
|
||||||
|
return text, options
|
||||||
|
|
||||||
# not validated yet
|
# not validated yet
|
||||||
prototype = _get_menu_prototype(caller)
|
prototype = _get_menu_prototype(caller)
|
||||||
|
|
@ -850,15 +960,13 @@ def node_prototype_save(caller, **kwargs):
|
||||||
|
|
||||||
options = (
|
options = (
|
||||||
{"key": ("[|wY|Wes|n]", "yes", "y"),
|
{"key": ("[|wY|Wes|n]", "yes", "y"),
|
||||||
"goto": lambda caller:
|
"goto": ("node_prototype_save",
|
||||||
node_prototype_save(caller,
|
{"accept": True, "prototype": prototype})},
|
||||||
{"accept": True, "prototype": prototype})},
|
|
||||||
{"key": ("|wN|Wo|n", "n"),
|
{"key": ("|wN|Wo|n", "n"),
|
||||||
"goto": "node_spawn"},
|
"goto": "node_spawn"},
|
||||||
{"key": "_default",
|
{"key": "_default",
|
||||||
"goto": lambda caller:
|
"goto": ("node_prototype_save",
|
||||||
node_prototype_save(caller,
|
{"accept": True, "prototype": prototype})})
|
||||||
{"accept": True, "prototype": prototype})})
|
|
||||||
|
|
||||||
return "\n".join(text), options
|
return "\n".join(text), options
|
||||||
|
|
||||||
|
|
@ -869,20 +977,15 @@ def _spawn(caller, **kwargs):
|
||||||
new_location = kwargs.get('location', None)
|
new_location = kwargs.get('location', None)
|
||||||
if new_location:
|
if new_location:
|
||||||
prototype['location'] = new_location
|
prototype['location'] = new_location
|
||||||
|
|
||||||
obj = spawner.spawn(prototype)
|
obj = spawner.spawn(prototype)
|
||||||
if obj:
|
if obj:
|
||||||
|
obj = obj[0]
|
||||||
caller.msg("|gNew instance|n {key} ({dbref}) |gspawned.|n".format(
|
caller.msg("|gNew instance|n {key} ({dbref}) |gspawned.|n".format(
|
||||||
key=obj.key, dbref=obj.dbref))
|
key=obj.key, dbref=obj.dbref))
|
||||||
else:
|
else:
|
||||||
caller.msg("|rError: Spawner did not return a new instance.|n")
|
caller.msg("|rError: Spawner did not return a new instance.|n")
|
||||||
|
return obj
|
||||||
|
|
||||||
def _update_spawned(caller, **kwargs):
|
|
||||||
"""update existing objects"""
|
|
||||||
prototype = kwargs['prototype']
|
|
||||||
objects = kwargs['objects']
|
|
||||||
num_changed = spawner.batch_update_objects_with_prototype(prototype, objects=objects)
|
|
||||||
caller.msg("|g{num} objects were updated successfully.|n".format(num=num_changed))
|
|
||||||
|
|
||||||
|
|
||||||
def node_prototype_spawn(caller, **kwargs):
|
def node_prototype_spawn(caller, **kwargs):
|
||||||
|
|
@ -926,9 +1029,9 @@ def node_prototype_spawn(caller, **kwargs):
|
||||||
if spawned_objects:
|
if spawned_objects:
|
||||||
options.append(
|
options.append(
|
||||||
{"desc": "Update {num} existing objects with this prototype".format(num=nspawned),
|
{"desc": "Update {num} existing objects with this prototype".format(num=nspawned),
|
||||||
"goto": (_update_spawned,
|
"goto": ("node_update_objects",
|
||||||
dict(prototype=prototype,
|
dict(prototype=prototype, opjects=spawned_objects,
|
||||||
opjects=spawned_objects))})
|
back_node="node_prototype_spawn"))})
|
||||||
options.extend(_wizard_options("prototype_spawn", "prototype_save", "index"))
|
options.extend(_wizard_options("prototype_spawn", "prototype_save", "index"))
|
||||||
return text, options
|
return text, options
|
||||||
|
|
||||||
|
|
@ -1008,6 +1111,7 @@ def start_olc(caller, session=None, prototype=None):
|
||||||
"node_location": node_location,
|
"node_location": node_location,
|
||||||
"node_home": node_home,
|
"node_home": node_home,
|
||||||
"node_destination": node_destination,
|
"node_destination": node_destination,
|
||||||
|
"node_update_objects": node_o
|
||||||
"node_prototype_desc": node_prototype_desc,
|
"node_prototype_desc": node_prototype_desc,
|
||||||
"node_prototype_tags": node_prototype_tags,
|
"node_prototype_tags": node_prototype_tags,
|
||||||
"node_prototype_locks": node_prototype_locks,
|
"node_prototype_locks": node_prototype_locks,
|
||||||
|
|
|
||||||
|
|
@ -547,7 +547,8 @@ def validate_prototype(prototype, protkey=None, protparents=None,
|
||||||
_flags = {"visited": [], "depth": 0, "typeclass": False, "errors": [], "warnings": []}
|
_flags = {"visited": [], "depth": 0, "typeclass": False, "errors": [], "warnings": []}
|
||||||
|
|
||||||
if not protparents:
|
if not protparents:
|
||||||
protparents = {prototype['prototype_key']: prototype for prototype in search_prototype()}
|
protparents = {prototype.get('prototype_key', "").lower(): prototype
|
||||||
|
for prototype in search_prototype()}
|
||||||
|
|
||||||
protkey = protkey and protkey.lower() or prototype.get('prototype_key', None)
|
protkey = protkey and protkey.lower() or prototype.get('prototype_key', None)
|
||||||
|
|
||||||
|
|
@ -568,17 +569,11 @@ def validate_prototype(prototype, protkey=None, protparents=None,
|
||||||
|
|
||||||
if typeclass and typeclass not in get_all_typeclasses("evennia.objects.models.ObjectDB"):
|
if typeclass and typeclass not in get_all_typeclasses("evennia.objects.models.ObjectDB"):
|
||||||
_flags['errors'].append(
|
_flags['errors'].append(
|
||||||
"Prototype {} is based on typeclass {} which could not be imported!".format(
|
"Prototype {} is based on typeclass {}, which could not be imported!".format(
|
||||||
protkey, typeclass))
|
protkey, typeclass))
|
||||||
|
|
||||||
# recursively traverese prototype_parent chain
|
# recursively traverese prototype_parent chain
|
||||||
|
|
||||||
if id(prototype) in _flags['visited']:
|
|
||||||
_flags['errors'].append(
|
|
||||||
"{} has infinite nesting of prototypes.".format(protkey or prototype))
|
|
||||||
|
|
||||||
_flags['visited'].append(id(prototype))
|
|
||||||
|
|
||||||
for protstring in make_iter(prototype_parent):
|
for protstring in make_iter(prototype_parent):
|
||||||
protstring = protstring.lower()
|
protstring = protstring.lower()
|
||||||
if protkey is not None and protstring == protkey:
|
if protkey is not None and protstring == protkey:
|
||||||
|
|
@ -587,8 +582,15 @@ def validate_prototype(prototype, protkey=None, protparents=None,
|
||||||
if not protparent:
|
if not protparent:
|
||||||
_flags['errors'].append("Prototype {}'s prototype_parent '{}' was not found.".format(
|
_flags['errors'].append("Prototype {}'s prototype_parent '{}' was not found.".format(
|
||||||
(protkey, protstring)))
|
(protkey, protstring)))
|
||||||
|
if id(prototype) in _flags['visited']:
|
||||||
|
_flags['errors'].append(
|
||||||
|
"{} has infinite nesting of prototypes.".format(protkey or prototype))
|
||||||
|
|
||||||
|
_flags['visited'].append(id(prototype))
|
||||||
_flags['depth'] += 1
|
_flags['depth'] += 1
|
||||||
validate_prototype(protparent, protstring, protparents, _flags)
|
validate_prototype(protparent, protstring, protparents,
|
||||||
|
is_prototype_base=is_prototype_base, _flags=_flags)
|
||||||
|
_flags['visited'].pop()
|
||||||
_flags['depth'] -= 1
|
_flags['depth'] -= 1
|
||||||
|
|
||||||
if typeclass and not _flags['typeclass']:
|
if typeclass and not _flags['typeclass']:
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,7 @@ def prototype_from_object(obj):
|
||||||
prot = obj.tags.get(category=_PROTOTYPE_TAG_CATEGORY, return_list=True)
|
prot = obj.tags.get(category=_PROTOTYPE_TAG_CATEGORY, return_list=True)
|
||||||
if prot:
|
if prot:
|
||||||
prot = protlib.search_prototype(prot[0])
|
prot = protlib.search_prototype(prot[0])
|
||||||
|
|
||||||
if not prot or len(prot) > 1:
|
if not prot or len(prot) > 1:
|
||||||
# no unambiguous prototype found - build new prototype
|
# no unambiguous prototype found - build new prototype
|
||||||
prot = {}
|
prot = {}
|
||||||
|
|
@ -187,6 +188,8 @@ def prototype_from_object(obj):
|
||||||
prot['prototype_desc'] = "Built from {}".format(str(obj))
|
prot['prototype_desc'] = "Built from {}".format(str(obj))
|
||||||
prot['prototype_locks'] = "spawn:all();edit:all()"
|
prot['prototype_locks'] = "spawn:all();edit:all()"
|
||||||
prot['prototype_tags'] = []
|
prot['prototype_tags'] = []
|
||||||
|
else:
|
||||||
|
prot = prot[0]
|
||||||
|
|
||||||
prot['key'] = obj.db_key or hashlib.md5(str(time.time())).hexdigest()[:6]
|
prot['key'] = obj.db_key or hashlib.md5(str(time.time())).hexdigest()[:6]
|
||||||
prot['typeclass'] = obj.db_typeclass_path
|
prot['typeclass'] = obj.db_typeclass_path
|
||||||
|
|
@ -233,6 +236,8 @@ def prototype_diff_from_object(prototype, obj):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
diff (dict): Mapping for every prototype key: {"keyname": "REMOVE|UPDATE|KEEP", ...}
|
diff (dict): Mapping for every prototype key: {"keyname": "REMOVE|UPDATE|KEEP", ...}
|
||||||
|
other_prototype (dict): The prototype for the given object. The diff is a how to convert
|
||||||
|
this prototype into the new prototype.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
prot1 = prototype
|
prot1 = prototype
|
||||||
|
|
@ -253,7 +258,7 @@ def prototype_diff_from_object(prototype, obj):
|
||||||
if key not in diff and key not in prot1:
|
if key not in diff and key not in prot1:
|
||||||
diff[key] = "REMOVE"
|
diff[key] = "REMOVE"
|
||||||
|
|
||||||
return diff
|
return diff, prot2
|
||||||
|
|
||||||
|
|
||||||
def batch_update_objects_with_prototype(prototype, diff=None, objects=None):
|
def batch_update_objects_with_prototype(prototype, diff=None, objects=None):
|
||||||
|
|
@ -475,8 +480,12 @@ def spawn(*prototypes, **kwargs):
|
||||||
protparents = {prot['prototype_key'].lower(): prot for prot in protlib.search_prototype()}
|
protparents = {prot['prototype_key'].lower(): prot for prot in protlib.search_prototype()}
|
||||||
|
|
||||||
# overload module's protparents with specifically given protparents
|
# overload module's protparents with specifically given protparents
|
||||||
protparents.update(
|
# we allow prototype_key to be the key of the protparent dict, to allow for module-level
|
||||||
{key.lower(): value for key, value in kwargs.get("prototype_parents", {}).items()})
|
# prototype imports. We need to insert prototype_key in this case
|
||||||
|
for key, protparent in kwargs.get("prototype_parents", {}).items():
|
||||||
|
key = str(key).lower()
|
||||||
|
protparent['prototype_key'] = str(protparent.get("prototype_key", key)).lower()
|
||||||
|
protparents[key] = protparent
|
||||||
|
|
||||||
if "return_parents" in kwargs:
|
if "return_parents" in kwargs:
|
||||||
# only return the parents
|
# only return the parents
|
||||||
|
|
@ -541,6 +550,9 @@ def spawn(*prototypes, **kwargs):
|
||||||
simple_attributes = []
|
simple_attributes = []
|
||||||
for key, value in ((key, value) for key, value in prot.items()
|
for key, value in ((key, value) for key, value in prot.items()
|
||||||
if not (key.startswith("ndb_"))):
|
if not (key.startswith("ndb_"))):
|
||||||
|
if key in _PROTOTYPE_META_NAMES:
|
||||||
|
continue
|
||||||
|
|
||||||
if is_iter(value) and len(value) > 1:
|
if is_iter(value) and len(value) > 1:
|
||||||
# (value, category)
|
# (value, category)
|
||||||
simple_attributes.append((key,
|
simple_attributes.append((key,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ from evennia.prototypes.prototypes import _PROTOTYPE_TAG_META_CATEGORY
|
||||||
_PROTPARENTS = {
|
_PROTPARENTS = {
|
||||||
"NOBODY": {},
|
"NOBODY": {},
|
||||||
"GOBLIN": {
|
"GOBLIN": {
|
||||||
|
"prototype_key": "GOBLIN",
|
||||||
|
"typeclass": "evennia.objects.objects.DefaultObject",
|
||||||
"key": "goblin grunt",
|
"key": "goblin grunt",
|
||||||
"health": lambda: randint(1, 1),
|
"health": lambda: randint(1, 1),
|
||||||
"resists": ["cold", "poison"],
|
"resists": ["cold", "poison"],
|
||||||
|
|
@ -24,21 +26,22 @@ _PROTPARENTS = {
|
||||||
"weaknesses": ["fire", "light"]
|
"weaknesses": ["fire", "light"]
|
||||||
},
|
},
|
||||||
"GOBLIN_WIZARD": {
|
"GOBLIN_WIZARD": {
|
||||||
"prototype": "GOBLIN",
|
"prototype_parent": "GOBLIN",
|
||||||
"key": "goblin wizard",
|
"key": "goblin wizard",
|
||||||
"spells": ["fire ball", "lighting bolt"]
|
"spells": ["fire ball", "lighting bolt"]
|
||||||
},
|
},
|
||||||
"GOBLIN_ARCHER": {
|
"GOBLIN_ARCHER": {
|
||||||
"prototype": "GOBLIN",
|
"prototype_parent": "GOBLIN",
|
||||||
"key": "goblin archer",
|
"key": "goblin archer",
|
||||||
"attacks": ["short bow"]
|
"attacks": ["short bow"]
|
||||||
},
|
},
|
||||||
"ARCHWIZARD": {
|
"ARCHWIZARD": {
|
||||||
|
"prototype_parent": "GOBLIN",
|
||||||
"attacks": ["archwizard staff"],
|
"attacks": ["archwizard staff"],
|
||||||
},
|
},
|
||||||
"GOBLIN_ARCHWIZARD": {
|
"GOBLIN_ARCHWIZARD": {
|
||||||
"key": "goblin archwizard",
|
"key": "goblin archwizard",
|
||||||
"prototype": ("GOBLIN_WIZARD", "ARCHWIZARD")
|
"prototype_parent": ("GOBLIN_WIZARD", "ARCHWIZARD")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,7 +50,8 @@ class TestSpawner(EvenniaTest):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestSpawner, self).setUp()
|
super(TestSpawner, self).setUp()
|
||||||
self.prot1 = {"prototype_key": "testprototype"}
|
self.prot1 = {"prototype_key": "testprototype",
|
||||||
|
"typeclass": "evennia.objects.objects.DefaultObject"}
|
||||||
|
|
||||||
def test_spawn(self):
|
def test_spawn(self):
|
||||||
obj1 = spawner.spawn(self.prot1)
|
obj1 = spawner.spawn(self.prot1)
|
||||||
|
|
@ -323,6 +327,7 @@ class TestMenuModule(EvenniaTest):
|
||||||
self.caller.ndb._menutree = menutree
|
self.caller.ndb._menutree = menutree
|
||||||
|
|
||||||
self.test_prot = {"prototype_key": "test_prot",
|
self.test_prot = {"prototype_key": "test_prot",
|
||||||
|
"typeclass": "evennia.objects.objects.DefaultObject",
|
||||||
"prototype_locks": "edit:all();spawn:all()"}
|
"prototype_locks": "edit:all();spawn:all()"}
|
||||||
|
|
||||||
def test_helpers(self):
|
def test_helpers(self):
|
||||||
|
|
@ -334,6 +339,8 @@ class TestMenuModule(EvenniaTest):
|
||||||
self.assertEqual(olc_menus._get_menu_prototype(caller), {})
|
self.assertEqual(olc_menus._get_menu_prototype(caller), {})
|
||||||
self.assertEqual(olc_menus._is_new_prototype(caller), True)
|
self.assertEqual(olc_menus._is_new_prototype(caller), True)
|
||||||
|
|
||||||
|
self.assertEqual(olc_menus._set_menu_prototype(caller, {}), {})
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
olc_menus._set_prototype_value(caller, "key", "TestKey"), {"key": "TestKey"})
|
olc_menus._set_prototype_value(caller, "key", "TestKey"), {"key": "TestKey"})
|
||||||
self.assertEqual(olc_menus._get_menu_prototype(caller), {"key": "TestKey"})
|
self.assertEqual(olc_menus._get_menu_prototype(caller), {"key": "TestKey"})
|
||||||
|
|
@ -349,13 +356,16 @@ class TestMenuModule(EvenniaTest):
|
||||||
|
|
||||||
self.assertEqual(olc_menus._wizard_options(
|
self.assertEqual(olc_menus._wizard_options(
|
||||||
"ThisNode", "PrevNode", "NextNode"),
|
"ThisNode", "PrevNode", "NextNode"),
|
||||||
[{'goto': 'node_PrevNode', 'key': ('|wb|Wack', 'b'), 'desc': '|W(PrevNode)|n'},
|
[{'goto': 'node_PrevNode', 'key': ('|wB|Wack', 'b'), 'desc': '|W(PrevNode)|n'},
|
||||||
{'goto': 'node_NextNode', 'key': ('|wf|Worward', 'f'), 'desc': '|W(NextNode)|n'},
|
{'goto': 'node_NextNode', 'key': ('|wF|Worward', 'f'), 'desc': '|W(NextNode)|n'},
|
||||||
{'goto': 'node_index', 'key': ('|wi|Wndex', 'i')},
|
{'goto': 'node_index', 'key': ('|wI|Wndex', 'i')},
|
||||||
{'goto': ('node_validate_prototype', {'back': 'ThisNode'}),
|
{'goto': ('node_validate_prototype', {'back': 'ThisNode'}),
|
||||||
'key': ('|wv|Walidate prototype', 'v')}])
|
'key': ('|wV|Walidate prototype', 'validate', 'v')}])
|
||||||
|
|
||||||
self.assertEqual(olc_menus._validate_prototype(self.test_prot, (False, Something)))
|
self.assertEqual(olc_menus._validate_prototype(self.test_prot), (False, Something))
|
||||||
|
self.assertEqual(olc_menus._validate_prototype(
|
||||||
|
{"prototype_key": "testthing", "key": "mytest"}),
|
||||||
|
(True, Something))
|
||||||
|
|
||||||
def test_node_helpers(self):
|
def test_node_helpers(self):
|
||||||
|
|
||||||
|
|
@ -363,23 +373,27 @@ class TestMenuModule(EvenniaTest):
|
||||||
|
|
||||||
with mock.patch("evennia.prototypes.menus.protlib.search_prototype",
|
with mock.patch("evennia.prototypes.menus.protlib.search_prototype",
|
||||||
new=mock.MagicMock(return_value=[self.test_prot])):
|
new=mock.MagicMock(return_value=[self.test_prot])):
|
||||||
|
# prototype_key helpers
|
||||||
self.assertEqual(olc_menus._check_prototype_key(caller, "test_prot"),
|
self.assertEqual(olc_menus._check_prototype_key(caller, "test_prot"),
|
||||||
"node_prototype_parent")
|
"node_prototype_parent")
|
||||||
caller.ndb._menutree.olc_new = True
|
caller.ndb._menutree.olc_new = True
|
||||||
self.assertEqual(olc_menus._check_prototype_key(caller, "test_prot"),
|
self.assertEqual(olc_menus._check_prototype_key(caller, "test_prot"),
|
||||||
"node_index")
|
"node_index")
|
||||||
|
|
||||||
|
# prototype_parent helpers
|
||||||
self.assertEqual(olc_menus._all_prototype_parents(caller), ['test_prot'])
|
self.assertEqual(olc_menus._all_prototype_parents(caller), ['test_prot'])
|
||||||
self.assertEqual(olc_menus._prototype_parent_examine(
|
self.assertEqual(olc_menus._prototype_parent_examine(
|
||||||
caller, 'test_prot'),
|
caller, 'test_prot'),
|
||||||
'|cprototype key:|n test_prot, |ctags:|n None, |clocks:|n edit:all();spawn:all() '
|
"|cprototype key:|n test_prot, |ctags:|n None, |clocks:|n edit:all();spawn:all() "
|
||||||
'\n|cdesc:|n None \n|cprototype:|n {\n \n}')
|
"\n|cdesc:|n None \n|cprototype:|n "
|
||||||
|
"{\n 'typeclass': 'evennia.objects.objects.DefaultObject', \n}")
|
||||||
self.assertEqual(olc_menus._prototype_parent_select(caller, self.test_prot), "node_key")
|
self.assertEqual(olc_menus._prototype_parent_select(caller, self.test_prot), "node_key")
|
||||||
self.assertEqual(olc_menus._get_menu_prototype(caller),
|
self.assertEqual(olc_menus._get_menu_prototype(caller),
|
||||||
{'prototype_key': 'test_prot',
|
{'prototype_key': 'test_prot',
|
||||||
'prototype_locks': 'edit:all();spawn:all()',
|
'prototype_locks': 'edit:all();spawn:all()',
|
||||||
'prototype_parent': "test_prot"})
|
'typeclass': 'evennia.objects.objects.DefaultObject'})
|
||||||
|
|
||||||
|
# typeclass helpers
|
||||||
with mock.patch("evennia.utils.utils.get_all_typeclasses",
|
with mock.patch("evennia.utils.utils.get_all_typeclasses",
|
||||||
new=mock.MagicMock(return_value={"foo": None, "bar": None})):
|
new=mock.MagicMock(return_value={"foo": None, "bar": None})):
|
||||||
self.assertEqual(olc_menus._all_typeclasses(caller), ["bar", "foo"])
|
self.assertEqual(olc_menus._all_typeclasses(caller), ["bar", "foo"])
|
||||||
|
|
@ -394,6 +408,53 @@ class TestMenuModule(EvenniaTest):
|
||||||
'prototype_locks': 'edit:all();spawn:all()',
|
'prototype_locks': 'edit:all();spawn:all()',
|
||||||
'typeclass': 'evennia.objects.objects.DefaultObject'})
|
'typeclass': 'evennia.objects.objects.DefaultObject'})
|
||||||
|
|
||||||
|
# attr helpers
|
||||||
|
self.assertEqual(olc_menus._caller_attrs(caller), [])
|
||||||
|
self.assertEqual(olc_menus._add_attr(caller, "test1=foo1"), (Something, {"key": "_default", "goto": Something}))
|
||||||
|
self.assertEqual(olc_menus._add_attr(caller, "test2;cat1=foo2"), (Something, {"key": "_default", "goto": Something}))
|
||||||
|
self.assertEqual(olc_menus._add_attr(caller, "test3;cat2;edit:false()=foo3"), (Something, {"key": "_default", "goto": Something}))
|
||||||
|
self.assertEqual(olc_menus._add_attr(caller, "test4;cat3;set:true();edit:false()=foo4"), (Something, {"key": "_default", "goto": Something}))
|
||||||
|
self.assertEqual(olc_menus._add_attr(caller, "test5;cat4;set:true();edit:false()=123"), (Something, {"key": "_default", "goto": Something}))
|
||||||
|
self.assertEqual(olc_menus._caller_attrs(
|
||||||
|
caller),
|
||||||
|
[("test1", "foo1", None, ''),
|
||||||
|
("test2", "foo2", "cat1", ''),
|
||||||
|
("test3", "foo3", "cat2", "edit:false()"),
|
||||||
|
("test4", "foo4", "cat3", "set:true();edit:false()"),
|
||||||
|
("test5", '123', "cat4", "set:true();edit:false()")])
|
||||||
|
self.assertEqual(olc_menus._edit_attr(caller, "test1", "1;cat5;edit:all()"), (Something, {"key": "_default", "goto": Something}))
|
||||||
|
self.assertEqual(olc_menus._examine_attr(caller, "test1"), Something)
|
||||||
|
|
||||||
|
# tag helpers
|
||||||
|
self.assertEqual(olc_menus._caller_tags(caller), [])
|
||||||
|
self.assertEqual(olc_menus._add_tag(caller, "foo1"), (Something, {"key": "_default", "goto": Something}))
|
||||||
|
self.assertEqual(olc_menus._add_tag(caller, "foo2;cat1"), (Something, {"key": "_default", "goto": Something}))
|
||||||
|
self.assertEqual(olc_menus._add_tag(caller, "foo3;cat2;dat1"), (Something, {"key": "_default", "goto": Something}))
|
||||||
|
self.assertEqual(olc_menus._caller_tags(
|
||||||
|
caller),
|
||||||
|
[('foo1', None, ""),
|
||||||
|
('foo2', 'cat1', ""),
|
||||||
|
('foo3', 'cat2', "dat1")])
|
||||||
|
self.assertEqual(olc_menus._edit_tag(caller, "foo1", "bar1;cat1"), (Something, {"key": "_default", "goto": Something}))
|
||||||
|
self.assertEqual(olc_menus._display_tag(olc_menus._caller_tags(caller)[0]), Something)
|
||||||
|
self.assertEqual(olc_menus._caller_tags(caller)[0], ("bar1", "cat1", ""))
|
||||||
|
|
||||||
|
protlib.save_prototype(**self.test_prot)
|
||||||
|
|
||||||
|
# spawn helpers
|
||||||
|
obj = olc_menus._spawn(caller, prototype=self.test_prot)
|
||||||
|
|
||||||
|
self.assertEqual(obj.typeclass_path, "evennia.objects.objects.DefaultObject")
|
||||||
|
self.assertEqual(obj.tags.get(category=spawner._PROTOTYPE_TAG_CATEGORY), self.test_prot['prototype_key'])
|
||||||
|
self.assertEqual(olc_menus._update_spawned(caller, prototype=self.test_prot, objects=[obj]), 0) # no changes to apply
|
||||||
|
self.test_prot['key'] = "updated key" # change prototype
|
||||||
|
self.assertEqual(self._update_spawned(caller, prototype=self.test_prot, objects=[obj]), 1) # apply change to the one obj
|
||||||
|
|
||||||
|
|
||||||
|
# load helpers
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch("evennia.prototypes.menus.protlib.search_prototype", new=mock.MagicMock(
|
@mock.patch("evennia.prototypes.menus.protlib.search_prototype", new=mock.MagicMock(
|
||||||
return_value=[{"prototype_key": "TestPrototype",
|
return_value=[{"prototype_key": "TestPrototype",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue