Add experimental evmenu helper template, currently non-persistent
This commit is contained in:
parent
c44681ebcf
commit
78c7214a46
1 changed files with 291 additions and 0 deletions
291
evennia/contrib/gametutorial.py
Normal file
291
evennia/contrib/gametutorial.py
Normal file
|
|
@ -0,0 +1,291 @@
|
||||||
|
"""
|
||||||
|
Game tutor
|
||||||
|
|
||||||
|
Evennia contrib - Griatch 2020
|
||||||
|
|
||||||
|
This contrib is a system for easily adding a tutor/tutorial for your game
|
||||||
|
(something that should be considered a necessity for any game ...).
|
||||||
|
|
||||||
|
It consists of a single room that will be created for each player/character
|
||||||
|
wanting to go through the tutorial. The text is presented as a menu of
|
||||||
|
self-sustained 'lessons' that the user can either jump freely between or step
|
||||||
|
through wizard-style. In each lesson, the tutor will track progress (for
|
||||||
|
example the user may be asked to try out a certain command, and the tutor will
|
||||||
|
not move on until that command has been tried).
|
||||||
|
::
|
||||||
|
# node Start
|
||||||
|
|
||||||
|
Neque ea alias perferendis molestiae eligendi. Debitis exercitationem
|
||||||
|
exercitationem quas blanditiis quisquam officia ut. Fugit aut fugit enim quia
|
||||||
|
non. Earum et excepturi animi ex esse accusantium et. Id adipisci eos enim
|
||||||
|
ratione.
|
||||||
|
|
||||||
|
## options
|
||||||
|
|
||||||
|
1: first option -> node1
|
||||||
|
2: second option -> node2
|
||||||
|
3: node3 -> gotonode3()
|
||||||
|
next;n: node2
|
||||||
|
top: start
|
||||||
|
>input: return to go back -> start
|
||||||
|
>input foo*: foo()
|
||||||
|
>input bar*: bar()
|
||||||
|
|
||||||
|
# node node1
|
||||||
|
|
||||||
|
Neque ea alias perferendis molestiae eligendi. Debitis exercitationem
|
||||||
|
exercitationem quas blanditiis quisquam officia ut. Fugit aut fugit enim quia
|
||||||
|
non. Earum et excepturi animi ex esse accusantium et. Id adipisci eos enim
|
||||||
|
ratione.
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from evennia import EvMenu
|
||||||
|
from fnmatch import fnmatch
|
||||||
|
|
||||||
|
# support # NODE name, #NODE name ...
|
||||||
|
_RE_NODE = re.compile(r"#\s*?NODE\s+?(?P<nodename>\S+?)$", re.I + re.M)
|
||||||
|
_RE_OPTIONS_SEP = re.compile(r"##\s*?OPTIONS\s*?$", re.I + re.M)
|
||||||
|
_RE_CALLABLE = re.compile(r"\S+?\(\)", re.I + re.M)
|
||||||
|
|
||||||
|
|
||||||
|
def gotofunc(caller, raw_string, **kwargs):
|
||||||
|
goto = kwargs['goto']
|
||||||
|
callables = kwargs['callables']
|
||||||
|
if _RE_CALLABLE.match(goto):
|
||||||
|
gotofunc = goto.strip()[:-2]
|
||||||
|
if gotofunc in callables:
|
||||||
|
return callables[gotofunc](caller, raw_string, **kwargs)
|
||||||
|
return goto
|
||||||
|
|
||||||
|
def inputgotofunc(caller, raw_string, **kwargs):
|
||||||
|
gotomap = kwargs['gotomap']
|
||||||
|
callables = kwargs['callables']
|
||||||
|
|
||||||
|
# start with glob patterns
|
||||||
|
for pattern, goto in gotomap.items():
|
||||||
|
if fnmatch(raw_string.lower(), pattern):
|
||||||
|
if _RE_CALLABLE.match(goto):
|
||||||
|
gotofunc = goto.strip()[:-2]
|
||||||
|
if gotofunc in callables:
|
||||||
|
return callables[gotofunc](caller, raw_string, **kwargs)
|
||||||
|
return goto
|
||||||
|
# no glob pattern match; try regex
|
||||||
|
for pattern, goto in gotomap.items():
|
||||||
|
if re.match(pattern, raw_string.lower(), flags=re.I + re.M):
|
||||||
|
if _RE_CALLABLE.match(goto):
|
||||||
|
gotofunc = goto.strip()[:-2]
|
||||||
|
if gotofunc in callables:
|
||||||
|
return callables[gotofunc](caller, raw_string, **kwargs)
|
||||||
|
return goto
|
||||||
|
# no match, rerun current node
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def generated_node(caller, raw_string, text="", options=None,
|
||||||
|
nodename="", **kwargs):
|
||||||
|
return text, options
|
||||||
|
|
||||||
|
|
||||||
|
class ParseMenuForm:
|
||||||
|
|
||||||
|
def __init__(self, caller, formstr, callables=None):
|
||||||
|
self.caller = caller
|
||||||
|
self.formstr = formstr
|
||||||
|
self.callables = callables or {}
|
||||||
|
self.menutree = self.parse(formstr)
|
||||||
|
|
||||||
|
def _generate_node(self, nodename, text, options):
|
||||||
|
"""
|
||||||
|
Generate a node from the parsed string
|
||||||
|
"""
|
||||||
|
def node(caller, raw_string, nodename=nodename, **kwargs):
|
||||||
|
return text, options
|
||||||
|
return node
|
||||||
|
|
||||||
|
def _parse_options(self, optiontxt):
|
||||||
|
"""
|
||||||
|
Parse option section into option dict.
|
||||||
|
"""
|
||||||
|
options = []
|
||||||
|
optiontxt = optiontxt[0].strip() if optiontxt else ""
|
||||||
|
optionlist = [optline.strip() for optline in optiontxt.split("\n")]
|
||||||
|
inputparsemap = {}
|
||||||
|
|
||||||
|
for inum, optline in enumerate(optionlist):
|
||||||
|
if optline.startswith("#") or not ":" in optline:
|
||||||
|
# skip comments or invalid syntax
|
||||||
|
continue
|
||||||
|
key = ""
|
||||||
|
desc = ""
|
||||||
|
pattern = None
|
||||||
|
|
||||||
|
key, goto = [part.strip() for part in optline.split(":", 1)]
|
||||||
|
|
||||||
|
# desc -> goto
|
||||||
|
if "->" in goto:
|
||||||
|
desc, goto = [part.strip() for part in goto.split("->", 1)]
|
||||||
|
|
||||||
|
# parse key [pattern]
|
||||||
|
key = [part.strip() for part in key.split(";")]
|
||||||
|
if not key:
|
||||||
|
# fall back to this being the Nth option
|
||||||
|
key = [f"{inum + 1}"]
|
||||||
|
main_key = key[0]
|
||||||
|
|
||||||
|
if main_key.startswith(">input"):
|
||||||
|
key[0] = "_default"
|
||||||
|
pattern = main_key[6:].strip()
|
||||||
|
|
||||||
|
if pattern is not None:
|
||||||
|
# if we have a pattern, build the arguments for _default later
|
||||||
|
inputparsemap[pattern] = goto
|
||||||
|
else:
|
||||||
|
# a regular goto string target
|
||||||
|
option = {
|
||||||
|
"key": key,
|
||||||
|
"goto": (gotofunc, {
|
||||||
|
"goto": goto,
|
||||||
|
"callables": self.callables})
|
||||||
|
}
|
||||||
|
if desc:
|
||||||
|
option["desc"] = desc
|
||||||
|
options.append(option)
|
||||||
|
|
||||||
|
if inputparsemap:
|
||||||
|
# if this exists we must create a _default entry too
|
||||||
|
options.append({
|
||||||
|
"key": "_default",
|
||||||
|
"goto": (inputgotofunc, {
|
||||||
|
"gotomap": inputparsemap,
|
||||||
|
"callables": self.callables
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return options
|
||||||
|
|
||||||
|
def parse(self, formstr):
|
||||||
|
"""
|
||||||
|
Parse the menu string format into a node tree.
|
||||||
|
"""
|
||||||
|
nodetree = {}
|
||||||
|
errors = []
|
||||||
|
splits = _RE_NODE.split(formstr)
|
||||||
|
splits = splits[1:] if splits else []
|
||||||
|
|
||||||
|
# from evennia import set_trace;set_trace(term_size=(140,120))
|
||||||
|
|
||||||
|
for node_ind in range(0, len(splits), 2):
|
||||||
|
nodename, nodetxt = splits[node_ind], splits[node_ind + 1]
|
||||||
|
text, *optiontxt = _RE_OPTIONS_SEP.split(nodetxt, maxsplit=2)
|
||||||
|
options = self._parse_options(optiontxt)
|
||||||
|
nodetree[nodename] = self._generate_node(nodename, text, options)
|
||||||
|
|
||||||
|
return nodetree
|
||||||
|
|
||||||
|
|
||||||
|
# class GameTutor(EvMenu):
|
||||||
|
#
|
||||||
|
# # tutorial helpers
|
||||||
|
#
|
||||||
|
# @staticmethod
|
||||||
|
# def nextprev(prevnode, nextnode, **kwargs):
|
||||||
|
# """
|
||||||
|
# Add return to options to add a prev/next entry
|
||||||
|
# """
|
||||||
|
# if kwargs:
|
||||||
|
# prevnode = (prevnode, kwargs)
|
||||||
|
# nextnode = (nextnode, kwargs)
|
||||||
|
#
|
||||||
|
# return (
|
||||||
|
# {"key": ("|w[p]|nrev", "prev", "p"),
|
||||||
|
# "goto": prevnode},
|
||||||
|
# {"key": ("|w[n]|next", "next", "n"),
|
||||||
|
# "goto": nextnode}
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
def test_generator(caller):
|
||||||
|
|
||||||
|
MENU_DESC = \
|
||||||
|
"""
|
||||||
|
# node start
|
||||||
|
|
||||||
|
Neque ea alias perferendis molestiae eligendi. Debitis exercitationem
|
||||||
|
exercitationem quas blanditiis quisquam officia ut. Fugit aut fugit enim quia
|
||||||
|
non. Earum et excepturi animi ex esse accusantium et. Id adipisci eos enim
|
||||||
|
ratione.
|
||||||
|
|
||||||
|
## options
|
||||||
|
|
||||||
|
1: first option -> node1
|
||||||
|
2: second option -> node2
|
||||||
|
3: node3 -> gotonode3()
|
||||||
|
next;n: node2
|
||||||
|
top: start
|
||||||
|
>input: return to go back -> start
|
||||||
|
>input foo*: foo()
|
||||||
|
>input bar*: bar()
|
||||||
|
|
||||||
|
|
||||||
|
# node node1
|
||||||
|
|
||||||
|
Neque ea alias perferendis molestiae eligendi. Debitis exercitationem
|
||||||
|
exercitationem quas blanditiis quisquam officia ut. Fugit aut fugit enim quia
|
||||||
|
non. Earum et excepturi animi ex esse accusantium et. Id adipisci eos enim
|
||||||
|
ratione.
|
||||||
|
|
||||||
|
## options
|
||||||
|
|
||||||
|
back: start
|
||||||
|
to node 2: node2
|
||||||
|
run foo (rerun node): foo()
|
||||||
|
|
||||||
|
|
||||||
|
# node node2
|
||||||
|
|
||||||
|
In node 2!
|
||||||
|
|
||||||
|
## options
|
||||||
|
|
||||||
|
back: back to start -> start
|
||||||
|
|
||||||
|
|
||||||
|
# node bar
|
||||||
|
|
||||||
|
In node bar!
|
||||||
|
|
||||||
|
## options
|
||||||
|
|
||||||
|
back: back to start -> start
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def gotonode3(caller, raw_string, **kwargs):
|
||||||
|
print("in gotonode3", caller, raw_string, kwargs)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def foo(caller, raw_string, **kwargs):
|
||||||
|
print("in foo", caller, raw_string, kwargs)
|
||||||
|
return "node2"
|
||||||
|
|
||||||
|
def bar(caller, raw_string, **kwargs):
|
||||||
|
print("in bar", caller, raw_string, kwargs)
|
||||||
|
return "bar"
|
||||||
|
|
||||||
|
callables = {"gotonode3": gotonode3, "foo": foo, "bar": bar}
|
||||||
|
|
||||||
|
mform = ParseMenuForm(caller, MENU_DESC, callables)
|
||||||
|
|
||||||
|
if isinstance(caller, str):
|
||||||
|
print(mform.menutree)
|
||||||
|
else:
|
||||||
|
EvMenu(caller, mform.menutree)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_generator("<GriatchCaller>")
|
||||||
Loading…
Add table
Add a link
Reference in a new issue