mirror of
https://github.com/evennia/evennia.git
synced 2026-03-28 02:36:32 +01:00
Reshuffling the Evennia package into the new template paradigm.
This commit is contained in:
parent
2846e64833
commit
2b3a32e447
371 changed files with 17250 additions and 304 deletions
|
|
@ -1,418 +0,0 @@
|
|||
"""
|
||||
This file contains the core methods for the Batch-command- and
|
||||
Batch-code-processors respectively. In short, these are two different
|
||||
ways to build a game world using a normal text-editor without having
|
||||
to do so 'on the fly' in-game. They also serve as an automatic backup
|
||||
so you can quickly recreate a world also after a server reset. The
|
||||
functions in this module is meant to form the backbone of a system
|
||||
called and accessed through game commands.
|
||||
|
||||
The Batch-command processor is the simplest. It simply runs a list of
|
||||
in-game commands in sequence by reading them from a text file. The
|
||||
advantage of this is that the builder only need to remember the normal
|
||||
in-game commands. They are also executing with full permission checks
|
||||
etc, making it relatively safe for builders to use. The drawback is
|
||||
that in-game there is really a builder-character walking around
|
||||
building things, and it can be important to create rooms and objects
|
||||
in the right order, so the character can move between them. Also
|
||||
objects that affects players (such as mobs, dark rooms etc) will
|
||||
affect the building character too, requiring extra care to turn
|
||||
off/on.
|
||||
|
||||
The Batch-code processor is a more advanced system that accepts full
|
||||
Python code, executing in chunks. The advantage of this is much more
|
||||
power; practically anything imaginable can be coded and handled using
|
||||
the batch-code processor. There is no in-game character that moves and
|
||||
that can be affected by what is being built - the database is
|
||||
populated on the fly. The drawback is safety and entry threshold - the
|
||||
code is executed as would any server code, without mud-specific
|
||||
permission checks and you have full access to modifying objects
|
||||
etc. You also need to know Python and Evennia's API. Hence it's
|
||||
recommended that the batch-code processor is limited only to
|
||||
superusers or highly trusted staff.
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
Batch-command processor file syntax
|
||||
|
||||
The batch-command processor accepts 'batchcommand files' e.g
|
||||
'batch.ev', containing a sequence of valid evennia commands in a
|
||||
simple format. The engine runs each command in sequence, as if they
|
||||
had been run at the game prompt.
|
||||
|
||||
Each evennia command must be delimited by a line comment to mark its
|
||||
end.
|
||||
|
||||
#INSERT path.batchcmdfile - this as the first entry on a line will
|
||||
import and run a batch.ev file in this position, as if it was
|
||||
written in this file.
|
||||
|
||||
This way entire game worlds can be created and planned offline; it is
|
||||
especially useful in order to create long room descriptions where a
|
||||
real offline text editor is often much better than any online text
|
||||
editor or prompt.
|
||||
|
||||
Example of batch.ev file:
|
||||
----------------------------
|
||||
|
||||
# batch file
|
||||
# all lines starting with # are comments; they also indicate
|
||||
# that a command definition is over.
|
||||
|
||||
@create box
|
||||
|
||||
# this comment ends the @create command.
|
||||
|
||||
@set box/desc = A large box.
|
||||
|
||||
Inside are some scattered piles of clothing.
|
||||
|
||||
|
||||
It seems the bottom of the box is a bit loose.
|
||||
|
||||
# Again, this comment indicates the @set command is over. Note how
|
||||
# the description could be freely added. Excess whitespace on a line
|
||||
# is ignored. An empty line in the command definition is parsed as a \n
|
||||
# (so two empty lines becomes a new paragraph).
|
||||
|
||||
@teleport #221
|
||||
|
||||
# (Assuming #221 is a warehouse or something.)
|
||||
# (remember, this comment ends the @teleport command! Don'f forget it)
|
||||
|
||||
# Example of importing another file at this point.
|
||||
#IMPORT examples.batch
|
||||
|
||||
@drop box
|
||||
|
||||
# Done, the box is in the warehouse! (this last comment is not necessary to
|
||||
# close the @drop command since it's the end of the file)
|
||||
-------------------------
|
||||
|
||||
An example batch file is game/gamesrc/commands/examples/batch_example.ev.
|
||||
|
||||
|
||||
==========================================================================
|
||||
|
||||
|
||||
Batch-code processor file syntax
|
||||
|
||||
The Batch-code processor accepts full python modules (e.g. "batch.py")
|
||||
that looks identical to normal Python files with a few exceptions that
|
||||
allows them to the executed in blocks. This way of working assures a
|
||||
sequential execution of the file and allows for features like stepping
|
||||
from block to block (without executing those coming before), as well
|
||||
as automatic deletion of created objects etc. You can however also run
|
||||
a batch-code python file directly using Python (and can also be de).
|
||||
|
||||
Code blocks are separated by python comments starting with special
|
||||
code words.
|
||||
|
||||
#HEADER - this denotes commands global to the entire file, such as
|
||||
import statements and global variables. They will
|
||||
automatically be pasted at the top of all code
|
||||
blocks. Observe that changes to these variables made in one
|
||||
block is not preserved between blocks!
|
||||
#CODE
|
||||
#CODE (info)
|
||||
#CODE (info) objname1, objname1, ... -
|
||||
This designates a code block that will be executed like a
|
||||
stand-alone piece of code together with any #HEADER
|
||||
defined. (info) text is used by the interactive mode to
|
||||
display info about the node to run. <objname>s mark the
|
||||
(variable-)names of objects created in the code, and which
|
||||
may be auto-deleted by the processor if desired (such as
|
||||
when debugging the script). E.g., if the code contains the
|
||||
command myobj = create.create_object(...), you could put
|
||||
'myobj' in the #CODE header regardless of what the created
|
||||
object is actually called in-game.
|
||||
#INSERT path.filename - This imports another batch_code.py file and
|
||||
runs it in the given position. paths are given as python
|
||||
path. The inserted file will retain its own HEADERs which
|
||||
will not be mixed with the HEADERs of the file importing
|
||||
this file.
|
||||
|
||||
The following variables are automatically made available for the script:
|
||||
|
||||
caller - the object executing the script
|
||||
|
||||
Example batch.py file
|
||||
-----------------------------------
|
||||
|
||||
#HEADER
|
||||
|
||||
import traceback
|
||||
from django.config import settings
|
||||
from src.utils import create
|
||||
from game.gamesrc.typeclasses import basetypes
|
||||
|
||||
GOLD = 10
|
||||
|
||||
#CODE obj, obj2
|
||||
|
||||
obj = create.create_object(basetypes.Object)
|
||||
obj2 = create.create_object(basetypes.Object)
|
||||
obj.location = caller.location
|
||||
obj.db.gold = GOLD
|
||||
caller.msg("The object was created!")
|
||||
|
||||
#INSERT another_batch_file
|
||||
|
||||
#CODE
|
||||
|
||||
script = create.create_script()
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
import codecs
|
||||
import traceback
|
||||
import sys
|
||||
#from traceback import format_exc
|
||||
from django.conf import settings
|
||||
from src.utils import utils
|
||||
#from game import settings as settings_module
|
||||
|
||||
ENCODINGS = settings.ENCODINGS
|
||||
CODE_INFO_HEADER = re.compile(r"\(.*?\)")
|
||||
|
||||
RE_INSERT = re.compile(r"^\#INSERT (.*)", re.MULTILINE)
|
||||
RE_CLEANBLOCK = re.compile(r"^\#.*?$|^\s*$", re.MULTILINE)
|
||||
RE_CMD_SPLIT = re.compile(r"^\#.*?$", re.MULTILINE)
|
||||
RE_CODE_SPLIT = re.compile(r"(^\#CODE.*?$|^\#HEADER)$", re.MULTILINE)
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Helper function
|
||||
#------------------------------------------------------------
|
||||
|
||||
def read_batchfile(pythonpath, file_ending='.py'):
|
||||
"""
|
||||
This reads the contents of a batch-file.
|
||||
Filename is considered to be a python path to a batch file
|
||||
relative the directory specified in settings.py.
|
||||
|
||||
file_ending specify which batchfile ending should be
|
||||
assumed (.ev or .py). The ending should not be included
|
||||
in the python path.
|
||||
"""
|
||||
|
||||
# open the file
|
||||
if pythonpath and not (pythonpath.startswith('src.') or pythonpath.startswith('game.')
|
||||
or pythonpath.startswith('contrib.')):
|
||||
abspaths = []
|
||||
for basepath in settings.BASE_BATCHPROCESS_PATHS:
|
||||
abspaths.append(utils.pypath_to_realpath("%s.%s" % (basepath, pythonpath), file_ending))
|
||||
else:
|
||||
abspaths = [utils.pypath_to_realpath(pythonpath, file_ending)]
|
||||
text, fobj = None, None
|
||||
fileerr, decoderr = [], []
|
||||
for abspath in abspaths:
|
||||
# try different paths, until we get a match
|
||||
# we read the file directly into unicode.
|
||||
for file_encoding in ENCODINGS:
|
||||
# try different encodings, in order
|
||||
try:
|
||||
fobj = codecs.open(abspath, 'r', encoding=file_encoding)
|
||||
text = fobj.read()
|
||||
except IOError, e:
|
||||
# could not find the file
|
||||
fileerr.append(str(e))
|
||||
break
|
||||
except (ValueError, UnicodeDecodeError), e:
|
||||
# this means an encoding error; try another encoding
|
||||
decoderr.append(str(e))
|
||||
continue
|
||||
break
|
||||
if not fobj:
|
||||
raise IOError("\n".join(fileerr))
|
||||
if not text:
|
||||
raise UnicodeDecodeError("\n".join(decoderr))
|
||||
|
||||
return text
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# Batch-command processor
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
|
||||
class BatchCommandProcessor(object):
|
||||
"""
|
||||
This class implements a batch-command processor.
|
||||
|
||||
"""
|
||||
def parse_file(self, pythonpath):
|
||||
"""
|
||||
This parses the lines of a batchfile according to the following
|
||||
rules:
|
||||
1) # at the beginning of a line marks the end of the command before
|
||||
it. It is also a comment and any number of # can exist on
|
||||
subsequent lines (but not inside comments).
|
||||
2) #INSERT at the beginning of a line imports another
|
||||
batch-cmd file file and pastes it into the batch file as if
|
||||
it was written there.
|
||||
3) Commands are placed alone at the beginning of a line and their
|
||||
arguments are considered to be everything following (on any
|
||||
number of lines) until the next comment line beginning with #.
|
||||
4) Newlines are ignored in command definitions
|
||||
5) A completely empty line in a command line definition is condered
|
||||
a newline (so two empty lines is a paragraph).
|
||||
6) Excess spaces and indents inside arguments are stripped.
|
||||
|
||||
"""
|
||||
|
||||
text = "".join(read_batchfile(pythonpath, file_ending='.ev'))
|
||||
|
||||
def replace_insert(match):
|
||||
"Map replace entries"
|
||||
return "\#\n".join(self.parse_file(match.group(1)))
|
||||
|
||||
# insert commands from inserted files
|
||||
text = RE_INSERT.sub(replace_insert, text)
|
||||
#text = re.sub(r"^\#INSERT (.*?)", replace_insert, text, flags=re.MULTILINE)
|
||||
# get all commands
|
||||
commands = RE_CMD_SPLIT.split(text)
|
||||
#commands = re.split(r"^\#.*?$", text, flags=re.MULTILINE)
|
||||
#remove eventual newline at the end of commands
|
||||
commands = [c.strip('\r\n') for c in commands]
|
||||
commands = [c for c in commands if c]
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# Batch-code processor
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
|
||||
def tb_filename(tb):
|
||||
"Helper to get filename from traceback"
|
||||
return tb.tb_frame.f_code.co_filename
|
||||
|
||||
|
||||
def tb_iter(tb):
|
||||
while tb is not None:
|
||||
yield tb
|
||||
tb = tb.tb_next
|
||||
|
||||
|
||||
class BatchCodeProcessor(object):
|
||||
"""
|
||||
This implements a batch-code processor
|
||||
|
||||
"""
|
||||
|
||||
def parse_file(self, pythonpath, debug=False):
|
||||
"""
|
||||
This parses the lines of a batchfile according to the following
|
||||
rules:
|
||||
|
||||
1) Lines starting with #HEADER starts a header block (ends other blocks)
|
||||
2) Lines starting with #CODE begins a code block (ends other blocks)
|
||||
3) #CODE headers may be of the following form:
|
||||
#CODE (info) objname, objname2, ...
|
||||
4) Lines starting with #INSERT are on form #INSERT filename.
|
||||
3) All lines outside blocks are stripped.
|
||||
4) All excess whitespace beginning/ending a block is stripped.
|
||||
|
||||
"""
|
||||
|
||||
text = "".join(read_batchfile(pythonpath, file_ending='.py'))
|
||||
|
||||
def clean_block(text):
|
||||
text = RE_CLEANBLOCK.sub("", text)
|
||||
#text = re.sub(r"^\#.*?$|^\s*$", "", text, flags=re.MULTILINE)
|
||||
return "\n".join([line for line in text.split("\n") if line])
|
||||
|
||||
def replace_insert(match):
|
||||
"Map replace entries"
|
||||
return "\#\n".join(self.parse_file(match.group(1)))
|
||||
|
||||
text = RE_INSERT.sub(replace_insert, text)
|
||||
#text = re.sub(r"^\#INSERT (.*?)", replace_insert, text, flags=re.MULTILINE)
|
||||
blocks = RE_CODE_SPLIT.split(text)
|
||||
#blocks = re.split(r"(^\#CODE.*?$|^\#HEADER)$", text, flags=re.MULTILINE)
|
||||
headers = []
|
||||
codes = [] # list of tuples (code, info, objtuple)
|
||||
if blocks:
|
||||
if blocks[0]:
|
||||
# the first block is either empty or an unmarked code block
|
||||
code = clean_block(blocks.pop(0))
|
||||
if code:
|
||||
codes.append((code, ""))
|
||||
iblock = 0
|
||||
for block in blocks[::2]:
|
||||
# loop over every second component; these are the #CODE/#HEADERs
|
||||
if block.startswith("#HEADER"):
|
||||
headers.append(clean_block(blocks[iblock + 1]))
|
||||
elif block.startswith("#CODE"):
|
||||
match = re.search(r"\(.*?\)", block)
|
||||
info = match.group() if match else ""
|
||||
objs = []
|
||||
if debug:
|
||||
# insert auto-delete lines into code
|
||||
objs = block[match.end():].split(",")
|
||||
objs = ["# added by Evennia's debug mode\n%s.delete()" % obj.strip() for obj in objs if obj]
|
||||
# build the code block
|
||||
code = "\n".join([clean_block(blocks[iblock + 1])] + objs)
|
||||
if code:
|
||||
codes.append((code, info))
|
||||
iblock += 2
|
||||
|
||||
# join the headers together to one header
|
||||
headers = "\n".join(headers)
|
||||
if codes:
|
||||
# add the headers at the top of each non-empty block
|
||||
codes = ["%s\n%s\n%s" % ("#CODE %s: " % tup[1], headers, tup[0]) for tup in codes if tup[0]]
|
||||
else:
|
||||
codes = ["#CODE: \n" + headers]
|
||||
return codes
|
||||
|
||||
|
||||
def code_exec(self, code, extra_environ=None, debug=False):
|
||||
"""
|
||||
Execute a single code block, including imports and appending global vars
|
||||
|
||||
extra_environ - dict with environment variables
|
||||
"""
|
||||
# define the execution environment
|
||||
environdict = {"settings_module": settings}
|
||||
environ = "settings_module.configure()"
|
||||
if extra_environ:
|
||||
for key, value in extra_environ.items():
|
||||
environdict[key] = value
|
||||
|
||||
# initializing the django settings at the top of code
|
||||
code = "# auto-added by Evennia\n" \
|
||||
"try: %s\n" \
|
||||
"except RuntimeError: pass\n" \
|
||||
"finally: del settings_module\n%s" % (environ, code)
|
||||
|
||||
# execute the block
|
||||
try:
|
||||
exec(code, environdict)
|
||||
except Exception:
|
||||
etype, value, tb = sys.exc_info()
|
||||
|
||||
fname = tb_filename(tb)
|
||||
for tb in tb_iter(tb):
|
||||
if fname != tb_filename(tb):
|
||||
break
|
||||
lineno = tb.tb_lineno - 1
|
||||
err = ""
|
||||
for iline, line in enumerate(code.split("\n")):
|
||||
if iline == lineno:
|
||||
err += "\n{w%02i{n: %s" % (iline + 1, line)
|
||||
elif lineno - 5 < iline < lineno + 5:
|
||||
err += "\n%02i: %s" % (iline + 1, line)
|
||||
|
||||
err += "\n".join(traceback.format_exception(etype, value, tb))
|
||||
return err
|
||||
return None
|
||||
|
||||
BATCHCMD = BatchCommandProcessor()
|
||||
BATCHCODE = BatchCodeProcessor()
|
||||
Loading…
Add table
Add a link
Reference in a new issue