Add non-working, initial concept for olc system.

This commit is contained in:
Griatch 2017-04-16 17:31:13 +02:00
parent ee4dd20fd9
commit 00b8dd4881
5 changed files with 535 additions and 0 deletions

View file

148
evennia/utils/olc/olc.py Normal file
View 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)

View 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

View 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

View 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)