Add non-working, initial concept for olc system.
This commit is contained in:
parent
ee4dd20fd9
commit
00b8dd4881
5 changed files with 535 additions and 0 deletions
0
evennia/utils/olc/__init__.py
Normal file
0
evennia/utils/olc/__init__.py
Normal file
148
evennia/utils/olc/olc.py
Normal file
148
evennia/utils/olc/olc.py
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
"""
|
||||||
|
OLC - On-Line Creation
|
||||||
|
|
||||||
|
This module is the core of the Evennia online creation helper system.
|
||||||
|
This is a resource intended for players with build privileges.
|
||||||
|
|
||||||
|
While the OLC command can be used to start the OLC "from the top", the
|
||||||
|
system is also intended to be plugged in to enhance existing build commands
|
||||||
|
with a more menu-like building style.
|
||||||
|
|
||||||
|
Functionality:
|
||||||
|
|
||||||
|
- Prototype management: Allows to create and edit Prototype
|
||||||
|
dictionaries. Can store such Prototypes on the Builder Player as an Attribute
|
||||||
|
or centrally on a central store that all builders can fetch prototypes from.
|
||||||
|
- Creates a new entity either from an existing prototype or by creating the
|
||||||
|
prototype on the fly for the sake of that single object (the prototype can
|
||||||
|
then also be saved for future use).
|
||||||
|
- Recording of session, for performing a series of recorded build actions in sequence.
|
||||||
|
Stored so as to be possible to reproduce.
|
||||||
|
- Export of objects created in recording mode to a batchcode file (Immortals only).
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
from time import time
|
||||||
|
from evennia.utils.evmenu import EvMenu
|
||||||
|
from evennia.commands.command import Command
|
||||||
|
|
||||||
|
|
||||||
|
# OLC settings
|
||||||
|
_SHOW_PROMPT = True # settings.OLC_SHOW_PROMPT
|
||||||
|
_DEFAULT_PROMPT = "" # settings.OLC_DEFAULT_PROMPT
|
||||||
|
_LEN_HISTORY = 10 # settings.OLC_HISTORY_LENGTH
|
||||||
|
|
||||||
|
|
||||||
|
# OLC Session
|
||||||
|
|
||||||
|
def _new_session():
|
||||||
|
|
||||||
|
"""
|
||||||
|
This generates an empty olcsession structure, which is used to hold state
|
||||||
|
information in the olc but which can also be pickled.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
olcsession (dict): An empty OLCSession.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
This is a customized dict which the Attribute system will
|
||||||
|
understand how to pickle and depickle since it provides
|
||||||
|
iteration.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
# header info
|
||||||
|
"caller": None, # the current user of this session
|
||||||
|
"modified": time.now(), # last time this olcsession was active
|
||||||
|
"db_model": None, # currently unused, ObjectDB for now
|
||||||
|
"prompt_template": _DEFAULT_PROMPT, # prompt display
|
||||||
|
"olcfields": OrderedDict(), # registered OLCFields. Order matters
|
||||||
|
"prototype_key": "", # current active prototype key
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _update_prompt(osession):
|
||||||
|
"""
|
||||||
|
Update the OLC status prompt.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
prompt (str): The prompt based on the
|
||||||
|
prompt template, populated with
|
||||||
|
the olcsession state.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def search_entity(osession, query):
|
||||||
|
"""
|
||||||
|
Perform a query for a specified entity. Which type of entity is determined by the osession
|
||||||
|
state.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query (str): This is a string, a #dbref or an extended search
|
||||||
|
|
||||||
|
"""
|
||||||
|
osession['db_model'].__class__.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def display_prototype(osession):
|
||||||
|
"""
|
||||||
|
Display prototype fields according to the order of the registered olcfields.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# TODO: Simple one column display to begin with - make multi-column later
|
||||||
|
pkey = osession['prototype_key']
|
||||||
|
outtxt = ["=== {pkey} ===".format(pkey=pkey)]
|
||||||
|
for field in osession['olcfields'].values():
|
||||||
|
fname, flabel, fvalue = field.name, field.label, field.display()
|
||||||
|
outtxt.append(" {fieldname} ({label}): {value}".format(fieldname=fname,
|
||||||
|
label=flabel, value=fvalue))
|
||||||
|
return '\n'.join(outtxt)
|
||||||
|
|
||||||
|
|
||||||
|
def display_field_value(osession, fieldname):
|
||||||
|
"""
|
||||||
|
Display info about a specific field.
|
||||||
|
"""
|
||||||
|
field = osession['olcfields'].get(fieldname, None)
|
||||||
|
if field:
|
||||||
|
return "{fieldname}: {value}".format(fieldname=field.name, value=field.display())
|
||||||
|
|
||||||
|
|
||||||
|
# Access function
|
||||||
|
|
||||||
|
def OLC(caller, target=None, startnode=None):
|
||||||
|
"""
|
||||||
|
This function is a common entry-point into the OLC menu system. It is used
|
||||||
|
by Evennia systems to jump into the different possible start points of the
|
||||||
|
OLC menu tree depending on what info is already available.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
caller (Object or Player): The one using the olc.
|
||||||
|
target (Object, optional): Object to operate on, if any is known.
|
||||||
|
startnode (str, optional): Where in the menu tree to start. If unset,
|
||||||
|
will be decided by whether target is given or not.
|
||||||
|
|
||||||
|
"""
|
||||||
|
startnode = startnode or (target and "node_edit_top") or "node_top"
|
||||||
|
EvMenu(caller, "evennia.utils.olc.olc_menu", startnode=startnode, target=target)
|
||||||
|
|
||||||
|
|
||||||
|
class CmdOLC(Command):
|
||||||
|
"""
|
||||||
|
Test OLC
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
olc [target]
|
||||||
|
|
||||||
|
Starts the olc to create a new object or to modify an existing one.
|
||||||
|
|
||||||
|
"""
|
||||||
|
key = "olc"
|
||||||
|
def func(self):
|
||||||
|
OLC(self.caller, target=self.args)
|
||||||
|
|
||||||
291
evennia/utils/olc/olc_fields.py
Normal file
291
evennia/utils/olc/olc_fields.py
Normal file
|
|
@ -0,0 +1,291 @@
|
||||||
|
"""
|
||||||
|
OLC fields describe how to edit and display a specific piece of data of a prototype within the OLC system.
|
||||||
|
|
||||||
|
The OLC system imports and adds these field classes to its prototype manipulation pages in order to
|
||||||
|
know what data to read and how to display it.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from collections import deque
|
||||||
|
# from django.conf import settings
|
||||||
|
|
||||||
|
_OLC_VALIDATION_ERROR = """
|
||||||
|
Error storing data in {fieldname}:
|
||||||
|
{value}
|
||||||
|
The reported error was
|
||||||
|
{error}
|
||||||
|
"""
|
||||||
|
|
||||||
|
_LEN_HISTORY = 10 # settings.OLC_HISTORY_LENGTH
|
||||||
|
|
||||||
|
|
||||||
|
class OLCField(object):
|
||||||
|
"""
|
||||||
|
This is the parent for all OLC fields. This docstring acts
|
||||||
|
as the help text for the field.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# name of this field, for error reporting
|
||||||
|
key = "Empty field"
|
||||||
|
# if this field must have a value different than None
|
||||||
|
required = False
|
||||||
|
# used for displaying extra info in the OLC
|
||||||
|
label = "Empty field"
|
||||||
|
# initial value of field if not given
|
||||||
|
initial = None
|
||||||
|
# actions available on this field. Available actions
|
||||||
|
# are replace, edit, append, remove, clear, help
|
||||||
|
actions = ['replace', 'edit', 'remove', 'clear', 'help']
|
||||||
|
|
||||||
|
def __init__(self, olcsession):
|
||||||
|
self.olcsession = olcsession
|
||||||
|
self._value_history = deque([self.initial], _LEN_HISTORY)
|
||||||
|
self._history_pos = 0
|
||||||
|
self._has_changed = False
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.display()
|
||||||
|
|
||||||
|
# storing data to the field in a history-aware way
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self._value_history[self._history_pos]
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value):
|
||||||
|
"""
|
||||||
|
Update field value by updating the history.
|
||||||
|
"""
|
||||||
|
original_value = value
|
||||||
|
try:
|
||||||
|
value = self.validate(value)
|
||||||
|
except Exception as err:
|
||||||
|
errtext = _OLC_VALIDATION_ERROR.format(fieldname=self.key, value=original_value, error=err)
|
||||||
|
self.olcsession.caller.msg(errtext)
|
||||||
|
return
|
||||||
|
if (self._value_history and isinstance(value, (basestring, bool, int, float)) and
|
||||||
|
self._value_history[0] == value):
|
||||||
|
# don't change/update history if re-adding the same thing
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self._has_changed = True
|
||||||
|
self._history_pos = 0
|
||||||
|
self._value_history.appendleft(value)
|
||||||
|
|
||||||
|
@value.deleter
|
||||||
|
def value(self):
|
||||||
|
self.history_pos = 0
|
||||||
|
self._value_history.appendleft(self.initial)
|
||||||
|
|
||||||
|
def history(self, step):
|
||||||
|
"""
|
||||||
|
Change history position.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
step (int): Step in the history stack. Positive movement
|
||||||
|
means moving futher back in history (with a maximum
|
||||||
|
of `settings.OLC_HISTORY_LENGTH`, negative steps
|
||||||
|
moves towards recent history (with 0 being the latest
|
||||||
|
value).
|
||||||
|
|
||||||
|
"""
|
||||||
|
self._history_pos = min(len(self.value_history)-1, max(0, self._history_pos + step))
|
||||||
|
|
||||||
|
def has_changed(self):
|
||||||
|
"""
|
||||||
|
Check if this field has changed.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
changed (bool): If the field changed or not.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return bool(self._has_changed)
|
||||||
|
|
||||||
|
# overloadable methods
|
||||||
|
|
||||||
|
def from_entity(self, entity, **kwargs):
|
||||||
|
"""
|
||||||
|
Populate this field from an entity.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entity (any): An object to use for
|
||||||
|
populating this field (like an Object).
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def to_prototype(self, prototype):
|
||||||
|
"""
|
||||||
|
Store this field value in a prototype.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prototype (dict): The prototype dict
|
||||||
|
to update with the value of this field.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def validate(self, value, **kwargs):
|
||||||
|
"""
|
||||||
|
Validate/preprocess data to store in this field.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (any): An input value to
|
||||||
|
validate
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
any (any): Optional info to send to field.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
validated_value (any): The value, correctly
|
||||||
|
validated and/or processed to store in this field.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Exception: If the field was given an
|
||||||
|
invalid value to validate.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
def display(self):
|
||||||
|
"""
|
||||||
|
How to display the field contents in the OLC display.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
|
# OLCFields for all the standard model properties
|
||||||
|
# key, location, destination, home, aliases,
|
||||||
|
# permissions, tags, attributes
|
||||||
|
# ...
|
||||||
|
|
||||||
|
|
||||||
|
class OLCKeyField(OLCField):
|
||||||
|
"""
|
||||||
|
The name (key) of the object is its main identifier, used
|
||||||
|
throughout listings even if may not always be visible to
|
||||||
|
the end user.
|
||||||
|
"""
|
||||||
|
key = 'Name'
|
||||||
|
required = True
|
||||||
|
label = "The object's name"
|
||||||
|
|
||||||
|
def from_entity(self, entity, **kwargs):
|
||||||
|
self.value = entity.db_key
|
||||||
|
|
||||||
|
def to_prototype(self, prototype):
|
||||||
|
prototype['key'] = self.value
|
||||||
|
|
||||||
|
|
||||||
|
class OLCLocationField(OLCField):
|
||||||
|
"""
|
||||||
|
An object's location is usually a Room but could be any
|
||||||
|
other in-game entity. By convention, Rooms themselves have
|
||||||
|
a None location. Objects are otherwise only placed in a
|
||||||
|
None location to take them out of the game.
|
||||||
|
"""
|
||||||
|
key = 'Location'
|
||||||
|
required = False
|
||||||
|
label = "The object's current location"
|
||||||
|
|
||||||
|
def validate(self, value):
|
||||||
|
return self.olcsession.search_by_string(value)
|
||||||
|
|
||||||
|
def from_entity(self, entity, **kwargs):
|
||||||
|
self.value = entity.db_location
|
||||||
|
|
||||||
|
def to_prototype(self, prototype):
|
||||||
|
prototype['location'] = self.value
|
||||||
|
|
||||||
|
|
||||||
|
class OLCHomeField(OLCField):
|
||||||
|
"""
|
||||||
|
An object's home location acts as a fallback when various
|
||||||
|
extreme situations occur. An example is when a location is
|
||||||
|
deleted - all its content (except exits) are then not deleted
|
||||||
|
but are moved to each object's home location.
|
||||||
|
"""
|
||||||
|
key = 'Home'
|
||||||
|
required = True
|
||||||
|
label = "The object's home location"
|
||||||
|
|
||||||
|
def validate(self, value):
|
||||||
|
return self.olcsession.search_by_string(value)
|
||||||
|
|
||||||
|
def from_entity(self, entity, **kwargs):
|
||||||
|
self.value = entity.db_home
|
||||||
|
|
||||||
|
def to_prototype(self, prototype):
|
||||||
|
prototype['home'] = self.value
|
||||||
|
|
||||||
|
class OLCDestinationField(OLCField):
|
||||||
|
"""
|
||||||
|
An object's destination is usually not set unless the object
|
||||||
|
represents an exit between game locations. If set, the
|
||||||
|
destination should be set to the location you get to when
|
||||||
|
passing through this exit.
|
||||||
|
|
||||||
|
"""
|
||||||
|
key = 'Destination'
|
||||||
|
required = False
|
||||||
|
label = "The object's (usually exit's) destination"
|
||||||
|
|
||||||
|
def validate(self, value):
|
||||||
|
return self.olcsession.search_by_string(value)
|
||||||
|
|
||||||
|
def from_entity(self, entity, **kwargs):
|
||||||
|
self.value = entity.db_destination
|
||||||
|
|
||||||
|
def to_prototype(self, prototype):
|
||||||
|
prototype['destination'] = self.value
|
||||||
|
|
||||||
|
|
||||||
|
class OLCAliasField(OLCField):
|
||||||
|
"""
|
||||||
|
Specify as a comma-separated list. Use quotes around the
|
||||||
|
alias if the alias itself contains a comma.
|
||||||
|
|
||||||
|
Aliases are alternate names for an object. An alias is just
|
||||||
|
as fast to search for as a key and two objects are assumed
|
||||||
|
to have the same name is *either* their name or any of their
|
||||||
|
aliases match.
|
||||||
|
|
||||||
|
"""
|
||||||
|
key = 'Aliases'
|
||||||
|
required = False
|
||||||
|
label = "The object's alternative name or names"
|
||||||
|
actions = OLCField.actions + ['append']
|
||||||
|
|
||||||
|
def validate(self, value):
|
||||||
|
return split_by_comma(value)
|
||||||
|
|
||||||
|
def from_entity(self, entity, **kwargs):
|
||||||
|
self.value = list(entity.db_aliases.all())
|
||||||
|
|
||||||
|
def to_prototype(self, prototype):
|
||||||
|
prototype['aliases'] = self.value
|
||||||
|
|
||||||
|
|
||||||
|
class OLCTagField(OLCField):
|
||||||
|
"""
|
||||||
|
Specify as a comma-separated list of tagname or tagname:category.
|
||||||
|
|
||||||
|
Aliases are alternate names for an object. An alias is just
|
||||||
|
as fast to search for as a key and two objects are assumed
|
||||||
|
to have the same name is *either* their name or any of their
|
||||||
|
aliases match.
|
||||||
|
|
||||||
|
"""
|
||||||
|
key = 'Aliases'
|
||||||
|
required = False
|
||||||
|
label = "The object's (usually exit's) destination"
|
||||||
|
actions = OLCField.actions + ['append']
|
||||||
|
|
||||||
|
def validate(self, value):
|
||||||
|
return [tagstr.split(':', 1) if ':' in tagstr else (tagstr, None)
|
||||||
|
for tagstr in split_by_comma(value)]
|
||||||
|
|
||||||
|
def from_entity(self, entity, **kwargs):
|
||||||
|
self.value = entity.tags.all(return_key_and_category=True)
|
||||||
|
|
||||||
|
def to_prototype(self, prototype):
|
||||||
|
prototype['tags'] = self.value
|
||||||
|
|
||||||
83
evennia/utils/olc/olc_menutree.py
Normal file
83
evennia/utils/olc/olc_menutree.py
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
"""
|
||||||
|
This describes the menu structure/logic of the OLC system editor, using the EvMenu subsystem. The
|
||||||
|
various nodes are modular and will when possible make use of the various utilities of the OLC rather
|
||||||
|
than hard-coding things in each node.
|
||||||
|
|
||||||
|
Menu structure:
|
||||||
|
|
||||||
|
start:
|
||||||
|
new object
|
||||||
|
edit object <dbref>
|
||||||
|
manage prototypes
|
||||||
|
export session to batchcode file (immortals only)
|
||||||
|
|
||||||
|
new/edit object:
|
||||||
|
Protoype
|
||||||
|
Typeclass
|
||||||
|
Key
|
||||||
|
Location
|
||||||
|
Destination
|
||||||
|
PErmissions
|
||||||
|
LOcks
|
||||||
|
Attributes
|
||||||
|
TAgs
|
||||||
|
Scripts
|
||||||
|
|
||||||
|
create/update object
|
||||||
|
copy object
|
||||||
|
save prototype
|
||||||
|
save/delete object
|
||||||
|
update existing objects
|
||||||
|
|
||||||
|
manage prototypes
|
||||||
|
list prototype
|
||||||
|
search prototype
|
||||||
|
import prototype (from global store)
|
||||||
|
|
||||||
|
export session
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def node_top(caller, raw_input):
|
||||||
|
# top level node
|
||||||
|
# links to edit, manage, export
|
||||||
|
text = """OnLine Creation System"""
|
||||||
|
options = ({"key": ("|yN|new", "new", "n"),
|
||||||
|
"desc": "New object",
|
||||||
|
"goto": "node_new_top",
|
||||||
|
"exec": _obj_to_prototype},
|
||||||
|
{"key": ("|yE|ndit", "edit", "e", "m"),
|
||||||
|
"desc": "Edit existing object",
|
||||||
|
"goto": "node_edit_top",
|
||||||
|
"exec": _obj_to_prototype},
|
||||||
|
{"key": ("|yP|nrototype", "prototype", "manage", "p", "m"),
|
||||||
|
"desc": "Manage prototypes",
|
||||||
|
"goto": "node_prototype_top"},
|
||||||
|
{"key": ("E|yx|nport", "export", "x"),
|
||||||
|
"desc": "Export to prototypes",
|
||||||
|
"goto": "node_prototype_top"},
|
||||||
|
{"key": ("|yQ|nuit", "quit", "q"),
|
||||||
|
"desc": "Quit OLC",
|
||||||
|
"goto": "node_quit"},)
|
||||||
|
return text, options
|
||||||
|
|
||||||
|
def node_quit(caller, raw_input):
|
||||||
|
return 'Exiting.', None
|
||||||
|
|
||||||
|
def node_new_top(caller, raw_input):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def node_edit_top(caller, raw_input):
|
||||||
|
# edit top level
|
||||||
|
text = """Edit object"""
|
||||||
|
|
||||||
|
|
||||||
|
def node_prototype_top(caller, raw_input):
|
||||||
|
# manage prototypes
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def node_export_top(caller, raw_input):
|
||||||
|
# export top level
|
||||||
|
pass
|
||||||
|
|
||||||
13
evennia/utils/olc/olc_utils.py
Normal file
13
evennia/utils/olc/olc_utils.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
"""
|
||||||
|
Miscellaneous utilities for the OLC system.
|
||||||
|
|
||||||
|
"""
|
||||||
|
import csv
|
||||||
|
|
||||||
|
|
||||||
|
def search_by_string(olcsession, query):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def split_by_comma(string):
|
||||||
|
return csv.reader([string], skipinitialspace=True)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue