mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Removed the superfluous reST build system in docs/sphinx. ReadTheDoc-style rst files are produced using tools directly in the wiki repo now instead.
This commit is contained in:
parent
399f896b03
commit
2306449ee1
81 changed files with 359 additions and 16545 deletions
119
docs/README.txt
119
docs/README.txt
|
|
@ -1,14 +1,19 @@
|
|||
|
||||
===========
|
||||
DOCS README
|
||||
===========
|
||||
|
||||
* Evennia docs and manual
|
||||
EVENNIA DOCUMENTATION
|
||||
=====================
|
||||
|
||||
- The most updated documentation is found in the online wiki,
|
||||
|
||||
http://code.google.com/p/evennia/wiki/Index
|
||||
|
||||
- Evennia is extensively documented. Our manual is the
|
||||
continuously updating online wiki,
|
||||
|
||||
https://github.com/evennia/evennia/wiki
|
||||
|
||||
- Snapshots of the manual are also mirrored in reST
|
||||
form to ReadTheDocs:
|
||||
|
||||
http://evennia.readthedocs.org/en/latest/
|
||||
|
||||
- You can also ask for help from the evennia community,
|
||||
|
||||
http://groups.google.com/group/evennia
|
||||
|
|
@ -17,45 +22,11 @@ DOCS README
|
|||
|
||||
#evennia on the Freenode network
|
||||
|
||||
|
||||
------------------------
|
||||
* Sphinx Manuals
|
||||
------------------------
|
||||
|
||||
The folder docs/sphinx contains a source tree with Evennia's online wiki
|
||||
formatted into reStructuredText for easy (and good looking!) offline
|
||||
browsing or printing.
|
||||
|
||||
To build the sources you need to install Sphinx. Linux users can get it
|
||||
through their package managers (in Debian it's called python-sphinx). Or
|
||||
you can download it here:
|
||||
|
||||
http://sphinx.pocoo.org/index.html
|
||||
|
||||
Go into docs/sphinx and run
|
||||
|
||||
make html
|
||||
|
||||
You will see a lot of output (and probably some errors too, Evennia's docs
|
||||
are not formatted to reST format by default). When done, point your
|
||||
web browser to docs/sphinx/build/html/index.html to see the nice manual.
|
||||
|
||||
If you don't want html output, you can output to a host of other formats,
|
||||
use just "make" for a list of options.
|
||||
|
||||
|
||||
Note: In docs/sphinx are two more dirs, wiki2rest and src2rest. These
|
||||
can be used to create reST-formatted documentation from raw sources.
|
||||
They depend on a host of external libraries however, so best stay away
|
||||
from them unless you are an Evennia dev. Read the headers of the
|
||||
respective *.py files for instructions.
|
||||
|
||||
|
||||
-------------------
|
||||
* Doxygen auto-docs
|
||||
-------------------
|
||||
|
||||
In docs/doxygen you can build the developer auto-docs
|
||||
You can build the developer auto-docs
|
||||
(a fancy searchable index of the entire source tree).
|
||||
This makes use of doxygen, a doc generator that parses
|
||||
the source tree and creates docs on the fly.
|
||||
|
|
@ -63,12 +34,14 @@ DOCS README
|
|||
- Install doxygen (v1.7+)
|
||||
|
||||
Doxygen is available for most platforms from
|
||||
|
||||
http://www.stack.nl/~dimitri/doxygen/
|
||||
|
||||
or through your package manager in Linux.
|
||||
|
||||
- Run
|
||||
|
||||
> doxygen config.dox
|
||||
doxygen config.dox
|
||||
|
||||
This will create the auto-docs in a folder 'doxygen/html'.
|
||||
|
||||
|
|
@ -82,12 +55,62 @@ DOCS README
|
|||
will be created with the latex sources. With the latex
|
||||
processing system installed, then run
|
||||
|
||||
> make
|
||||
make
|
||||
|
||||
in the newly created folder to create the pdf. Be warned
|
||||
however that the pdf docs are >340 pages long!
|
||||
|
||||
- Doxyfile is lavishly documented and allows for plenty of
|
||||
configuration to get the docs to look the way you want.
|
||||
You can also output to other formats suitable for various
|
||||
developer environments, Windows help files etc.
|
||||
- Doxyfile is allows for plenty of configuration to get the
|
||||
docs to look the way you want. You can also output to other
|
||||
formats suitable for various developer environments, Windows
|
||||
help files etc.
|
||||
|
||||
------------------------
|
||||
* Sphinx Manuals
|
||||
------------------------
|
||||
|
||||
If you want to build the reST manuals yourself, you basically need to
|
||||
convert the wiki. First place yourself in a location where you want
|
||||
to clone the wiki repo to, then clone it:
|
||||
|
||||
git clone https://github.com/evennia/evennia.wiki.git
|
||||
|
||||
- Enter this directory and check out the sphinx branch:
|
||||
|
||||
git checkout sphinx
|
||||
|
||||
This branch has, apart from all the wiki pages (*.md files), also has a
|
||||
an extra directory sphinx/ that will hold the converted data.
|
||||
|
||||
- You need Pandoc for the markdown-to-reST conversion:
|
||||
|
||||
http://johnmacfarlane.net/pandoc/installing.html
|
||||
|
||||
You need a rather recent version. The versions coming with some linux
|
||||
repos are too old to support "github-flavoured markdown" conversion.
|
||||
See that page for getting the Haskill build environment in that case.
|
||||
|
||||
- You also need sphinx,
|
||||
|
||||
http://sphinx-doc.org/
|
||||
|
||||
You can most likely get it with 'pip install sphinx' under Linux.
|
||||
|
||||
- With all this in place, go to the pylib/ folder and run the
|
||||
converter script:
|
||||
|
||||
python update_rest_docs.py
|
||||
|
||||
If all goes well, you will see all the wiki pages getting converted.
|
||||
The converted *.rst files will end up in the sphinx/ directory.
|
||||
|
||||
- Finally, go to sphinx/ and run
|
||||
|
||||
make html
|
||||
|
||||
If sphinx is installed, this will create the html files in
|
||||
sphinx/.build. To look at them, point your browser to
|
||||
|
||||
<path-to-wiki-repo>/sphinx/.build/index.html
|
||||
|
||||
|
||||
|
|
|
|||
288
docs/config.dox
Normal file
288
docs/config.dox
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
# Doxyfile 1.8.1.2
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Project related configuration options
|
||||
#---------------------------------------------------------------------------
|
||||
DOXYFILE_ENCODING = UTF-8
|
||||
PROJECT_NAME = "Evennia"
|
||||
PROJECT_NUMBER = Git-Beta
|
||||
PROJECT_BRIEF = Python MUD development system
|
||||
PROJECT_LOGO = ../../src/web/media/images/evennia_logo_small.png
|
||||
OUTPUT_DIRECTORY =
|
||||
CREATE_SUBDIRS = NO
|
||||
OUTPUT_LANGUAGE = English
|
||||
BRIEF_MEMBER_DESC = YES
|
||||
REPEAT_BRIEF = YES
|
||||
ABBREVIATE_BRIEF =
|
||||
ALWAYS_DETAILED_SEC = NO
|
||||
INLINE_INHERITED_MEMB = NO
|
||||
FULL_PATH_NAMES = YES
|
||||
STRIP_FROM_PATH =
|
||||
STRIP_FROM_INC_PATH =
|
||||
SHORT_NAMES = NO
|
||||
JAVADOC_AUTOBRIEF = NO
|
||||
QT_AUTOBRIEF = NO
|
||||
MULTILINE_CPP_IS_BRIEF = NO
|
||||
INHERIT_DOCS = YES
|
||||
SEPARATE_MEMBER_PAGES = NO
|
||||
TAB_SIZE = 8
|
||||
ALIASES =
|
||||
TCL_SUBST =
|
||||
OPTIMIZE_OUTPUT_FOR_C = NO
|
||||
OPTIMIZE_OUTPUT_JAVA = NO
|
||||
OPTIMIZE_FOR_FORTRAN = NO
|
||||
OPTIMIZE_OUTPUT_VHDL = NO
|
||||
EXTENSION_MAPPING =
|
||||
MARKDOWN_SUPPORT = YES
|
||||
BUILTIN_STL_SUPPORT = NO
|
||||
CPP_CLI_SUPPORT = NO
|
||||
SIP_SUPPORT = NO
|
||||
IDL_PROPERTY_SUPPORT = YES
|
||||
DISTRIBUTE_GROUP_DOC = NO
|
||||
SUBGROUPING = YES
|
||||
INLINE_GROUPED_CLASSES = NO
|
||||
INLINE_SIMPLE_STRUCTS = NO
|
||||
TYPEDEF_HIDES_STRUCT = NO
|
||||
SYMBOL_CACHE_SIZE = 0
|
||||
LOOKUP_CACHE_SIZE = 0
|
||||
#---------------------------------------------------------------------------
|
||||
# Build related configuration options
|
||||
#---------------------------------------------------------------------------
|
||||
EXTRACT_ALL = YES
|
||||
EXTRACT_PRIVATE = NO
|
||||
EXTRACT_PACKAGE = NO
|
||||
EXTRACT_STATIC = YES
|
||||
EXTRACT_LOCAL_CLASSES = YES
|
||||
EXTRACT_LOCAL_METHODS = NO
|
||||
EXTRACT_ANON_NSPACES = NO
|
||||
HIDE_UNDOC_MEMBERS = NO
|
||||
HIDE_UNDOC_CLASSES = NO
|
||||
HIDE_FRIEND_COMPOUNDS = NO
|
||||
HIDE_IN_BODY_DOCS = NO
|
||||
INTERNAL_DOCS = NO
|
||||
CASE_SENSE_NAMES = YES
|
||||
HIDE_SCOPE_NAMES = NO
|
||||
SHOW_INCLUDE_FILES = YES
|
||||
FORCE_LOCAL_INCLUDES = NO
|
||||
INLINE_INFO = YES
|
||||
SORT_MEMBER_DOCS = YES
|
||||
SORT_BRIEF_DOCS = NO
|
||||
SORT_MEMBERS_CTORS_1ST = NO
|
||||
SORT_GROUP_NAMES = NO
|
||||
SORT_BY_SCOPE_NAME = NO
|
||||
STRICT_PROTO_MATCHING = NO
|
||||
GENERATE_TODOLIST = YES
|
||||
GENERATE_TESTLIST = YES
|
||||
GENERATE_BUGLIST = YES
|
||||
GENERATE_DEPRECATEDLIST= YES
|
||||
ENABLED_SECTIONS =
|
||||
MAX_INITIALIZER_LINES = 30
|
||||
SHOW_USED_FILES = YES
|
||||
SHOW_FILES = YES
|
||||
SHOW_NAMESPACES = YES
|
||||
FILE_VERSION_FILTER =
|
||||
LAYOUT_FILE =
|
||||
CITE_BIB_FILES =
|
||||
#---------------------------------------------------------------------------
|
||||
# configuration options related to warning and progress messages
|
||||
#---------------------------------------------------------------------------
|
||||
QUIET = NO
|
||||
WARNINGS = YES
|
||||
WARN_IF_UNDOCUMENTED = YES
|
||||
WARN_IF_DOC_ERROR = YES
|
||||
WARN_NO_PARAMDOC = NO
|
||||
WARN_FORMAT = "$file:$line: $text"
|
||||
WARN_LOGFILE =
|
||||
#---------------------------------------------------------------------------
|
||||
# configuration options related to the input files
|
||||
#---------------------------------------------------------------------------
|
||||
INPUT = ../..
|
||||
INPUT_ENCODING = UTF-8
|
||||
FILE_PATTERNS = *.py
|
||||
RECURSIVE = YES
|
||||
EXCLUDE = ../../docs
|
||||
EXCLUDE_SYMLINKS = NO
|
||||
EXCLUDE_PATTERNS = 0*.py
|
||||
EXCLUDE_SYMBOLS =
|
||||
EXAMPLE_PATH =
|
||||
EXAMPLE_PATTERNS =
|
||||
EXAMPLE_RECURSIVE = NO
|
||||
IMAGE_PATH =
|
||||
INPUT_FILTER =
|
||||
FILTER_PATTERNS =
|
||||
FILTER_SOURCE_FILES = NO
|
||||
FILTER_SOURCE_PATTERNS =
|
||||
#---------------------------------------------------------------------------
|
||||
# configuration options related to source browsing
|
||||
#---------------------------------------------------------------------------
|
||||
SOURCE_BROWSER = YES
|
||||
INLINE_SOURCES = NO
|
||||
STRIP_CODE_COMMENTS = YES
|
||||
REFERENCED_BY_RELATION = NO
|
||||
REFERENCES_RELATION = NO
|
||||
REFERENCES_LINK_SOURCE = YES
|
||||
USE_HTAGS = NO
|
||||
VERBATIM_HEADERS = YES
|
||||
#---------------------------------------------------------------------------
|
||||
# configuration options related to the alphabetical class index
|
||||
#---------------------------------------------------------------------------
|
||||
ALPHABETICAL_INDEX = YES
|
||||
COLS_IN_ALPHA_INDEX = 8
|
||||
IGNORE_PREFIX =
|
||||
#---------------------------------------------------------------------------
|
||||
# configuration options related to the HTML output
|
||||
#---------------------------------------------------------------------------
|
||||
GENERATE_HTML = YES
|
||||
HTML_OUTPUT = html
|
||||
HTML_FILE_EXTENSION = .html
|
||||
HTML_HEADER =
|
||||
HTML_FOOTER =
|
||||
HTML_STYLESHEET =
|
||||
HTML_EXTRA_FILES =
|
||||
HTML_COLORSTYLE_HUE = 220
|
||||
HTML_COLORSTYLE_SAT = 100
|
||||
HTML_COLORSTYLE_GAMMA = 80
|
||||
HTML_TIMESTAMP = YES
|
||||
HTML_DYNAMIC_SECTIONS = NO
|
||||
HTML_INDEX_NUM_ENTRIES = 100
|
||||
GENERATE_DOCSET = NO
|
||||
DOCSET_FEEDNAME = "Doxygen generated docs"
|
||||
DOCSET_BUNDLE_ID = org.doxygen.Project
|
||||
DOCSET_PUBLISHER_ID = org.doxygen.Publisher
|
||||
DOCSET_PUBLISHER_NAME = Publisher
|
||||
GENERATE_HTMLHELP = NO
|
||||
CHM_FILE =
|
||||
HHC_LOCATION =
|
||||
GENERATE_CHI = NO
|
||||
CHM_INDEX_ENCODING =
|
||||
BINARY_TOC = NO
|
||||
TOC_EXPAND = YES
|
||||
GENERATE_QHP = NO
|
||||
QCH_FILE =
|
||||
QHP_NAMESPACE = org.doxygen.Project
|
||||
QHP_VIRTUAL_FOLDER = doc
|
||||
QHP_CUST_FILTER_NAME =
|
||||
QHP_CUST_FILTER_ATTRS =
|
||||
QHP_SECT_FILTER_ATTRS =
|
||||
QHG_LOCATION =
|
||||
GENERATE_ECLIPSEHELP = NO
|
||||
ECLIPSE_DOC_ID = org.doxygen.Project
|
||||
DISABLE_INDEX = YES
|
||||
GENERATE_TREEVIEW = YES
|
||||
ENUM_VALUES_PER_LINE = 4
|
||||
TREEVIEW_WIDTH = 250
|
||||
EXT_LINKS_IN_WINDOW = NO
|
||||
FORMULA_FONTSIZE = 10
|
||||
FORMULA_TRANSPARENT = YES
|
||||
USE_MATHJAX = NO
|
||||
MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
|
||||
MATHJAX_EXTENSIONS =
|
||||
SEARCHENGINE = YES
|
||||
SERVER_BASED_SEARCH = NO
|
||||
#---------------------------------------------------------------------------
|
||||
# configuration options related to the LaTeX output
|
||||
#---------------------------------------------------------------------------
|
||||
GENERATE_LATEX = NO
|
||||
LATEX_OUTPUT = latex
|
||||
LATEX_CMD_NAME = latex
|
||||
MAKEINDEX_CMD_NAME = makeindex
|
||||
COMPACT_LATEX = NO
|
||||
PAPER_TYPE = a4
|
||||
EXTRA_PACKAGES =
|
||||
LATEX_HEADER =
|
||||
LATEX_FOOTER =
|
||||
PDF_HYPERLINKS = YES
|
||||
USE_PDFLATEX = YES
|
||||
LATEX_BATCHMODE = NO
|
||||
LATEX_HIDE_INDICES = NO
|
||||
LATEX_SOURCE_CODE = NO
|
||||
LATEX_BIB_STYLE = plain
|
||||
#---------------------------------------------------------------------------
|
||||
# configuration options related to the RTF output
|
||||
#---------------------------------------------------------------------------
|
||||
GENERATE_RTF = NO
|
||||
RTF_OUTPUT = rtf
|
||||
COMPACT_RTF = NO
|
||||
RTF_HYPERLINKS = NO
|
||||
RTF_STYLESHEET_FILE =
|
||||
RTF_EXTENSIONS_FILE =
|
||||
#---------------------------------------------------------------------------
|
||||
# configuration options related to the man page output
|
||||
#---------------------------------------------------------------------------
|
||||
GENERATE_MAN = NO
|
||||
MAN_OUTPUT = man
|
||||
MAN_EXTENSION = .3
|
||||
MAN_LINKS = NO
|
||||
#---------------------------------------------------------------------------
|
||||
# configuration options related to the XML output
|
||||
#---------------------------------------------------------------------------
|
||||
GENERATE_XML = NO
|
||||
XML_OUTPUT = xml
|
||||
XML_SCHEMA =
|
||||
XML_DTD =
|
||||
XML_PROGRAMLISTING = YES
|
||||
#---------------------------------------------------------------------------
|
||||
# configuration options for the AutoGen Definitions output
|
||||
#---------------------------------------------------------------------------
|
||||
GENERATE_AUTOGEN_DEF = NO
|
||||
#---------------------------------------------------------------------------
|
||||
# configuration options related to the Perl module output
|
||||
#---------------------------------------------------------------------------
|
||||
GENERATE_PERLMOD = NO
|
||||
PERLMOD_LATEX = NO
|
||||
PERLMOD_PRETTY = YES
|
||||
PERLMOD_MAKEVAR_PREFIX =
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the preprocessor
|
||||
#---------------------------------------------------------------------------
|
||||
ENABLE_PREPROCESSING = YES
|
||||
MACRO_EXPANSION = NO
|
||||
EXPAND_ONLY_PREDEF = NO
|
||||
SEARCH_INCLUDES = YES
|
||||
INCLUDE_PATH =
|
||||
INCLUDE_FILE_PATTERNS =
|
||||
PREDEFINED =
|
||||
EXPAND_AS_DEFINED =
|
||||
SKIP_FUNCTION_MACROS = YES
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration::additions related to external references
|
||||
#---------------------------------------------------------------------------
|
||||
TAGFILES =
|
||||
GENERATE_TAGFILE =
|
||||
ALLEXTERNALS = NO
|
||||
EXTERNAL_GROUPS = YES
|
||||
PERL_PATH = /usr/bin/perl
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the dot tool
|
||||
#---------------------------------------------------------------------------
|
||||
CLASS_DIAGRAMS = YES
|
||||
MSCGEN_PATH =
|
||||
HIDE_UNDOC_RELATIONS = YES
|
||||
HAVE_DOT = NO
|
||||
DOT_NUM_THREADS = 0
|
||||
DOT_FONTNAME = Helvetica
|
||||
DOT_FONTSIZE = 10
|
||||
DOT_FONTPATH =
|
||||
CLASS_GRAPH = YES
|
||||
COLLABORATION_GRAPH = YES
|
||||
GROUP_GRAPHS = YES
|
||||
UML_LOOK = NO
|
||||
UML_LIMIT_NUM_FIELDS = 10
|
||||
TEMPLATE_RELATIONS = NO
|
||||
INCLUDE_GRAPH = YES
|
||||
INCLUDED_BY_GRAPH = YES
|
||||
CALL_GRAPH = NO
|
||||
CALLER_GRAPH = NO
|
||||
GRAPHICAL_HIERARCHY = YES
|
||||
DIRECTORY_GRAPH = YES
|
||||
DOT_IMAGE_FORMAT = png
|
||||
INTERACTIVE_SVG = NO
|
||||
DOT_PATH =
|
||||
DOTFILE_DIRS =
|
||||
MSCFILE_DIRS =
|
||||
DOT_GRAPH_MAX_NODES = 50
|
||||
MAX_DOT_GRAPH_DEPTH = 0
|
||||
DOT_TRANSPARENT = NO
|
||||
DOT_MULTI_TARGETS = YES
|
||||
GENERATE_LEGEND = YES
|
||||
DOT_CLEANUP = YES
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,130 +0,0 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Evennia.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Evennia.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/Evennia"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Evennia"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
make -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
|
||||
|
||||
Wiki convertion and autodocs
|
||||
----------------------------
|
||||
|
||||
The source/ directory contains Evennia's wiki documentation converted
|
||||
to ReST form. This can be built to a html document by installing
|
||||
python-sphinx (sphinx-doc.org) and running 'make html' from this
|
||||
directory. The output will appear in under build/ - point your browser
|
||||
to the index.html file.
|
||||
|
||||
If you want to (re-)build the documentation yourself, wiki2rest/
|
||||
contains programs for converting Evennia's wiki documentation to ReST
|
||||
files. Read the header of wiki2rest.py for setting up the converter.
|
||||
|
||||
The src2rest folder contains a reprecated program for building
|
||||
documented ReST source code from Evennia's documentation. You can
|
||||
arguably get as good autodocs using doxygen.
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Evennia.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Evennia.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
||||
|
|
@ -1,220 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Evennia documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sat Sep 10 14:19:20 2011.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
|
||||
EVENNIA_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
sys.path.insert(0, EVENNIA_ROOT)
|
||||
sys.path.insert(0, os.path.dirname(EVENNIA_ROOT)) # also import top container
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['.templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Evennia'
|
||||
copyright = u'2012, Evennia-development team'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = 'Beta'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = 'Beta'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = []
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
html_logo = "../../../src/web/media/images/evennia_logo_small.png"
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
html_favicon = "../../../src/web/media/images/favicon.ico"
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['.static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Evenniadoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
#latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Evennia.tex', u'Evennia Documentation',
|
||||
u'Evennia-devs', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
latex_logo = "../../../src/web/media/images/evennia_logo_small.png"
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'evennia', u'Evennia Documentation',
|
||||
[u'Evennia-devs'], 1)
|
||||
]
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
|
||||
Alphabetical page index
|
||||
=======================
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
|
||||
wiki/AddingCommandTutorial
|
||||
wiki/AddingObjectTypeclassTutorial
|
||||
wiki/AdminDocs
|
||||
wiki/ApacheConfig
|
||||
wiki/AsyncProcess
|
||||
wiki/Attributes
|
||||
wiki/Banning
|
||||
wiki/BatchCodeProcessor
|
||||
wiki/BatchCommandProcessor
|
||||
wiki/BatchProcessors
|
||||
wiki/BuilderDocs
|
||||
wiki/BuildingPermissions
|
||||
wiki/BuildingQuickstart
|
||||
wiki/Caches
|
||||
wiki/ChoosingAnSQLServer
|
||||
wiki/CodingIntroduction
|
||||
wiki/CodingUtils
|
||||
wiki/Colours
|
||||
wiki/CommandCooldown
|
||||
wiki/CommandPrompt
|
||||
wiki/Commands
|
||||
wiki/Communications
|
||||
wiki/ConnectionScreen
|
||||
wiki/Contributing
|
||||
wiki/DefaultCommandHelp
|
||||
wiki/DeveloperCentral
|
||||
wiki/DirectoryOverview
|
||||
wiki/evAPI
|
||||
wiki/EvenniaDevel
|
||||
wiki/EvenniaIntroduction
|
||||
wiki/ExecutePythonCode
|
||||
wiki/GamePlanning
|
||||
wiki/GettingHelp
|
||||
wiki/GettingStarted
|
||||
wiki/HelpSystem
|
||||
wiki/IMC2
|
||||
wiki/Index
|
||||
wiki/Internationalization
|
||||
wiki/IRC
|
||||
wiki/Licensing
|
||||
wiki/Locks
|
||||
wiki/Nicks
|
||||
wiki/Objects
|
||||
wiki/OnlineSetup
|
||||
wiki/Players
|
||||
wiki/PortalAndServer
|
||||
wiki/Quirks
|
||||
wiki/RemovingColour
|
||||
wiki/RSS
|
||||
wiki/Scripts
|
||||
wiki/ServerConf
|
||||
wiki/SessionProtocols
|
||||
wiki/SoftCode
|
||||
wiki/StartStopReload
|
||||
wiki/TextEncodings
|
||||
wiki/TickerScripts
|
||||
wiki/Tutorials
|
||||
wiki/TutorialWorldIntroduction
|
||||
wiki/Typeclasses
|
||||
wiki/UnitTesting
|
||||
wiki/UpdatingYourGame
|
||||
wiki/UsingMUXAsAStandard
|
||||
wiki/VersionControl
|
||||
wiki/WebFeatures
|
||||
wiki/Workshop
|
||||
wiki/WorkshopDefaultGame
|
||||
wiki/Zones
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
.. Evennia documentation master file, created by
|
||||
sphinx-quickstart on Sat Sep 10 14:19:20 2011.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to Evennia's documentation!
|
||||
===================================
|
||||
|
||||
`Evennia <http://www.evennia.com>`_ is a barebones Python
|
||||
MUD/MUX/MU\* creation system and server. It is using modern technologies and offering
|
||||
great freedom to design the multi-user online text game of your dreams. You
|
||||
code your game using normal Python modules - can't get more flexible
|
||||
than that!
|
||||
|
||||
This Sphinx-based (reST) documentation is built from the `online wiki <http://code.google.com/p/evennia/wiki/Index?tm=6>`_ at regular
|
||||
intervals.
|
||||
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:maxdepth: 1
|
||||
|
||||
Enter Manual <index.html>
|
||||
How to give and get help <wiki/HowToGetAndGiveHelp>
|
||||
Links <wiki/Links>
|
||||
Alphabetical Page index <contents>
|
||||
|
||||
.. Code
|
||||
.. -----
|
||||
.. .. toctree::
|
||||
.. :titlesonly:
|
||||
|
||||
.. Browse Code <code/modules>
|
||||
|
||||
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
Adding a new command - a step by step guide
|
||||
===========================================
|
||||
|
||||
This is a quick first-time tutorial expanding on the
|
||||
`Commands <Commands.html>`_ documentation.
|
||||
|
||||
Let's assume you have just downloaded Evennia and want to try to add a
|
||||
new command. This is the fastest way to do it.
|
||||
|
||||
Tell Evennia where to look for custom commands/cmdsets
|
||||
------------------------------------------------------
|
||||
|
||||
We will tell Evennia that you want to override the default cmdset with
|
||||
new additional commands of your own.
|
||||
|
||||
#. Go to ``game/gamesrc/commands``.
|
||||
#. There is a subfolder here named ``examples``. *Copy* the files
|
||||
``examples/command.py`` and ``examples/cmdset.py`` to your current
|
||||
directory (game/gamesrc/commands). You can rename them as you please,
|
||||
but in this example we assume you don't.
|
||||
#. Edit ``game/settings.py``, adding the following line:
|
||||
|
||||
``CMDSET_CHARACTER="game.gamesrc.commands.cmdset.CharacterCmdSet"``
|
||||
|
||||
Evennia will now look for default commands in the ``CharacterCmdSet``
|
||||
class of your newly copied module. You only need to do this once.
|
||||
|
||||
Creating a custom command
|
||||
-------------------------
|
||||
|
||||
#. Edit your newly copied ``game/gamesrc/commands/command.py``. This
|
||||
template already imports everything you need.
|
||||
#. Create a new class in ``command.py`` that inherits from
|
||||
``MuxCommand``. Let's call it ``CmdEcho`` in this example.
|
||||
#. Set the class variable ``key`` to a good command name, like ``echo``.
|
||||
#. Set the ``locks`` property on the command to a suitable
|
||||
[Locks#Defining\_locks lockstring]. If you are unsure, use
|
||||
``"cmd:all()"``.
|
||||
#. Give your class a useful *\_doc\_* string, this acts as the help
|
||||
entry for the command.
|
||||
#. Define a class method ``func()`` that does stuff. Below is an example
|
||||
how it all could look.
|
||||
|
||||
::
|
||||
|
||||
# file game/gamesrc/commands/command.py
|
||||
#[...]
|
||||
from ev import default_cmds
|
||||
class CmdEcho(default_cmds.MuxCommand):
|
||||
"""
|
||||
Simple command example
|
||||
|
||||
Usage:
|
||||
echo <text>
|
||||
|
||||
This command simply echoes text back to the caller.
|
||||
"""
|
||||
|
||||
key = "echo"
|
||||
locks = "cmd:all()"
|
||||
|
||||
def func(self):
|
||||
"This actually does things"
|
||||
if not self.args:
|
||||
self.caller.msg("You didn't enter anything!")
|
||||
else:
|
||||
self.caller.msg("You gave the string: '%s'" % self.args)
|
||||
|
||||
Adding the Command to a default Cmdset
|
||||
--------------------------------------
|
||||
|
||||
The command is not available to use until it is part of a Command Set.
|
||||
In this example we will go the easiest route and add it to the default
|
||||
Character command set we already prepared.
|
||||
|
||||
#. Edit your recently copied ``game/gamesrc/commands/cmdset.py``
|
||||
#. In this copied module you will find the ``DefaultCmdSet`` class
|
||||
already imported and prepared for you. Import your new command module
|
||||
here with ``from game.gamesrc.commands.command import CmdEcho``.
|
||||
#. Add a line ``self.add(CmdEcho())`` to ``CharacterCmdSet``, in the
|
||||
``at_cmdset_creation`` method (the template tells you where). This is
|
||||
approximately how it should look at this point:
|
||||
|
||||
::
|
||||
|
||||
# file gamesrc/commands/cmdset.py
|
||||
#[...]
|
||||
from game.gamesrc.commands.command import CmdEcho
|
||||
#[...]
|
||||
class CharacterCmdSet(default_cmds.CharacterCmdSet):
|
||||
|
||||
key = DefaultCharacter
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
|
||||
# this first adds all default commands
|
||||
super(DefaultSet, self).at_cmdset_creation()
|
||||
|
||||
# all commands added after this point will extend or
|
||||
# overwrite the default commands.
|
||||
self.add(CmdEcho())
|
||||
|
||||
#. Reboot/restart Evennia (``@reload`` from inside the game). You should
|
||||
now be able to use your new ``echo`` command from inside the game.
|
||||
Use ``help echo`` to see the documentation for the command.
|
||||
|
||||
If you have trouble, make sure to check the log for error messages
|
||||
(probably due to syntax errors in your command definition).
|
||||
|
||||
Adding new commands to the default cmdset in the future now only
|
||||
involves creating the function class and adding it to the cmdset in the
|
||||
same place. If you want to overload existing default commands (such as
|
||||
``look`` or ``get``), just add your new command with the same key as the
|
||||
old one - it will overload the default one. Just remember that you must
|
||||
``@reload`` the server before you see any changes.
|
||||
|
||||
See `Commands <Commands.html>`_ for many more details and possibilities
|
||||
when defining Commands and using Cmdsets in various ways.
|
||||
|
||||
Adding the command to specific object types
|
||||
-------------------------------------------
|
||||
|
||||
You do not *have* to expand the ``CharacterCmdSet``, it's just the
|
||||
easiest example. The cmdset system is very generic. You can create your
|
||||
own cmdsets and add them to objects as you please (just how to control
|
||||
how they merge with the existing set is described in detail in the
|
||||
[Commands#Command\_Sets Command Set documentation]).
|
||||
|
||||
::
|
||||
|
||||
# file gamesrc/commands/cmdset.py
|
||||
#[...]
|
||||
from game.gamesrc.commands.command import CmdEcho
|
||||
#[...]
|
||||
class MyCmdSet(default_cmds.CmdSet):
|
||||
|
||||
key = MyCmdSet
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
self.add(CmdEcho())
|
||||
|
||||
Now you just need to add this to an object. To test things (as
|
||||
superuser) you can do
|
||||
|
||||
::
|
||||
|
||||
@py self.cmdset.add("cmdset.MyCmdSet")
|
||||
|
||||
This will add the cmdset (and the echo command) to yourself so you can
|
||||
test it. This is not permanent though, if you do a ``@reload`` the
|
||||
merger will be gone. You *can* add the ``permanent=True`` keyword to the
|
||||
``cmdset.add`` call. This will however only make the new merged cmdset
|
||||
permanent on that single object, not on other objects of that type,
|
||||
which is usually what you want.
|
||||
|
||||
To make sure all new created objects get your new merged set, put the
|
||||
``cmdset.add`` call in your custom `Typeclass <Typeclasses.html>`_'
|
||||
``at_object_creation`` method:
|
||||
|
||||
::
|
||||
|
||||
from ev import Object
|
||||
class MyObject(Object):
|
||||
|
||||
def at_object_creation(self):
|
||||
"called when the object is first created"
|
||||
self.cmdset.add("cmdset.MyCmdSet")
|
||||
|
||||
|
||||
All new objects of this typeclass will now start with this cmdset.
|
||||
|
||||
*Note:* An important caveat with this is that ``at_object_creation`` is
|
||||
only called *once*, when the object is first created. This means that if
|
||||
you already have existing objects in your databases using that
|
||||
typeclass, they will not have been initiated the same way. There are
|
||||
many ways to update them; since it's a one-time update you can usually
|
||||
just simply loop through them. As superuser, try the following:
|
||||
|
||||
::
|
||||
|
||||
@py [obj.cmdset.add("cmdset.MyCmdSet") for obj in
|
||||
ev.managers.typeclass_search("game.gamesrc.objects.objects.mytypeclass.MyTypeClass"]
|
||||
|
||||
This goes through all objects in your database having the right
|
||||
typeclass, adding the new cmdset to each. The good news is that you only
|
||||
have to do this if you want to post-add cmdsets. If you just want to add
|
||||
a new command, you can just add that command to the cmdset's
|
||||
``at_cmdset_creation`` and @reload.
|
||||
|
|
@ -1,219 +0,0 @@
|
|||
Creating your own object classes
|
||||
================================
|
||||
|
||||
Evennia comes with a few very basic classes of in-game entities:
|
||||
|
||||
::
|
||||
|
||||
Object
|
||||
|
|
||||
Character
|
||||
Room
|
||||
Exit
|
||||
|
||||
The more specific object-types are just children of the basic ``Object``
|
||||
class (technically these are all `Typeclassed <Typeclasses.html>`_
|
||||
entities, but for this tutorial, just treat them as normal Python
|
||||
classes).
|
||||
|
||||
For your own game you will most likely want to expand on these very
|
||||
simple beginnings. It's normal to want your Characters to have various
|
||||
attributes. Maybe Rooms should hold extra information or even *all*
|
||||
Objects in your game should have properties not included in basic
|
||||
Evennia.
|
||||
|
||||
First a brief overview of how Evennia handles its object classes. The
|
||||
default classes are defined under ``src/objects/objects.py``. You can
|
||||
look at them there or you can bring up a Python prompt and interactively
|
||||
examine ``ev.Object``, ``ev.Room`` etc.
|
||||
|
||||
You will create your own object classes in ``game/gamesrc/objects``. You
|
||||
should normally inherit from the default classes (normal Python class
|
||||
inheritance) and go from there. Once you have a working new class you
|
||||
can immediately start to create objects in-game inheriting from that
|
||||
class.
|
||||
|
||||
If you want to change the *default* object classes, there is one more
|
||||
step. Evennia's default commands and internal creation mechanisms look
|
||||
at a range of variables in your ``settings.py`` file to determine which
|
||||
are the "default" classes. These defaults are used by the vanilla
|
||||
creation commands if you don't specify the typeclass specifically. They
|
||||
are also used as a fallback by Evennia if there are errors in other
|
||||
typeclasses, so make sure that your new default class is bug-free.
|
||||
|
||||
The following sections spells this out more explicitly.
|
||||
|
||||
Create a new Typeclass
|
||||
----------------------
|
||||
|
||||
This is the simplest case. Say you want to create a new "Heavy" object
|
||||
that characters should not have the ability to pick up.
|
||||
|
||||
#. Go to ``game/gamesrc/objects/``. It should already contain a
|
||||
directory ``examples/``.
|
||||
#. Create a new module here, named ``heavy.py``. Alternatively you can
|
||||
copy ``examples/object.py`` up one level and rename that file to
|
||||
``heavy.py`` instead - you will then have a template to start from.
|
||||
#. Code away in the ``heavy.py`` module, implementing the heavy
|
||||
functionality. See `Objects <Objects.html>`_ for more details and the
|
||||
example class below. Let's call the typeclass simply "``Heavy``\ ".
|
||||
#. Once you are done, log into the game with a build-capable account and
|
||||
do ``@create/drop rock:heavy.Heavy`` to drop a new heavy "rock"
|
||||
object in your location. Next try to pick it up. *Note - the
|
||||
superuser (User #1) will ignore all locks. Always test functionality
|
||||
like this with a non-superuser character.*
|
||||
|
||||
That's it. Below is a ``Heavy`` Typeclass that you could try. Note that
|
||||
the `lock <Locks.html>`_ and `Attribute <Attribute.html>`_ here set in
|
||||
the typeclass could just as well have been set using commands in-game,
|
||||
so this is a *very* simple example. We also add a custom
|
||||
[Commands#Command\_Sets Command set] to it.
|
||||
|
||||
::
|
||||
|
||||
# file game/gamesrc/objects/heavy.py
|
||||
from ev import Object
|
||||
# let's assume we defined our own cmdset earlier
|
||||
from game.gamesrc.commands.mycmdsets import HeavySet
|
||||
|
||||
class Heavy(Object):
|
||||
"Heavy object"
|
||||
def at_object_creation(self):
|
||||
"Called whenever a new object is created"
|
||||
# lock the object down by default
|
||||
self.locks.add("get:false()")
|
||||
# the default "get" command looks for this Attribute in order
|
||||
# to return a customized error message (we just happen to know
|
||||
# this, you'd have to look at the code of the 'get' command to
|
||||
# find out).
|
||||
self.db.get_err_msg = "This is too heavy for you to pick up."
|
||||
# expand the default cmdset with your own custom set (defined elsewhere)
|
||||
self.cmdset.add(HeavySet)
|
||||
|
||||
Change Default Rooms, Exits, Character Typeclass
|
||||
------------------------------------------------
|
||||
|
||||
This is only slightly more complex than creating any other Typeclass. In
|
||||
fact it only includes one extra step - telling Evennia to use the new
|
||||
default.
|
||||
|
||||
Let's say we want to change Rooms to use our new typeclass ``MyRoom``.
|
||||
|
||||
#. Create a new module in ``game/gamesrc/objects/myroom.py`` and code
|
||||
your ``MyRoom`` typeclass as described in the previous section. Make
|
||||
sure to test it by digging a few rooms of this class (e.g.
|
||||
``@dig Hall:myroom.MyRoom``).
|
||||
#. Once you are sure the new class works as it should, edit
|
||||
``game/settings.py`` and add
|
||||
``BASE_ROOM_TYPECLASS="game.gamesrc.objects.myroom.MyRoom"``.
|
||||
#. Reload Evennia.
|
||||
|
||||
For example the ``@dig`` and ``@tunnel`` commands will henceforth use
|
||||
this new default when digging new rooms whenever you don't give a
|
||||
typeclass explicitly. For the other sub-types, change
|
||||
``BASE_CHARACTER_TYPECLASS`` (used by character creation commands) and
|
||||
``BASE_EXIT_TYPECLASS`` (used by ``@dig``/``@tunnel`` etc) respectively.
|
||||
|
||||
Change the default Object Typeclass
|
||||
-----------------------------------
|
||||
|
||||
Changing the root ``Object`` class works identically to changing the
|
||||
``Character``, ``Room`` or ``Exit`` typeclass. After having created your
|
||||
new typeclass, set ``settings.BASE_OBJECT_TYPECLASS`` to point to your
|
||||
new class. Let's say you call your new default ``Object`` class
|
||||
``MyObject``.
|
||||
|
||||
There however one important further thing to remember: ``Characters``,
|
||||
``Rooms`` and ``Exits`` will still inherit from the *old* ``Object`` at
|
||||
this point (not ``MyObject``). This is by design - depending on your
|
||||
type of game, you may not need some or all of these subclasses to
|
||||
inherit any of the new stuff you put in ``MyObject``.
|
||||
|
||||
If you do want that however, you need to also overload these subclasses.
|
||||
For each of the ``Character``, ``Room`` and ``Exit`` you want to
|
||||
customize, do the following:
|
||||
|
||||
#. Create a new module in ``game/gamesrc/``, e.g. ``mycharacter.py``
|
||||
etc. A good flexible solution for overloading only parts of the
|
||||
default is to make inheriting classes *multi-inherited* (see below).
|
||||
As a place-holder you can make the class empty for now (just put
|
||||
``pass`` in it).
|
||||
#. In your ``settings.py`` file, add and define
|
||||
``BASE_CHARACTER_TYPECLASS``, ``BASE_ROOM_TYPECLASS`` and
|
||||
``BASE_EXIT_TYPECLASS`` to point to your new typeclasses.
|
||||
#. Reload Evennia.
|
||||
|
||||
This will give you maximum flexibility with creating and expanding your
|
||||
own types of rooms, characters and exit objects (or not). Below is an
|
||||
example of a new ``myroom.py``:
|
||||
|
||||
::
|
||||
|
||||
# file gamesrc/objects/myroom.py
|
||||
from ev import Object
|
||||
from gamesrc.objects.myobject import MyObject
|
||||
# we use multi-inheritance, this will primarily use MyObject,
|
||||
# falling back to the default Object for things MyObject do
|
||||
# not overload
|
||||
class MyRoom(MyObject, Object):
|
||||
"My own expandable room class"
|
||||
pass
|
||||
|
||||
Updating existing objects
|
||||
=========================
|
||||
|
||||
Let's say you have already created a slew of objects (Characters, Rooms,
|
||||
what have you). Now you change the default typeclass for that type of
|
||||
object (as described above). Unfortunately those old objects will not
|
||||
know about this yet. If you want to update them you have to do this
|
||||
manually. Luckily you only have to do this once, but it's a good case
|
||||
for planning your game and its base typeclasses *before* starting to
|
||||
build stuff.
|
||||
|
||||
Typeclassed objects have a useful method called ``swap_typeclass``. All
|
||||
you need to do is to flip through all existing objects, calling this.
|
||||
Here is an example of how to do it using some Django magic:
|
||||
|
||||
::
|
||||
|
||||
from django.conf import settings
|
||||
import ev
|
||||
|
||||
old_default = "src.objects.objects.Object"
|
||||
new_default = "game.gamesrc.objects.myobj.MyObject"
|
||||
|
||||
# use Django to query the database for all objects with the
|
||||
# old typeclass path (typeclass path is stored in a database
|
||||
# field 'db_typeclass_path)'
|
||||
|
||||
for obj in ev.managers.objects.filter(db_typeclass_path=old_default):
|
||||
obj.swap_typeclass(new_default)
|
||||
|
||||
Above we use one of the Django database managers to query the database.
|
||||
We are looking for the main thing typeclasses store in the database,
|
||||
namely the full python path to the typeclass. We find all objects still
|
||||
using the old typeclass and swap them to the new on. For more on Django
|
||||
database access, see the Django manual and/or peruse ``ev.managers``.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
All above examples puts each class in its own module. This makes it easy
|
||||
to find, but it is really up to you how you organize things. There is
|
||||
nothing stopping you from putting all base classes into one module, for
|
||||
example.
|
||||
|
||||
Also remember that Python may dynamically rename module classes as they
|
||||
are imported. So if you feel it annoying to have to refer to your new
|
||||
default as ``MyObject`` all the time, you can also import them to
|
||||
another name like in the below example:
|
||||
|
||||
::
|
||||
|
||||
from ev import Object as BaseObject
|
||||
from gamesrc.objects.myobject import MyObject as Object
|
||||
class MyRoom(Object, BaseObject):
|
||||
[...]
|
||||
|
||||
This doesn't actually change the meaning of the code, but might make the
|
||||
relationships clearer inside a module.
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
Administrative Documentation
|
||||
============================
|
||||
|
||||
The following pages are aimed at game administrators -- the higher-ups
|
||||
that possess shell access and/or are responsible for the game.
|
||||
|
||||
Installation and Early Life
|
||||
---------------------------
|
||||
|
||||
- `Choosing an SQL Server <ChoosingAnSQLServer.html>`_
|
||||
- `Getting Started - Installing Evennia <GettingStarted.html>`_
|
||||
- `Starting, stopping, reloading and resetting
|
||||
Evennia <StartStopReload.html>`_
|
||||
- `Keeping your game up to date <UpdatingYourGame.html>`_
|
||||
- `Making your game available online <OnlineSetup.html>`_
|
||||
|
||||
Customizing the server
|
||||
----------------------
|
||||
|
||||
- `Changing the Settings <SettingsDefault.html>`_
|
||||
- `Change Evennia's language <Internationalization.html>`_
|
||||
- `Apache webserver configuration <ApacheConfig.html>`_ (optional)
|
||||
- `Changing text encodings used by the server <TextEncodings.html>`_
|
||||
- `How to connect Evennia to IRC channels <IRC.html>`_
|
||||
- `How to connect Evennia to an IMC2 network <IMC2.html>`_
|
||||
- `How to connect Evennia to RSS feeds <RSS.html>`_
|
||||
|
||||
Administrating the running game
|
||||
-------------------------------
|
||||
|
||||
- `Banning <Banning.html>`_ and deleting users
|
||||
|
||||
Working with Evennia
|
||||
--------------------
|
||||
|
||||
- `Setting up your work environment with version
|
||||
control <VersionControl.html>`_
|
||||
- `First steps coding with Evennia <FirstStepsCoding.html>`_
|
||||
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
Apache Configuration
|
||||
====================
|
||||
|
||||
This is an optional section only relevant for advanced users preferring
|
||||
to use a third-party web server to power Evennia's front-end. For most
|
||||
users the in-built Twisted web server should be enough. The in-built
|
||||
server works out of the box without any extra configuration. Note that
|
||||
the ajax web client will probably *not* work (at least not without
|
||||
tweaking) on a third-party web server.
|
||||
|
||||
You can run Evennia's web front end with
|
||||
`apache2 <http://httpd.apache.org/>`_ and
|
||||
`mod\_wsgi <http://code.google.com/p/modwsgi/>`_. However, there seems
|
||||
to be no reason why the codebase should not also work with other modern
|
||||
web servers like nginx/lighttpd + gunicorn, Tornado, uwsgi, etc.
|
||||
|
||||
Note that the Apache instructions below might be slightly outdated. If
|
||||
something is not working right, or you use Evennia with a different
|
||||
server, please let us know.
|
||||
|
||||
SQLite Note
|
||||
-----------
|
||||
|
||||
It's important to note that on Windows, you will be unable to run the
|
||||
game and your web presence at the same time due to evennia.db3 being
|
||||
locked while one has it opened. While Linux/Unix will let you run both
|
||||
concurrently, there **may** be issues with simultaneous read/writes by
|
||||
the game and the web front-end. The best bet to any game wishing to
|
||||
power their web presence with Evennia is to use Postgres, MySQL, Oracle,
|
||||
or any other supported full-blown relational database.
|
||||
|
||||
mod\_wsgi Setup
|
||||
---------------
|
||||
|
||||
Install mod\_wsgi
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
mod\_wsgi is an excellent, secure, and high-performance way to serve
|
||||
Python projects. Code reloading is a breeze, Python modules are executed
|
||||
as a user of your choice (which is a great security win), and mod\_wsgi
|
||||
is easy to set up on most distributions.
|
||||
|
||||
For the sake of brevity, this guide will refer you to mod\_wsgi's
|
||||
`installation
|
||||
instructions <http://code.google.com/p/modwsgi/wiki/InstallationInstructions>`_
|
||||
page, as their guides are great. For those that are running Debian or
|
||||
Ubuntu, you may install the entire stack with the following command:
|
||||
|
||||
``sudo aptitude install libapache2-mod-wsgi``
|
||||
|
||||
This should install apache2 (if it isn't already), mod\_wsgi, and load
|
||||
the module. On Fedora or CentOS, you'll do this with ``yum`` and a
|
||||
similar package name that you'll need to search for. On Windows, you'll
|
||||
need to download and install apache2 and mod\_wsgi binaries.
|
||||
|
||||
Copy and modify the VHOST
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
After mod\_wsgi is installed, copy the
|
||||
``evennia/game/web/utils/evennia_wsgi_apache.conf`` file to your apache2
|
||||
vhosts/sites folder. On Debian/Ubuntu, this is
|
||||
``/etc/apache2/sites-enabled/``. Make your modifications **after**
|
||||
copying the file there.
|
||||
|
||||
Read the comments and change the paths to point to the appropriate
|
||||
locations within your setup.
|
||||
|
||||
Restart/Reload Apache
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You'll then want to reload or restart apache2. On Debian/Ubuntu, this
|
||||
may be done via:
|
||||
|
||||
``sudo /etc/init.d/apache2 restart`` or
|
||||
``sudo /etc/init.d/apache2 reload``
|
||||
|
||||
Enjoy
|
||||
~~~~~
|
||||
|
||||
With any luck, you'll be able to point your browser at your domain or
|
||||
subdomain that you set up in your vhost and see the nifty default
|
||||
Evennia webpage. If not, read the hopefully informative error message
|
||||
and work from there. Questions may be directed to our `Evennia Community
|
||||
site <http://evennia.com>`_.
|
||||
|
||||
A note on code reloading
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If your mod\_wsgi is set up to run on daemon mode (as will be the case
|
||||
by default on Debian and Ubuntu), you may tell mod\_wsgi to reload by
|
||||
using the ``touch`` command on
|
||||
``evennia/game/web/utils/apache_wsgi.conf``. When mod\_wsgi sees that
|
||||
the file modification time has changed, it will force a code reload. Any
|
||||
modifications to the code will not be propagated to the live instance of
|
||||
your site until reloaded.
|
||||
|
||||
If you are not running in daemon mode or want to force the issue, simply
|
||||
restart or reload apache2 to apply your changes.
|
||||
|
||||
Further notes and hints:
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you get strange (and usually uninformative) ``Permission denied``
|
||||
errors from Apache, make sure that your ``evennia`` directory is located
|
||||
in a place the webserver may actually access. For example, some Linux
|
||||
distributions may default to very restrictive access permissions on a
|
||||
user's ``/home`` directory.
|
||||
|
||||
One user commented that they had to add the following to their Apache
|
||||
config to get things to work. Not confirmed, but worth trying if there
|
||||
are trouble.
|
||||
|
||||
::
|
||||
|
||||
<Directory "/home/<yourname>/evennia/game/web">
|
||||
Options +ExecCGI
|
||||
Allow from all
|
||||
</Directory>
|
||||
|
||||
|
|
@ -1,261 +0,0 @@
|
|||
Asynchronous code
|
||||
=================
|
||||
|
||||
*This is considered an advanced topic.*
|
||||
|
||||
Synchronous versus Asynchronous
|
||||
-------------------------------
|
||||
|
||||
Most code operate *synchronously*. This means that each statement in
|
||||
your code gets processed and finishes before the next can begin. This
|
||||
makes for easy-to-understand code. It is also a *requirement* in many
|
||||
cases - a subsequent piece of code often depend on something calculated
|
||||
or defined in a previous statement.
|
||||
|
||||
Consider this piece of code:
|
||||
|
||||
::
|
||||
|
||||
print "before call ..."
|
||||
long_running_function()
|
||||
print "after call ..."
|
||||
|
||||
When run, this will print ``"before call ..."``, after which the
|
||||
``long_running_function`` gets to work for however long time. Only once
|
||||
that is done, the system prints ``"after call ..."``. Easy and logical
|
||||
to follow. Most of Evennia work in this way. Most of the time we want to
|
||||
make sure that commands get executed in strict order after when they
|
||||
where entered.
|
||||
|
||||
The main problem is that Evennia is a multi-user server. It swiftly
|
||||
switches between dealing with player input in the order it is sent to
|
||||
it. So if one user, say, run a command containing that
|
||||
``long_running_function``, *all* other players are effectively forced to
|
||||
wait until it finishes ... hardly an ideal solution.
|
||||
|
||||
Now, it should be said that on a modern computer system this is rarely
|
||||
an issue. Very few commands run so long that other users notice it. And
|
||||
as mentioned, most of the time you *want* to enforce all commands to
|
||||
occur in strict sequence.
|
||||
|
||||
When delays do become noticeable and you don't care which order the
|
||||
command actually completes, you can run it *asynchronously*. This makes
|
||||
use of the ``run_async()`` function in ``src/utils/utils.py``.
|
||||
|
||||
::
|
||||
|
||||
from ev import utils
|
||||
print "before call ..."
|
||||
utils.run_async(long_running_function)
|
||||
print "after call ..."
|
||||
|
||||
Now, when running this you will find that the program will not wait
|
||||
around for ``long_running_function`` to finish. Infact you will see
|
||||
``"before call ..."`` and ``"after call ..."`` printed out right away.
|
||||
The long-running function will run in the background and you (and other
|
||||
users) can go on as normal.
|
||||
|
||||
Customizing asynchronous operation
|
||||
----------------------------------
|
||||
|
||||
A complication with using asynchronous calls is what to do with the
|
||||
result from that call. What if ``long_running_function`` returns a value
|
||||
that you need? It makes no real sense to put any lines of code after the
|
||||
call to try to deal with the result from ``long_running_function`` above
|
||||
- as we saw the ``"after call ..."`` got printed long before
|
||||
``long_running_function`` was finished, making that line quite pointless
|
||||
for processing any data from the function. Instead one has to use
|
||||
*callbacks*.
|
||||
|
||||
``utils.run_async`` takes reserved arguments.
|
||||
|
||||
- ``at_return(r)`` (the *callback*) is called when the asynchronous
|
||||
function (``long_running_function`` above) finishes successfully. The
|
||||
argument ``r`` will then be the return value of that function (or
|
||||
``None``). Example:
|
||||
|
||||
::
|
||||
|
||||
def at_return(r):
|
||||
print r
|
||||
|
||||
- ``at_return_kwargs`` - an optional dictionary that will be fed as
|
||||
keyword arguments to the ``at_return`` callback.
|
||||
- ``at_err(e)`` (the *errback*) is called if the asynchronous function
|
||||
fails and raises an exception. This exception is passed to the
|
||||
errback wrapped in a *Failure* object ``e``. If you do not supply an
|
||||
errback of your own, Evennia will automatically add one that silently
|
||||
writes errors to the evennia log. An example of an errback is found
|
||||
below:
|
||||
|
||||
::
|
||||
|
||||
def at_err(e):
|
||||
print "There was an error:", str(e)
|
||||
|
||||
- ``at_err_kwargs`` - an optional dictionary that will be fed as
|
||||
keyword arguments to the ``at_err`` errback.
|
||||
|
||||
An example of making an asynchronous call from inside a
|
||||
`Command <Commands.html>`_ definition:
|
||||
|
||||
::
|
||||
|
||||
from ev import utils
|
||||
from game.gamesrc.commands.basecommand import Command
|
||||
|
||||
class CmdAsync(Command):
|
||||
|
||||
key = "asynccommand"
|
||||
|
||||
def func(self):
|
||||
|
||||
def long_running_function():
|
||||
#[... lots of time-consuming code
|
||||
return final_value
|
||||
|
||||
def at_return(r):
|
||||
self.caller.msg("The final value is %s" % r)
|
||||
|
||||
def at_err(e):
|
||||
self.caller.msg("There was an error: %s" % e)
|
||||
|
||||
# do the async call, setting all callbacks
|
||||
utils.run_async(long_running_function, at_return, at_err)
|
||||
|
||||
That's it - from here on we can forget about ``long_running_function``
|
||||
and go on with what else need to be done. *Whenever* it finishes, the
|
||||
``at_return`` function will be called and the final value will pop up
|
||||
for us to see. If not we will see an error message.
|
||||
|
||||
Process Pool
|
||||
------------
|
||||
|
||||
The ``ProcPool`` is an Evennia subsystem that launches a pool of
|
||||
processes based on the `ampoule <https://launchpad.net/ampoule>`_
|
||||
package (included with Evennia). When active, ``run_async`` will use
|
||||
this pool to offload its commands. ``ProcPool`` is deactivated by
|
||||
default, it can be turned on with ``settings.PROCPOOL_ENABLED``. *It
|
||||
should be noted that the default SQLite3 database is not suitable for
|
||||
for multiprocess operation. So if you use ``ProcPool`` you should
|
||||
consider switching to another database such as MySQL or PostgreSQL.*
|
||||
|
||||
The Process Pool makes several additional options available to
|
||||
``run_async``.
|
||||
|
||||
The following keyword arguments make sense when ``ProcPool`` is active:
|
||||
|
||||
- ``use_thread`` - this force-reverts back to thread operation (as
|
||||
above). It effectively deactivates all additional features
|
||||
``ProcPool`` offers.
|
||||
- ``proc_timeout`` - this enforces a timeout for the running process in
|
||||
seconds; after this time the process will be killed.
|
||||
- ``at_return``, ``at_err`` - these work the same as above.
|
||||
|
||||
In addition to feeding a single callable to ``run_async``, the first
|
||||
argument may also be a source string. This is a piece of python source
|
||||
code that will be executed in a subprocess via ``ProcPool``. Any extra
|
||||
keyword arguments to ``run_async`` that are not one of the reserved ones
|
||||
will be used to specify what will be available in the execution
|
||||
environment.
|
||||
|
||||
There is one special variable used in the remove execution: ``_return``.
|
||||
This is a function, and all data fed to ``_return`` will be returned
|
||||
from the execution environment and appear as input to your ``at_return``
|
||||
callback (if it is defined). You can call ``_return`` multiple times in
|
||||
your code - the return value will then be a list.
|
||||
|
||||
Example:
|
||||
|
||||
::
|
||||
|
||||
from src.utils.utils import run_async
|
||||
|
||||
source = """
|
||||
from time import sleep
|
||||
sleep(5) # sleep five secs
|
||||
val = testvar + 5
|
||||
_return(val)
|
||||
_return(val + 5)
|
||||
"""
|
||||
|
||||
# we assume myobj is a character retrieved earlier
|
||||
# these callbacks will just print results/errors
|
||||
def callback(ret):
|
||||
myobj.msg(ret)
|
||||
def errback(err):
|
||||
myobj.msg(err)
|
||||
testvar = 3
|
||||
|
||||
# run async
|
||||
run_async(source, at_return=callback, at_err=errback, testvar=testvar)
|
||||
|
||||
# this will return '[8, 13]'
|
||||
|
||||
You can also test the async mechanism from in-game using the ``@py``
|
||||
command:
|
||||
|
||||
::
|
||||
|
||||
@py from src.utils.utils import run_async;run_async("_return(1+2)",at_return=self.msg)
|
||||
|
||||
Note: The code execution runs without any security checks, so it should
|
||||
not be available to unprivileged users. Try
|
||||
``contrib.evlang.evlang.limited_exec`` for running a more restricted
|
||||
version of Python for untrusted users. This will use ``run_async`` under
|
||||
the hood.
|
||||
|
||||
delay
|
||||
-----
|
||||
|
||||
The ``delay`` function is a much simpler sibling to ``run_async``. It is
|
||||
in fact just a way to delay the execution of a command until a future
|
||||
time. This is equivalent to something like ``time.sleep()`` except delay
|
||||
is asynchronous while ``sleep`` would lock the entire server for the
|
||||
duration of the sleep.
|
||||
|
||||
::
|
||||
|
||||
def callback(obj):
|
||||
obj.msg("Returning!")
|
||||
delay(10, caller, callback=callback)
|
||||
|
||||
This will delay the execution of the callback for 10 seconds. This
|
||||
function is explored much more in `Command Duration
|
||||
Tutorial <CommandDuration.html>`_.
|
||||
|
||||
Assorted notes
|
||||
--------------
|
||||
|
||||
Note that the ``run_async`` will try to launch a separate thread behind
|
||||
the scenes. Some databases, notably our default database SQLite3, does
|
||||
*not* allow concurrent read/writes. So if you do a lot of database
|
||||
access (like saving to an Attribute) in your function, your code might
|
||||
actually run *slower* using this functionality if you are not careful.
|
||||
Extensive real-world testing is your friend here.
|
||||
|
||||
Overall, be careful with choosing when to use asynchronous calls. It is
|
||||
mainly useful for large administration operations that has no direct
|
||||
influence on the game world (imports and backup operations come to
|
||||
mind). Since there is no telling exactly when an asynchronous call
|
||||
actually ends, using them for in-game commands is to potentially invite
|
||||
confusion and inconsistencies (and very hard-to-reproduce bugs).
|
||||
|
||||
The very first synchronous example above is not *really* correct in the
|
||||
case of Twisted, which is inherently an asynchronous server. Notably you
|
||||
might find that you will *not* see the first ``before call ...`` text
|
||||
being printed out right away. Instead all texts could end up being
|
||||
delayed until after the long-running process finishes. So all commands
|
||||
will retain their relative order as expected, but they may appear with
|
||||
delays or in groups.
|
||||
|
||||
Further reading
|
||||
---------------
|
||||
|
||||
Technically, ``run_async`` is just a very thin and simplified wrapper
|
||||
around a `Twisted
|
||||
Deferred <http://twistedmatrix.com/documents/9.0.0/core/howto/defer.html>`_
|
||||
object; the wrapper sets up a separate thread and assigns a default
|
||||
errback also if none is supplied. If you know what you are doing there
|
||||
is nothing stopping you from bypassing the utility function, building a
|
||||
more sophisticated callback chain after your own liking.
|
||||
|
|
@ -1,342 +0,0 @@
|
|||
Attributes
|
||||
==========
|
||||
|
||||
When performing actions in Evennia it is often important that you store
|
||||
data for later. If you write a menu system, you have to keep track of
|
||||
the current location in the menu tree so that the player can give
|
||||
correct subsequent commands. If you are writing a combat system, you
|
||||
might have a combattant's next roll get easier dependent on if their
|
||||
opponent failed. Your characters will probably need to store
|
||||
roleplaying-attributes like strength and agility. And so on.
|
||||
|
||||
`Typeclassed <Typeclasses.html>`_ game entities
|
||||
(`Players <Players.html>`_, `Objects <Objects.html>`_ and
|
||||
`Scripts <Scripts.html>`_) always have *Attributes* associated with
|
||||
them. Attributes are used to store any type of data 'on' such entities.
|
||||
This is different from storing data in properties already defined on
|
||||
entities (such as ``key`` or ``location``) - these have very specific
|
||||
names and require very specific types of data (for example you couldn't
|
||||
assign a python *list* to the ``key`` property no matter how hard you
|
||||
tried). ``Attributes`` come into play when you want to assign arbitrary
|
||||
data to arbitrary names.
|
||||
|
||||
Saving and Retrieving data
|
||||
--------------------------
|
||||
|
||||
To save persistent data on a Typeclassed object you normally use the
|
||||
``db`` (DataBase) operator. Let's try to save some data to a *Rose* (an
|
||||
`Object <Objects.html>`_):
|
||||
|
||||
::
|
||||
|
||||
# saving
|
||||
rose.db.has_thorns = True
|
||||
# getting it back
|
||||
is_ouch = rose.db.has_thorns
|
||||
|
||||
This looks like any normal Python assignment, but that ``db`` makes sure
|
||||
that an *Attribute* is created behind the scenes and is stored in the
|
||||
database. Your rose will continue to have thorns throughout the life of
|
||||
the server now, until you deliberately remove them.
|
||||
|
||||
To be sure to save **non-persistently**, i.e. to make sure NOT to create
|
||||
a database entry, you use ``ndb`` (NonDataBase). It works in the same
|
||||
way:
|
||||
|
||||
::
|
||||
|
||||
# saving
|
||||
rose.ndb.has_thorns = True
|
||||
# getting it back
|
||||
is_ouch = rose.ndb.has_thorns
|
||||
|
||||
Strictly speaking, ``ndb`` has nothing to do with ``Attributes``,
|
||||
despite how similar they look. No ``Attribute`` object is created behind
|
||||
the scenes when using ``ndb``. In fact the database is not invoked at
|
||||
all since we are not interested in persistence.
|
||||
|
||||
You can also ``del`` properties on ``db`` and ``ndb`` as normal. This
|
||||
will for example delete an ``Attribute``:
|
||||
|
||||
::
|
||||
|
||||
del rose.db.has_thorns
|
||||
|
||||
Both ``db`` and ``ndb`` defaults to offering an ``all()`` method on
|
||||
themselves. This returns all associated attributes or non-persistent
|
||||
properties.
|
||||
|
||||
::
|
||||
|
||||
list_of_all_rose_attributes = rose.db.all()
|
||||
list_of_all_rose_ndb_attrs = rose.ndb.all()
|
||||
|
||||
If you use ``all`` as the name of an attribute, this will be used
|
||||
instead. Later deleting your custom ``all`` will return the default
|
||||
behaviour.
|
||||
|
||||
Properties of Attributes
|
||||
------------------------
|
||||
|
||||
An Attribute object is stored in the database. It has the following
|
||||
properties:
|
||||
|
||||
- ``key`` - the name of the Attribute. When doing e.g.
|
||||
``obj.db.attrname = value``, this property is set to ``attrname``.
|
||||
- ``value`` - this is the value of the Attribute. This value can be
|
||||
anything which can be pickled - objects, lists, numbers or what have
|
||||
you (see
|
||||
[Attributes#What\_types\_of\_data\_can\_I\_save\_in\_an\_Attribute
|
||||
this section] for more info). In the example
|
||||
``obj.db.attrname = value``, the ``value`` is stored here.
|
||||
- ``category`` - this is an optional property that is set to None for
|
||||
most Attributes. Setting this allows to use Attributes for different
|
||||
functionality. This is usually not needed unless you want to use
|
||||
Attributes for very different functionality (`Nicks <Nicks.html>`_ is
|
||||
an example of using Attributes in this way). To modify this property
|
||||
you need to use the [Attributes#The\_Attribute\_Handler Attribute
|
||||
Handler].
|
||||
- ``strvalue`` - this is a separate value field that only accepts
|
||||
strings. This severaly limits the data possible to store, but allows
|
||||
for easier database lookups. This property is usually not used except
|
||||
when re-using Attributes for some other purpose
|
||||
(`Nicks <Nicks.html>`_ use it). It is only accessible via the
|
||||
[Attributes#The\_Attribute\_Handler Attribute Handler].
|
||||
|
||||
Non-database attributes have no equivalence to category nor strvalue.
|
||||
|
||||
The Attribute Handler
|
||||
---------------------
|
||||
|
||||
The Attribute handler is what is used under the hood to manage the
|
||||
Attributes on an object. It is accessible as ``obj.attributes``. For
|
||||
most operations, the ``db`` or ``ndb`` wrappers are enough. But
|
||||
sometimes you won't know the attribute name beforehand or you need to
|
||||
manipulate your Attributes in more detail. The Attribute handler has the
|
||||
following methods (the argument lists are mostly shortened; you can see
|
||||
the full call signatures in ``src.typeclasses.models``):
|
||||
|
||||
- ``attributes.has(...)`` - this checks if the object has an Attribute
|
||||
with this key. This is equivalent to doing ``obj.db.key``.
|
||||
- ``get(...)`` - this retrieves the given Attribute. Normally the
|
||||
``value`` property of the Attribute is returned, but the method takes
|
||||
keywords for returning the Attribute object itself. By supplying an
|
||||
``accessing_object`` oto the call one can also make sure to check
|
||||
permissions before modifying anything.
|
||||
- ``add(...)`` - this adds a new Attribute to the object. An optional
|
||||
`lockstring <Locks.html>`_ can be supplied here to restrict future
|
||||
access and also the call itself may be checked against locks.
|
||||
- ``remove(...)`` - Remove the given Attribute. This can optionally be
|
||||
made to check for permission before performing the deletion.
|
||||
- ``clear(...)`` - removes all Attributes from object.
|
||||
- ``all(...)`` - returns all Attributes (of the given category)
|
||||
attached to this object.
|
||||
|
||||
See [Attributes#Locking\_and\_checking\_Attributes this section] for
|
||||
more about locking down Attribute access and editing.
|
||||
|
||||
There is an equivalent ``nattribute`` handler for managing non-database
|
||||
Attributes. This has the same methods but is much simpler since it does
|
||||
not concern itself with category nor strvalue. It also offers no concept
|
||||
of access control.
|
||||
|
||||
Persistent vs non-persistent
|
||||
----------------------------
|
||||
|
||||
So *persistent* data means that your data will survive a server reboot,
|
||||
whereas with *non-persistent* data it will not ...
|
||||
|
||||
... So why would you ever want to use non-persistent data? The answer
|
||||
is, you don't have to. Most of the time you really want to save as much
|
||||
as you possibly can. Non-persistent data is potentially useful in a few
|
||||
situations though.
|
||||
|
||||
- You are worried about database performance. Since Evennia caches
|
||||
Attributes very aggressively, this is not an issue unless you are
|
||||
reading *and* writing to your Attribute very often (like many times
|
||||
per second). Reading from an already cached Attribute is as fast as
|
||||
reading any Python property. But even then this is not likely
|
||||
something to worry about: Apart from Evennia's own caching, modern
|
||||
database systems themselves also cache data very efficiently for
|
||||
speed. Our default database even runs completely in RAM if possible,
|
||||
alleviating much of the need to write to disk during heavy loads.
|
||||
- A more valid reason for using non-persistent data is if you *want* to
|
||||
loose your state when logging off. Maybe you are storing throw-away
|
||||
data that are re-initialized at server startup. Maybe you are
|
||||
implementing some caching of your own. Or maybe you are testing a
|
||||
buggy `Script <Scripts.html>`_ that does potentially harmful stuff to
|
||||
your character object. With non-persistent storage you can be sure
|
||||
that whatever is messed up, it's nothing a server reboot can't clear
|
||||
up.
|
||||
- You want to implement a fully or partly *non-persistent world*. Who
|
||||
are we to argue with your grand vision!
|
||||
|
||||
What types of data can I save in an Attribute?
|
||||
----------------------------------------------
|
||||
|
||||
Evennia uses the ``pickle`` module to serialize Attribute data into the
|
||||
database. So if you store a single object (that is, not an iterable list
|
||||
of objects), you can practically store any Python object that can be
|
||||
`pickled <http://docs.python.org/library/pickle.html>`_.
|
||||
|
||||
If you store many objects however, you can only store them using normal
|
||||
Python structures (i.e. in either a *tuple*, *list*, *dictionary* or
|
||||
*set*). All other iterables (such as custom containers) are converted to
|
||||
*lists* by the Attribute (see next section for the reason for this).
|
||||
Since you can nest dictionaries, sets, lists and tuples together in any
|
||||
combination, this is usually not much of a limitation.
|
||||
|
||||
There is one notable type of object that cannot be pickled - and that is
|
||||
a Django database object. These will instead be stored as a wrapper
|
||||
object containing the ID and its database model. It will be read back to
|
||||
a new instantiated `typeclass <Typeclasses.html>`_ when the Attribute is
|
||||
accessed. Since erroneously trying to save database objects in an
|
||||
Attribute will lead to errors, Evennia will try to detect database
|
||||
objects by analyzing the data being stored. This means that Evennia must
|
||||
recursively traverse all iterables to make sure all database objects in
|
||||
them are stored safely. So for efficiency, it can be a good idea to
|
||||
avoid deeply nested lists with objects if you can.
|
||||
|
||||
*Note that you could fool the safety check if you for example created
|
||||
custom, non-iterable classes and stored database objects in them. So to
|
||||
make this clear - saving such an object is **not supported** and will
|
||||
probably make your game unstable. Store your database objects using
|
||||
lists, tuples, dictionaries, sets or a combination of the four and you
|
||||
should be fine.*
|
||||
|
||||
Examples of valid attribute data:
|
||||
|
||||
::
|
||||
|
||||
# a single value
|
||||
obj.db.test1 = 23
|
||||
obj.db.test1 = False
|
||||
# a database object (will be stored as dbref)
|
||||
obj.db.test2 = myobj
|
||||
# a list of objects
|
||||
obj.db.test3 = [obj1, 45, obj2, 67]
|
||||
# a dictionary
|
||||
obj.db.test4 = {'str':34, 'dex':56, 'agi':22, 'int':77}
|
||||
# a mixed dictionary/list
|
||||
obj.db.test5 = {'members': [obj1,obj2,obj3], 'enemies':[obj4,obj5]}
|
||||
# a tuple with a list in it
|
||||
obj.db.test6 = (1,3,4,8, ["test", "test2"], 9)
|
||||
# a set will still be stored and returned as a list [1,2,3,4,5]!
|
||||
obj.db.test7 = set([1,2,3,4,5])
|
||||
# in-situ manipulation
|
||||
obj.db.test8 = [1,2,{"test":1}]
|
||||
obj.db.test8[0] = 4
|
||||
obj.db.test8[2]["test"] = 5
|
||||
# test8 is now [4,2,{"test":5}]
|
||||
|
||||
Example of non-supported save:
|
||||
|
||||
::
|
||||
|
||||
# this will fool the dbobj-check since myobj (a database object) is "hidden"
|
||||
# inside a custom object. This is unsupported and will lead to unexpected
|
||||
# results!
|
||||
class BadStorage(object):
|
||||
pass
|
||||
bad = BadStorage()
|
||||
bad.dbobj = myobj
|
||||
obj.db.test8 = bad # this will likely lead to a traceback
|
||||
|
||||
Retrieving Mutable objects
|
||||
--------------------------
|
||||
|
||||
A side effect of the way Evennia stores Attributes is that Python Lists,
|
||||
Dictionaries and Sets are handled by custom objects called PackedLists,
|
||||
PackedDicts and PackedSets. These behave just like normal lists and
|
||||
dicts except they have the special property that they save to the
|
||||
database whenever new data gets assigned to them. This allows you to do
|
||||
things like ``self.db.mylist[4]`` = val without having to extract the
|
||||
mylist Attribute into a temporary variable first.
|
||||
|
||||
There is however an important thing to remember. If you retrieve this
|
||||
data into another variable, e.g. ``mylist2 = obj.db.mylist``, your new
|
||||
variable (``mylist2``) will *still* be a PackedList! This means it will
|
||||
continue to save itself to the database whenever it is updated! This is
|
||||
important to keep in mind so you are not confused by the results.
|
||||
|
||||
::
|
||||
|
||||
obj.db.mylist = [1,2,3,4]
|
||||
mylist = obj.db.mylist
|
||||
mylist[3] = 5 # this will also update database
|
||||
print mylist # this is now [1,2,3,5]
|
||||
print mylist.db.mylist # this is also [1,2,3,5]
|
||||
|
||||
To "disconnect" your extracted mutable variable from the database you
|
||||
simply need to convert the PackedList or PackedDict to a normal Python
|
||||
list or dictionary. This is done with the builtin ``list()`` and
|
||||
``dict()`` functions. In the case of "nested" lists and dicts, you only
|
||||
have to convert the "outermost" list/dict in order to cut the entire
|
||||
structure's connection to the database.
|
||||
|
||||
::
|
||||
|
||||
obj.db.mylist = [1,2,3,4]
|
||||
mylist = list(obj.db.mylist) # convert to normal list
|
||||
mylist[3] = 5
|
||||
print mylist # this is now [1,2,3,5]
|
||||
print obj.db.mylist # this remains [1,2,3,4]
|
||||
|
||||
Remember, this is only valid for mutable iterables - lists and dicts and
|
||||
combinations of the two.
|
||||
`Immutable <http://en.wikipedia.org/wiki/Immutable>`_ objects (strings,
|
||||
numbers, tuples etc) are already disconnected from the database from the
|
||||
onset. So making the outermost iterable into a tuple is also a way to
|
||||
stop any changes to the structure from updating the database.
|
||||
|
||||
::
|
||||
|
||||
obj.db.mytup = (1,2,[3,4])
|
||||
obj.db.mytup[0] = 5 # this fails since tuples are immutable
|
||||
obj.db.mytup[2][1] = 5 # this works but will NOT update database since outermost iterable is a tuple
|
||||
print obj.db.mytup[2][1] # this still returns 4, not 5
|
||||
mytup1 = obj.db.mytup
|
||||
# mytup1 is already disconnected from database since outermost
|
||||
# iterable is a tuple, so we can edit the internal list as we want
|
||||
# without affecting the database.
|
||||
|
||||
Locking and checking Attributes
|
||||
-------------------------------
|
||||
|
||||
Attributes are normally not locked down by default, but you can easily
|
||||
change that for individual Attributes (like those that may be
|
||||
game-sensitive in games with user-level building).
|
||||
|
||||
First you need to set a *lock string* on your Attribute. Lock strings
|
||||
are specified `here <Locks.html>`_. The relevant lock types are
|
||||
|
||||
- *attrread* - limits who may read the value of the Attribute
|
||||
- *attredit* - limits who may set/change this Attribute
|
||||
|
||||
You cannot use the ``db`` handler to modify Attribute object (such as
|
||||
setting a lock on them) - The ``db`` handler will return the Attribute's
|
||||
*value*, not the Attribute object itself. Instead you use
|
||||
``get_attribute_obj`` (see next section) which allows you to set the
|
||||
lock something like this:
|
||||
|
||||
::
|
||||
|
||||
obj.attributes.get("myattr", return_obj=True).locks.add("attread:all();attredit:perm(Wizards)")
|
||||
|
||||
A lock is no good if nothing checks it -- and by default Evennia does
|
||||
not check locks on Attributes. You have to add a check to your
|
||||
commands/code wherever it fits (such as before setting an Attribute).
|
||||
|
||||
::
|
||||
|
||||
# in some command code where we want to limit
|
||||
# setting of a given attribute name on an object
|
||||
attr = obj.attributes.get(attrname, return_obj=True, accessing_obj=caller, default=None, default_access=False)
|
||||
if not attr:
|
||||
caller.msg("You cannot edit that Attribute!")
|
||||
return
|
||||
# edit the Attribute here
|
||||
|
||||
The same keywords are available to use with ``obj.attributes.set()`` and
|
||||
``obj.attributes.remove()``, those will check for the *attredit* lock
|
||||
type.
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
Sometimes it's just not worth the grief ...
|
||||
===========================================
|
||||
|
||||
Whether due to abuse, blatant breaking of your rules, or some other
|
||||
reason you will eventually find no other recourse but to kick out a
|
||||
particularly troublesome player. The default command set has admin tools
|
||||
to handle this, primarily ``@ban, @unban`` and ``@boot``.
|
||||
|
||||
Creating a ban
|
||||
==============
|
||||
|
||||
Say we have a troublesome player "YouSuck" - this is a guy that refuse
|
||||
common courtesy - an abusive and spammy account that is clearly created
|
||||
by some bored internet hooligan only to cause grief. You have tried to
|
||||
be nice. Now you just want this troll gone.
|
||||
|
||||
Name ban
|
||||
--------
|
||||
|
||||
The easiest is to block the account YouSuck from ever connecting again.
|
||||
|
||||
::
|
||||
|
||||
@ban YouSuck
|
||||
|
||||
This will lock the name YouSuck (as well as 'yousuck' and any other
|
||||
combination), and next time they try to log in with this name the server
|
||||
will not let them!
|
||||
|
||||
You can also give a reason so you remember later why this was a good
|
||||
thing (the banned player will never see this)
|
||||
|
||||
::
|
||||
|
||||
@ban YouSuck:This is just a troll.
|
||||
|
||||
If you are sure this is just a spam account, you might even consider
|
||||
deleting the player account outright:
|
||||
|
||||
::
|
||||
|
||||
@delplayer YouSuck
|
||||
|
||||
Generally banning the name is the easier and safer way to stop the use
|
||||
of an account -- if you change your mind you can always remove the block
|
||||
later whereas a deletion is permanent.
|
||||
|
||||
IP ban
|
||||
------
|
||||
|
||||
Just because you block YouSuck's name might not mean the trolling human
|
||||
behind that account gives up. They can just create a new account
|
||||
YouSuckMore and be back at it. One way to make things harder for them is
|
||||
to tell the server to not allow connections from their particular IP
|
||||
address.
|
||||
|
||||
First, when the offending player is online, check which IP address they
|
||||
use. This you can do with the ``who`` command, which will show you
|
||||
something like this:
|
||||
|
||||
::
|
||||
|
||||
Player Name On for Idle Room Cmds Host
|
||||
YouSuck 01:12 2m 22 212 237.333.0.223
|
||||
|
||||
The "Host" bit is the IP address from which the player is connecting.
|
||||
Use this to define the ban instead of the name:
|
||||
|
||||
::
|
||||
|
||||
@ban 237.333.0.223
|
||||
|
||||
This will stop YouSuck connecting from his computer. Note however that
|
||||
IP addresses might change easily - either due to how the player's
|
||||
Internet Service Provider operates or by the user simply changing
|
||||
computer. You can make a more general ban by putting asterisks ``*`` as
|
||||
wildcards for the groups of three digits in the address. So if you
|
||||
figure out that YouSuck mainly connects from 237.333.0.223,
|
||||
237.333.0.225 and 237.333.0.256 (only changes in the local subnet), it
|
||||
might be an idea to put down a ban like this to include any number in
|
||||
that subnet:
|
||||
|
||||
::
|
||||
|
||||
@ban 237.333.0.*
|
||||
|
||||
You should combine the IP ban with a name-ban too of course, so the
|
||||
account YouSuck is truly locked regardless of from where they connect.
|
||||
|
||||
Be careful with too general IP bans however (more asterisks above). If
|
||||
you are unlucky you could be blocking out innocent players who just
|
||||
happen to connect from the same subnet as the offender.
|
||||
|
||||
Booting
|
||||
=======
|
||||
|
||||
YouSuck is not really noticing all this banning yet though - and won't
|
||||
until having logged out and tries to log back in again. Let's help the
|
||||
troll along.
|
||||
|
||||
::
|
||||
|
||||
@boot YouSuck
|
||||
|
||||
Good riddance. You can give a reason for booting too (to be echoed to
|
||||
the player before getting kicked out).
|
||||
|
||||
::
|
||||
|
||||
@boot YouSuck:Go troll somewhere else.
|
||||
|
||||
Lifting a ban
|
||||
=============
|
||||
|
||||
Give the ``@unban`` (or ``@ban``) command without any arguments and you
|
||||
will see a list of all currently active bans:
|
||||
|
||||
::
|
||||
|
||||
Active bans
|
||||
id name/ip date reason
|
||||
1 yousuck Fri Jan 3 23:00:22 2020 This is just a Troll.
|
||||
2 237.333.0.* Fri Jan 3 23:01:03 2020 YouSuck's IP.
|
||||
|
||||
Use the ``id`` from this list to find out which ban to lift.
|
||||
|
||||
::
|
||||
|
||||
@unban 2
|
||||
|
||||
Cleared ban 2: 237.333.0.*
|
||||
|
||||
|
|
@ -1,247 +0,0 @@
|
|||
The Batch-Code processor
|
||||
========================
|
||||
|
||||
For an introduction and motivation to using batch processors, see
|
||||
`here <BatchProcessors.html>`_. This page describes the Batch-*code*
|
||||
processor. The Batch-*command* one is covered
|
||||
`here <BatchCommandProcessor.html>`_.
|
||||
|
||||
Basic Usage
|
||||
-----------
|
||||
|
||||
The batch-command processor is a superuser-only function, invoked by
|
||||
|
||||
::
|
||||
|
||||
> @batchcode path.to.batchcodefile
|
||||
|
||||
Where ``path.to.batchcodefile`` is the path to a *batch-code file*. Such
|
||||
a file should have a name ending in "``.py``\ " (but you shouldn't
|
||||
include that when you batch-run the file from the game below). The path
|
||||
is given like a python path relative to a folder you define to hold your
|
||||
batch files, set by ``BATCH_IMPORT_PATH`` in your settings. Default
|
||||
folder is ``game/gamesrc/world``. So if you want to run the example
|
||||
batch file in ``game/gamesrc/world/examples/batch_code.py``, you could
|
||||
simply use
|
||||
|
||||
::
|
||||
|
||||
> @batchcommand examples.batch_code
|
||||
|
||||
This will try to run through the entire batch file in one go. For more
|
||||
gradual, *interactive* control you can use the ``/interactive`` switch.
|
||||
The switch ``/debug`` will put the processor in *debug* mode. Read below
|
||||
for more info.
|
||||
|
||||
The batch file
|
||||
--------------
|
||||
|
||||
A batch-code file is mostly a normal Python source file. The only thing
|
||||
separating a batch file from any standard Python module is that the code
|
||||
is wrapped into *blocks* using a special syntax. These blocks allow the
|
||||
batch processor more control over execution, especially when using the
|
||||
processor's *interactive* mode. In interactive mode these blocs allow
|
||||
the batchcode runner to pause and only execute certain blocks at a time.
|
||||
There is however nothing stopping you from coding everything in one
|
||||
single block if you don't want to split things up into chunks like this.
|
||||
|
||||
Here are the rules of syntax of the batch-command ``*.py`` file.
|
||||
|
||||
- ``#HEADER`` as the first on a line marks the start of a *header*
|
||||
block. This is intended to hold imports and variables that might be
|
||||
of use for other blocks. All python code defined in a header block
|
||||
will always be inserted at the top of all ``#CODE`` blocks in the
|
||||
file. You may have more than one ``#HEADER`` block, but that is
|
||||
equivalent to having one big one. Comments in ``#HEADER`` blocks are
|
||||
stripped out before merging.
|
||||
- ``#CODE`` as the first on a line marks the start of a *code* block.
|
||||
Code blocks contain functional python code. ``#HEADER`` blocks are
|
||||
added to the top of code blocks at runtime.
|
||||
- ``#CODE (info) obj1, obj2, ...`` is an optional form of the code
|
||||
block header. The ``(info)`` field gives extra info about what's
|
||||
going on in the block and is displayed by the batch processor. The
|
||||
``obj1, obj2, ...`` parts are optional object labels used by the
|
||||
processor's *debug* mode in order to auto-delete objects after a test
|
||||
run.
|
||||
- ``#INSERT path.filename`` as the first on a line loads the contents
|
||||
of another batch-code file into this one. Its ``#CODE`` blocks will
|
||||
be executed as if they were defined in this file, but they will not
|
||||
share ``#HEADER``\ s with the current file, but only use its own, if
|
||||
any.
|
||||
- A new ``#HEADER``, ``#CODE`` or ``#INSERT`` (or the end of the file)
|
||||
ends a previous block. Text before the first block are ignored.
|
||||
- A ``#`` that is not starting a ``#HEADER``, ``#CODE`` or ``#INSERT``
|
||||
instruction is considered a comment.
|
||||
- Inside a block, normal Python syntax rules apply. For the sake of
|
||||
indentation, each block acts as a separate python module.
|
||||
- The variable ``caller`` is always made available to the script,
|
||||
pointing to the object executing the batchcommand.
|
||||
|
||||
Below is a version of the example file found in
|
||||
``game/gamesrc/world/examples/batch_code.py``.
|
||||
|
||||
::
|
||||
|
||||
#
|
||||
# This is an example batch-code build file for Evennia.
|
||||
#
|
||||
|
||||
#HEADER
|
||||
|
||||
# This will be included in all other #CODE blocks
|
||||
|
||||
from src.utils import create, search
|
||||
from game.gamesrc.objects.examples import red_button
|
||||
from game.gamesrc.objects import baseobjects
|
||||
|
||||
limbo = search.objects(caller, 'Limbo', global_search=True)[0]
|
||||
|
||||
|
||||
#CODE (create red button)
|
||||
|
||||
red_button = create.create_object(red_button.RedButton, key="Red button",
|
||||
location=limbo, aliases=["button"])
|
||||
|
||||
# caller points to the one running the script
|
||||
caller.msg("A red button was created.")
|
||||
|
||||
# importing more code from another batch-code file
|
||||
#INSERT examples.batch_code_insert
|
||||
|
||||
#CODE (create table and chair) table, chair
|
||||
|
||||
table = create.create_object(baseobjects.Object, key="Blue Table", location=limbo)
|
||||
chair = create.create_object(baseobjects.Object, key="Blue Chair", location=limbo)
|
||||
|
||||
string = "A %s and %s were created. If debug was active, they were deleted again."
|
||||
caller.msg(string % (table, chair))
|
||||
|
||||
This uses Evennia's Python API to create three objects in sequence.
|
||||
|
||||
Debug mode
|
||||
----------
|
||||
|
||||
Try to run the example script with
|
||||
|
||||
::
|
||||
|
||||
> @batchcode/debug examples.batch_code
|
||||
|
||||
The batch script will run to the end and tell you it completed. You will
|
||||
also get messages that the button and the two pieces of furniture where
|
||||
created. Look around and you should see the button there. But you won't
|
||||
see any chair nor a table! This is because we ran this with the
|
||||
``/debug`` switch. The debug mode of the processor is intended to be
|
||||
used when you test out a script. Maybe you are looking for bugs in your
|
||||
code or try to see if things behave as they should. Running the script
|
||||
over and over would then create an ever-growing stack of buttons, chairs
|
||||
and tables, all with the same name. You would have to go back and
|
||||
painstakingly delete them later. The debug mode simply tries to
|
||||
automatically delete the objects that where created so as to not crowd
|
||||
the room with unwanted objects.
|
||||
|
||||
The second ``#CODE`` block supplies the variable names ``table`` and
|
||||
``chair``, which match the actual variables we later assign our new
|
||||
ojects to. In debug mode the batch-code processor will look for these
|
||||
references and simply run ``delete()`` on them. Since the
|
||||
button-creating block does not define any such variables the processor
|
||||
can't help us there - meaning the button stays also in debug mode.
|
||||
|
||||
Interactive mode
|
||||
----------------
|
||||
|
||||
Interactive mode works very similar to the `batch-command processor
|
||||
counterpart <BatchCommandProcessor.html>`_. It allows you more step-wise
|
||||
control over how the batch file is executed. This is useful for
|
||||
debugging or for picking and choosing only particular blocks to run. Use
|
||||
``@batchcommand`` with the ``/interactive`` flag to enter interactive
|
||||
mode.
|
||||
|
||||
::
|
||||
|
||||
> @batchcode/interactive examples.batch_code
|
||||
|
||||
You should see the following:
|
||||
|
||||
::
|
||||
|
||||
01/02: #CODE (create red button) [...] (hh for help)
|
||||
|
||||
This shows that you are on the first ``#CODE`` block, the first of only
|
||||
two commands in this batch file. Observe that the block has *not*
|
||||
actually been executed at this point!
|
||||
|
||||
To take a look at the full code snippet you are about to run, use ``ll``
|
||||
(a batch-processor version of ``look``).
|
||||
|
||||
::
|
||||
|
||||
from src.utils import create, search
|
||||
from game.gamesrc.objects.examples import red_button
|
||||
from game.gamesrc.objects import baseobjects
|
||||
|
||||
limbo = search.objects(caller, 'Limbo', global_search=True)[0]
|
||||
|
||||
red_button = create.create_object(red_button.RedButton, key="Red button",
|
||||
location=limbo, aliases=["button"])
|
||||
|
||||
# caller points to the one running the script
|
||||
caller.msg("A red button was created.")
|
||||
|
||||
Compare with the example code given earlier. Notice how the content of
|
||||
``#HEADER`` has been pasted at the top of the ``#CODE`` block. Use
|
||||
``pp`` to actually execute this block (this will create the button and
|
||||
give you a message). Use ``nn`` (next) to go to the next command. Use
|
||||
``hh`` for a list of commands.
|
||||
|
||||
If there are tracebacks, fix them in the batch file, then use ``rr`` to
|
||||
reload the file. You will still be at the same code block and can rerun
|
||||
it easily with ``pp`` as needed. This makes for a simple debug cycle. It
|
||||
also allows you to rerun individual troublesome blocks - as mentioned,
|
||||
in a large batch file this can be very useful (don't forget the
|
||||
``/debug`` mode either).
|
||||
|
||||
Use ``nn`` and ``bb`` (next and back) to step through the file; e.g.
|
||||
``nn 12`` will jump 12 steps forward (without processing any blocks in
|
||||
between). All normal commands of Evennia should work too while working
|
||||
in interactive mode.
|
||||
|
||||
Limitations and Caveats
|
||||
-----------------------
|
||||
|
||||
The batch-code processor is by far the most flexible way to build a
|
||||
world in Evennia. There are however some caveats you need to keep in
|
||||
mind.
|
||||
|
||||
- *Safety*. Or rather the lack of it. There is a reason only
|
||||
*superusers* are allowed to run the batch-code processor by default.
|
||||
The code-processor runs *without any Evennia security checks* and
|
||||
allows full access to Python. If an untrusted party could run the
|
||||
code-processor they could execute arbitrary python code on your
|
||||
machine, which is potentially a very dangerous thing. If you want to
|
||||
allow other users to access the batch-code processor you should make
|
||||
sure to run Evennia as a separate and very limited-access user on
|
||||
your machine (i.e. in a 'jail'). By comparison, the batch-command
|
||||
processor is much safer since the user running it is still 'inside'
|
||||
the game and can't really do anything outside what the game commands
|
||||
allow them to.
|
||||
- *You cannot communicate between code blocks*. Global variables won't
|
||||
work in code batch files, each block is executed as stand-alone
|
||||
environments. Similarly you cannot in one ``#CODE`` block assign to
|
||||
variables from the ``#HEADER`` block and expect to be able to read
|
||||
the changes from another ``#CODE`` block (whereas a python execution
|
||||
limitation, allowing this would also lead to very hard-to-debug code
|
||||
when using the interactive mode). The main issue with this is when
|
||||
building e.g. a room in one code block and later want to connect that
|
||||
room with a room you built in another block. To do this, you must
|
||||
perform a database search for the name of the room you created (since
|
||||
you cannot know in advance which dbref it got assigned). This sounds
|
||||
iffy, but there is an easy way to handler this - use object aliases.
|
||||
You can assign any number of aliases to any object. Make sure that
|
||||
one of those aliases is unique (like "room56") and you will
|
||||
henceforth be able to always find it later by searching for it from
|
||||
other code blocks regardless of if the main name is shared with
|
||||
hundreds of other rooms in your world (coincidentally, this is also
|
||||
one way of implementing "zones", should you want to group rooms
|
||||
together).
|
||||
|
||||
|
|
@ -1,222 +0,0 @@
|
|||
The Batch-Command processor
|
||||
===========================
|
||||
|
||||
For an introduction and motivation to using batch processors, see
|
||||
`here <BatchProcessors.html>`_. This page describes the Batch-*command*
|
||||
processor. The Batch-*code* one is covered
|
||||
`here <BatchCodeProcessor.html>`_.
|
||||
|
||||
Basic Usage
|
||||
-----------
|
||||
|
||||
The batch-command processor is a superuser-only function, invoked by
|
||||
|
||||
::
|
||||
|
||||
> @batchcommand path.to.batchcmdfile
|
||||
|
||||
Where ``path.to.batchcmdfile`` is the path to a *batch-command file*
|
||||
with the "``.ev``\ " file ending. This path is given like a python path
|
||||
relative to a folder you define to hold your batch files, set with
|
||||
``BATCH_IMPORT_PATH`` in your settings. Default folder is
|
||||
``game/gamesrc/world``. So if you want to run the example batch file in
|
||||
``game/gamesrc/world/examples/batch_cmds.ev``, you could use
|
||||
|
||||
::
|
||||
|
||||
> @batchcommand examples.batch_cmds
|
||||
|
||||
A batch-command file contains a list of Evennia in-game commands
|
||||
separated by comments. The processor will run the batch file from
|
||||
beginning to end. Note that *it will not stop if commands in it fail*
|
||||
(there is no universal way for the processor to know what a failure
|
||||
looks like for all different commands). So keep a close watch on the
|
||||
output, or use *Interactive mode* (see below) to run the file in a more
|
||||
controlled, gradual manner.
|
||||
|
||||
The batch file
|
||||
--------------
|
||||
|
||||
The batch file is a simple plain-text file containing Evennia commands.
|
||||
Just like you would write them in-game, except you have more freedom
|
||||
with line breaks.
|
||||
|
||||
Here are the rules of syntax of an ``*.ev`` file. You'll find it's
|
||||
really, really simple:
|
||||
|
||||
- All lines having the ``#`` (hash)-symbol *as the first one on the
|
||||
line* are considered *comments*. All non-comment lines are treated as
|
||||
a command and/or their arguments.
|
||||
- Comment lines have an actual function -- they mark the *end of the
|
||||
previous command definition*. So never put two commands directly
|
||||
after one another in the file - separate them with a comment, or the
|
||||
second of the two will be considered an argument to the first one.
|
||||
Besides, using plenty of comments is good practice anyway.
|
||||
- A line that starts with the word ``#INSERT`` is a comment line but
|
||||
also signifies a special instruction. The syntax is
|
||||
``#INSERT <path.batchfile>`` and tries to import a given batch-cmd
|
||||
file into this one. The inserted batch file (file ending ``.ev``)
|
||||
will run normally from the point of the ``#INSERT`` instruction.
|
||||
- Extra whitespace in a command definition is *ignored*.
|
||||
- A completely empty line translates in to a line break in texts. Two
|
||||
empty lines thus means a new paragraph (this is obviously only
|
||||
relevant for commands accepting such formatting, such as the
|
||||
``@desc`` command).
|
||||
- The very last command in the file is not required to end with a
|
||||
comment.
|
||||
- You *cannot* nest another ``@batchcommand`` statement into your batch
|
||||
file. If you want to link many batch-files together, use the
|
||||
``#INSERT`` batch instruction instead. You also cannot launch the
|
||||
``@batchcode`` command from your batch file, the two batch processors
|
||||
are not compatible.
|
||||
|
||||
Below is a version of the example file found in
|
||||
``game/gamesrc/commands/examples/batch_cmds.ev``.
|
||||
|
||||
::
|
||||
|
||||
#
|
||||
# This is an example batch build file for Evennia.
|
||||
#
|
||||
|
||||
# This creates a red button
|
||||
@create button:examples.red_button.RedButton
|
||||
# (This comment ends input for @create)
|
||||
# Next command. Let's create something.
|
||||
@set button/desc =
|
||||
This is a large red button. Now and then
|
||||
it flashes in an evil, yet strangely tantalizing way.
|
||||
|
||||
A big sign sits next to it. It says:
|
||||
|
||||
|
||||
-----------
|
||||
|
||||
Press me!
|
||||
|
||||
-----------
|
||||
|
||||
|
||||
... It really begs to be pressed! You
|
||||
know you want to!
|
||||
|
||||
# This inserts the commands from another batch-cmd file named
|
||||
# batch_insert_file.ev.
|
||||
#INSERT examples.batch_insert_file
|
||||
|
||||
|
||||
# (This ends the @set command). Note that single line breaks
|
||||
# and extra whitespace in the argument are ignored. Empty lines
|
||||
# translate into line breaks in the output.
|
||||
# Now let's place the button where it belongs (let's say limbo #2 is
|
||||
# the evil lair in our example)
|
||||
@teleport #2
|
||||
# (This comments ends the @teleport command.)
|
||||
# Now we drop it so others can see it.
|
||||
# The very last command in the file needs not be ended with #.
|
||||
drop button
|
||||
|
||||
To test this, run ``@batchcommand`` on the file. A button will be
|
||||
created, described and dropped in Limbo. All commands will be executed
|
||||
by the user calling the command. *Note that if you interact with the
|
||||
button, you might find that its description changes, loosing your
|
||||
custom-set description above. This is just the way this particular
|
||||
object works.*
|
||||
|
||||
Interactive mode
|
||||
----------------
|
||||
|
||||
Interactive mode allows you to more step-wise control over how the batch
|
||||
file is executed. This is useful for debugging and also if you have a
|
||||
large batch file and is only updating a small part of it -- running the
|
||||
entire file again would be a waste of time (and in the case of
|
||||
``@create``-ing objects you would to end up with multiple copies of
|
||||
same-named objects, for example). Use ``@batchcommand`` with the
|
||||
``/interactive`` flag to enter interactive mode.
|
||||
|
||||
::
|
||||
|
||||
> @batchcommand/interactive examples.batch_cmds
|
||||
|
||||
You will see this:
|
||||
|
||||
::
|
||||
|
||||
01/04: @create button:examples.red_button.RedButton (hh for help)
|
||||
|
||||
This shows that you are on the ``@create`` command, the first out of
|
||||
only four commands in this batch file. Observe that the command
|
||||
``@create`` has *not* been actually processed at this point!
|
||||
|
||||
To take a look at the full command you are about to run, use ``ll`` (a
|
||||
batch-processor version of ``look``). Use ``pp`` to actually process the
|
||||
current command (this will actually ``@create`` the button) -- and make
|
||||
sure it worked as planned. Use ``nn`` (next) to go to the next command.
|
||||
Use ``hh`` for a list of commands.
|
||||
|
||||
If there are errors, fix them in the batch file, then use ``rr`` to
|
||||
reload the file. You will still be at the same command and can rerun it
|
||||
easily with ``pp`` as needed. This makes for a simple debug cycle. It
|
||||
also allows you to rerun individual troublesome commands - as mentioned,
|
||||
in a large batch file this can be very useful. Do note that in many
|
||||
cases, commands depend on the previous ones (e.g. if ``@create`` in the
|
||||
example above had failed, the following commands would have had nothing
|
||||
to operate on).
|
||||
|
||||
Use ``nn`` and ``bb`` (next and back) to step through the file; e.g.
|
||||
``nn 12`` will jump 12 steps forward (without processing any command in
|
||||
between). All normal commands of Evennia should work too while working
|
||||
in interactive mode.
|
||||
|
||||
Limitations and Caveats
|
||||
-----------------------
|
||||
|
||||
The batch-command processor is great for automating smaller builds or
|
||||
for testing new commands and objects repeatedly without having to write
|
||||
so much. There are several caveats you have to be aware of when using
|
||||
the batch-command processor for building larger, complex worlds though.
|
||||
|
||||
The main issue is that when you run a batch-command script you (*you*,
|
||||
as in your superuser character) are actually moving around in the game
|
||||
creating and building rooms in sequence, just as if you had been
|
||||
entering those commands manually, one by one. You have to take this into
|
||||
account when creating the file, so that you can 'walk' (or teleport) to
|
||||
the right places in order.
|
||||
|
||||
This also means there are several pitfalls when designing and adding
|
||||
certain types of objects. Here are some examples:
|
||||
|
||||
- *Rooms that changes your `cmdset <Commands.html>`_*: Imagine that you
|
||||
build a 'dark' room, which severely limits the cmdsets of those
|
||||
entering it (maybe you have to find the light switch to proceed). In
|
||||
your batch script you would create this room, then teleport to it -
|
||||
and promptly be shifted into the dark state where none of your normal
|
||||
build commands work ...
|
||||
- *Auto-teleportation*: Rooms that automatically teleport those that
|
||||
enter them to another place (like a trap room, for example). You
|
||||
would be teleported away too.
|
||||
- *Mobiles*: If you add aggressive mobs, they might attack you, drawing
|
||||
you into combat. If they have AI they might even follow you around
|
||||
when building - or they might move away from you before you've had
|
||||
time to finish describing and equipping them!
|
||||
|
||||
The solution to all these is to plan ahead. Make sure that superusers
|
||||
are never affected by whatever effects are in play. Add an on/off switch
|
||||
to objects and make sure it's always set to *off* upon creation. It's
|
||||
all doable, one just needs to keep it in mind.
|
||||
|
||||
Assorted notes
|
||||
--------------
|
||||
|
||||
The fact that you build as 'yourself' can also be considered an
|
||||
advantage however, should you ever decide to change the default command
|
||||
to allow others than superusers to call the processor. Since normal
|
||||
access-checks are still performed, a malevolent builder with access to
|
||||
the processor should not be able to do all that much damage (this is the
|
||||
main drawback of the `batch-code processor <BatchCodeProcessor.html>`_)
|
||||
|
||||
`GNU Emacs <http://en.wikipedia.org/wiki/Emacs>`_ users might find it
|
||||
interesting to use emacs' *evennia mode*. This is an Emacs major mode
|
||||
found in ``src/utils/evennia-mode.el``. It offers correct syntax
|
||||
highlighting and indentation with ``<tab>`` when editing ``.ev`` files
|
||||
in Emacs. See the header of that file for installation instructions.
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
Batch Processors - overview
|
||||
===========================
|
||||
|
||||
Building a game world is a lot of work, especially when starting out.
|
||||
Rooms should be created, descriptions have to be written, objects must
|
||||
be detailed and placed in their proper places. In many traditional MUD
|
||||
setups you had to do all this online, line by line, over a telnet
|
||||
session.
|
||||
|
||||
Evennia already moves away from much of this by shifting the main coding
|
||||
work to external Python modules. But also building would be helped if
|
||||
one could do some or all of it externally. Enter Evennia's *batch
|
||||
command processors* (there are two of them). The processors allows you,
|
||||
as a game admin, to build your game completely offline in normal text
|
||||
files (*batch files*) that the processors understands. Then, when you
|
||||
are ready, you use the processors to read it all into Evennia (and into
|
||||
the database) in one go.
|
||||
|
||||
You can of course still build completely online should you want to -
|
||||
this is certainly the easiest way to go when learning and for small
|
||||
build projects. But for major building work, the advantages of using the
|
||||
batch-processors are many:
|
||||
|
||||
- It's hard to compete with the comfort of a modern desktop text
|
||||
editor; Compared to a traditional MUD line input, you can get much
|
||||
better overview and many more features. Also, accidentally pressing
|
||||
Return won't immediately commit things to the database.
|
||||
- You might run external spell checkers on your batch files. In the
|
||||
case of one of the batch-processors (the one that deals with Python
|
||||
code), you could also run external debuggers and code analyzers on
|
||||
your file to catch problems before feeding it to Evennia.
|
||||
- The batch files (as long as you keep them) are records of your work.
|
||||
They make a natural starting point for quickly re-building your world
|
||||
should you ever decide to start over.
|
||||
- If you are an Evennia developer, using a batch file is a fast way to
|
||||
setup a test-game after having reset the database.
|
||||
- The batch files might come in useful should you ever decide to
|
||||
distribute all or part of your world to others.
|
||||
|
||||
There are two batch processors, the Batch-*command* processor and the
|
||||
Batch-*code* processor. The first one is the simpler of the two. It has
|
||||
the advantage of you not needing to know any programming - you basically
|
||||
just list game commands in a text file. The code-processor on the other
|
||||
hand is much more powerful but also more complex - it lets you to use
|
||||
Evennia's API to code your world in full-fledged Python code.
|
||||
|
||||
- The `Batch-command processor <BatchCommandProcessor.html>`_
|
||||
- The `Batch-code processor <BatchCodeProcessor.html>`_
|
||||
|
||||
If you plan to use international characters in your batchfiles you are
|
||||
wise to read about *file encodings* below.
|
||||
|
||||
A note on File Encodings
|
||||
------------------------
|
||||
|
||||
As mentioned, both the processors take text files as input and then
|
||||
proceeds to process them. As long as you stick to the standard
|
||||
`ASCII <http://en.wikipedia.org/wiki/Ascii>`_ character set (which means
|
||||
the normal English characters, basically) you should not have to worry
|
||||
much about this section.
|
||||
|
||||
Many languages however use characters outside the simple ``ASCII``
|
||||
table. Common examples are various apostrophes and umlauts but also
|
||||
completely different symbols like those of the greek or cyrillic
|
||||
alphabets.
|
||||
|
||||
First, we should make it clear that Evennia itself handles international
|
||||
characters just fine. It (and Django) uses
|
||||
`unicode <http://en.wikipedia.org/wiki/Unicode>`_ strings internally.
|
||||
|
||||
The problem is that when reading a text file like the batchfile, we need
|
||||
to know how to decode the byte-data stored therein to universal unicode.
|
||||
That means we need an *encoding* (a mapping) for how the file stores its
|
||||
data. There are many, many byte-encodings used around the world, with
|
||||
opaque names such as ``Latin-1``, ``ISO-8859-3`` or ``ARMSCII-8`` to
|
||||
pick just a few examples. Problem is that it's practially impossible to
|
||||
determine which encoding was used to save a file just by looking at it
|
||||
(it's just a bunch of bytes!). You have to *know*.
|
||||
|
||||
With this little introduction it should be clear that Evennia can't
|
||||
guess but has to *assume* an encoding when trying to load a batchfile.
|
||||
The text editor and Evennia must speak the same "language" so to speak.
|
||||
Evennia will by default first try the international ``UTF-8`` encoding,
|
||||
but you can have Evennia try any sequence of different encodings by
|
||||
customizing the ``ENCODINGS`` list in your settings file. Evennia will
|
||||
use the first encoding in the list that do not raise any errors. Only if
|
||||
none work will the server give up and return an error message.
|
||||
|
||||
You can often change the text editor encoding (this depends on your
|
||||
editor though), otherwise you need to add the editor's encoding to
|
||||
Evennia's ``ENCODINGS`` list. If you are unsure, write a test file with
|
||||
lots of non-ASCII letters in the editor of your choice, then import to
|
||||
make sure it works as it should.
|
||||
|
||||
More help with encodings can be found in the entry `Text
|
||||
Encodings <TextEncodings.html>`_ and also in the Wikipedia article
|
||||
`here <http://en.wikipedia.org/wiki/Text_encodings>`_.
|
||||
|
||||
**A footnote for the batch-code processor**: Just because *Evennia* can
|
||||
parse your file and your fancy special characters, doesn't mean that
|
||||
*Python* allows their use. Python syntax only allows international
|
||||
characters inside *strings*. In all other source code only ``ASCII`` set
|
||||
characters are allowed.
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
Builder Documentation
|
||||
=====================
|
||||
|
||||
This section contains information useful to world builders.
|
||||
|
||||
Building basics
|
||||
---------------
|
||||
|
||||
- `Default in-game commands <DefaultCommandHelp.html>`_
|
||||
- `Building Quick-start <BuildingQuickstart.html>`_
|
||||
- `Giving build permissions to others <BuildingPermissions.html>`_
|
||||
- `Adding colour <Colours.html>`_
|
||||
- `Customizing the connection screen <ConnectionScreen.html>`_
|
||||
|
||||
Advanced building and World building
|
||||
------------------------------------
|
||||
|
||||
- `Overview of batch processors <BatchProcessors.html>`_
|
||||
|
||||
- `Batch-command processor <BatchCommandProcessor.html>`_
|
||||
- `Batch-code processor <BatchCodeProcessor.html>`_
|
||||
|
||||
- `Adding zones <Zones.html>`_
|
||||
|
||||
The Tutorial world
|
||||
------------------
|
||||
|
||||
- `Introduction and setup <TutorialWorldIntroduction.html>`_
|
||||
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
Giving permissions to your staff
|
||||
================================
|
||||
|
||||
*OBS: This gives only a brief introduction to the access system. Locks
|
||||
and permissions are fully detailed* `here <Locks.html>`_.
|
||||
|
||||
The super user
|
||||
--------------
|
||||
|
||||
There are strictly speaking two types of users in Evennia, the *super
|
||||
user* and everyone else. The superuser is the first user you create,
|
||||
object #1. This is the all-powerful server-owner account. A superuser
|
||||
account has access to everything and no locks affect them. Technically
|
||||
the superuser not only has all access, it even bypasses the permission
|
||||
checks entirely. This makes the superuser impossible to lock out, but
|
||||
makes it unsuitable to actually play-test the game's locks and
|
||||
restrictions with. Usually there is no need to have but one superuser.
|
||||
|
||||
Assigning permissions
|
||||
---------------------
|
||||
|
||||
Whereas permissions can be used for anything, those put in
|
||||
settings.PERMISSION\_HIERARCHY will have a ranking relative each other
|
||||
as well. By default Evennia creates the following hierarchy:
|
||||
|
||||
#. *Immortals* - these basically have all the same access as superusers
|
||||
(except that they do not sidestep the Permission system). Assign only
|
||||
to really trusted server-admin staff.
|
||||
#. *Wizards* can do everything except affecting the server functions
|
||||
itself. So a wizard couldn't reload or shutdown the server for
|
||||
example. They also cannot execute arbitrary Python code on the
|
||||
console or import files from the hard drive.
|
||||
#. *Builders* has all the build commands, but cannot affect other
|
||||
players or mess with the server.
|
||||
#. *PlayerHelpers* are almost like a normal *Player*, but they can also
|
||||
add help files to the database.
|
||||
#. *Players* is the default group that new players end up in. A new
|
||||
player have permission to use tells, to use and create new channels.
|
||||
|
||||
A user having a certain level of permission automatically have access to
|
||||
locks specifying access of a lower level.
|
||||
|
||||
To assign a new permission from inside the game, you need to be able to
|
||||
use the ``@perm`` command. This is an *Immortal*-level command, but it
|
||||
could in principle be made lower-access since it only allows assignments
|
||||
equal or lower to your current level (so you cannot use it to escalate
|
||||
your own permission level). So, assuming you yourself have *Immortal*
|
||||
access (or is superuser), you assign a new player "Tommy" to your core
|
||||
staff with the command
|
||||
|
||||
::
|
||||
|
||||
@perm/add *Tommy = Immortals
|
||||
|
||||
The ``*`` makes sure to put the permission on the *Player* and not on
|
||||
any eventual *Character* that may also be named Tommy. This is usually
|
||||
what you want since the Player will then remain an Immortal regardless
|
||||
of which Character they are currently controlling. To limit permission
|
||||
to a per-Character level you should instead use *quelling* (see below).
|
||||
|
||||
Quelling your permissions
|
||||
-------------------------
|
||||
|
||||
When developing it can be useful to check just how things would look had
|
||||
your permission-level been lower. For this you can use *quelling*.
|
||||
Normally, when you puppet a Character you are using your Player-level
|
||||
permission. So even if your Character only has *Players* level
|
||||
permissions, your *Immortals*-level Player will take precedence. With
|
||||
the ``@quell`` command you can change so that the Character's permission
|
||||
takes precedence instead:
|
||||
|
||||
::
|
||||
|
||||
@quell
|
||||
|
||||
This will allow you to test out the game using the current Character's
|
||||
permission level. A developer or builder can thus in principle maintain
|
||||
several test characters, all using different permission levels. Note
|
||||
that you cannot escalate your permissions this way; If the Character
|
||||
happens to have a *higher* permission level than the Player, the
|
||||
Player's permission will still be used.
|
||||
|
|
@ -1,354 +0,0 @@
|
|||
Building Quick-start
|
||||
====================
|
||||
|
||||
The default `command <Commands.html>`_ definitions coming with Evennia
|
||||
follows a style `similar <UsingMUXAsAStandard.html>`_ to that of MUX, so
|
||||
the commands should be familiar if you used any such code bases before.
|
||||
If you haven't, you might be confused by the use of ``@`` all over. This
|
||||
is just a naming convention - commands related to out-of-character or
|
||||
admin-related actions tend to start with ``@``, the symbol has no
|
||||
meaning of its own.
|
||||
|
||||
The default commands have the following style (where ``[...]`` marks
|
||||
optional parts):
|
||||
|
||||
::
|
||||
|
||||
command[/switch/switch...] [arguments ...]
|
||||
|
||||
A *switch* is a special, optional flag to the command to make it behave
|
||||
differently. It is always put directly after the command name, and
|
||||
begins with a forward slash (``/``). The *arguments* are one or more
|
||||
inputs to the commands. It's common to use an equal sign (``=``) when
|
||||
assigning something to an object.
|
||||
|
||||
Below are some examples of commands. Use ``help <command>`` for learning
|
||||
more about each command and their detailed options.
|
||||
|
||||
Stepping down from godhood
|
||||
--------------------------
|
||||
|
||||
If you just installed Evennia, your very first player account is called
|
||||
user #1, also known as the *superuser* or *god user*. This user is very
|
||||
powerful, so powerful that it will override many game restrictions such
|
||||
as locks. This can be useful, but it also hides some functionality that
|
||||
you might want to test.
|
||||
|
||||
To temporarily step down from your superuser position you can use the
|
||||
``@quell`` command:
|
||||
|
||||
::
|
||||
|
||||
> @quell
|
||||
|
||||
This will make you start using the permission of your current
|
||||
`Character <Objects.html>`_ instead of your superuser level. If you
|
||||
didn't change any settings your game Character should have an *Immortal*
|
||||
level permission - high as can be without bypassing locks like the
|
||||
superuser does. This will work fine for the examples on this page. Use
|
||||
``@unquell`` to get back to superuser status again afterwards.
|
||||
|
||||
Creating an object
|
||||
------------------
|
||||
|
||||
Basic objects can be anything -- swords, flowers and non-player
|
||||
characters. They are created using the ``@create`` command:
|
||||
|
||||
::
|
||||
|
||||
> @create box
|
||||
|
||||
This created a new 'box' (of the default object type) in your inventory.
|
||||
Use the command ``inventory`` (or ``i``) to see it. Now, 'box' is a
|
||||
rather short name, let's is give a few aliases.
|
||||
|
||||
::
|
||||
|
||||
> @name box = very large box;box;very;crate
|
||||
|
||||
We now actually renamed the box to *very large box* (and this is what we
|
||||
will see when looking at it), but we will also recognize it by any of
|
||||
the other names we give - like *crate* or simply *box* as before. We
|
||||
could have given these aliases directly after the name in the
|
||||
``@create`` command, this is true for all creation commands - you can
|
||||
always tag on a list of ;-separated aliases to the name of your new
|
||||
object. If you had wanted to not change the name itself, but to only add
|
||||
aliases, you could have used the ``@alias`` command.
|
||||
|
||||
We are currently carrying the box. Let's drop it.
|
||||
|
||||
::
|
||||
|
||||
> drop box
|
||||
|
||||
Hey presto - there it is on the ground, in all its normality (you can
|
||||
also create & drop in one go using the ``/drop`` switch, like this:
|
||||
``@create/drop box``).
|
||||
|
||||
::
|
||||
|
||||
> examine box
|
||||
|
||||
This will show some technical details about the box object (you can
|
||||
normally just write ``ex`` as a short for ``examine``).
|
||||
|
||||
Try to ``look`` at the box to see the (default) description.
|
||||
|
||||
::
|
||||
|
||||
> look box
|
||||
|
||||
Let's add some flavor.
|
||||
|
||||
::
|
||||
|
||||
> @describe box = This is a large and very heavy box.
|
||||
|
||||
If you try the ``get`` command we will pick up the box. So far so good,
|
||||
but if we really want this to be a large and heavy box, people should
|
||||
*not* be able to run off with it that easily. To prevent this we need to
|
||||
lock it down. This is done by assigning a *Lock* to it. Make sure the
|
||||
box was dropped in the room, then try this:
|
||||
|
||||
::
|
||||
|
||||
> @lock box = get:false()
|
||||
|
||||
Locks are a rather `big topic <Locks.html>`_, but for now that will do
|
||||
what we want. This will lock the box so noone can lift it. The exception
|
||||
is superusers, they override all locks and will pick it up anyway. Make
|
||||
sure you are quelling your superuser powers and try to get the box now:
|
||||
|
||||
::
|
||||
|
||||
> get box
|
||||
You can't get that.
|
||||
|
||||
Think the default error message looks dull? The ``get`` command looks
|
||||
for an `Attribute <Attributes.html>`_ named ``get_err_msg`` for
|
||||
returning a nicer error message (we just happen to know this, you would
|
||||
need to peek into the code for the ``get`` command to find out). You set
|
||||
attributes using the ``@set`` command:
|
||||
|
||||
::
|
||||
|
||||
> @set box/get_err_msg = It's way too heavy for you to lift.
|
||||
|
||||
Try to get it now and you should see a nicer error message echoed back
|
||||
to you.
|
||||
|
||||
Get a personality
|
||||
-----------------
|
||||
|
||||
`Scripts <Scripts.html>`_ are powerful things that allows time-dependent
|
||||
effects on objects. To try out a first script, let's put one on
|
||||
ourselves. There is an example script in
|
||||
``game/gamesrc/scripts/examples/bodyfunctions.py`` that is called
|
||||
``BodyFunctions``. To add this to us we will use the ``@script``
|
||||
command:
|
||||
|
||||
::
|
||||
|
||||
> @script self = examples.bodyfunctions.BodyFunctions
|
||||
|
||||
(note that you don't have to give the full path as long as you are
|
||||
pointing to a place inside the ``gamesrc/scripts`` directory). Wait a
|
||||
while and you will notice yourself starting making random observations.
|
||||
|
||||
::
|
||||
|
||||
> @script self
|
||||
|
||||
This will show details about scripts on yourself (also ``examine``
|
||||
works). You will see how long it is until it "fires" next. Don't be
|
||||
alarmed if nothing happens when the countdown reaches zero - this
|
||||
particular script has a randomizer to determine if it will say something
|
||||
or not. So you will not see output every time it fires.
|
||||
|
||||
When you are tired of your character's "insights", kill the script with
|
||||
|
||||
::
|
||||
|
||||
> @script/stop self = examples.bodyfunctions.BodyFunctions
|
||||
|
||||
Pushing your buttons
|
||||
--------------------
|
||||
|
||||
If we get back to the box we made, there is only so much fun you can do
|
||||
with it at this point. It's just a dumb generic object. If you renamed
|
||||
it to ``stone`` and changed its description noone would be the wiser.
|
||||
However, with the combined use of custom
|
||||
`Typeclasses <Typeclasses.html>`_, `Scripts <Scripts.html>`_ and
|
||||
object-based `Commands <Commands.html>`_, you could expand it and other
|
||||
items to be as unique, complex and interactive as you want.
|
||||
|
||||
Let's take an example. So far we have only created objects that use the
|
||||
default object typeclass named simply ``Object``. Let's create an object
|
||||
that is a little more interesting. Under ``game/gamesrc/objects/`` there
|
||||
is a directory ``examples`` with a module ``red_button.py``. It contains
|
||||
the enigmatic RedButton typeclass.
|
||||
|
||||
Let's make us one of *those*!
|
||||
|
||||
::
|
||||
|
||||
> @create/drop button:examples.red_button.RedButton
|
||||
|
||||
We import the RedButton python class the same way you would import it in
|
||||
Python except Evennia defaults to looking in ``game/gamesrc/objects/``
|
||||
so you don't have to write the full path every time. There you go - one
|
||||
red button.
|
||||
|
||||
The RedButton is an example object intended to show off a few of
|
||||
Evennia's features. You will find that the `Scripts <Scripts.html>`_ and
|
||||
`Commands <Commands.html>`_ controlling it are scattered in
|
||||
``examples``-folders all across ``game/gamesrc/``.
|
||||
|
||||
If you wait for a while (make sure you dropped it!) the button will
|
||||
blink invitingly. Why don't you try to push it ...? Surely a big red
|
||||
button is meant to be pushed. You know you want to.
|
||||
|
||||
Making yourself a house
|
||||
-----------------------
|
||||
|
||||
The main command for shaping the game world is ``@dig``. For example, if
|
||||
you are standing in Limbo you can dig a route to your new house location
|
||||
like this:
|
||||
|
||||
::
|
||||
|
||||
> @dig house = large red door;door;in, to the outside;out
|
||||
|
||||
This will create a new room named 'house'. It will also directly create
|
||||
an exit from your current location named 'large red door' and a
|
||||
corresponding exit named 'to the outside' in the house room leading back
|
||||
to Limbo. We also define a few aliases to those exits, so people don't
|
||||
have to write the full thing all the time.
|
||||
|
||||
If you wanted to use normal compass directions (north, west, southwest
|
||||
etc), you could do that with ``@dig`` too. But Evennia also has a
|
||||
limited version of ``@dig`` that helps for compass directions (and also
|
||||
up/down and in/out). It's called ``@tunnel``:
|
||||
|
||||
::
|
||||
|
||||
> @tunnel sw = cliff
|
||||
|
||||
This will create a new room "cliff" with an exit "southwest" leading
|
||||
there and a path "northeast" leading back from the cliff to your current
|
||||
location.
|
||||
|
||||
You can create new exits from where you are using the ``@open`` command:
|
||||
|
||||
::
|
||||
|
||||
> @open north;n = house
|
||||
|
||||
This opens an exit ``north`` to the previously created room ``house``.
|
||||
|
||||
If you have many rooms named ``house`` you will get a list of matches
|
||||
and have to select which one you want to link to. You can also give its
|
||||
database (#dbref) number, which is unique to every object. This can be
|
||||
found with the ``examine`` command or by looking at the latest
|
||||
constructions with ``@objects``.
|
||||
|
||||
Follow the north exit to your 'house' or ``@teleport`` to it:
|
||||
|
||||
::
|
||||
|
||||
> north
|
||||
|
||||
or:
|
||||
|
||||
::
|
||||
|
||||
> @teleport house
|
||||
|
||||
To manually open an exit back to Limbo (if you didn't do so with the
|
||||
``@dig`` command):
|
||||
|
||||
::
|
||||
|
||||
> @open door = limbo
|
||||
|
||||
(or give limbo's dbref which is #2)
|
||||
|
||||
Reshuffling the world
|
||||
---------------------
|
||||
|
||||
You can find things using the ``@find`` command. Assuming you are back
|
||||
at ``Limbo``, let's teleport the *large box to our house*.
|
||||
|
||||
::
|
||||
|
||||
> @teleport box = house
|
||||
very large box is leaving Limbo, heading for house.
|
||||
Teleported very large box -> house.
|
||||
|
||||
We can still find the box by using @find:
|
||||
|
||||
::
|
||||
|
||||
> @find box
|
||||
One Match(#1-#8):
|
||||
very large box(#8) - src.objects.objects.Object
|
||||
|
||||
Knowing the #dbref of the box (#8 in this example), you can grab the box
|
||||
and get it back here without actually yourself going to ``house`` first:
|
||||
|
||||
::
|
||||
|
||||
> @teleport #8 = here
|
||||
|
||||
(You can usually use ``here`` to refer to your current location. To
|
||||
refer to yourself you can use ``self`` or ``me``). The box should now be
|
||||
back in Limbo with you.
|
||||
|
||||
We are getting tired of the box. Let's destroy it.
|
||||
|
||||
::
|
||||
|
||||
> @destroy box
|
||||
|
||||
You can destroy many objects in one go by giving a comma-separated list
|
||||
of objects (or their #dbrefs, if they are not in the same location) to
|
||||
the command.
|
||||
|
||||
Adding a help entry
|
||||
-------------------
|
||||
|
||||
An important part of building is keeping the help files updated. You can
|
||||
add, delete and append to existing help entries using the ``@sethelp``
|
||||
command.
|
||||
|
||||
::
|
||||
|
||||
> @sethelp/add MyTopic = This help topic is about ...
|
||||
|
||||
Adding a World
|
||||
--------------
|
||||
|
||||
After this brief introduction to building you may be ready to see a more
|
||||
fleshed-out example. Evennia comes with a tutorial world for you to
|
||||
explore.
|
||||
|
||||
First you need to switch back to *superuser* by using the ``@unquell``
|
||||
command. Next, place yourself in ``Limbo`` and run the following
|
||||
command:
|
||||
|
||||
::
|
||||
|
||||
> @batchcommand contrib.tutorial_world.build
|
||||
|
||||
This will take a while (be patient and don't re-run the command). You
|
||||
will see all the commands used to build the world scroll by as the world
|
||||
is built for you.
|
||||
|
||||
You will end up with a new exit from Limbo named *tutorial*. Apart from
|
||||
being a little solo-adventure in its own right, the tutorial world is a
|
||||
good source for learning Evennia building (and coding).
|
||||
|
||||
Read
|
||||
`contrib/tutorial\_world/build.ev <https://code.google.com/p/evennia/source/browse/contrib/tutorial_world/build.ev>`_
|
||||
to see exactly how it's built, step by step. See also more info about
|
||||
the tutorial world `here <TutorialWorldIntroduction.html>`_.
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
Caches
|
||||
======
|
||||
|
||||
*Note: This is an advanced topic. You might want to skip it on a first
|
||||
read-through.*
|
||||
|
||||
Evennia is a fully persistent system, which means that it will store
|
||||
things in the database whenever its state changes. Since accessing the
|
||||
database i comparably expensive, Evennia uses an extensive *caching*
|
||||
scheme. Caching normally means that once data is read from the database,
|
||||
it is stored in memory for quick retrieval henceforth. Only when data
|
||||
changes will the database be accessed again (and the cache updated).
|
||||
|
||||
With a few exceptions, caching are primarily motivated by speed and to
|
||||
minimize bottlenecks found by profiling the server. Some systems must
|
||||
access certain pieces of data often, and going through the django API
|
||||
over and over builds up. Depending on operation, individual speedups of
|
||||
hundreds of times can be achieved by clever caching.
|
||||
|
||||
The price for extended caching is memory consumption and added
|
||||
complexity. This page tries to explain the various cache strategies in
|
||||
place. Most users should not have to worry about them, but if you ever
|
||||
try to "bang the metal" with Evennia, you should know what you are
|
||||
seeing.
|
||||
|
||||
All caching schemes except Idmapper is centralized in
|
||||
``src/server.caches/py``. You can turn off all caches handled by that
|
||||
module by use of the ``GAME_CACHE_TYPE`` setting.
|
||||
|
||||
The default ``@server`` command will give a brief listing of the memory
|
||||
usage of most relevant caches.
|
||||
|
||||
Idmapper
|
||||
--------
|
||||
|
||||
Evennia's django object model is extended by *idmapper* functionality.
|
||||
The idmapper is an external third-party system that sits in
|
||||
``src/utils/idmapper``. The idmapper is an on-demand memory mapper for
|
||||
all database models in the game. This means that a given database object
|
||||
is represented by the same memory area whenever it is accessed. This may
|
||||
sound trivial but it is not - in plain Django there is no such
|
||||
guarantee. Doing something like ``objdb.test = "Test"`` (were objdb is a
|
||||
django model instance) would be unsafe and most likely the ``test``
|
||||
variable would be lost next time the model is retrieved from the
|
||||
database. As Evennia ties `Typeclasses <Typeclasses.html>`_ to django
|
||||
models, this would be a catastophy.
|
||||
|
||||
Idmapper is originally a memory saver for django websites. In the case
|
||||
of a website, object access is brief and fleeting - not so for us. So we
|
||||
have extended idmapper to never loose its reference to a stored object
|
||||
(not doing this was the cause of a very long-standing, very hard-to-find
|
||||
bug).
|
||||
|
||||
Idmapper is an on-demand cache, meaning that it will only cache objects
|
||||
that are actually accessed. Whereas it is possible to clean the idmapper
|
||||
cache via on-model methods, this does not necessarily mean its memory
|
||||
will be freed - this depends on any lingering references in the system
|
||||
(this is how Python's reference counting works). If you ever need to
|
||||
clean the idmapper cache, the safest way is therefore a soft-reload of
|
||||
the server (via e.g. the ``@reload`` command).
|
||||
|
||||
Most developers will not need to care with the idmapper cache - it just
|
||||
makes models work intuitively. It is visible mostly in that many
|
||||
database models in Evennia inherit from
|
||||
``src.utils.idmapper.models.SharedMemoryModel``.
|
||||
|
||||
On-object variable cache
|
||||
------------------------
|
||||
|
||||
All database fields on all objects in Evennia are cached by use of
|
||||
`Python
|
||||
properties <http://docs.python.org/library/functions.html#property>`_.
|
||||
So when you do ``name = obj.key``, you are actually *not* directly
|
||||
accessing a database field "key" on the object. What you are doing is
|
||||
actually to access a handler. This handler reads the field ``db_key``
|
||||
and caches its value for next time it using a call to the the
|
||||
``server.caches`` module.
|
||||
|
||||
The naming scheme is consistent, so a given field property ``obj.foo``
|
||||
is always a handler for a database field ``obj.db_key.`` The handler
|
||||
methods for the property are always named ``obj.foo_get()``,
|
||||
``obj.foo_set()`` and ``obj.foo_del()`` (all are not always needed).
|
||||
|
||||
Apart from caching, property handlers also serves another function -
|
||||
they hide away Django administration. So doing ``obj.key = "Peter"``
|
||||
will not only assign (and cache) the string "Peter" in the database
|
||||
field ``obj.db_key``, it will also call ``obj.save()`` for you in order
|
||||
to update the database.
|
||||
|
||||
Hiding away the model fields presents one complication for developers,
|
||||
and that is searching using normal django methods. Basically, if you
|
||||
search using e.g. the standard django ``filter`` method, you must search
|
||||
for ``db_key``, not ``key``. Only the former is known by django, the
|
||||
latter will give an invalid-field error. If you use Evennia's own search
|
||||
methods you don't need to worry about this, they look for the right
|
||||
things behind the scenes for you.
|
||||
|
||||
Content cache
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
A special case of on-object caching is the *content* cache. Finding the
|
||||
"contents" of a particular location turns out to be a very common and
|
||||
pretty expensive operation. Whenever a person moves, says something or
|
||||
does other things, "everyone else" in a given location must be informed.
|
||||
This means a database search for which objects share the location.
|
||||
|
||||
``obj.contents`` is a convenient container that at every moment contains
|
||||
a cached list of all objects "inside" that particular object. It is
|
||||
updated by the ``location`` property. So ``obj1.location = obj2`` will
|
||||
update ``obj2.contents`` on the fly to contain ``obj1``. It will also
|
||||
remove ``obj1`` from the ``contents`` list of its previous location.
|
||||
Testing shows that when moving from one room to another, finding and
|
||||
messaging everyone in the room took up as much as *25%* of the total
|
||||
computer time needed for the operation. After caching ``contents``,
|
||||
messaging now takes up *0.25%* instead ...
|
||||
|
||||
The contents cache should be used at all times. The main thing to
|
||||
remember is that if you were to somehow bypass the ``location`` handler
|
||||
(such as by setting the ``db_location`` field manually), you will bring
|
||||
the cache and database out of sync until you reload the server.
|
||||
|
||||
Typeclass cache
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
All typeclasses are cached on the database model. This allows for quick
|
||||
access to the typeclass through ``dbobj.typeclass``. Behind the scenes
|
||||
this operation will import the typeclass definition from a path stored
|
||||
in ``db_typeclass_path`` (available via the property handler
|
||||
``typeclass_path``). All checks and eventual debug messages will be
|
||||
handled, and the result cached.
|
||||
|
||||
The only exception to the caching is if the typeclass module had some
|
||||
sort of syntax error or other show-stopping bug. The default typeclass
|
||||
(as defined in ``settings``) will then be loaded instead. The error will
|
||||
be reported and *no* caching will take place. This is in order to keep
|
||||
reloading the typeclass also next try, until it is fixed.
|
||||
|
||||
On-object Attribute cache
|
||||
-------------------------
|
||||
|
||||
`Attribute <Attributes.html>`_ lookups on objects are also cached. This
|
||||
means that after the first access or assignment, ``obj.db.attrname`` is
|
||||
as fast as accessing any normal python property - this removes the
|
||||
necessity for subsequent database look-ups in order to retrieve
|
||||
attributes. Both ``db`` and ``ndn`` work the same way in this regard.
|
||||
|
||||
Due to the possibility of storing objects in Attributes, the system
|
||||
cannot cache the value of data stored in the Attribute (this would allow
|
||||
for the system not detecting a stored Object being deleted elsewhere -
|
||||
it would still be accessible from the Attribute). So having to re-access
|
||||
such objects every load does incur a minor speed penalty.
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
Choosing an SQL Server
|
||||
======================
|
||||
|
||||
Since Evennia uses `Django <http://djangoproject.com>`_, most of our
|
||||
notes are based off of what we know from the community and their
|
||||
documentation. While the information below may be useful, you can always
|
||||
find the most up-to-date and "correct" information at Django's `Notes
|
||||
about supported
|
||||
Databases <http://docs.djangoproject.com/en/dev/ref/databases/#ref-databases>`_
|
||||
page.
|
||||
|
||||
SQLite3
|
||||
-------
|
||||
|
||||
This is the default database used, and for the vast majority of Evennia
|
||||
installs it will probably be more than adequate. It's definitely
|
||||
recommended for most of your development. No server process is needed,
|
||||
the administrative overhead is tiny (as is resource consumption). The
|
||||
database will appear as a simple file (``game/evennia.db3``) and since
|
||||
we run SQLite as an in-memory process without any socket overhead, it
|
||||
might well be faster than Postgres/MySQL unless your database is huge.
|
||||
|
||||
The drawback with SQLite3 is that it does not work very well will
|
||||
multiple concurrent threads or processes. This has to do with
|
||||
file-locking clashes of the database file. So for a production server
|
||||
making heavy use of process- or threadpools (or when using a third-party
|
||||
webserver like Apache), a more full-featured database may be the better
|
||||
choice.
|
||||
|
||||
Postgres
|
||||
--------
|
||||
|
||||
This is Django's recommended database engine, While not as fast as
|
||||
SQLite for normal usage, it will scale better than SQLite, especially if
|
||||
your game has an very large database and/or extensive web presence
|
||||
through a separate server process.
|
||||
|
||||
**Warning:** Postgres has issues with Evennia on some installs at the
|
||||
moment. `Issue
|
||||
151 <http://code.google.com/p/evennia/issues/detail?id=151>`_ outlines
|
||||
this. If unsure, avoid Postgres for now.
|
||||
|
||||
MySQL
|
||||
-----
|
||||
|
||||
MySQL *may* be slightly faster than Postgres depending on your setup and
|
||||
software versions involved. Older versions of MySQL had some
|
||||
peculiarities though, so check out Django's `Notes about supported
|
||||
Databases <http://docs.djangoproject.com/en/dev/ref/databases/#ref-databases>`_
|
||||
to make sure you use the correct version.
|
||||
|
||||
Others
|
||||
------
|
||||
|
||||
No testing has been performed with Oracle, but it is also supported.
|
||||
There are community maintained drivers for `MS
|
||||
SQL <http://code.google.com/p/django-mssql/>`_ and possibly a few others
|
||||
(found via our friend, Google).
|
||||
|
||||
Inspecting database data
|
||||
========================
|
||||
|
||||
If you know SQL you can easily get command line access to your database
|
||||
like this:
|
||||
|
||||
::
|
||||
|
||||
python game/gamesrc.py dbshell
|
||||
|
||||
This will drop you into the command line interface for your respective
|
||||
database.
|
||||
|
||||
There are also a host of easier graphical interfaces for the various
|
||||
databases. For SQLite3 we recommend `SQLite
|
||||
manager <https://addons.mozilla.org/En-us/firefox/addon/sqlite-manager/>`_.
|
||||
This is a plugin for the
|
||||
`Firefox <http://www.mozilla.org/en-US/firefox/new/>`_ web browser
|
||||
making it usable across all operating systems. Just use it to open the
|
||||
game/evennia.db3 file.
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
Evennia coding introduction
|
||||
===========================
|
||||
|
||||
Evennia allows for a lot of freedom when designing your game - but to
|
||||
code efficiently you still need to adopt some best practices as well as
|
||||
find a good place to start to learn.
|
||||
|
||||
Here are some pointers to get you going.
|
||||
|
||||
Code in \`game/gamesrc\`, not in \`src/\`
|
||||
-----------------------------------------
|
||||
|
||||
You will create and code your game by adding Python modules in
|
||||
``game/gamesrc/`` (see the `directory
|
||||
overview <DirectoryOverview.html>`_). This is your home. You should
|
||||
*never* need to modify anything under ``src/`` (anything you download
|
||||
from us, really). Treat ``src/`` as a kind of library. You import useful
|
||||
functionality from here. If you see code you like, copy&paste it out
|
||||
into ``game/gamesrc`` and edit it there.
|
||||
|
||||
If you find that ``src/`` *doesn't* support some functionality you need,
|
||||
make a `Feature
|
||||
Request <https://code.google.com/p/evennia/issues/list>`_ about it. Same
|
||||
goes for `bugs <https://code.google.com/p/evennia/issues/list>`_. If you
|
||||
add features or fix bugs yourself, please consider
|
||||
`contributing <Contributing.html>`_ your changes upstream!
|
||||
|
||||
Learn with \`ev\`
|
||||
-----------------
|
||||
|
||||
Learn the `ev interface <evAPI.html>`_. This is a great way to explore
|
||||
what Evennia has to offer. For example, start an interactive python
|
||||
shell, import ``ev`` and just look around.
|
||||
|
||||
You can complement your exploration by peeking at the sections of the
|
||||
much more detailed `Developer Central <DeveloperCentral.html>`_. The
|
||||
`Tutorials <Tutorials.html>`_ section also contains a growing collection
|
||||
of system- or implementation-specific help.
|
||||
|
||||
Plan before you code
|
||||
--------------------
|
||||
|
||||
Before you start coding away at your dream game, take a look at our
|
||||
`game planning hints and tips <GamePlanning.html>`_ page. It might
|
||||
hopefully help you avoid some common pitfalls and time sinks.
|
||||
|
||||
Docs are here to help you
|
||||
-------------------------
|
||||
|
||||
Some people find reading documentation extremely dull and shun it out of
|
||||
principle. That's your call, but reading docs really *does* help you,
|
||||
promise! Evennia's documentation is pretty thorough and knowing what is
|
||||
possible can often give you a lot of new cool game ideas. That said, if
|
||||
you can't find the answer in the docs, don't be shy to ask questions!
|
||||
The `discussion
|
||||
group <https://sites.google.com/site/evenniaserver/discussions>`_ and
|
||||
the `irc chat <http://webchat.freenode.net/?channels=evennia>`_ are
|
||||
there for you.
|
||||
|
||||
The most important point
|
||||
------------------------
|
||||
|
||||
And finally, of course, have fun!
|
||||
|
|
@ -1,256 +0,0 @@
|
|||
Utils
|
||||
=====
|
||||
|
||||
Evennia comes with many utilities to help with common coding tasks. Some
|
||||
of these are part of the command interface but most can be found in the
|
||||
``src/utils/`` folder. They are used all over the server, but offer help
|
||||
for many common tasks when coding your own game as well. This is not a
|
||||
complete list, check the module for more goodies.
|
||||
|
||||
Search
|
||||
------
|
||||
|
||||
A common thing to do is to search for objects. The most common time one
|
||||
needs to do this is inside a command body. There it's easiest to use the
|
||||
``search`` method defined on all objects. This will search for objects
|
||||
in the same location and inside the caller:
|
||||
|
||||
::
|
||||
|
||||
obj = self.caller.search(objname)
|
||||
|
||||
Give the keyword ``global_search=True`` to extend search to encompass
|
||||
entire database. Also aliases with be matched by this search. You will
|
||||
find multiple examples of this functionality in the default command set.
|
||||
|
||||
If you need to search for objects in a code module you can use the
|
||||
functions in ``src.utils.search``. You can access these as shortcuts
|
||||
``ev.search_*``.
|
||||
|
||||
::
|
||||
|
||||
from ev import search_object
|
||||
obj = search_object(objname)
|
||||
|
||||
``utils.search`` contains properties to the relevant database search
|
||||
method. They are really just shortcuts to the django-database search
|
||||
methods, like ``ObjectDB.objects.search()``.
|
||||
|
||||
**Note:** If you are a Django wizz, you might be tempted to use Django's
|
||||
database search functions directly (using ``filter``, ``get`` etc). Just
|
||||
remember that such operations will give you a django model whereas
|
||||
Evennia's manager methods will give you a typeclass. It's easy to
|
||||
convert between them with ``dbobj.typeclass`` and ´typeclass.dbobj´,
|
||||
but you should remember this distinction. If you stick with Evennia's
|
||||
search methods you will always get typeclasses back.
|
||||
|
||||
Create
|
||||
------
|
||||
|
||||
Apart from the in-game build commands (``@create`` etc), you can also
|
||||
build all of Evennia's game entities directly in code (for example when
|
||||
defining new create commands). This *must* be done using
|
||||
``src.utils.create`` or their shortcuts ``ev.create_*``- these functions
|
||||
are responsible for setting up all the background intricacies of the
|
||||
typeclass system and other things. Creating database instances using raw
|
||||
Django will *not* work. Examples:
|
||||
|
||||
::
|
||||
|
||||
import ev
|
||||
#
|
||||
myobj = ev.create_objects("game.gamesrc.objects.myobj.MyObj", key="MyObj")
|
||||
myscr = ev.create_script("game.gamesrc.scripts.myscripts.MyScript", obj=myobj)
|
||||
help = ev.create_help_entry("Emoting", "Emoting means that ...")
|
||||
msg = ev.create_message(senderobj, [receiverobj], "Hello ...")
|
||||
chan = ev.create_channel("news")
|
||||
player = ev.create_player("Henry", "henry@test.com", "H@passwd")
|
||||
|
||||
Each of these create functions have a host of arguments to further
|
||||
customize the created entity. See ``src/utils/create.py`` for more
|
||||
information.
|
||||
|
||||
Logging
|
||||
-------
|
||||
|
||||
Normally you can use Python ``print`` statements to see output to the
|
||||
terminal (if you started Evennia in *interactive mode* with the -i
|
||||
switch). This should only be used for debugging though, for real output,
|
||||
use the logger - it will log to the terminal in interactive mode, to the
|
||||
log file otherwise.
|
||||
|
||||
::
|
||||
|
||||
from ev import logger
|
||||
#
|
||||
logger.log_errmsg("This is an Error!")
|
||||
logger.log_warnmsg("This is a Warning!")
|
||||
logger.log_infomsg("This is normal information")
|
||||
logger.log_depmsg("This feature is deprecated")
|
||||
|
||||
There is also a special log-message type that is intended to be called
|
||||
from inside a traceback - this can be very useful for relaying the
|
||||
traceback message back to log without having it kill the server.
|
||||
|
||||
::
|
||||
|
||||
try:
|
||||
# [some code that may fail...]
|
||||
except Exception:
|
||||
logger.log_trace("This text will be appended to the traceback info")
|
||||
|
||||
inherits\_from()
|
||||
----------------
|
||||
|
||||
This useful function takes two arguments - an object to check and a
|
||||
parent. It returns ``True`` if object inherits from parent *at any
|
||||
distance* (as opposed to Python's in-built ``is_instance()`` that will
|
||||
only catch immediate dependence). This function also accepts as input
|
||||
any combination of classes, instances or python-paths-to-classes.
|
||||
|
||||
Note that Python code should usually work with `duck
|
||||
typing <http://en.wikipedia.org/wiki/Duck_typing>`_. But in Evennia's
|
||||
case it can sometimes be useful to check if an object inherits from a
|
||||
given `Typeclass <Typeclasses.html>`_ as a way of identification. Say
|
||||
for example that we have a typeclass *Animal*. This has a subclass
|
||||
*Felines* which in turns is a parent to *HouseCat*. Maybe there are a
|
||||
bunch of other animal types too, like horses and dogs. Using
|
||||
``inherits_from`` will allow you to check for all animals in one go:
|
||||
|
||||
::
|
||||
|
||||
from ev import utils
|
||||
if (utils.inherits_from(obj, "game.gamesrc.objects.animals.Animal"):
|
||||
obj.msg("The bouncer stops you in the door. He says: 'No talking animals allowed.'")
|
||||
|
||||
delay()
|
||||
-------
|
||||
|
||||
This is a thin wrapper around a Twisted construct called a *deferred*.
|
||||
It simply won't return until a given number of seconds have passed, at
|
||||
which time it will trigger a given callback with whatever argument. This
|
||||
is a small and lightweight (non-persistent) alternative to a full
|
||||
`Script <Scripts.html>`_. Contrary to a Script it can also handle
|
||||
sub-second timing precision (although this is not something you should
|
||||
normally need to worry about).
|
||||
|
||||
Some text utilities
|
||||
-------------------
|
||||
|
||||
In a text game, you are naturally doing a lot of work shuffling text
|
||||
back and forth. Here is a *non-complete* selection of text utilities
|
||||
found in ``src/utils/utils.py`` (shortcut ``ev.utils``). If nothing else
|
||||
it can be good to look here before starting to develop a solution of
|
||||
your own.
|
||||
|
||||
fill()
|
||||
~~~~~~
|
||||
|
||||
This flood-fills a text to a given width (shuffles the words to make
|
||||
each line evenly wide). It also indents as needed.
|
||||
|
||||
::
|
||||
|
||||
outtxt = fill(intxt, width=78, indent=4)
|
||||
|
||||
crop()
|
||||
~~~~~~
|
||||
|
||||
This function will crop a very long line, adding a suffix to show the
|
||||
line actually continues. This can be useful in listings when showing
|
||||
multiple lines would mess up things.
|
||||
|
||||
::
|
||||
|
||||
intxt = "This is a long text that we want to crop."
|
||||
outtxt = crop(intxt, width=19, suffix="[...]")
|
||||
# outtxt is now "This is a long text[...]"
|
||||
|
||||
dedent()
|
||||
~~~~~~~~
|
||||
|
||||
This solves what may at first glance appear to be a trivial problem with
|
||||
text - removing indentations. It is used to shift entire paragraphs to
|
||||
the left, without disturbing any further formatting they may have. A
|
||||
common case for this is when using Python triple-quoted strings in code
|
||||
- they will retain whichever indentation they have in the code, and to
|
||||
make easily-readable source code one usually don't want to shift the
|
||||
string to the left edge.
|
||||
|
||||
::
|
||||
|
||||
#python code is entered at a given indentation
|
||||
intxt = """
|
||||
This is an example text that will end
|
||||
up with a lot of whitespace on the left.
|
||||
It also has indentations of
|
||||
its own."""
|
||||
outtxt = dedent(intxt)
|
||||
# outtxt will now retain all internal indentation
|
||||
# but be shifted all the way to the left.
|
||||
|
||||
Normally you do the dedent in the display code (this is for example how
|
||||
the help system homogenizes help entries).
|
||||
|
||||
time\_format()
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This function takes a number of seconds as input and converts it to a
|
||||
nice text output in days, hours etc. It's useful when you want to show
|
||||
how old something is. It converts to four different styles of output
|
||||
using the *style* keyword:
|
||||
|
||||
- style 0 - ``5d:45m:12s`` (standard colon output)
|
||||
- style 1 - ``5d`` (shows only the longest time unit)
|
||||
- style 2 - ``5 days, 45 minutes`` (full format, ignores seconds)
|
||||
- style 3 - ``5 days, 45 minutes, 12 seconds`` (full format, with
|
||||
seconds)
|
||||
|
||||
text conversion()
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Evennia supplies two utility functions for converting text to the
|
||||
correct encodings. ``to_str()`` and ``to_unicode()``. The difference
|
||||
from Python's in-built ``str()`` and ``unicode()`` operators are that
|
||||
the Evennia ones makes use of the ``ENCODINGS`` setting and will try
|
||||
very hard to never raise a traceback but instead echo errors through
|
||||
logging. See `TextEncodings <TextEncodings.html>`_ for more info.
|
||||
|
||||
format\_table()
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
This function creates nicely formatted tables - columns of text all
|
||||
lined up. It will automatically widen each column so all entries fit.
|
||||
|
||||
To use it, you need to create a list of lists - each sublist contains
|
||||
the content of one column. The result will be a list of ready-formatted
|
||||
strings to print.
|
||||
|
||||
::
|
||||
|
||||
# title line
|
||||
cols = [["num"],["x"],["y"]]
|
||||
# creating a dummy table with integers
|
||||
for i in range(3):
|
||||
cols[0].append(i)
|
||||
cols[1].append(i+1)
|
||||
cols[2].append(i+2)
|
||||
# format the table (returns list with rows)
|
||||
ftable = format_table(cols, extra_space=3)
|
||||
# print the rows, making header bright white
|
||||
for irow, row in enumerate(ftable):
|
||||
if irow == 0: # header
|
||||
print "{w%s{x" % row
|
||||
else:
|
||||
print row
|
||||
# Output (no colors shown):
|
||||
#
|
||||
# num x y
|
||||
# 1 2 3
|
||||
# 2 3 4
|
||||
# 3 4 5
|
||||
#
|
||||
|
||||
Note that you cannot add colour codes to the input to ``format_table`` -
|
||||
these would mess up the width of each column. Instead you can add this
|
||||
to the output when printing.
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
Adding Colour to your game
|
||||
==========================
|
||||
|
||||
*Note that the Docs does not display colour the way it would look on the
|
||||
screen.*
|
||||
|
||||
Evennia supports the ``ANSI`` standard for displaying text. This means
|
||||
that you can put markers in your text and if the user's
|
||||
client/console/display supports those markers, they will see the text in
|
||||
the specified colour. Remember that whereas there is, for example, one
|
||||
special marker meaning "yellow", which colour (hue) of yellow is
|
||||
*actually* displayed on the user's screen depends on the settings of
|
||||
their particular mud client/viewer. They could even swap around the
|
||||
colours displayed if they wanted to. or turn them off altogether. Some
|
||||
clients don't support colour from the onset - text games are also played
|
||||
with special reading equipment by people who are blind or have otherwise
|
||||
diminished eyesight. So a good rule of thumb is to use colour to enhance
|
||||
your game, but don't *rely* on it to display critical information. If
|
||||
you are coding the game, you can add functionality to let users disable
|
||||
colours as they please, as described `here <RemovingColour.html>`_.
|
||||
|
||||
Adding colour to in-game text is easy. You just put in special markers
|
||||
in your text that tell Evennia when a certain colour begins and when it
|
||||
ends. There are two markup styles. The traditional(?) one use ``%c#`` to
|
||||
mark colour:
|
||||
|
||||
::
|
||||
|
||||
This is a %crRed text%cn This is normal text again.
|
||||
%cRThis text has red background%cn this is normal text.
|
||||
|
||||
``%c#`` - markup works like a switch that is on until you actively turn
|
||||
it off with ``%cn`` (this returns the text to your default setting).
|
||||
Capital letters mean background colour, lower-case means letter-colour.
|
||||
So ``%cR`` means a red area behind your normal-colour text. If you
|
||||
combine red background with red foreground text - ``%cR%cr``, you get a
|
||||
solid red block with no characters visible! Similarly, ``%cR%cx`` gives
|
||||
red background with black text. ``%ch`` 'hilights' your current text, so
|
||||
grey becomes white, dark yellow becomes bright yellow etc.
|
||||
|
||||
The drawback of the ``%cs`` style has to do with how Python formats
|
||||
strings - the ``%`` is used in Python to create special text formatting,
|
||||
and combining that with colour codes easily leads to messy and
|
||||
unreadable code. It is thus often easier to use ``{#`` style codes:
|
||||
|
||||
::
|
||||
|
||||
This is a {rBright red text{n This is normal text again
|
||||
|
||||
The ``{x`` format don't include background colour, it only colours the
|
||||
foreground text. The basic rule is that lower-case letter means bright
|
||||
(hilighted) colour, whereas the upper-case one is for darker colour. So
|
||||
``{g`` means bright green and ``{G`` means dark green. ``{n`` returns to
|
||||
normal text colour. The equivalent in ``%c``-style markup is ``%cg%ch``
|
||||
for bright green and ``%cg`` for dark green.
|
||||
|
||||
You can find a list of all the parsed ``ANSI``-colour codes in
|
||||
``src/utils/ansi.py``.
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
*This tutorial requires that you first well understand how
|
||||
`Commands <Commands.html>`_ work.*
|
||||
|
||||
Cooldowns
|
||||
=========
|
||||
|
||||
Some types of games want to limit how often a command can be run. If a
|
||||
character casts the spell *Firestorm*, you might not want them to spam
|
||||
that command over and over. Or in an advanced combat system, a massive
|
||||
swing may offer a chance of lots of damage at the cost of not being able
|
||||
to re-do it for a while. Such effects are called *cooldowns*.
|
||||
|
||||
Evennia allows for many ways to implement cooldowns. Here are some
|
||||
ideas.
|
||||
|
||||
Simplest way - single-command, non-persistent cooldown
|
||||
------------------------------------------------------
|
||||
|
||||
This little recipe will limit how often a particular command can be run.
|
||||
Since Commands are class instances, and those are cached in memory, a
|
||||
command instance will remember things you store on it. So just store the
|
||||
current time of execution! Next time the command is run, it just needs
|
||||
to check if it has that time stored, and compare it with the current
|
||||
time to see if a desired delay has passed.
|
||||
|
||||
::
|
||||
|
||||
import time
|
||||
from ev import default_cmds
|
||||
|
||||
class CmdSpellFirestorm(default_cmds.MuxCommand):
|
||||
"""
|
||||
Spell - Firestorm
|
||||
|
||||
Usage:
|
||||
cast firestorm <target>
|
||||
|
||||
This will unleash a storm of flame. You can only release one
|
||||
firestorm every five minutes (assuming you have the mana).
|
||||
"""
|
||||
key = "cast firestorm"
|
||||
locks = "cmd:isFireMage()"
|
||||
|
||||
def func(self):
|
||||
"Implement the spell"
|
||||
|
||||
# check cooldown (5 minute cooldown)
|
||||
if hasattr(self, "lastcast") and time.time()-self.lastcast < 5*60:
|
||||
self.caller.msg("You need to wait before casting this spell again.")
|
||||
return
|
||||
|
||||
#[the spell effect is implemented]
|
||||
|
||||
# if the spell was successfully cast, store the casting time
|
||||
self.lastcast = time.time()
|
||||
|
||||
We just check the ``lastcast`` flag, and update it if everything works
|
||||
out. Simple and very effective since everything is just stored in
|
||||
memory. The drawback of this simple scheme is that it's non-persistent.
|
||||
If you do ``@reload``, the cache is cleaned and all such ongoing
|
||||
cooldowns will be forgotten. It is also limited only to this one
|
||||
command, other commands cannot (easily) check for this value.
|
||||
|
||||
Persistent cooldown
|
||||
-------------------
|
||||
|
||||
This is essentially the same mechanism as the simple one above, except
|
||||
we use the database to store the information which means the cooldown
|
||||
will survive a server reload/reboot. Since commands themselves have no
|
||||
representation in the database, you need to use the caster for the
|
||||
storage.
|
||||
|
||||
::
|
||||
|
||||
#[...]
|
||||
# check cooldown (5 minute cooldown)
|
||||
lastcall = self.caller.db.firestorm_lastcast # returns None if not exist yet
|
||||
if lastcast and time.time() - lastcast < 5*60:
|
||||
self.caller.msg("You need to wait before casting this spell again.")
|
||||
return
|
||||
|
||||
#[the spell effect is implemented]
|
||||
|
||||
# if the spell was successfully cast, store the casting time
|
||||
self.caller.db.firestorm_lastcast = time.time()
|
||||
|
||||
Since we are storing as an `Attribute <Attributes.html>`_, we need to
|
||||
identify the variable as ``firestorm_lastcast`` so we are sure we get
|
||||
the right one (we'll likely have other skills with cooldowns after all).
|
||||
But this method of using cooldowns also has the advantage of working
|
||||
*between* commands - you can for example let all fire-related spells
|
||||
check the same cooldown to make sure the casting of *Firestorm* blocks
|
||||
all fire-related spells for a while. Or, in the case of taking that big
|
||||
swing with the sword, this could now block all other types of attacks
|
||||
for a while before the warrior can recover.
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
Adding a command prompt
|
||||
=======================
|
||||
|
||||
A *prompt* is quite common in MUDs. The prompt display useful details
|
||||
about your character that you are likely to want to keep tabs on at all
|
||||
times, such as health, magical power etc. It might also show things like
|
||||
in-game time, weather and so on.
|
||||
|
||||
Prompt after the command
|
||||
------------------------
|
||||
|
||||
One common form of prompt appears after every command you send. So, say
|
||||
you enter the look command; you would then get the result of the look
|
||||
command, followed by the prompt. As an example:Â
|
||||
|
||||
::
|
||||
|
||||
> look
|
||||
You see nothing special.
|
||||
HP:10, SP:20, MP: 5
|
||||
|
||||
MUD clients can be set to detect prompts like this and display them in
|
||||
various client-specific ways.
|
||||
|
||||
To add this kind of "after-every-command-prompt", you can use the
|
||||
``at_post_cmd()`` hook. This is to be defined on the Command class and
|
||||
Evennia will always call it right after ``func()`` has finished
|
||||
executing. For this to appear after every command you enter, it's best
|
||||
to put this in the parent for your own commands. You can also put it
|
||||
only in certain commands (might not be too useful to show it if you are
|
||||
doing game administration for example).
|
||||
|
||||
::
|
||||
|
||||
class MyCommand(Command):
|
||||
|
||||
[...]
|
||||
|
||||
def at_post_cmd(self):
|
||||
|
||||
# we assume health/stamina/magic are just stored
|
||||
# as simple attributes on the character.
|
||||
|
||||
hp = self.caller.db.hp
|
||||
sp = self.caller.db.sp
|
||||
mp = self.caller.db.mp
|
||||
|
||||
self.caller.msg("HP: %i, SP: %i, MP: %i" % (hp, sp, mp))
|
||||
|
||||
Note that if you are using the default commands, they will *not* display
|
||||
a command prompt after this change - they are inheriting from
|
||||
``src.commands.default.muxcommand.MuxCommand``. If you want to modify
|
||||
default commands you need to copy&paste the default command definitions
|
||||
to ``game/gamesrc/commands`` and modify them so they inherit from your
|
||||
new parent (and re-point Evennia to use your custom versions as
|
||||
described `here <AddingCommandTutorial.html>`_). If you only want to add
|
||||
the hook and don't need to change anything else you can just create
|
||||
stubs in modules in ``game/gamesrc/commands`` on this form:
|
||||
|
||||
::
|
||||
|
||||
from ev import default_cmds
|
||||
from game.gamesrc.commands.basecommand import MyCommand
|
||||
|
||||
class CmdLook(MyCommand, default_cmds.CmdLook):
|
||||
pass
|
||||
|
||||
class CmdGet(MyCommand, default_cmds.CmdGet):
|
||||
pass
|
||||
|
||||
This multiple inheritance should make use of your custom ``at_post_cmd``
|
||||
hook while otherwise using the default command's code. This type of
|
||||
overload is useful not only for adding prompts but for many different
|
||||
forms of overloading default functionality.
|
||||
|
||||
Prompt on the same line
|
||||
-----------------------
|
||||
|
||||
Another, more advanced type of prompt is one that appears before the
|
||||
return of every command, on the same line:
|
||||
|
||||
::
|
||||
|
||||
> look
|
||||
HP: 10, SP:20, MP:5 -- You see nothing special.
|
||||
|
||||
Now, there is an ``at_pre_cmd()`` hook analogous to the hook from last
|
||||
section except called just *before* parsing of the command. But putting
|
||||
prompt code in that would just have the prompt appear on the *line
|
||||
before* the function return:
|
||||
|
||||
::
|
||||
|
||||
> look
|
||||
HP:10, SP:20, MP: 5
|
||||
You see nothing special.
|
||||
|
||||
... which might be cool too, but is not what we wanted. To have the
|
||||
prompt appear on the same line as the return, we need to change how
|
||||
messages are returned to the player. This means a slight modification to
|
||||
our *Character typeclass* (see [Objects#Characters here] on how to
|
||||
change the default Character class to your custom one).
|
||||
|
||||
All in-game commands use the ``object.msg()`` method for communicating
|
||||
with the player (this is usually called as ``self.caller.msg()`` inside
|
||||
command classes). This method is defined in ``src/objects/models.py``,
|
||||
on the ``ObjectDB`` base class. This is the signature of ``msg()``
|
||||
method:
|
||||
|
||||
::
|
||||
|
||||
def msg(self, outgoing_message, from_obj=None, data=None):
|
||||
...
|
||||
|
||||
The only argument we are interested in here is the ``outgoing_message``,
|
||||
which contains the text that is about to be passed on to the player. We
|
||||
want to make sure that ``msg()`` always tack our prompt in front of the
|
||||
``outgoing_message`` before sending it on. This is done by simply
|
||||
overloading the ``msg()`` method in our custom Character class. On your
|
||||
custom Character typeclass add this:
|
||||
|
||||
::
|
||||
|
||||
def msg(self, outgoing_message, from_obj=None, data=None):
|
||||
|
||||
# prepend the prompt in front of the message
|
||||
|
||||
hp = self.db.hp
|
||||
sp = self.db.sp
|
||||
mp = self.db.mp
|
||||
prompt = "%i, %i, %i -- " % (hp, sp, mp)
|
||||
outgoing_message = prompt + outgoing_message
|
||||
|
||||
# pass this on to the original msg() method on the database object
|
||||
|
||||
self.dbobj.msg(outgoing_message, from_obj=from_obj, data=data)
|
||||
|
||||
Note that this solution will *always* give you the prompt, also if you
|
||||
use admin commands, which could get annoying. You might want to have
|
||||
some attribute defined on your character for turning on/off the prompt
|
||||
(the msg() method could look for it to determine if it should add the
|
||||
prompt or not). You can of course also name the above method
|
||||
``msg_prompt()`` and make sure that only commands that *should* return a
|
||||
prompt call that method.
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,150 +0,0 @@
|
|||
Communications
|
||||
==============
|
||||
|
||||
Apart from moving around in the game world and talking, players might
|
||||
need other forms of communication. This is offered by Evennia's ``Comm``
|
||||
system. Stock evennia implements a 'MUX-like' system of channels, but
|
||||
there is nothing stopping you from changing things to better suit your
|
||||
taste.
|
||||
|
||||
Comms rely on two main database objects - ``Msg`` and ``Channel``. There
|
||||
is also the ``TempMsg`` which mimics the API of a ``Msg`` but has no
|
||||
connection to the database.
|
||||
|
||||
Msg
|
||||
---
|
||||
|
||||
The ``Msg`` object is the basic unit of communication in Evennia. A
|
||||
message works a little like an e-mail; it always has a sender (a
|
||||
`Player <Players.html>`_) and one or more recipients. The recipients may
|
||||
be either other Players, or a *Channel* (see below). You can mix
|
||||
recipients to send the message to both Channels and Players if you like.
|
||||
|
||||
Once created, a ``Msg`` is normally not changed. It is peristently saved
|
||||
in the database. This allows for comprehensive logging of
|
||||
communications, both in channels, but also for allowing
|
||||
senders/receivers to have 'mailboxes' with the messages they want to
|
||||
keep.
|
||||
|
||||
Properties defined on \`Msg\`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- ``senders`` - this is a reference to one or many
|
||||
`Player <Players.html>`_ or `Objects <Objects.html>`_ (normally
|
||||
*Characters*) sending the message. This could also be an *External
|
||||
Connection* such as a message coming in over IRC/IMC2 (see below).
|
||||
There is usually only one sender, but the types can also be mixed in
|
||||
any combination.
|
||||
- ``receivers`` - a list of target `Players <Players.html>`_,
|
||||
`Objects <Objects.html>`_ (usually *Characters*) or *Channels* to
|
||||
send the message to. The types of receivers can be mixed in any
|
||||
combination.
|
||||
- ``header`` - this has a max-length of 128 characters. This could be
|
||||
used to store mime-type information for this type of message (such as
|
||||
if it's a mail or a page), but depending on your game it could also
|
||||
instead be used for the subject line or other types of header info
|
||||
you want to track. Being an indexed field it can be used for quick
|
||||
look-ups in the database.
|
||||
- ``message`` - the actual text being sent.
|
||||
- ``date_sent`` - when message was sent (auto-created).
|
||||
- ``locks`` - a `lock definition <Locks.html>`_.
|
||||
- ``hide_from`` - this can optionally hold a list of objects, players
|
||||
or channels to hide this ``Msg`` from. This relationship is stored in
|
||||
the database primarily for optimization reasons, allowing for quickly
|
||||
post-filter out messages not intended for a given target. There is no
|
||||
in-game methods for setting this, it's intended to be done in code.
|
||||
|
||||
You create new messages in code using ``ev.create_message`` (or
|
||||
``src.utils.create.create_message.``)
|
||||
|
||||
!TempMsg
|
||||
--------
|
||||
|
||||
``src.comms.models`` contains a class called ``TempMsg`` which mimics
|
||||
the API of ``Msg`` but is not connected to the database. It's not used
|
||||
by default but you could use it in code to send non-persistent messages
|
||||
to systems expecting a ``Msg`` (like *Channels*, see the example in the
|
||||
next section).
|
||||
|
||||
Channels
|
||||
--------
|
||||
|
||||
Channels are `Typeclassed <Typeclass.html>`_ entities, which mean they
|
||||
can be easily extended and their functionality modified. To change which
|
||||
channel typeclass Evennia uses, change
|
||||
settings.BASE\_CHANNEL\_TYPECLASS.
|
||||
|
||||
Channels act as generic distributors of messages. Think of them as
|
||||
"switch boards" redistributing ``Msg`` objects. Internally they hold a
|
||||
list of "listening" objects and any ``Msg`` (or ``TempMsg`` sent to the
|
||||
channel will be distributed out to all channel listeners. Channels have
|
||||
`Locks <Locks.html>`_ to limit who may listen and/or send messages
|
||||
through them.
|
||||
|
||||
There are three default channels created in stock Evennia - ``MUDinfo``,
|
||||
``MUDconnections`` and ``Public``. Two first ones are server-related
|
||||
messages meant for Admins, the last one is open to everyone to chat on
|
||||
(all new players are automatically joined to it when logging in, useful
|
||||
for asking questions). The default channels created are defined by
|
||||
``settings.CHANNEL_PUBLIC``, ``settings.CHANNEL_MUDINFO`` and
|
||||
``settings.CHANNEL_CONNECTINFO``.
|
||||
|
||||
You create new channels with ``ev.create_channel`` (or
|
||||
``src.utils.create.create_channel``).
|
||||
|
||||
In code, messages are sent to a channel using the ``msg`` or ``tempmsg``
|
||||
methods of channels:
|
||||
|
||||
::
|
||||
|
||||
channel.msg(msgobj, header=None, senders=None, persistent=True)
|
||||
|
||||
The argument ``msgobj`` can be either a string, a previously constructed
|
||||
``Msg`` or a ``TempMsg`` - in the latter cases all the following
|
||||
keywords are ignored. If ``msgobj`` is a string, the other keywords are
|
||||
used for creating a new ``Msg`` or ``TempMsg`` on the fly, depending on
|
||||
if ``persistent`` is set or not. Default is to use ``TempMsg`` for
|
||||
channel communication (i.e. not save everything to the database).
|
||||
|
||||
::
|
||||
|
||||
# assume we have a 'sender' object and a channel named 'mychan'
|
||||
|
||||
# send and store Msg in database
|
||||
from src.utils import create
|
||||
mymsg = create.create_message(sender, "Hello!", channels=[mychan])
|
||||
# use the Msg object directly, no other keywords are needed
|
||||
mychan.msg(mymsg)
|
||||
|
||||
# Send a non-persistent message to a channel
|
||||
mychan.msg("Hello!", senders=[sender])
|
||||
|
||||
# send a message to list, save it to the database
|
||||
# (note how the senders keyword can also be used
|
||||
# without a list if there is only one sender)
|
||||
mychan.msg("Hello!", senders=sender, persistent=True)
|
||||
|
||||
On a more advanced note, when a player enters something like
|
||||
``ooc Hello!`` (where ``ooc`` is the name/alias of a channel), this is
|
||||
treated as a `System Command <Commands.html>`_ by Evennia. You may
|
||||
completely customize how this works by defining a system command with
|
||||
your own code. See `Commands <Commands.html>`_ for more details.
|
||||
|
||||
Properties defined on \`Channel\`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- ``key`` - main name for channel
|
||||
- ``aliases`` - alternative native names for channels
|
||||
- ``desc`` - optional description of channel (seen in listings)
|
||||
- ``keep_log`` (bool) - if the channel should store messages (default)
|
||||
- ``locks`` - A `lock definition <Locks.html>`_. Channels normally use
|
||||
the access\_types ``send, admin`` and ``listen``.
|
||||
|
||||
External Connections
|
||||
====================
|
||||
|
||||
Channels may also communicate through what is called an *External
|
||||
Connection*. Whereas normal users send messages through in-game Evennia
|
||||
commands, an external connection instead takes data from a remote
|
||||
location. `IMC2 <IMC2.html>`_ and `IRC <IRC.html>`_ connections make use
|
||||
of this.
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
The Connection Screen
|
||||
=====================
|
||||
|
||||
When you first connect to your game you are greeted by Evennia's default
|
||||
connection screen. It welcomes you, gives you the server version and
|
||||
tells you how to connect.
|
||||
|
||||
::
|
||||
|
||||
|
||||
==============================================================
|
||||
Welcome to Evennia, version Beta-ra4d24e8a3cab+!
|
||||
|
||||
If you have an existing account, connect to it by typing:
|
||||
connect <username> <password>
|
||||
If you need to create an account, type (without the <>'s):
|
||||
create <username> <password>
|
||||
|
||||
If you have spaces in your username, enclose it in quotes.
|
||||
Enter help for more info. look will re-show this screen.
|
||||
==============================================================
|
||||
|
||||
Effective, but not very exciting. You will most likely want to change
|
||||
this to be more unique for your game.
|
||||
|
||||
You can customize the connection screen easily. If you look in
|
||||
``game/gamesrc/conf/examples/`` you will find a module named
|
||||
``connection_screens.py``. Copy this module up one level (to
|
||||
``game/gamesrc/conf/``) and set ``settings.CONNECTION_SCREEN_MODULE`` to
|
||||
point to your new module.
|
||||
|
||||
Evennia looks into this module for globally defined strings (only).
|
||||
These strings are used as connection screens and shown to the user at
|
||||
startup. If more than one screen is defined in the module, a random
|
||||
screen will be picked from among those available.
|
||||
|
||||
Evennia's default screen is imported as ``DEFAULT_SCREEN`` from
|
||||
``src.commands.connection_screen``. Remove the import at the top or
|
||||
redefine ``DEFAULT_SCREEN`` to get rid of the default. There is a
|
||||
commented-out example screen in the module that you can start from. You
|
||||
can define and import things as normal into the module, but remember
|
||||
that *all* global strings will be picked up and potentially used as a
|
||||
connection screen.
|
||||
|
||||
You can also customize the `commands <Commands.html>`_ available during
|
||||
the connection screen (``connect``, ``create`` etc). These commands are
|
||||
a bit special since when the screen is running the player is not yet
|
||||
logged in. A command is made available at the login screen by adding
|
||||
them to the command set specified by settings.CMDSET\_UNLOGGEDIN. The
|
||||
default commands are found in ``src/commands/default/unloggedin.py``.
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
Contributing to Evennia
|
||||
=======================
|
||||
|
||||
Wanna help out? Great! Here's how.
|
||||
|
||||
Contributing by spreading the word
|
||||
----------------------------------
|
||||
|
||||
Even if you are not keen on working on the server code yourself, just
|
||||
spreading the word is a big help - it will help attract more people
|
||||
which leads to more feedback, motivation and interest. Rating and
|
||||
writing a review on places like
|
||||
`ohloh <http://www.ohloh.net/p/evennia>`_, talk about what you do in a
|
||||
blog post or in mud forums, that kind of thing.
|
||||
|
||||
Contributing with Documentation
|
||||
-------------------------------
|
||||
|
||||
Evennia depends heavily on good documentation and we are always looking
|
||||
for extra eyes and hands to improve it. Even small things such as fixing
|
||||
typos are a great help!
|
||||
|
||||
The documentation is a wiki and to edit it you need wiki-contributor
|
||||
access. We are happy to give this - just ask (on the forum/mailing list
|
||||
or in the chat channel) if you want to help out. Otherwise, it goes a
|
||||
long way just pointing out wiki errors so devs can fix them (in an Issue
|
||||
or just over chat/forum). You can also commit wiki changes over
|
||||
Mercurial - just go to the wiki repository
|
||||
`here <http://code.google.com/p/evennia/source/checkout?repo=wiki>`_ and
|
||||
then continue from point ``2`` below.
|
||||
|
||||
Contributing with Code through a clone repository
|
||||
-------------------------------------------------
|
||||
|
||||
We always need more eyes and hands on the code. Even if you don't feel
|
||||
confident with tackling any major bugs or features, just correcting
|
||||
typos, adjusting formatting or simply using the thing helps us a lot in
|
||||
improving things.
|
||||
|
||||
The most elegant way to contribute code to Evennia is to use Mercurial
|
||||
to create an online *clone* of the Evennia repository and make your
|
||||
changes to that. Here's how to create your own clone (you only need to
|
||||
do this once):
|
||||
|
||||
#. Go to the
|
||||
`Checkout <http://code.google.com/p/evennia/source/checkout>`_ page.
|
||||
#. If you are logged in, you should see a button named *Create a Clone*.
|
||||
Click that.
|
||||
#. You are asked to fill in a few fields. Name your clone repository
|
||||
something useful, like "Johns-evennia-fixes". Give a brief summary,
|
||||
like "my repo for contributing to Evennia". Accept.
|
||||
#. Your new repo is created. You should see it appear in the `clone-repo
|
||||
list <https://code.google.com/p/evennia/source/clones>`_. This is
|
||||
actually your own mini-version of the Evennia page!
|
||||
#. Choose your repo and you will find it has its own Checkout page. Use
|
||||
the command shown there to get a local copy of your clone to your
|
||||
computer.
|
||||
|
||||
Once you have an online clone and a local copy of it:
|
||||
|
||||
#. Make sure that you have edited Mercurial's config file (``hgrc``) and
|
||||
under the header ``[ui]`` added the line
|
||||
``username=Yourname <your email>``. This is important for proper
|
||||
crediting and eventual conversions. See the first point of the
|
||||
`Mercurial
|
||||
Quickstart <http://mercurial.selenic.com/wiki/QuickStart>`_.
|
||||
#. Code away on your computer, fixing bugs or whatnot (you can be
|
||||
offline for this). Commit your code to your local clone as you work,
|
||||
as often as you like. There are some suggestions for setting up a
|
||||
sane local work environment with Mercurial
|
||||
`here <http://code.google.com/p/evennia/wiki/VersionControl>`_.
|
||||
#. When you have something you feel is worthwhile (or just want to ask
|
||||
people's opinions or make an online backup), *push* your local code
|
||||
up to your online repository with Mercurial.
|
||||
#. Let people know what you did - talk and discuss. If you think your
|
||||
changes should be merged into main Evennia (maybe you have made
|
||||
bugfixes, added new features etc), make a new
|
||||
`Issue <http://code.google.com/p/evennia/issues/list>`_ using the
|
||||
"Merge Request" template. Try to separate features with different
|
||||
commits, so it's possible to pick individual features.
|
||||
|
||||
From your online repo, Evennia devs can then, assuming the change is
|
||||
deemed good, pick and merge your work into Evennia proper. Mercurial
|
||||
will automatically make sure you get proper credit for your contribution
|
||||
in the source code history.
|
||||
|
||||
Contributing with Patches
|
||||
-------------------------
|
||||
|
||||
To help with Evennia development it's recommended to do so using a clone
|
||||
repository as described above. But for small, well isolated fixes you
|
||||
are also welcome to submit your suggested Evennia fixes/addendums as
|
||||
*patches*. You can use `normal
|
||||
patches <https://secure.wikimedia.org/wikipedia/en/wiki/Patch_%28computing%29>`_,
|
||||
but it might be easier to use mercurial's own patch mechanism. Make sure
|
||||
you have committed your latest fixes first, then
|
||||
|
||||
::
|
||||
|
||||
hg export tip > mypatch.patch
|
||||
|
||||
This will create a patch file ``mypatch.patch`` that can be imported by
|
||||
others with ``hg import mypatch.patch``. Depending on what fits best,
|
||||
post your patch to the `issue
|
||||
tracker <https://code.google.com/p/evennia/issues/list>`_ or to the
|
||||
`discussion forum <https://groups.google.com/forum/#!forum/evennia>`_.
|
||||
Please avoid pasting the full patch text directly in your post though,
|
||||
best is to use a site like `Pastebin <http://pastebin.com/>`_ and just
|
||||
supply the link.
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,80 +0,0 @@
|
|||
Developer Central
|
||||
=================
|
||||
|
||||
This page serves as a central nexus for useful information regarding
|
||||
coding using the Evennia codebase or developing the codebase itself.
|
||||
Everyone is welcome to `help
|
||||
out <http://code.google.com/p/evennia/wiki/Contributing>`_! If you have
|
||||
any questions, please feel free to ask them in the `Forum/Discussion
|
||||
Group <http://www.evennia.com/discussions>`_. If you want more docs on a
|
||||
particular issue, consider filling out our `online
|
||||
form <https://docs.google.com/spreadsheet/viewform?hl=en_US&formkey=dGN0VlJXMWpCT3VHaHpscDEzY1RoZGc6MQ#gid=0>`_
|
||||
and tell us! Bugs should be reported to the `Issue
|
||||
tracker <http://code.google.com/p/evennia/issues/list>`_. You can find
|
||||
more links to Evennia resources from the `Links <Links.html>`_ page.
|
||||
|
||||
General Evennia development information
|
||||
---------------------------------------
|
||||
|
||||
- `Introduction to coding with Evennia <CodingIntroduction.html>`_
|
||||
- `Evennia Licensing FAQ <Licensing.html>`_
|
||||
- `Contributing to Evennia <Contributing.html>`_
|
||||
- `Code Style
|
||||
Guide <http://code.google.com/p/evennia/source/browse/CODING_STYLE.txt>`_
|
||||
(Important!)
|
||||
- `Policy for 'MUX-like' default commands <UsingMUXAsAStandard.html>`_
|
||||
- `Setting up a Mercurial environment for
|
||||
coding <VersionControl.html>`_
|
||||
- `Planning your own Evennia game <GamePlanning.html>`_
|
||||
- `First steps coding Evennia <FirstStepsCoding.html>`_
|
||||
|
||||
Evennia Component Documentation
|
||||
-------------------------------
|
||||
|
||||
- `ev - the flat API <evAPI.html>`_
|
||||
|
||||
- `Directory Overview <DirectoryOverview.html>`_
|
||||
- `Portal and Server <PortalAndServer.html>`_
|
||||
- `Session <Session.html>`_
|
||||
- `Commands <Commands.html>`_
|
||||
- `Typeclass system <Typeclasses.html>`_
|
||||
|
||||
- `Objects <Objects.html>`_
|
||||
- `Scripts <Scripts.html>`_
|
||||
- `Players <Players.html>`_
|
||||
- [Communications#Channels Channels]
|
||||
- `Attributes <Attributes.html>`_
|
||||
|
||||
- `Locks and Permissions <Locks.html>`_
|
||||
- `Communications <Communications.html>`_
|
||||
- `Help System <HelpSystem.html>`_
|
||||
- `Nicks <Nicks.html>`_
|
||||
- `Tags <Tags.html>`_
|
||||
- `Sessions and Protocols <SessionProtocols.html>`_
|
||||
- `Caches <Caches.html>`_
|
||||
- `Web features <WebFeatures.html>`_
|
||||
- `Out-of-band communication <OOB.html>`_
|
||||
- `Configuration and module plugins <ServerConf.html>`_
|
||||
|
||||
Programming Evennia
|
||||
-------------------
|
||||
|
||||
- `Running and Testing Python code from inside the
|
||||
game <ExecutePythonCode.html>`_
|
||||
- `Useful coding utilities <CodingUtils.html>`_
|
||||
- `Running and writing unit tests for Evennia <UnitTesting.html>`_
|
||||
- `Running processes asynchronously <AsyncProcess.html>`_
|
||||
- `Expanding Evennia with new database models <NewModels.html>`_
|
||||
|
||||
Work in Progress - Developer brainstorms and whitepages
|
||||
-------------------------------------------------------
|
||||
|
||||
*In this section, contributors may suggest, discuss and plan out new
|
||||
features and ideas. Items here may or may not make it into Evennia down
|
||||
the road.*
|
||||
|
||||
- `Basic game system implementation <WorkshopDefaultGame.html>`_
|
||||
(inactive)
|
||||
- `Rtclient protocol <Workshop.html>`_ (deprecated)
|
||||
- `Change log <EvenniaDevel.html>`_ of big Evennia updates over time
|
||||
|
||||
|
|
@ -1,332 +0,0 @@
|
|||
Evennia directory overview
|
||||
==========================
|
||||
|
||||
::
|
||||
|
||||
evennia/
|
||||
ev.py
|
||||
contrib/
|
||||
docs/
|
||||
game/
|
||||
locale/
|
||||
src/
|
||||
|
||||
Evennia's main directory (``evennia``) is divided into five sub
|
||||
directories - ``src/``, ``game/``, ``contrib/`` , ``locale`` and
|
||||
``doc/``. The first two are the most important ones. ``game/`` is the
|
||||
place where you will create your own game, whereas ``src/`` is the home
|
||||
of the Evennia server itself. Your code should usually just import
|
||||
resources from ``src/`` and not change anything in there.
|
||||
|
||||
All directories contain files ending in ``.py``. These are Python
|
||||
*modules* and are the basic units of Python code. The roots of
|
||||
directories also have empty files named ``__init__.py``. These are
|
||||
required by Python so as to be able to find and import modules in other
|
||||
directories. When you have run Evennia at least once you will find that
|
||||
there will also be ``.pyc`` files appearing, these are pre-compiled
|
||||
binary versions of the ``.py`` files to speed up execution.
|
||||
|
||||
The file ``ev.py`` is important to remember. This is the home of
|
||||
Evennia's flat API, essentially a set of shortcuts to various important
|
||||
places in ``src/``. By importing ``ev`` from your code in ``game/`` you
|
||||
have access to most important Evennia systems without *having* to know
|
||||
where everything is located, as described in the following sections.
|
||||
|
||||
The \`docs/\` directory
|
||||
-----------------------
|
||||
|
||||
This contains Evennia's offline documentation. The main source of
|
||||
up-to-date documentation is the online wiki however.
|
||||
|
||||
Read ``sphinx/README`` for instructions on building the ReST
|
||||
documentation, based on a current snapshot of the wiki. This can be
|
||||
browsed offline or made into a PDF for printing etc. Since these files
|
||||
are automatically converted directly from the wiki files, there may be
|
||||
formatting problems here and there, especially if trying to convert to
|
||||
printable format.
|
||||
|
||||
You can create the Evennia *autodocs* by following the instructions in
|
||||
``doxygen/README``. This will make use of the source code itself to
|
||||
create a nice browsable web-index of all the sources and comments. In
|
||||
the same way you could in theory also create nice ``LaTeX``-formatted
|
||||
PDFs of the Evennia source (all 400+ pages of it ...).
|
||||
|
||||
The \`locale/\` directory
|
||||
-------------------------
|
||||
|
||||
This contains internationalization strings for translating the Evennia
|
||||
core server to different languages. See
|
||||
`Internationalization <Internationalization.html>`_ for more
|
||||
information.
|
||||
|
||||
The \`contrib/\` ("contributions") directory
|
||||
--------------------------------------------
|
||||
|
||||
This directory contains various stand-alone code snippets that are
|
||||
potentially useful but which are deemed too game-specific to be a
|
||||
regular part of the server. Modules in ``contrib/`` are not used unless
|
||||
you explicitly import and use them. The contrib folder also contains the
|
||||
`Tutorial World <TutorialWorldIntroduction.html>`_ game example. See
|
||||
``contrib/README`` for more information.
|
||||
|
||||
The \`game/\` directory
|
||||
-----------------------
|
||||
|
||||
``game/`` contains everything related to a particular game world. If you
|
||||
ever wanted to start over with a new game implementation you could
|
||||
replace the ``game`` directory and start from scratch. The root of this
|
||||
directory contains the all-important ``manage.py`` and ``evennia.py``
|
||||
which you need in order to `get started <GettingStarted.html>`_ and run
|
||||
the server.
|
||||
|
||||
::
|
||||
|
||||
game/
|
||||
evennia.py
|
||||
manage.py
|
||||
|
||||
gamesrc/
|
||||
commands/
|
||||
examples/
|
||||
scripts/
|
||||
examples/
|
||||
objects/
|
||||
examples/
|
||||
world/
|
||||
examples/
|
||||
conf/
|
||||
|
||||
\`game/gamesrc/\`
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
``game/gamesrc`` is where you will be spending most of your time. All
|
||||
the things going into your own dream game should be put here, by adding
|
||||
Python modules. Throughout the ``gamesrc`` directories are ``examples``
|
||||
folders that all define different aspects of an example
|
||||
`object <Objects.html>`_ called *Red Button*. This is a button that
|
||||
blinks and does interesting stuff when pressed. It's designed to combine
|
||||
many different systems and to show off several advanced features of
|
||||
Evennia.
|
||||
|
||||
\`gamesrc/commands/\`
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``gamesrc/commands/`` contains modules for defining
|
||||
`Commands <Commands.html>`_. In ``commands/examples`` you will find
|
||||
templates for starting to define your own commands and cmdsets. Copy
|
||||
these out into the parent ``command`` folder and work from there.
|
||||
|
||||
\`gamesrc/scripts/\`
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``gamesrc/scripts/`` holds everything related to
|
||||
`Scripts <Scripts.html>`_. ``scripts/examples`` holds templates you can
|
||||
make copies of and build from to define your own scripts.
|
||||
|
||||
\`gamesrc/objects/\`
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``gamesrc/objects/`` should contain the definitions for all your
|
||||
`Objects <Objects.html>`_. ``objects/examples`` contain templates for
|
||||
*Object* as well as its three basic subclasses *Character*, *Room* and
|
||||
*Exit*. Make copies of these templates to have somthing to start from
|
||||
when defining your own in-game entities.
|
||||
|
||||
\`gamesrc/world/\`
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``gamesrc/world/``, contains all the rest that make up your world. This
|
||||
is where you would put your own custom economic system, combat mechanic,
|
||||
emote-parser or what have you; organized in whatever way you like. Just
|
||||
remember that if you create new folders under ``world``, they must
|
||||
contain an empty file ``__init__.py``, or Python will not know how to
|
||||
import modules from them. The ``world`` folder is also where Evennia's
|
||||
`batch processors <BatchProcessors.html>`_ by default look for their
|
||||
input files. These allow you to build your world offline using your
|
||||
favourite text editor rather than have to do it online over a command
|
||||
line. The `Batch-Command processor <BatchCommandProcessor.html>`_
|
||||
expects files ending with ``.ev``, whereas the more advanced `Batch-Code
|
||||
processor <BatchCodeProcessor.html>`_ takes ``.py`` with some special
|
||||
formatting.
|
||||
|
||||
``world/examples/`` contains one batch file for each processor. Each
|
||||
creates a *Red Button* object in *Limbo* using their respective special
|
||||
syntax.
|
||||
|
||||
\`gamesrc/conf/\`
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
``gamesrc/conf/`` holds optional extension modules for the Evennia
|
||||
engine. It is empty by default, but in ``conf/examples/`` are templates
|
||||
for the various config files that the server undertands. Each template
|
||||
file contains instructions for how you should use them; copy out the
|
||||
ones you want into the ``conf/`` directory and edit them there.
|
||||
|
||||
The \`src/\` directory
|
||||
----------------------
|
||||
|
||||
``src/`` contains the main running code of the Evennia server. You can
|
||||
import files directly from here, but normally you will probably find it
|
||||
easier to use the shortcuts in the top-level ``ev`` module.
|
||||
|
||||
You should never modify anything in this folder directly since it might
|
||||
be changed when we release updates. If you want to use some code as a
|
||||
base for your own work (such as new commands), copy the relevant code
|
||||
out into your own modules in ``game/gamesrc`` instead. If you find bugs
|
||||
or features missing, file a bug report or send us a message.
|
||||
|
||||
::
|
||||
|
||||
src/
|
||||
settings_defaults.py
|
||||
|
||||
commands/
|
||||
comms/
|
||||
help/
|
||||
objects/
|
||||
locks/
|
||||
players/
|
||||
scripts/
|
||||
server/
|
||||
portal/
|
||||
typeclasses/
|
||||
utils/
|
||||
web/
|
||||
|
||||
Most of the folders in ``src/`` are technically "Django apps",
|
||||
identified by containing a file ``models.py`` and usually
|
||||
``managers.py``. A Django *model* is a template for how to save data to
|
||||
the database. In order to offer full-persistence, Evennia uses models
|
||||
extensively. The *manager* is used to conveniently access objects in the
|
||||
database. Even if you don't know Django, you can easily use the methods
|
||||
in the respective managers by accessing them through the *objects*
|
||||
property of each corresponding model. Example: in
|
||||
``src/objects/models.py`` there is a model named ``ObjectDB``. In the
|
||||
same folder, there is also a manager found in
|
||||
``src/objects/managers.py``. To access one of the manager's methods,
|
||||
such as ``object_search()``, you would need to do
|
||||
``ObjectDB.objects.object_search(...)``.
|
||||
|
||||
All Django app folders also have a file ``admin.py``. This tells
|
||||
Django's web features to automatically build a nice web-based admin
|
||||
interface to the database. This means that you can add/edit/delete
|
||||
objects through your browser.
|
||||
|
||||
In the root of the ``src`` directory lies the ``settings_defaults.py``
|
||||
file. This is the main configuration file of Evennia. You should
|
||||
copy&paste entries from this file to your ``game/settings.py`` file if
|
||||
you want to customize any setting.
|
||||
|
||||
\`src/commands/\`
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
This directory contains the `command system <Commands.html>`_ of
|
||||
Evennia. It defines basic command function, parsing and command-set
|
||||
handling.
|
||||
|
||||
``commands/default/`` holds a multitude of modules that together form
|
||||
Evennia's default ('`MUX-like <UsingMUXAsAStandard.html>`_\ ') command
|
||||
set. The files ``game/gamesrc/basecommand.py`` and
|
||||
``game/gamesrc/basecmdset.py`` both link to their respective parents
|
||||
here. If you want to edit a default command, copy&paste the respective
|
||||
module to ``game/gamesrc/commands/`` and edit the default cmdset to
|
||||
point to your copy.
|
||||
|
||||
\`src/comms/\`
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
``src/comms/`` defines all aspects of OOC
|
||||
`communication <Communications.html>`_, notably *channels*, *messages*
|
||||
and the basic operators for connecting external listeners to channels.
|
||||
|
||||
\`src/help/\`
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This defines the `help system <HelpSystem.html>`_ of Evennia, the
|
||||
command auto-help as well as the database-centric storage of in-game
|
||||
help files.
|
||||
|
||||
\`src/objects/\`
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
``src/objects/`` defines how the in-game `objects <Objects.html>`_ are
|
||||
stored, found and handled in the database.
|
||||
|
||||
\`src/locks/\`
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This directory defines the powerful `lock system <Locks.html>`_ of
|
||||
Evennia, a system that serves to restrict access to objects. The default
|
||||
lock functions are found here.
|
||||
|
||||
\`src/players/\`
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The `Player <Players.html>`_ is the OOC-represention of the person
|
||||
connected to the game. This directory defines the database handling and
|
||||
methods acting on the Player object.
|
||||
|
||||
\`src/scripts/\`
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
``src/scripts/`` defines all aspects of `Scripts <Scripts.html>`_ - how
|
||||
they are activated, repeated and stored in-memory or in-database. The
|
||||
main engine scripts (e.g. for keeping track of game-time, uptime and
|
||||
connection timeouts) are also defined here.
|
||||
|
||||
\`src/server/\`
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
This directory is the heart of Evennia. It holds the server process
|
||||
itself (started from ``game/evennia.py``). Its subfolder ``portal/``
|
||||
holds the portal and all `sessions and
|
||||
protocols <SessionProtocols.html>`_ that allow users to connect to the
|
||||
game.
|
||||
|
||||
\`src/typeclasses/\`
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``src/typeclasses/`` defines the `Typeclass system <Typeclasses.html>`_
|
||||
that permeates Evennia, allowing coders to interact with normal Python
|
||||
classes instead of caring about the underlying database implementation.
|
||||
This directory is rarely accessed directly, rather both Objects, Scripts
|
||||
and Players all inherit from its core classes. Also
|
||||
`attributes <Attributes.html>`_ are defined here, being an vital part of
|
||||
the typeclass system.
|
||||
|
||||
\`src/utils/\`
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
``src/utils/`` is a useful directory that contains helper functions for
|
||||
the MUD coder. The ``utils/create.py`` module for example gathers
|
||||
methods for creating all sorts of database models (objects, scripts,
|
||||
help entries etc) without having to go into the respective database
|
||||
managers directly. ``utils/search.py`` search a similar function for
|
||||
searching the database. This directory also contains many helper modules
|
||||
for parsing and converting data in various ways.
|
||||
|
||||
\`src/web/\`
|
||||
~~~~~~~~~~~~
|
||||
|
||||
This directory contains features related to running Evennia's `web site
|
||||
and ajax web client <WebFeatures.html>`_. It will be customizable by the
|
||||
user, but it's currently not established how to conveniently hook into
|
||||
this from game/, so for the moment the suggested way is to make a copy
|
||||
of this directory in ``game/gamesrc``, re-link the right settings in
|
||||
your settings file and edit things from there.
|
||||
|
||||
Assorted notes
|
||||
==============
|
||||
|
||||
Whereas ``game/gamesrc/`` contains a set of directories already, you
|
||||
might find that another structure suits your development better. For
|
||||
example, it could sometimes be easier to put all the commands and
|
||||
scripts a certain object needs in the same module as that object, rather
|
||||
than slavishly split them out into their respective directories and
|
||||
import. Don't be shy to define your own directory structure as needed. A
|
||||
basic rule of thumb should nevertheless be to avoid code-duplication. So
|
||||
if a certain script or command could be useful for other objects, break
|
||||
it out into its own module and import from it. Don't forget that if you
|
||||
add a new directory, it must contain an ``__init__.py`` file (it can be
|
||||
empty) in order for Python to recognize it as a place it can import
|
||||
modules from.
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,157 +0,0 @@
|
|||
The \`@py\` command
|
||||
===================
|
||||
|
||||
The ``@py`` command supplied with the default command set of Evennia
|
||||
allows you to execute Python commands directly from inside the game. An
|
||||
alias to ``@py`` is simply "``!``\ ". *Access to the ``@py`` command
|
||||
should be severely restricted*. This is no joke - being able to execute
|
||||
arbitrary Python code on the server is not something you should entrust
|
||||
to just anybody.
|
||||
|
||||
::
|
||||
|
||||
@py 1+2
|
||||
<<< 3
|
||||
|
||||
Available variables
|
||||
-------------------
|
||||
|
||||
A few local variables are made available when running ``@py``. These
|
||||
offer entry into the running system.
|
||||
|
||||
- **self** / **me** - the calling object (i.e. you)
|
||||
- **here** - the current caller's location
|
||||
- **obj** - a dummy `Object <Objects.html>`_ instance
|
||||
- **ev** - Evennia's flat API - through this you can access all of
|
||||
Evennia.
|
||||
|
||||
Returning output
|
||||
----------------
|
||||
|
||||
This is an example where we import and test one of Evennia's utilities
|
||||
found in ``src/utils/utils.py``, but also accessible through
|
||||
``ev.utils``:
|
||||
|
||||
::
|
||||
|
||||
@py from ev import utils; utils.time_format(33333)
|
||||
<<< Done.
|
||||
|
||||
Note that we didn't get any return value, all we where told is that the
|
||||
code finished executing without error. This is often the case in more
|
||||
complex pieces of code which has no single obvious return value. To see
|
||||
the output from the ``time_format()`` function we need to tell the
|
||||
system to echo it to us explicitly with ``self.msg()``.
|
||||
|
||||
::
|
||||
|
||||
@py from ev import utils; self.msg(utils.time_format(33333))
|
||||
09:15
|
||||
<<< Done.
|
||||
|
||||
If you were to use Python's standard ``print``, you will see the result
|
||||
in your current ``stdout`` (your terminal by default), *if* you are
|
||||
running Evennia in *interactive mode* (with the ``-i`` flag).
|
||||
|
||||
Finding objects
|
||||
---------------
|
||||
|
||||
A common use for ``@py`` is to explore objects in the database, for
|
||||
debugging and performing specific operations that are not covered by a
|
||||
particular command.
|
||||
|
||||
Locating an object is best done using ``self.search()``:
|
||||
|
||||
::
|
||||
|
||||
@py self.search("red_ball")
|
||||
<<< Ball
|
||||
|
||||
@py self.search("red_ball").db.color = "red"
|
||||
<<< Done.
|
||||
|
||||
@py self.search("red_ball").db.color
|
||||
<<< red
|
||||
|
||||
``self.search()`` is by far the most used case, but you can also search
|
||||
other database tables for other Evennia entities like scripts or
|
||||
configuration entities. To do this you can use the generic search
|
||||
entries found in ``ev.search_*``.
|
||||
|
||||
::
|
||||
|
||||
@py ev.search_script("sys_game_time")
|
||||
<<< [<src.utils.gametime.GameTime object at 0x852be2c>]
|
||||
|
||||
(Note that since this becomes a simple statement, we don't have to wrap
|
||||
it in ``self.msg()`` to get the output). You can also use the database
|
||||
model managers directly (accessible through the ``objects`` properties
|
||||
of database models or as ``ev.managers.*``). This is a bit more flexible
|
||||
since it gives you access to the full range of database search methods
|
||||
defined in each manager.
|
||||
|
||||
::
|
||||
|
||||
@py ev.managers.scripts.script_search("sys_game_time")
|
||||
<<< [<src.utils.gametime.GameTime object at 0x852be2c>]
|
||||
|
||||
The managers are useful for all sorts of database studies.
|
||||
|
||||
::
|
||||
|
||||
@py ev.managers.configvalues.all()
|
||||
<<< [<ConfigValue: default_home]>, <ConfigValue:site_name>, ...]
|
||||
|
||||
In doing so however, keep in mind the difference between `Typeclasses
|
||||
and Database Objects <Typeclasses.html>`_: Using the search commands in
|
||||
the managers will return *TypeClasses*. Using Django's default search
|
||||
methods (``get``, ``filter`` etc) will return *Database objects*. This
|
||||
distinction can often be disregarded, but as a convention you should try
|
||||
to stick with the manager search functions and work with TypeClasses in
|
||||
most situations.
|
||||
|
||||
::
|
||||
|
||||
# this uses Evennia's manager method get_id().
|
||||
# It returns a Character typeclass instance
|
||||
@py ev.managers.objects.get_id(1).__class__
|
||||
<<< Character
|
||||
|
||||
# this uses the standard Django get() query.
|
||||
# It returns a django database model instance.
|
||||
@py ev.managers.objects.get(id=1).__class__
|
||||
<<< <class 'src.objects.models.ObjectDB'>
|
||||
|
||||
Running a Python Parser outside the game
|
||||
========================================
|
||||
|
||||
``@py`` has the advantage of operating inside a running server, where
|
||||
you can test things in real time. Much of this *can* be done from the
|
||||
outside too though.
|
||||
|
||||
Go to the ``game`` directory and get into a new terminal.
|
||||
|
||||
::
|
||||
|
||||
python manage.py shell
|
||||
|
||||
Your default Python intrepeter will start up, configured to be able to
|
||||
work with and import all modules of your Evennia installation. From here
|
||||
you can explore the database and test-run individual modules as desired.
|
||||
Most of the time you can get by with just the ``ev`` module though. A
|
||||
fully featured Python interpreter like
|
||||
`iPython <http://ipython.scipy.org/moin/>`_ allow you to work over
|
||||
several lines, but also has lots of other editing features, usch as
|
||||
tab-completion and ``__doc__``-string reading.
|
||||
|
||||
::
|
||||
|
||||
$ python manage.py shell
|
||||
|
||||
IPython 0.10 -- An enhanced Interactive Python
|
||||
...
|
||||
|
||||
In [1]: import ev
|
||||
In [2]: ev.managers.objects.all()
|
||||
Out[3]: [<ObjectDB: Harry>, <ObjectDB: Limbo>, ...]
|
||||
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
Game development tips and tricks
|
||||
================================
|
||||
|
||||
So you have Evennia up and running. You have a great game idea in mind.
|
||||
Now it's time to start cracking! But where to start? Here are some ideas
|
||||
for a workflow. Note that the suggestions on this page are just that -
|
||||
suggestions. Also, they are primarily aimed at a lone hobby designer or
|
||||
a small team developing a game in their free time.
|
||||
|
||||
Phases of Evennia game development
|
||||
==================================
|
||||
|
||||
Below are some minimal steps for getting the first version of a new game
|
||||
world going with players. It's worth to at least make the attempt to do
|
||||
these steps in order even if you are itching to jump ahead in the
|
||||
development cycle. On the other hand, you should also make sure to keep
|
||||
your work fun for you, or motivation will falter. Making a full game is
|
||||
a lot of work as it is, you'll need all your motivation to make it a
|
||||
reality.
|
||||
|
||||
Remember that *99.99999% of all great game ideas never lead to an online
|
||||
game*. So your first all overshadowing goal is to beat those odds and
|
||||
get *something* out the door! *Even* if it's a scaled-down version of
|
||||
your dream game, lacking many "must-have" features! It's better to get
|
||||
it out there and expand on it later than to code in isolation forever
|
||||
until you burn out, lose interest or your hard drive crashes.
|
||||
|
||||
Like is common with online games, getting a game out the door does not
|
||||
mean you are going to be "finished" with the game - most MUDs add
|
||||
features gradually over the course of years - it's often part of the
|
||||
fun!
|
||||
|
||||
1: Planning
|
||||
-----------
|
||||
|
||||
This is what you do before having coded a single line or built a single
|
||||
room. Many prospective game developers are very good at *parts* of this
|
||||
process, namely in defining what their world is "about": The theme, the
|
||||
world concept, cool monsters and so on. This is by all means important -
|
||||
yes critical to the appeal of your game. But it's unfortunately not
|
||||
enough to make your game a reality. To do that you must have an idea of
|
||||
how to actually map those great ideas onto Evennia.
|
||||
|
||||
A good start is to begin by planning out the basic primitives of the
|
||||
game and what they need to be able to do.
|
||||
|
||||
- **Rooms** - consider the most basic room in your game. How "big" is
|
||||
it in a game sense? What should Players be able to do inside it? Is a
|
||||
simple description enough? Can it be dark (description
|
||||
changed/hidden)? Should it have smells, sounds? Weather? Different
|
||||
terrain? How are those to be conveyed? Are there special "magic"
|
||||
rooms that do things to people entering? Can a person hide in the
|
||||
room? Should all rooms have the ability to be this complex or should
|
||||
there be different types of rooms? Evennia allows you to change the
|
||||
very concept of rooms should you be very ambitious, but is that a
|
||||
road you really want to go down for your project?
|
||||
- **Objects** - consider the most basic (non-player-controlled) object
|
||||
in your game. What should a Player be able to do with it? Smash it?
|
||||
If so, will it need some measure of its health? Does it have weight
|
||||
or volume (so you cannot carry an infinite amount of them)? How do
|
||||
you handle multiple identical objects? Try to give rough
|
||||
classifications. Is a weapon a different type of object or are you
|
||||
supposed to be able to fight with a chair as well? What about
|
||||
NPCs/mobs, should they have some sort of AI?
|
||||
- **Systems** - These are the behind-the-scenes features that exist in
|
||||
your game, often without being represented by a specific in-game
|
||||
object. For a role playing game, you need to define chances of
|
||||
success ("rolls") for example. Will weather messages be random in
|
||||
every room or should it follow some sort of realistic pattern over
|
||||
all rooms? Do you have a game-wide economy - if so, how is that
|
||||
supposed to work? If magic is dependent on the position of the
|
||||
planets, the planets must change with time. What about spreading
|
||||
rumors? Mail boxes? Bulletin boards?
|
||||
- **Characters** - to do all those things with the rooms, objects and
|
||||
systems in the game, what will the Characters need to have? What
|
||||
skill will decide if they can "hide" in a room? Wield a chair as a
|
||||
weapon? How to tell how much they can carry or which objects they can
|
||||
smash? Can they gain experience and how? How about skills, classes,
|
||||
attributes?
|
||||
|
||||
A MUD's a lot more involved than you would think and these things hang
|
||||
together in a complex web. It can easily become overwhelming and it's
|
||||
tempting to want *all* functionality right out of the door. Try to
|
||||
identify the basic things that "make" your game and focus on them for
|
||||
the first release. Make a list. Keep future expansions in mind but limit
|
||||
yourself.
|
||||
|
||||
2: Coding
|
||||
---------
|
||||
|
||||
This is the actual work of creating the "game" part of your game. Many
|
||||
"game-designer" types tend to gloss over this bit and jump directly to
|
||||
**Building**. Vice-versa, many "game-coder" types tend to jump directly
|
||||
to this part without doing the **Planning** first. Neither is good and
|
||||
*will* lead to you having to redo all your hard work at least once,
|
||||
probably more.
|
||||
|
||||
Evennia's `Developer Central <DeveloperCentral.html>`_ is focused on how
|
||||
to perform this bit of the development. Evennia tries hard to make this
|
||||
part easier for you, but there is no way around the fact that if you
|
||||
want anything but a very basic Talker-type game you *will* have to bite
|
||||
the bullet and code your game (or find a coder willing to do it for
|
||||
you). Even if you won't code anything yourself, as a designer you need
|
||||
to at least understand the basic paradigms of Evennia, such as objects,
|
||||
commands and scripts and how they hang together. We recommend you go
|
||||
through the `Tutorial World <TutorialWorldIntroduction.html>`_ in detail
|
||||
(as well as skimming its code) to get at least a feel for what is
|
||||
involved behind the scenes.
|
||||
|
||||
During Coding you look back at the things you wanted during the
|
||||
**Planning** phase and try to implement them. Don't be shy to update
|
||||
your plans if you find things easier/harder than you thought. The
|
||||
earlier you revise problems, the easier they will be to fix.
|
||||
`Here <VersionControl.html>`_ are some hints for setting up a sane
|
||||
coding environment.
|
||||
|
||||
"Tech Demo" Building
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This is an integral part of your Coding. It might seem obvious to
|
||||
experienced coders, but it cannot be emphasized enough that you should
|
||||
*test* things on a *small* scale before putting your untested code into
|
||||
a large game-world. The earlier you test, the easier and cheaper it will
|
||||
be to fix bugs and even rework things that didn't work out the way you
|
||||
thought they would. You might even have to go back to the **Planning**
|
||||
phase if your ideas can't handle their meet with reality.
|
||||
|
||||
This means building singular in-game examples. Make one room and one
|
||||
object of each important type and test they work as they should in
|
||||
isolation, then add more if they are supposed to interact with each
|
||||
other in some way. Build a small series of rooms to test how mobs move
|
||||
around ... and so on. In short, a test-bed for your growing code. It
|
||||
should be done gradually until you have a fully functioning (if not
|
||||
guaranteed bug-free) miniature tech demo that shows *all* the features
|
||||
you want in the first release of your game. There does not need to be
|
||||
any game play or even a theme to your tests, but the more testing you do
|
||||
on this small scale, the less headaches you will have in the next phase.
|
||||
|
||||
3: World Building
|
||||
-----------------
|
||||
|
||||
Up until this point we've only had a few tech-demo objects in the
|
||||
database. This step is the act of populating the database with a larger,
|
||||
thematic world. Too many would-be developers jump to this stage too soon
|
||||
and then have to go back and rework things on already existing objects.
|
||||
Evennia's typeclass system does allow you to edit the properties of
|
||||
existing objects, but some hooks are only called at object creation, and
|
||||
you are in for a *lot* of unnecessary work if you build stuff en masse
|
||||
without having the underlying code systems in some reasonable shape
|
||||
first.
|
||||
|
||||
So, at this point the "game" bit (Coding + Testing) should be more or
|
||||
less complete, *at least to the level of your initial release*. Building
|
||||
often involves non-coders, so you also get to test whatever custom build
|
||||
systems you have made at this point. You don't have to complete your
|
||||
entire world in one go - just enough to make the game's "feel" come
|
||||
across - an actual world where the intended game play can be tested and
|
||||
roughly balanced. You can always add new areas later, so limit yourself.
|
||||
|
||||
Alpha Release
|
||||
-------------
|
||||
|
||||
As mentioned, don't hold onto your world more than necessary. *Get it
|
||||
out there* with a huge *Alpha* flag and let people try it! Call upon
|
||||
your alpha-players to try everything - they *will* find ways to break
|
||||
your game in ways that you never could have imagined. In Alpha you might
|
||||
be best off to focus on inviting friends and maybe other MUD developers,
|
||||
people who you can pester to give proper feedback and bug reports (there
|
||||
*will* be bugs, there is no way around it). Follow the quick
|
||||
instructions `here <OnlineSetup.html>`_ to make your game visible
|
||||
online.
|
||||
|
||||
Beta Release/Perpetual Beta
|
||||
---------------------------
|
||||
|
||||
Once things stabilize in Alpha you can move to *Beta* and let more
|
||||
people in. Many MUDs are in `perpetual
|
||||
beta <http://en.wikipedia.org/wiki/Perpetual_beta>`_, meaning they are
|
||||
never considered "finished", but just repeat the cycle of Planning,
|
||||
Coding, Testing and Building over and over as new features get
|
||||
implemented or Players come with suggestions. As the game designer it's
|
||||
up to you to perfect your vision.
|
||||
|
||||
Congratulations, at this point you have joined the small, exclusive
|
||||
crowd who have made their dream game a reality!
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
Getting Help
|
||||
============
|
||||
|
||||
The best way to get help is via our `Google
|
||||
Group <http://evennia.com>`_. Simply post a message with a clear
|
||||
question and you are more than likely to receive a response. This way
|
||||
issues can be tracked over time too.
|
||||
|
||||
If you want more direct discussions with developers and other users,
|
||||
consider dropping into our IRC chat channel
|
||||
`#evennia <http://webchat.freenode.net/?channels=evennia>`_ on the
|
||||
Freenode network. Please note however that you have to be patient if you
|
||||
don't get any response immediately; we are all in very different time
|
||||
zones and many have busy personal lifes. So you might have to lurk about
|
||||
for a while - but if you are patient you'll get noticed eventually!
|
||||
|
|
@ -1,431 +0,0 @@
|
|||
Getting Started
|
||||
===============
|
||||
|
||||
This will help you download, install and start Evennia for the first
|
||||
time.
|
||||
|
||||
*Note: You don't need to make anything visible to the 'net in order to
|
||||
run and test out Evennia. Apart from installing and updating you don't
|
||||
even need to have an internet connection. Of course you'll probably want
|
||||
to put your game online once it matures enough, but until then it works
|
||||
fine to develop and play around completely in the sanctity and isolation
|
||||
of your local machine.*
|
||||
|
||||
Quick start
|
||||
-----------
|
||||
|
||||
For you who are extremely impatient, here's the gist of getting a
|
||||
vanilla Evennia install running.
|
||||
|
||||
#. *Get the pre-requisites (Python, Django, Twisted, South and
|
||||
Mercurial)*.
|
||||
#. *Start a command terminal/dos prompt and change directory to where
|
||||
you want to have your 'evennia' folder appear*.
|
||||
#. ``hg clone https://code.google.com/p/evennia/ evennia``
|
||||
#. *Change directory to evennia/game*.
|
||||
#. ``python manage.py``
|
||||
#. ``python manage.py syncdb``
|
||||
#. ``python manage.py migrate``
|
||||
#. ``python evennia.py -i start``
|
||||
|
||||
Evennia should now be running and you can connect to it by pointing a
|
||||
web browser to ``http://localhost:8000`` or a MUD telnet client to
|
||||
``localhost:4000``.
|
||||
|
||||
Read on for more detailed instructions and configurations.
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
As far as operating systems go, any system with Python support should
|
||||
work.
|
||||
|
||||
- Linux/Unix
|
||||
- Windows (2000, XP, Vista, Win7, Win8)
|
||||
- Mac OSX (>=10.5 recommended)
|
||||
|
||||
If you run into problems, or have success running Evennia on another
|
||||
platform, please let us know.
|
||||
|
||||
You'll need the following packages and minimum versions in order to run
|
||||
Evennia:
|
||||
|
||||
- **`Python <http://www.python.org>`_** (v2.6+, not supporting v3.x).
|
||||
|
||||
- Windows users are recommended to use
|
||||
`ActivePython <http://www.activestate.com/activepython/downloads>`_
|
||||
instead.
|
||||
|
||||
- **`Twisted <http://twistedmatrix.com>`_** (v11.0+)
|
||||
|
||||
- `ZopeInterface <http://www.zope.org/Products/ZopeInterface>`_
|
||||
(v3.0+) - usually included in Twisted packages
|
||||
- Windows users might also need
|
||||
`pywin32 <http://sourceforge.net/projects/pywin32>`_.
|
||||
|
||||
- **`Django <http://www.djangoproject.com>`_** (v1.5+)
|
||||
|
||||
- `PIL <http://www.pythonware.com/products/pil>`_ (Python Image
|
||||
Library) - often distributed with Django.
|
||||
|
||||
- **`South <http://south.aeracode.org/>`_** (v0.8+)
|
||||
|
||||
- South is used to track and apply changes to the database's
|
||||
structure.
|
||||
|
||||
To download/update Evennia:
|
||||
|
||||
- **`Mercurial <http://mercurial.selenic.com/>`_**
|
||||
|
||||
Optional:
|
||||
|
||||
- **`PyPy <http://pypy.org>`_** (v1.7+)
|
||||
|
||||
- Optional faster implementation of Python. See
|
||||
[`GettingStarted <GettingStarted.html>`_\ #Optional:\ *Running\_under\_PyPy
|
||||
here] for how to run Evennia under PyPy.*
|
||||
|
||||
Installing pre-requisites
|
||||
-------------------------
|
||||
|
||||
**All platforms** can set up an \_virtual Python environment and
|
||||
install Evennia to that. All you need pre-installed is Python.
|
||||
Setup is described in detail
|
||||
[`GettingStarted <GettingStarted.html>`_\ #Optional:\ *A\_separate\_installation\_environment\_with\_virtualenv
|
||||
here]. Windows users will probably want to go the ActivePython
|
||||
route instead (see below) since there are issues with installing
|
||||
certain extensions under Windows.*
|
||||
|
||||
**Linux** package managers should usually handle all this for you.
|
||||
Python itself is definitely available through all distributions.
|
||||
On Debian-derived systems (such as Ubuntu) you can do something
|
||||
like this (as root) to get all you need:
|
||||
|
||||
::
|
||||
|
||||
apt-get install python python-django python-twisted mercurial python-django-south
|
||||
|
||||
(Gentoo note: Gentoo (and maybe other distros?) seems to
|
||||
distribute Twisted in multiple packages. Beyond the main twisted
|
||||
package you will also need to get at least twisted-conch and
|
||||
twisted-web too).\ **
|
||||
|
||||
Distributions can usually not keep fully up-to-date with the
|
||||
latest security fixes. So for an online server it is highly
|
||||
recommended to use Python's
|
||||
`easy\_install <http://packages.python.org/distribute/easy_install.html>`_
|
||||
or the newer
|
||||
`pip <http://www.pip-installer.org/en/latest/index.html>`_ to get
|
||||
some or all of the dependencies instead:
|
||||
|
||||
::
|
||||
|
||||
easy_install django twisted pil mercurial south
|
||||
|
||||
::
|
||||
|
||||
pip install django twisted pil mercurial south
|
||||
|
||||
If you already have Python and have downloaded Evennia, the
|
||||
package comes with a ``requirements.txt`` file. This can be used
|
||||
with ``pip`` to install the remaining dependencies. This is useful
|
||||
for automated build systems:
|
||||
|
||||
::
|
||||
|
||||
pip install -r requirements.txt
|
||||
|
||||
**Mac** users should be able to get most dependencies through
|
||||
``easy_install`` or ``pip`` like Linux users do. All interaction
|
||||
is done from a terminal window. There are some reports that you
|
||||
might need to get the
|
||||
`Xcode <https://developer.apple.com/xcode/>`_ development system
|
||||
to install the packages that requires extension compiling. You can
|
||||
also retrieve the dependencies directly and install them through
|
||||
their native installers or python setups. Some users have reported
|
||||
problems compiling the ``PIL`` library on Mac, it's however not
|
||||
strictly required in order to use Django (it's used for images).
|
||||
|
||||
**Windows** users should first and foremost recognize that the
|
||||
Evennia server is run from the command line, something which some
|
||||
might not be familiar with (based on the questions we have
|
||||
received). In the Windows launch menu, just start \_All Programs
|
||||
-> Accessories -> command prompt and you will get the Windows
|
||||
command line interface. There are plenty of online tutorials on
|
||||
using the Windows command line, one example is found
|
||||
`here <http://www.bleepingcomputer.com/tutorials/windows-command-prompt-introduction/>`_.
|
||||
|
||||
Windows users may want to install
|
||||
`ActivePython <http://www.activestate.com/activepython/downloads>`_
|
||||
instead of the usual Python. Get the 32-bit version (it seems the 64-bit
|
||||
one won't let you download any packages without paying for a "Business"
|
||||
license). If ActivePython is installed, you can use
|
||||
`pypm <http://docs.activestate.com/activepython/2.6/pypm.html>`_ in the
|
||||
same manner as ``easy_install``/``pip`` above. This *greatly* simplifies
|
||||
getting started on Windows - this platform defaults to lacking sane
|
||||
developer tools and package management.
|
||||
|
||||
After installing ActivePython you may need to restart the terminal/DOS
|
||||
window to make the pypm command available on the command line. Then
|
||||
write:
|
||||
|
||||
::
|
||||
|
||||
pypm install Django Twisted PIL Mercurial South
|
||||
|
||||
This installs everything you need in one go.
|
||||
|
||||
Windows users not using ActivePython or virtual environments will have
|
||||
to manually download and install the packages in turn (including their
|
||||
own dependencies in the list above). Most have normal Windows
|
||||
installers, but in some cases you'll need to know how to use the Windows
|
||||
command prompt to execute python install scripts (it's usually not
|
||||
harder than running ``python setup.py install`` from the downloaded
|
||||
package's folder).
|
||||
|
||||
Step 1: Obtaining the Server
|
||||
----------------------------
|
||||
|
||||
To download Evennia you need the Mercurial client to grab a copy of the
|
||||
source.
|
||||
|
||||
For command-line Mercurial client users, something like this will do the
|
||||
trick (first place yourself in a directory where you want a new folder
|
||||
``evennia`` to be created):
|
||||
|
||||
::
|
||||
|
||||
hg clone https://code.google.com/p/evennia/ evennia
|
||||
|
||||
(Mercurial is abbreviated ``hg`` since this is the chemical symbol for
|
||||
mercury).
|
||||
|
||||
In the future, you just do
|
||||
|
||||
::
|
||||
|
||||
hg pull
|
||||
hg update
|
||||
|
||||
from your ``evennia/`` directory to obtain the latest updates.
|
||||
|
||||
If you use a graphical Mercurial client, use the equivalent buttons to
|
||||
perform the above operations. See
|
||||
`here <http://code.google.com/p/evennia/wiki/VersionControl>`_ for more
|
||||
advanced suggestions to set up a development environment with Mercurial.
|
||||
|
||||
Step 2: Setting up the Server
|
||||
-----------------------------
|
||||
|
||||
From within the Evennia ``game`` directory (``evennia/game/``, if you
|
||||
followed the Mercurial instructions above) type the following to trigger
|
||||
the automatic creation of an empty ``settings.py`` file.
|
||||
|
||||
::
|
||||
|
||||
python manage.py
|
||||
|
||||
Your new ``settings.py`` file will just be an empty template initially.
|
||||
In ``evennia/src/settings_default.py`` you will find the settings that
|
||||
may be copied/pasted into your ``settings.py`` to override the defaults.
|
||||
This will be the case if you want to adjust paths or use something other
|
||||
than the default SQLite3 database engine. You *never* want to modify
|
||||
``settings_default.py`` directly - as the server is developed, this file
|
||||
might be overwritten with new versions and features.
|
||||
|
||||
If you would like to use something other than the default SQLite setup
|
||||
(which works "out of the box"), you'll need to copy the ``DATABASE_*``
|
||||
variables from ``settings_defaults.py`` and paste them to
|
||||
``settings.py``, making your modifications there.
|
||||
|
||||
*Note that the settings.py file is in fact a normal python module which
|
||||
imports the default settings. This means that all variables have been
|
||||
set to default values by the time you get to change things. So to
|
||||
customize a particular variable you have to copy&paste it to your
|
||||
settings file - and you have to do so also for variables that depend on
|
||||
that variable (if any), or the dependent variables will remain at the
|
||||
default values.*
|
||||
|
||||
Finally, enter the following command in a terminal or shell to create
|
||||
the database file (in the case of SQLite3) and populate the database
|
||||
with the standard tables and values:
|
||||
|
||||
::
|
||||
|
||||
python manage.py syncdb
|
||||
|
||||
You will see a lot of spammy install messages. If all goes well, you're
|
||||
ready to continue to the next step. If not, look at the error messages
|
||||
and double-check your ``settings.py`` file.
|
||||
|
||||
Next you migrate the database to the current revision:
|
||||
|
||||
::
|
||||
|
||||
python manage.py migrate
|
||||
|
||||
This can take a while. When we make changes to the database schema in
|
||||
the future (we announce this on the homepage) you just need to re-run
|
||||
this command to have your existing database converted for you.
|
||||
|
||||
Step 3: Starting and Stopping the Server
|
||||
----------------------------------------
|
||||
|
||||
To start the server, make sure you're in the ``evennia/game`` directory
|
||||
and execute ``evennia.py`` like this:
|
||||
|
||||
::
|
||||
|
||||
python evennia.py -i start
|
||||
|
||||
(The ``-i`` flag means that the server starts in *interactive mode*, as
|
||||
a foreground process. You will see debug/log messages directly in the
|
||||
terminal window instead of logging them to a file.)
|
||||
|
||||
You should be asked to create a superuser. Make **sure** you create a
|
||||
superuser here when asked, this becomes your login name for the
|
||||
superuser (owner) account in game. It will ask for email address and
|
||||
password. The email address does not have to be an existing one.
|
||||
|
||||
After entering the superuser information, the server and portal will
|
||||
start for the first time. Evennia will quickly run some first-time
|
||||
configurations, restart once and then be running.
|
||||
|
||||
To stop Evennia, do:
|
||||
|
||||
::
|
||||
|
||||
python evennia.py stop
|
||||
|
||||
See `Running
|
||||
Evennia <https://code.google.com/p/evennia/wiki/StartStopReload>`_ for
|
||||
more advanced options on controlling Evennia's processes.
|
||||
|
||||
Step 4: Connecting to the server
|
||||
--------------------------------
|
||||
|
||||
The Evennia server is now up and running. You should be able to login
|
||||
with any mud client or telnet client using the email address and
|
||||
password you specified when syncing the database. If you are just
|
||||
testing the server out on your local machine, the server name will most
|
||||
likely be ``localhost`` whereas the port used by default is ``4000``.
|
||||
|
||||
If the defaults are not changed, Evennia will also start its own
|
||||
Twisted-based web server on port 8000. Point your web browser to
|
||||
``http://localhost:8000/``. The *admin interface* allows you to edit the
|
||||
game database online and you can connect directly to the game by use of
|
||||
the ajax web client.
|
||||
|
||||
Welcome to Evennia! Why not try `building
|
||||
something <BuildingQuickstart.html>`_ next?
|
||||
|
||||
Optional: A separate installation environment with virtualenv
|
||||
=============================================================
|
||||
|
||||
Apart from installing the packages and versions as above, you can also
|
||||
set up a very easy self-contained Evennia install using the
|
||||
`virtualenv <http://pypi.python.org/pypi/virtualenv>`_ program. If you
|
||||
are unsure how to get it, just grab the
|
||||
`virtualenv.py <https://raw.github.com/pypa/virtualenv/master/virtualenv.py>`_
|
||||
file and run it directly in the terminal with ``python virtualenv.py``.
|
||||
|
||||
Virtualenv sets aside a folder on your harddrive as a stand-alone Python
|
||||
environment. It should work both on Linux/Unix and Windows. First,
|
||||
install Python as normal, then get virtualenv and install it so you can
|
||||
run it from the command line. This is an example for setting up Evennia
|
||||
in an isolated new folder *mudenv*:
|
||||
|
||||
::
|
||||
|
||||
virtualenv mudenv
|
||||
|
||||
Or, if you grabbed ``virtualenv.py`` and is running it directly:
|
||||
|
||||
::
|
||||
|
||||
python virtualenv.py mudenv
|
||||
|
||||
Followed by
|
||||
|
||||
::
|
||||
|
||||
cd mudenv
|
||||
|
||||
Now we should be in our new directory *mudenv*. Next we activate the
|
||||
virtual environment in here.
|
||||
|
||||
::
|
||||
|
||||
# for Linux/Unix:
|
||||
source bin/activate
|
||||
# for Windows:
|
||||
<path_to_this_place>\Scripts\activate.bat
|
||||
|
||||
The virtual environment within our *mudenv* folder is now active. Things
|
||||
will not seem to have changed very much, and indeed they haven't - the
|
||||
only difference is that python programs will now look to the python
|
||||
installation in this folder instead of the system-centric ones.
|
||||
|
||||
Next we get all the requirements with *pip*, which is included with
|
||||
virtualenv:
|
||||
|
||||
::
|
||||
|
||||
pip install django twisted pil mercurial south
|
||||
|
||||
These newly installed packages are *only* localized to the virtual
|
||||
environment, they do not affect the normal versions of programs you run
|
||||
in the rest of your system. So you could for example experiment with
|
||||
bleeding-edge, unstable libraries or go back to older versions without
|
||||
having to worry about messing up other things. It's also very easy to
|
||||
uninstall the whole thing in one go - just delete your ``mudenv``
|
||||
folder.
|
||||
|
||||
You can now refer to **Step 1** above and continue on from there to
|
||||
install Evennia into *mudenv*. In the future, just go into the folder
|
||||
and activate it before starting or working with Evennia.
|
||||
|
||||
Optional: Running under !PyPy
|
||||
=============================
|
||||
|
||||
Evennia can also be run under `PyPy <http://pypy.org>`_, a fast
|
||||
alternative implementation of standard Python. Evennia under PyPy
|
||||
generally takes longer to start but may run faster (just how much is
|
||||
currently untested so feel free to report your findings).
|
||||
|
||||
You first need to download and install PyPy. See the
|
||||
`PyPy <http://pypy.org>`_ homepage for instructions. This may be the
|
||||
most tricky step depending on your operating system.
|
||||
|
||||
The easiest way to set up Evennia for running under pypy is to do so in
|
||||
a separate *virtualenv*. First get the virtualenv program as described
|
||||
in the previous section and create your virtual environment like this:
|
||||
|
||||
::
|
||||
|
||||
virtualenv mudenv --python=/usr/bin/pypy
|
||||
|
||||
Replace ``/usr/bin/pypy`` with the full path to your PyPy binary on your
|
||||
system.
|
||||
|
||||
Next you activate and set up the virtual environment as normal. Make
|
||||
sure to install all dependencies to the virtual environment. If ``pip``
|
||||
aborts with a message about "dependence is already satisfied", use the
|
||||
--upgrade option. This way PyPy-enhanced versions of the dependencies
|
||||
will be installed wherever applicable.
|
||||
|
||||
Download and configure Evennia as usual. Then just start it like this:
|
||||
|
||||
::
|
||||
|
||||
pypy evennia.py -i start
|
||||
|
||||
Inside the game you can do the following (as superuser) to test that
|
||||
PyPy is working:
|
||||
|
||||
::
|
||||
|
||||
@py import __pypy__
|
||||
|
||||
If this works Evennia is running under pypy. If you get an ImportError
|
||||
there was some problem and normal Python is still being used.
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
Help system
|
||||
===========
|
||||
|
||||
An important part of Evennia is the online help system. This allows the
|
||||
players and staff alike to learn how to use the game's commands as well
|
||||
as other information pertinent to the game. The help system has many
|
||||
different aspects, from the normal editing of help entries from inside
|
||||
the game, to auto-generated help entries during code development using
|
||||
the *auto-help system*.
|
||||
|
||||
Viewing the help database
|
||||
-------------------------
|
||||
|
||||
The main command is ``help``.
|
||||
|
||||
::
|
||||
|
||||
help [searchstring]
|
||||
|
||||
This will show a list of help entries, ordered after categories. You
|
||||
will find two sections, *Command help entries* and *Other help entries*
|
||||
(initially you will only have the first one). You can use help to get
|
||||
more info about an entry; you can also give partial matches to get
|
||||
suggestions. If you give category names you will only be shown the
|
||||
topics in that category.
|
||||
|
||||
Command Auto-help system
|
||||
------------------------
|
||||
|
||||
One important use of the help system is to teach the use of in-game
|
||||
commands. Evennia offers a dynamically updated help system for all your
|
||||
commands, new and old, known simply as the *auto-help system*. Only
|
||||
commands that you and your character can actually currently use are
|
||||
picked up by the auto-help system. That means an admin will see a
|
||||
considerably larger amount of help topics than a normal player when
|
||||
using the ``help`` command.
|
||||
|
||||
The auto-help system uses the ``__doc__`` strings of your command
|
||||
definitions and formats this to a nice-looking help entry. This makes
|
||||
for a very easy way to keep the help updated - just document your
|
||||
commands well abd updating the help file is just a ``@reload`` away.
|
||||
There is no need to manually create and maintain help database entries
|
||||
for commands; as long as you keep the docstrings updated your help will
|
||||
be dynamically updated for you as well.
|
||||
|
||||
Example (from a module with command definitions):
|
||||
|
||||
::
|
||||
|
||||
class CmdMyCmd(Command):
|
||||
"""
|
||||
mycmd - my very own command
|
||||
|
||||
Usage:
|
||||
mycmd[/switches] <args>
|
||||
|
||||
Switches:
|
||||
test - test the command
|
||||
run - do something else
|
||||
|
||||
This is my own command that does things to you when you
|
||||
supply it with arguments.
|
||||
|
||||
"""
|
||||
...
|
||||
help_category = "Building"
|
||||
...
|
||||
|
||||
The text at the very top of the command class definition is the class'
|
||||
``__doc__``-string and will be shown to users looking for help. Try to
|
||||
use a consistent format - all default commands are using the structure
|
||||
shown above. You should also supply the ``help_category`` class property
|
||||
if you can; this helps to group help entries together for easier finding
|
||||
(if you don't supply a help\_category, "General" will be assumed).
|
||||
|
||||
Other help entries (entries stored in database)
|
||||
-----------------------------------------------
|
||||
|
||||
Not all help entries concern commands. You will also probably need help
|
||||
entries describing how your particular game is played - its rules, world
|
||||
descriptions and other types of information not covered by the auto-help
|
||||
system. Such entries are manually added to the database by staff and
|
||||
will appear under a heading "Other help" whenever you have any. Instead
|
||||
of being generated from the code by Evennia, these entries are stored in
|
||||
the database as a ``HelpEntry``.
|
||||
|
||||
A help entry consists of four parts:
|
||||
|
||||
- The *topic*. This is the name of the help entry. This is what players
|
||||
search for when they are looking for help. The topic can contain
|
||||
spaces and also partual matches will be found.
|
||||
- The *help category*. Examples are *Administration*, *Building*,
|
||||
*Comms* or *General*. This is an overall grouping of similar help
|
||||
topics, used by the engine to give a better overview.
|
||||
- The *text* - the help text itself, of any length.
|
||||
- locks - a `lock definition <Locks.html>`_. This can be used to limit
|
||||
access to this help entry, maybe because it's staff-only or otherwise
|
||||
meant to be restricted. Help commands check for ``access_type``\ s
|
||||
``view`` and ``edit``. An example of a lock string would be
|
||||
``view:perm(Builders)``.
|
||||
|
||||
You can create new help entries in code by using
|
||||
``src.utils.create.create_help_entry()``:
|
||||
|
||||
::
|
||||
|
||||
from src.utils import create
|
||||
entry = create.create_help_entry("emote",
|
||||
"Emoting is important because ...",
|
||||
category="Roleplaying", locks="view:all()"):
|
||||
|
||||
From inside the game those with the right permissions can use the
|
||||
``@sethelp`` command to add and modify help entries.
|
||||
|
||||
::
|
||||
|
||||
> @sethelp/add emote = The emote command is ...
|
||||
|
||||
Using ``@sethelp`` you can add, delete and append text to existing
|
||||
entries. By default new entries will go in the *General* help category.
|
||||
You can change this using a different form of the ``@sethelp`` command:
|
||||
|
||||
::
|
||||
|
||||
> @sethelp/add emote, Roleplaying = Emoting is important because ...
|
||||
|
||||
If the category *Roleplaying* did not already exist, it is created and
|
||||
will appear in the help index.
|
||||
|
||||
You can, finally, define a lock for the help entry by following the
|
||||
category with a `lock definition <Locks.html>`_:
|
||||
|
||||
::
|
||||
|
||||
> @sethelp/add emote, Roleplaying, view:all() = Emoting is important because ...
|
||||
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
How to \_get\_ Help
|
||||
===================
|
||||
|
||||
If you cannot find what you are looking for in the `online
|
||||
documentation <Index.html>`_, here's what to do:
|
||||
|
||||
- If you think the documentation is not clear enough, fill in our quick
|
||||
little `online
|
||||
form <https://docs.google.com/spreadsheet/viewform?hl=en_US&formkey=dGN0VlJXMWpCT3VHaHpscDEzY1RoZGc6MQ#gid=0>`_
|
||||
and say so (no login required). Maybe the docs need to be improved or
|
||||
a new tutorial added! Note that this form will help you by helping us
|
||||
improve documentation, but you cannot get direct, specific answers
|
||||
back from it.
|
||||
- If you have trouble with a missing feature or a problem you think is
|
||||
a bug, go to the `issue
|
||||
tracker <http://code.google.com/p/evennia/issues/list>`_. If you
|
||||
can't find the issue already reported, file a new one by filling in
|
||||
the handy form.
|
||||
- If you need help, want to start a discussion or get input on
|
||||
something, make a post in our `forum <http://evennia.com>`_. This is
|
||||
technically a 'mailing list', but you don't need to use e-mail; you
|
||||
can post and read all messages just as easily from your browser via
|
||||
the online interface.
|
||||
- If you want more direct discussions with developers and other users,
|
||||
consider dropping into our IRC chat channel
|
||||
`#evennia <http://webchat.freenode.net/?channels=evennia>`_ on the
|
||||
*Freenode* network. Please note however that you have to be patient
|
||||
if you don't get any response immediately; we are all in very
|
||||
different time zones and many have busy personal lives. So you might
|
||||
have to lurk about for a while - you'll get noticed eventually!
|
||||
|
||||
How to \_give\_ Help
|
||||
====================
|
||||
|
||||
Evennia is a completely un-funded project. It relies on the time donated
|
||||
by its users and developers in order to progress.
|
||||
|
||||
The first and easiest way you as a user can help us out is by taking
|
||||
part in `community
|
||||
discussions <http://groups.google.com/group/evennia/>`_ and by giving
|
||||
feedback on what is good or bad. Report bugs you find and features you
|
||||
lack to our `issue
|
||||
tracker <http://code.google.com/p/evennia/issues/list>`_. Just the
|
||||
simple act of letting developers know you are out there using their
|
||||
program is worth a lot.
|
||||
|
||||
If you'd like to help develop Evennia more hands-on, here are some ways
|
||||
to get going:
|
||||
|
||||
- Look through our `online documentation wiki <Index.html>`_ and see if
|
||||
you can help improve or expand the documentation (even small things
|
||||
like fixing typos!).
|
||||
- Send a message to our
|
||||
`forum <http://groups.google.com/group/evennia/>`_ and/or our `IRC
|
||||
chat <http://webchat.freenode.net/?channels=evennia>`_ asking about
|
||||
what needs doing, along with what your interests and skills are.
|
||||
- Take a look at our `issue
|
||||
tracker <http://code.google.com/p/evennia/issues/list>`_ and see if
|
||||
there's something you feel like taking on.
|
||||
- Check out the `Contributing <Contributing.html>`_ page on how to
|
||||
contribute with code in practice.
|
||||
|
||||
See `Contributing to Evennia <Contributing.html>`_ for more detailed
|
||||
information.
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
IMC2
|
||||
====
|
||||
|
||||
`IMC2 <http://en.wikipedia.org/wiki/InterMUD>`_, *InterMud
|
||||
Communications, protocol version 2*, is a protocol that allows
|
||||
individual mud games (Evennia-powered or not) to connect to a remote
|
||||
server for the purpose of IRC-like communication with other games. By
|
||||
connecting your MUD to IMC, you and your admins/players will be able to
|
||||
communicate with players on other muds connected to the network! Note
|
||||
that you can use IMC2 also if your Evennia install is only running
|
||||
locally on your computer -all you need is an internet connection.
|
||||
|
||||
Evennia's IMC implementation is special in that it integrates Evennia's
|
||||
normal channel system with IMC. The basic principle is that you
|
||||
"connect" an IMC channel to an existing Evennia channel. Users on the
|
||||
IMC network will then see what is said on the channel and vice versa.
|
||||
|
||||
Joining the IMC network
|
||||
-----------------------
|
||||
|
||||
To configure IMC, you first need to activate it by setting
|
||||
``IMC2_ENABLED=True`` in your settings file. This will make several new
|
||||
IMC-related commands available to a privileged user. Since the IMC
|
||||
network will need to know your mud's name, make sure you have also set
|
||||
``settings.SERVERNAME`` to the mud name you want.
|
||||
|
||||
Next you need to register your mud at a IMC2 network. We suggest the
|
||||
`MudBytes IMC2 network <http://www.mudbytes.net/intermud>`_. You can
|
||||
join for free
|
||||
`here <http://www.mudbytes.net/imc2-intermud-join-network>`_. On that
|
||||
page, follow the following steps:
|
||||
|
||||
#. From the drop-down list, select "Other unsupported IMC2 version",
|
||||
then click Submit
|
||||
#. You will get to a form. In "Short Mud name" you need to enter the
|
||||
name of your mud as defined by ``settings.SERVERNAME``.
|
||||
#. Give client- and server passwords to anything you want, but remember
|
||||
them.
|
||||
#. Give an admin e-mail. This shouldn't be too critical.
|
||||
#. Choose a server. It shouldn't really matter which you choose, as long
|
||||
as you remember what you picked (Evennia's development channel
|
||||
``ievennia`` is found on ``Server01``). Click "Join".
|
||||
|
||||
You have now created an account on the Mudbytes IMC network and are
|
||||
ready to go on.
|
||||
|
||||
Now fill in the rest of the IMC2 information in your settings file -
|
||||
give the network name and port, as well as the client- and server
|
||||
passwords you used when registering on mudbytes.
|
||||
|
||||
For testing, you can connect your mud client to the IMC mini-mud called
|
||||
*Talon* on ``talon.mudbytes.net:2000``. This works pretty much like an
|
||||
IRC client.
|
||||
|
||||
Creating an Evennia channel
|
||||
---------------------------
|
||||
|
||||
Evennia maps in-game channels to remote IMC channels. This means that
|
||||
you get all of the features of the local comm system for remote IMC
|
||||
channels as well (channel history, permissions-based channel entry,
|
||||
etc.)
|
||||
|
||||
Let's create a dedicated Evennia channel to host imc communications (you
|
||||
could also use an existing channel like ``ooc`` if you wanted):
|
||||
|
||||
::
|
||||
|
||||
@ccreate imc2 = This is connected to an IMC2 channel!
|
||||
|
||||
You should join the channel automatically.
|
||||
|
||||
Setting up a Channel \`<->\` IMC2 binding
|
||||
-----------------------------------------
|
||||
|
||||
Evennia developers have an open-access IMC channel called ``ievennia``
|
||||
on ``Server01`` of the Mudbytes network. For Evennia development we
|
||||
recommend you connect to this for sharing evennia anecdotes!
|
||||
|
||||
Activating IMC2 have made new commands available, the one you need is
|
||||
``@imc2chan``. You use this to create a permanent link between an IMC
|
||||
channel and an existing Evennia channel of your choice. You can use the
|
||||
``imcchanlist`` to see which IMC channels are available on the network.
|
||||
|
||||
Let's connect our new ``imc2`` channel to the ``ievennia`` channel
|
||||
on Server01.
|
||||
|
||||
::
|
||||
|
||||
@imc2chan imc2 = ievennia
|
||||
|
||||
To test, use the IMC mud *Talon*, make sure you "listen" to
|
||||
``ievennia``, then write something to the channel. You should see the
|
||||
text appear in Evennia's ``imc2`` channel and vice versa.
|
||||
|
||||
Administration and notes
|
||||
------------------------
|
||||
|
||||
You can view all your IMC-to-Evennia mappings using ``@imc2chan/list``.
|
||||
To permanently remove a connection, use ``@imc2chan`` with the
|
||||
``/delete`` switch like this:
|
||||
|
||||
::
|
||||
|
||||
@imc2chan/delete imc2 = ievennia
|
||||
|
||||
A single Evennia channel may *listen* to any number of remote IMC
|
||||
channels. Just use ``@imc2chan`` to add more connections. Your channel
|
||||
can however only ever *send* to one IMC2 channel and that is the first
|
||||
one you link to it (``ievennia`` in this example).
|
||||
|
||||
The ``@imclist`` command will list all other MUDs connected to the
|
||||
network (not including yourself).
|
||||
|
||||
Talk between IRC and IMC
|
||||
------------------------
|
||||
|
||||
This is easy - just bind IMC to the same evennia channel that IRC binds
|
||||
to. The process of binding IRC channels is described in more detail
|
||||
`here <IRC.html>`_.
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
IRC
|
||||
===
|
||||
|
||||
`IRC (Internet Relay
|
||||
Chat) <http://en.wikipedia.org/wiki/Internet_Relay_Chat>`_ is a long
|
||||
standing chat protocol used by many open-source projects for
|
||||
communicating in real time. By connecting one of Evennia's
|
||||
`Channels <Communications.html>`_ to an IRC channel you can communicate
|
||||
also with people not on an mud themselves. Note that you can use IRC
|
||||
also if you are only running your Evennia MUD locally on your computer
|
||||
(your game don't need to be open to the public)! All you need is an
|
||||
internet connection. For IRC operation you also need
|
||||
`twisted.words <http://twistedmatrix.com/trac/wiki/TwistedWords>`_. This
|
||||
is available simply as a package *python-twisted-words* in many Linux
|
||||
distros, or directly downloadable from the link.
|
||||
|
||||
Configuring IRC
|
||||
---------------
|
||||
|
||||
To configure IRC, you'll need to activate it in your settings file. You
|
||||
do this by copying the ``IRC_ENABLED`` flag from
|
||||
``evennia/src/config_defaults.py`` into your
|
||||
``evennia/game/settings.py`` and setting it to ``True``.
|
||||
|
||||
Start Evennia and log in as a privileged user. You should now have a new
|
||||
command availabele: ``@irc2chan``. This command is called like this:
|
||||
|
||||
::
|
||||
|
||||
@irc2chan[/switches] <evennia_channel> = <ircnetwork> <port> <#irchannel> <botname>
|
||||
|
||||
If you already know how IRC works, this should be pretty self-evident to
|
||||
use. Read the help entry for more features.
|
||||
|
||||
Setting up IRC, step by step
|
||||
----------------------------
|
||||
|
||||
You can connect IRC to any Evennia channel, but for testing, let's set
|
||||
up a new channel ``irc``.
|
||||
|
||||
::
|
||||
|
||||
@ccreate irc = This is connected to an irc channel!
|
||||
|
||||
You will automatically join the new channel.
|
||||
|
||||
Next we will create a connection to an external IRC network and channel.
|
||||
There are many, many IRC nets.
|
||||
`Here <http://www.irchelp.org/irchelp/networks/popular.html>`_ is a list
|
||||
of some of the biggest ones, the one you choose is not really very
|
||||
important unless you want to connect to a particular channel (also make
|
||||
sure that the network allows for "bots" to connect).
|
||||
|
||||
For testing, we choose the *Freenode* network, ``irc.freenode.net``. We
|
||||
will connect to a test channel, let's call it *#myevennia-test* (an IRC
|
||||
channel always begins with ``#``). It's best if you pick an obscure
|
||||
channel name that didn't exist previously - if it didn't exist it will
|
||||
be created for you. *Don't* connect to ``#evennia`` for testing and
|
||||
debugging, that is Evennia's official chat channel!
|
||||
|
||||
(By the way, you *are* welcome to connect your game to ``#evennia`` once
|
||||
you have everything working - it can be a good way to get help and
|
||||
ideas. But if you do, please do so with an in-game channel open only to
|
||||
your game admins and developers).
|
||||
|
||||
The *port* needed depends on the network. For Freenode this is ``6667``.
|
||||
|
||||
What will happen is that your Evennia server will connect to this IRC
|
||||
channel as a normal user. This "user" (or "bot") needs a name, which you
|
||||
must also supply. Let's call it "mud-bot".
|
||||
|
||||
To test that the bot connects correctly you also want to log onto this
|
||||
channel with a separate, third-party IRC client. There are hundreds of
|
||||
such clients available. If you use Firefox, the *Chatzilla* plugin is
|
||||
good and easy. There are also web-based clients like *Mibbit* that you
|
||||
can try. Once you have connected to a network, the command to join is
|
||||
usually ``/join channelname`` (don't forget the #).
|
||||
|
||||
Next we connect Evennia with the IRC channel.
|
||||
|
||||
::
|
||||
|
||||
@irc2chan irc = irc.freenode.net 6667 #myevennia-test mud-bot
|
||||
|
||||
Evennia will now create a new IRC bot ``mud-bot`` and connect it to the
|
||||
IRC network and the channel #myevennia. If you are connected to the IRC
|
||||
channel you will soon see the user *mud-bot* connect.
|
||||
|
||||
Write something in the Evennia channel *irc*.
|
||||
|
||||
::
|
||||
|
||||
irc Hello, World!
|
||||
[irc] Anna: Hello, World!
|
||||
|
||||
If you are viewing your IRC channel with a separate IRC client you
|
||||
should see your text appearing there, spoken by the bot:
|
||||
|
||||
::
|
||||
|
||||
mud-bot> [irc] Anna: Hello, World!
|
||||
|
||||
Write ``Hello!`` in your IRC client window and it will appear in your
|
||||
normal channel, marked with the name of the IRC channel you used
|
||||
(#evennia here).
|
||||
|
||||
::
|
||||
|
||||
|
||||
[irc] Anna@#myevennia-test: Hello!
|
||||
|
||||
Your Evennia gamers can now chat with users on external IRC channels!
|
||||
|
||||
Talking between IRC and IMC
|
||||
---------------------------
|
||||
|
||||
You can easily connect IRC and IMC by simply connecting them to the same
|
||||
Evennia channel. IMC connections are described `here <IMC2.html>`_.
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
Evennia Documentation
|
||||
=====================
|
||||
|
||||
This is Evennia's manual. You should hopefully find all you need to know
|
||||
about coding with, extending and using the codebase among these pages.
|
||||
If you have trouble with unclear documentation, fill in our quick
|
||||
`online form <http://tinyurl.com/c4tue23>`_ and tell us so - maybe more
|
||||
details or a new tutorial is needed!
|
||||
|
||||
The documentation is divided into several main categories. If you are
|
||||
new, it might be an idea to browse the sections in the order they are
|
||||
listed. If you get stuck or feel the urge to improve things, see the
|
||||
section on `how to get and give help <HowToGetAndGiveHelp.html>`_.
|
||||
|
||||
Sections
|
||||
--------
|
||||
|
||||
- A `Lengthier introduction <EvenniaIntroduction.html>`_ to what
|
||||
Evennia is.
|
||||
|
||||
- The `Getting Started <GettingStarted.html>`_ page helps with
|
||||
downloading, installing and setting up Evennia for the first time.
|
||||
- The `Administrative Documentation <AdminDocs.html>`_ covers the
|
||||
practicalities of running and maintaining an Evennia game once it is
|
||||
in place.
|
||||
- The `Builder Docs <BuilderDocs.html>`_ section contains instructions
|
||||
for starting to build a game world using Evennia.
|
||||
- The `Developer Central <DeveloperCentral.html>`_ covers
|
||||
implementation details and guides of interest for game-world coders
|
||||
as well as for current and prospective Evennia developers.
|
||||
- The `Tutorials & Examples <Tutorials.html>`_ section summarizes help
|
||||
pages on a step-by-step or tutorial format. Some of these are
|
||||
reachable from their respective sections as well.
|
||||
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
Internationalization
|
||||
====================
|
||||
|
||||
*Internationalization* (often abbreviated *i18n* since there are 18
|
||||
characters between the first "i" and the last "n" in that word) allows
|
||||
Evennia's core server to return texts in other languages than English -
|
||||
without anyone having to go in and add it manually. Take a look at the
|
||||
``locale`` directory of the Evennia installation, there you will find
|
||||
which languages are currently supported.
|
||||
|
||||
Note, what is translated in this way are hard-coded strings from the
|
||||
server, things like "Connection closed" or "Server restarted" - things
|
||||
that Players will see and which game devs are not supposed to change on
|
||||
their own. So stuff seen in the log file or on stdout will not be
|
||||
translated. It also means that the default command set is *not*
|
||||
translated. The reason for this is that commands are *intended* to be
|
||||
modified by users. Adding *i18n* code to commands tend to add complexity
|
||||
to code that will be changed anyway. One of the goals of Evennia is to
|
||||
keep the user-changeable code as clean and easy-to-read as possible.
|
||||
|
||||
Changing server language
|
||||
------------------------
|
||||
|
||||
Change language by adding the following to your ``game/settings.py``
|
||||
file:
|
||||
|
||||
::
|
||||
|
||||
USE_I18N = True
|
||||
LANGUAGE_CODE = 'en'
|
||||
|
||||
Here ``'en'`` should be changed to the abbreviation for one of the
|
||||
supported languages found in ``locale/``. Restart the server to activate
|
||||
i18n.
|
||||
|
||||
Translating Evennia
|
||||
-------------------
|
||||
|
||||
If you cannot find your language in ``locale/`` it's because noone has
|
||||
translated it yet. Alternatively you might have the language but find
|
||||
the translation bad ... You are welcome to help improve the situation!
|
||||
|
||||
To start a new translation, place yourself in Evennia's root directory
|
||||
and run
|
||||
|
||||
::
|
||||
|
||||
django-admin makemessages -l <language-code>
|
||||
|
||||
where ``<language-code>`` is the two-letter locale code for the language
|
||||
you want, like 'sv' for Swedish or 'es' for Spanish.
|
||||
|
||||
Next head to ``locale/<language-code>/LC_MESSAGES`` and edit the
|
||||
``*.po`` file you find there. There is no need to edit this file using a
|
||||
normal text editor -- best is to use a po-file editor from the web
|
||||
(search the web for "po editor" for many free alternatives).
|
||||
|
||||
The concept of translating is simple, it's just a matter of taking the
|
||||
english strings you find in the ``*.po`` file and add your language's
|
||||
translation best you can. The ``*.po`` format (and many supporting
|
||||
editors) allow you to mark translations as "fuzzy". This tells the
|
||||
system (and future translators) that you are unsure about the
|
||||
translation, or that you couldn't find a translation that exactly
|
||||
matched the intention of the original text.
|
||||
|
||||
Finally, you need to compile your translation into a more efficient
|
||||
form.
|
||||
|
||||
::
|
||||
|
||||
django-admin compilemessages
|
||||
|
||||
This will go through all languages and create/update compiled files
|
||||
(``*.mo``) for them. This needs to be done whenever a ``*.po`` file is
|
||||
updated.
|
||||
|
||||
When you are done, send the ``*.po`` and ``*.mo`` file to the Evennia
|
||||
developer list (or push it into your own repository clone) so we can
|
||||
integrate your translation into Evennia!
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
Evennia License FAQ
|
||||
===================
|
||||
|
||||
Evennia is licensed under the very friendly
|
||||
`BSD <http://en.wikipedia.org/wiki/BSD_license>`_ (3-clause) license.
|
||||
You can find the license as ``LICENSE.txt`` in the Evennia root
|
||||
directory. You can also read the full license file
|
||||
`here <http://code.google.com/p/evennia/source/browse/LICENSE.txt>`_
|
||||
(it's not long).
|
||||
|
||||
Q: When creating a game using Evennia, what does the license permit me to do with it?
|
||||
-------------------------------------------------------------------------------------
|
||||
|
||||
**A:** It's your own game world to do with as you please! Keep it to
|
||||
yourself or re-distribute it under any license of your choice - or sell
|
||||
it and become filthy rich for all we care.
|
||||
|
||||
Q: I have modified Evennia itself, what does the license say about that?
|
||||
------------------------------------------------------------------------
|
||||
|
||||
**A:** The BSD license allows you to do whatever you want with your
|
||||
modified Evennia, including re-distributing or selling it, as long as
|
||||
you include our license and copyright info in the ``LICENSE.txt`` file
|
||||
along with your distribution.
|
||||
|
||||
... Of course, if you make bug fixes or add some new snazzy feature we
|
||||
*softly nudge* you to make those changes available so they can be added
|
||||
to the core Evennia package for everyone's benefit. The license don't
|
||||
require you to do it, but that doesn't mean we can't still greatly
|
||||
appreciate it if you do!
|
||||
|
||||
Q: Can I re-distribute the Evennia server package along with my custom game implementation?
|
||||
-------------------------------------------------------------------------------------------
|
||||
|
||||
**A:** Sure. As long as the text in LICENSE.txt is included.
|
||||
|
||||
Q: What about Contributions?
|
||||
----------------------------
|
||||
|
||||
The contributions in ``evennia/contrib`` are considered to be released
|
||||
under the same license as Evennia itself unless the individual
|
||||
contributor has specifically defined otherwise.
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
External links and resources
|
||||
============================
|
||||
|
||||
This is a list of resources that may be useful for Evennia users and
|
||||
developers.
|
||||
|
||||
Evennia links
|
||||
-------------
|
||||
|
||||
- `Evennia's main portal page <http://www.evennia.com>`_ (RSS feeds to
|
||||
updates)
|
||||
- `Evennia forums <http://www.evennia.com/discussions>`_
|
||||
- `Evennia Commit
|
||||
Log <http://groups.google.com/group/evennia-commits>`_ (hg commit
|
||||
notification)
|
||||
- `Sequential source update
|
||||
list <http://code.google.com/p/evennia/source/list>`_ (easily see
|
||||
what was changed in each update)
|
||||
- `Evennia mailing list <http://groups.google.com/group/evennia>`_
|
||||
(better web interface can be found from the portal)
|
||||
- `Evennia development blog <http://evennia.blogspot.se/>`_
|
||||
|
||||
Third-party Evennia links
|
||||
-------------------------
|
||||
|
||||
- `Evennia's manual on
|
||||
ReadTheDocs <http://readthedocs.org/projects/evennia/>`_ (also
|
||||
convert to PDF)
|
||||
- `Hosting Evennia on
|
||||
Webfaction <http://lotek.heavy.ch/evennia#Hosting>`_
|
||||
- `Evennia on Ohloh <http://www.ohloh.net/projects/6906>`_
|
||||
- `Evennia on OpenHatch <http://openhatch.org/+projects/Evennia>`_
|
||||
- `Evennia on
|
||||
PyPi <http://pypi.python.org/pypi/Evennia%20MUD%20Server/Alpha>`_
|
||||
|
||||
- `Avaloria <http://code.google.com/p/avaloria/>`_ (MUD under
|
||||
development, using Evennia)
|
||||
- `Winter's Oasis <http://blog.wintersoasis.com/>`_ (MUCK under
|
||||
development, using Evennia)
|
||||
- `Latitude <https://github.com/dbenoy/latitude>`_ (MUCK under
|
||||
development, using Evennia)
|
||||
|
||||
- `notimetoplay
|
||||
post <http://notimetoplay.org/2013/08/29/evennia-a-mud-building-toolkit/>`_
|
||||
about Evennia
|
||||
|
||||
General mud/game development ideas and discussions
|
||||
--------------------------------------------------
|
||||
|
||||
- `MudLab <http://mudlab.org/>`_ mud design discussion forum
|
||||
- `MudConnector <http://www.mudconnect.com/>`_ mud listing and forums
|
||||
- `MudBytes <http://www.mudbytes.net/>`_ mud listing and forums
|
||||
- `Top Mud Sites <http://www.topmudsites.com/>`_ mud listing and forums
|
||||
- `MudStandards wiki <http://www.mudstandards.org/MudStandards_Wiki>`_
|
||||
is an attempt at gathering protocol standards for MUD servers.
|
||||
|
||||
- `Planet Mud-Dev <http://planet-muddev.disinterest.org/>`_ is a blog
|
||||
aggregator following blogs of current MUD development (including
|
||||
Evennia) around the 'net.
|
||||
- Mud Dev mailing list archive
|
||||
(`mirror1 <http://nilgiri.net/MUD-Dev-archive/>`_),(\ `mirror2 <http://www.disinterest.org/resource/MUD-Dev/>`_)
|
||||
- Influential mailing list active 1996-2004. Advanced game design
|
||||
discussions.
|
||||
- `Imaginary
|
||||
Realities <http://disinterest.org/resource/imaginary-realities/>`_ is
|
||||
an e-magazine on game/MUD design which were active 1998-2001.
|
||||
Interesting articles.
|
||||
- `Mud-dev wiki <http://mud-dev.wikidot.com/>`_ is a slowly growing
|
||||
resource on MUD creation
|
||||
- `Nick Gammon's hints
|
||||
thread <http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=5959>`_
|
||||
holds a very useful list of things to think about when starting your
|
||||
new MUD.
|
||||
|
||||
- `Lost Garden <http://www.lostgarden.com/>`_ is a game development
|
||||
blog with long and interesting articles (not MUD-specific)
|
||||
- `What Games Are <http://whatgamesare.com/>`_ is a blog about general
|
||||
game design (not MUD-specific)
|
||||
- `The Alexandrian <http://thealexandrian.net/>`_ is a blog about
|
||||
tabletop roleplaying and board games, but with lots of general
|
||||
discussion about rule systems and game balance that could be
|
||||
applicable also for MUDs.
|
||||
|
||||
Litterature
|
||||
-----------
|
||||
|
||||
- Richard Bartle *Designing Virtual Worlds* (`amazon
|
||||
page <http://www.amazon.com/Designing-Virtual-Worlds-Richard-Bartle/dp/0131018167>`_)
|
||||
- Essential reading for the design of any persistent game world,
|
||||
written by the co-creator of the original game *MUD*. Discusses
|
||||
basically everything you need to think about and more.
|
||||
- Richard Cantillon *An Essay on Economic Theory* (`free
|
||||
pdf <http://mises.org/books/essay_on_economic_theory_cantillon.pdf>`_)
|
||||
- A very good English translation of *Essai sur la Nature du Commerce
|
||||
en Général*, one of the foundations of modern economic theory.
|
||||
Written in 1730 but the translation is annotated and is very easy to
|
||||
follow for a modern reader. Required reading if you think of
|
||||
implementing a sane game economic system.
|
||||
- David M. Beazley *Python Essential Reference (4th ed)* (`amazon
|
||||
page <http://www.amazon.com/Python-Essential-Reference-David-Beazley/dp/0672329786/>`_)
|
||||
- Our recommended book on Python; it not only efficiently summarizes
|
||||
the language but is also an excellent reference to the standard
|
||||
library for more experienced Python coders.
|
||||
|
||||
Frameworks
|
||||
----------
|
||||
|
||||
- `Django's homepage <http://www.djangoproject.com/>`_
|
||||
|
||||
- `Documentation <http://docs.djangoproject.com/en>`_
|
||||
- `Code <http://code.djangoproject.com/>`_
|
||||
|
||||
- `Twisted homepage <http://twistedmatrix.com/>`_
|
||||
|
||||
- `Documentation <http://twistedmatrix.com/documents/current/core/howto/index.html>`_
|
||||
- `Code <http://twistedmatrix.com/trac/browser>`_
|
||||
|
||||
Tools
|
||||
-----
|
||||
|
||||
- `Mercurial tutorial <http://mercurial.selenic.com/wiki/Tutorial>`_
|
||||
- `Another, pretty funny, Mercurial tutorial <http://hginit.com/>`_
|
||||
- `Sphinx manual <http://sphinx.pocoo.org/contents.html>`_ (compile
|
||||
documentation)
|
||||
- `Doxygen <http://www.stack.nl/~dimitri/doxygen/>`_ (build auto-docs)
|
||||
|
||||
Python Info
|
||||
-----------
|
||||
|
||||
- `Python Website <http://www.python.org/>`_
|
||||
|
||||
- `Documentation <http://www.python.org/doc/>`_
|
||||
- `Tutorial <http://docs.python.org/tut/tut.html>`_
|
||||
- `Library Reference <http://docs.python.org/lib/lib.html>`_
|
||||
- `Language Reference <http://docs.python.org/ref/ref.html>`_
|
||||
|
||||
|
||||
|
|
@ -1,498 +0,0 @@
|
|||
Locks
|
||||
=====
|
||||
|
||||
For most games it is a good idea to restrict what people can do. In
|
||||
Evennia such restrictions are applied and checked by something called
|
||||
*locks*. All Evennia entities (`commands <Commands.html>`_,
|
||||
`objects <Objects.html>`_, `scripts <Scripts.html>`_,
|
||||
`players <Players.html>`_, `help system <HelpSystem.html>`_,
|
||||
[Communications#Msg messages] and [Communications#Channels channels])
|
||||
are accessed through locks.
|
||||
|
||||
A lock can be thought of as an "access rule" restricting a particular
|
||||
use of an Evennia entity. Whenever another entity wants that kind of
|
||||
access the lock will analyze that entity in different ways to determine
|
||||
if access should be granted or not. Evennia implements a "lockdown"
|
||||
philosophy - all entities are inaccessible unless you explicitly define
|
||||
a lock that allows some or full access.
|
||||
|
||||
Let's take an example: An object has a lock on itself that restricts how
|
||||
people may "delete" that object. Apart from knowing that it restricts
|
||||
deletion, the lock also knows that only players with the specific ID of,
|
||||
say, '34' are allowed to delete it. So whenever a player tries to run
|
||||
@delete on the object, the @delete command makes sure to check if this
|
||||
player is really allowed to do so. It calls the lock, which in turn
|
||||
checks if the player's id is 34. Only then will it allow @delete to go
|
||||
on with its job.
|
||||
|
||||
Setting and checking a lock
|
||||
---------------------------
|
||||
|
||||
The in-game command for setting locks on objects is ``@lock``:
|
||||
|
||||
::
|
||||
|
||||
> @lock obj = <lockstring>
|
||||
|
||||
The ``<lockstring>`` is a string on a certain form that defines the
|
||||
behaviour of the lock. We will go into more detail on how
|
||||
``<lockstring>`` should look in the next section.
|
||||
|
||||
Code-wise, Evennia handles locks through what is usually called
|
||||
``locks`` on all relevant entities. This is a handler that allows you to
|
||||
add, delete and check locks.
|
||||
|
||||
::
|
||||
|
||||
myobj.locks.add(<lockstring>)
|
||||
|
||||
One can call ``locks.check()`` to perform a lock check, but to hide the
|
||||
underlying implementation all objects also have a convenience function
|
||||
called ``access``. This should preferably be used. In the example below,
|
||||
``accessing_obj`` is the object requesting the 'delete' access whereas
|
||||
``obj`` is the object that might get deleted. This is how it would (and
|
||||
do) look from inside the ``@delete`` command:
|
||||
|
||||
::
|
||||
|
||||
if not obj.access(accessing_obj, 'delete'):
|
||||
accessing_obj.msg("Sorry, you may not delete that.")
|
||||
return
|
||||
|
||||
Defining locks
|
||||
--------------
|
||||
|
||||
Defining a lock (i.e. an access restriction) in Evennia is done by
|
||||
adding simple strings of lock definitions to the object's ``locks``
|
||||
property using ``obj.locks.add()``.
|
||||
|
||||
Here are some examples of lock strings (not including the quotes):
|
||||
|
||||
::
|
||||
|
||||
delete:id(34) # only allow obj #34 to delete
|
||||
edit:all() # let everyone edit
|
||||
get: not attr(very_weak) or perm(Wizard) # only those who are not "very_weak" or are Wizards may pick this up
|
||||
|
||||
Formally, a lockstring has the following syntax:
|
||||
|
||||
::
|
||||
|
||||
access_type:[not] func1([arg1,..])[[and|or][ not] func2([arg1,...])[...]]
|
||||
|
||||
where ``[..]`` marks optional parts. AND, OR and NOT are not case
|
||||
sensitive and excess spaces are ignored. ``func1, func2`` etc are
|
||||
special *lock functions* available to the lock system.
|
||||
|
||||
So, a lockstring consists of the type of restriction (the
|
||||
``access_type``), a colon (``:``) and then a list of function calls that
|
||||
determine what is needed to pass the lock. Each function returns either
|
||||
``True`` or ``False``. AND, OR and NOT work as they do normally in
|
||||
Python. If the total result is True, the lock is passed.
|
||||
|
||||
You can create several lock types one after the other by separating them
|
||||
with a semicolon (``;``) in the lockstring. The string below is
|
||||
identical to the first two rows of the previous example:
|
||||
|
||||
::
|
||||
|
||||
delete:id(34);edit:all()
|
||||
|
||||
Valid access\_types
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
An ``access_type``, the first part of a lockstring, defines what kind of
|
||||
capability a lock controls, such as "delete" or "edit". You may in
|
||||
principle name your ``access_type`` anything as long as it is unique for
|
||||
the particular object. Access\_types are not case-sensitive.
|
||||
|
||||
If you want to make sure the lock is used however, you should pick
|
||||
``access_type`` names that you (or the default command set) actually
|
||||
tries, as in the example of ``@delete`` above that uses the 'delete'
|
||||
``access_type``.
|
||||
|
||||
Below are the access\_types checked by the default commandset.
|
||||
|
||||
- `Commands <Commands.html>`_: ``cmd`` - this defines who may call this
|
||||
command at all.
|
||||
- `Objects <Objects.html>`_:
|
||||
|
||||
- ``control`` - who is the "owner" of the object. Can set locks,
|
||||
delete it etc. Defaults to the creator of the object.
|
||||
- ``call`` - who may call object-commands on this object.
|
||||
- ``examine`` - who may examine this object's properties.
|
||||
- ``delete`` - who may delete the object.
|
||||
- ``edit`` - who may edit properties and attributes of the object.
|
||||
- ``get``- who may pick up the object and carry it around.
|
||||
- ``puppet`` - who may "become" this object and control it as their
|
||||
"character".
|
||||
- ``attrcreate`` - who may create new attributes on the object
|
||||
(default True)
|
||||
|
||||
- [Objects#Characters Characters]: ``<Same as Objects>``
|
||||
- [Objects#Exits Exits]: ``<Same as Objects>`` + ``traverse`` - who may
|
||||
pass the exit.
|
||||
- `Players <Players.html>`_:
|
||||
|
||||
- ``examine`` - who may examine the player's properties.
|
||||
- ``delete`` - who may delete the player.
|
||||
- ``edit`` - who may edit the player's attributes and properties.
|
||||
- ``msg`` - who may send messages to the player.
|
||||
- ``boot`` - who may boot the player.
|
||||
|
||||
- `Attributes <Attributes.html>`_: (*only checked by
|
||||
``obj.secure_attr``*)
|
||||
|
||||
- ``attrread`` - see/access attribute
|
||||
- ``attredit`` - change/delete attribute
|
||||
|
||||
- [Communications#Channels Channels]:
|
||||
|
||||
- ``control`` - who is administrating the channel. This means the
|
||||
ability to delete the channel, boot listeners etc.
|
||||
- ``send`` - who may send to the channel.
|
||||
- ``listen`` - who may subscribe and listen to the channel.
|
||||
|
||||
- `HelpEntry <HelpSystem.html>`_:
|
||||
|
||||
- ``examine`` - who may view this help entry (usually everyone)
|
||||
- ``edit`` - who may edit this help entry.
|
||||
|
||||
So to take an example, whenever an exit is to be traversed, a lock of
|
||||
the type *traverse* will be checked. Defining a suitable lock type for
|
||||
an exit object would thus involve a lockstring
|
||||
``traverse: <lock functions>``.
|
||||
|
||||
Lock functions
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
You are not allowed to use just any function in your lock definition;
|
||||
you are infact only allowed to use those functions defined in one of the
|
||||
modules given in ``settings.LOCK_FUNC_MODULES``. All functions in any of
|
||||
those modules will automatically be considered a valid lock function.
|
||||
The default ones are found in ``src/locks/lockfuncs.py`` or via
|
||||
``ev.lockfuncs``.
|
||||
|
||||
A lock function must always accept at least two arguments - the
|
||||
*accessing object* (this is the object wanting to get access) and the
|
||||
*accessed object* (this is the object with the lock). Those two are fed
|
||||
automatically as the first two arguments the function when the lock is
|
||||
checked. Any arguments explicitly given in the lock definition will
|
||||
appear as extra arguments.
|
||||
|
||||
::
|
||||
|
||||
# A simple example lock function. Called with e.g. id(34)
|
||||
|
||||
def id(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
if args:
|
||||
wanted_id = args[0]
|
||||
return accessing_obj.id == wanted_id
|
||||
return False
|
||||
|
||||
(Using the ``*`` and ``**`` syntax causes Python to magically put all
|
||||
extra arguments into a list ``args`` and all keyword arguments into a
|
||||
dictionary ``kwargs`` respectively. If you are unfamiliar with how
|
||||
``*args`` and ``**kwargs`` work, see the Python manuals).
|
||||
|
||||
Some useful default lockfuncs (see ``src/locks/lockfuncs.py`` for more):
|
||||
|
||||
- ``true()/all()`` - give access to everyone
|
||||
- ``false()/none()/superuser()`` - give access to noone. Superusers
|
||||
bypass the check entirely.
|
||||
- ``perm(perm)`` - this tries to match a given ``permission`` property,
|
||||
on a Player firsthand, on a Character second. See [Locks#Permissions
|
||||
below].
|
||||
- ``perm_above(perm)`` - like ``perm`` but requires a "higher"
|
||||
permission level than the one given.
|
||||
- ``id(num)/dbref(num)`` - checks so the access\_object has a certain
|
||||
dbref/id.
|
||||
- ``attr(attrname)`` - checks if a certain
|
||||
`Attribute <Attributes.html>`_ exists on accessing\_object.
|
||||
- ``attr(attrname, value)`` - checks so an attribute exists on
|
||||
accessing\_object *and* has the given value.
|
||||
- ``attr_gt(attrname, value)`` - checks so accessing\_object has a
|
||||
value larger (``>``) than the given value.
|
||||
- ``attr_ge, attr_lt, attr_le, attr_ne`` - corresponding for ``>=``,
|
||||
``<``, ``<=`` and ``!=``.
|
||||
- ``holds(objid)`` - checks so the accessing objects contains an object
|
||||
of given name or dbref.
|
||||
- ``pperm(perm)``, ``pid(num)/pdbref(num)`` - same as ``perm``,
|
||||
``id/dbref`` but always looks for permissions and dbrefs of
|
||||
*Players*, not on Characters.
|
||||
|
||||
Checking simple strings
|
||||
-----------------------
|
||||
|
||||
Sometimes you don't really need to look up a certain lock, you just want
|
||||
to check a lockstring. A common use is inside Commands, in order to
|
||||
check if a user has a certain permission. The lockhandler has a method
|
||||
``check_lockstring(accessing_obj, lockstring, bypass_superuser=False)``
|
||||
that allows this.
|
||||
|
||||
::
|
||||
|
||||
# inside command definition
|
||||
if not self.caller.locks.check_lockstring(self.caller, "dummy:perm(Wizards)"):
|
||||
self.caller.msg("You must be Wizard or higher to do this!"
|
||||
return
|
||||
|
||||
Note here that the ``access_type`` can be left to a dummy value since
|
||||
this method does not actually do a Lock lookup.
|
||||
|
||||
Default locks
|
||||
-------------
|
||||
|
||||
Evennia sets up a few basic locks on all new objects and players (if we
|
||||
didn't, noone would have any access to anything from the start). This is
|
||||
all defined in the root `Typeclasses <Typeclasses.html>`_ of the
|
||||
respective entity, in the hook method ``basetype_setup()`` (which you
|
||||
usually don't want to edit unless you want to change how basic stuff
|
||||
like rooms and exits store their internal variables). This is called
|
||||
once, before ``at_object_creation``, so just put them in the latter
|
||||
method on your child object to change the default. Also creation
|
||||
commands like ``@create`` changes the locks of objects you create - for
|
||||
example it sets the ``control`` lock\_type so as to allow you, its
|
||||
creator, to control and delete the object.
|
||||
|
||||
Permissions
|
||||
===========
|
||||
|
||||
A *permission* is simply a list of text strings stored on the property
|
||||
``permissions`` on ``Objects`` and ``Players``. Permissions can be used
|
||||
as a convenient way to structure access levels and hierarchies. It is
|
||||
set by the ``@perm`` command.
|
||||
|
||||
::
|
||||
|
||||
@perm *Tommy = Builders
|
||||
|
||||
Note the use of the asterisk ``*`` above. For the ``@perm`` command it
|
||||
means assigning to the `Player <Players.html>`_ Tommy instead of any
|
||||
`Character <Objects.html>`_ that also happens to be named Tommy. Putting
|
||||
permissions on the Player guarantees that they are kept regardless of
|
||||
which Character they are currently puppeting.
|
||||
|
||||
All new players are given a default set of permissions defined by
|
||||
``settings.PERMISSION_PLAYER_DEFAULT``.
|
||||
|
||||
Selected permission strings can be organized in a *permission hierarchy*
|
||||
by editing the tuple ``settings.PERMISSION_HIERARCHY``. Evennia's
|
||||
default permission hierarchy is as follows:
|
||||
|
||||
::
|
||||
|
||||
Immortals
|
||||
Wizards
|
||||
Builders
|
||||
PlayerHelpers
|
||||
Players # this is what all new Players start with by default
|
||||
|
||||
The main use of this is that if you use the lock function ``perm()``
|
||||
mentioned above, a lock check for a particular permission in the
|
||||
hierarchy will *also* grant access to those with *higher* hierarchy
|
||||
access. So if you have the permission "Wizards" you will also pass a
|
||||
lock defined as ``perm(Builders)`` or any of those levels below
|
||||
"Wizards". When doing an access check from an `Object <Objects.html>`_
|
||||
or Character, the ``perm()`` lock function will always first use the
|
||||
permissions of any Player connected to that Object before checking for
|
||||
permissions on the Object. In the case of hierarchical permissions
|
||||
(Wizards, Builders etc), the Player permission will always be used (this
|
||||
stops a Player from escalating their permission by puppeting a
|
||||
high-level Character). If the permission looked for is not in the
|
||||
hierarchy, an exact match is required, first on the Player and if not
|
||||
found there (or if no Player is connected), then on the Object itself.
|
||||
|
||||
Below is an example of an object without any connected player
|
||||
|
||||
::
|
||||
|
||||
obj1.permissions = ["Builders", "cool_guy"]
|
||||
obj2.locks.add("enter:perm_above(Players) and perm(cool_guy)")
|
||||
|
||||
obj2.access(obj1, "enter") # this returns True!
|
||||
|
||||
And one example of a puppet with a connected player:
|
||||
|
||||
::
|
||||
|
||||
player.permissions = ["Players"]
|
||||
puppet.permissions = ["Builders", "cool_guy"]
|
||||
obj2.locks.add("enter:perm_above(Players) and perm(cool_guy)")
|
||||
|
||||
obj2.access(puppet, "enter") # this returns False!
|
||||
|
||||
Superusers
|
||||
----------
|
||||
|
||||
There is normally only one *superuser* account and that is the one first
|
||||
created when starting Evennia (User #1). This is sometimes known as the
|
||||
"Owner" or "God" user. A superuser has more than full access - it
|
||||
completely *bypasses* all locks so no checks are even run. This allows
|
||||
for the superuser to always have access to everything in an emergency.
|
||||
But it also hides any eventual errors you might have made in your lock
|
||||
definitions. So when trying out game systems you should use a secondary
|
||||
character rather than #1 so your locks get tested correctly.
|
||||
|
||||
Quelling
|
||||
--------
|
||||
|
||||
The ``@quell`` command can be used to enforce the ``perm()`` lockfunc to
|
||||
ignore permissions on the Player and instead use the permissions on the
|
||||
Character only. This can be used e.g. by staff to test out things with a
|
||||
lower permission level. Return to the normal operation with
|
||||
``@unquell``. Note that quelling will use the smallest of any
|
||||
hierarchical permission on the Player or Character, so one cannot
|
||||
escalate one's Player permission by quelling to a high-permission
|
||||
Character. Also, the superuser cannot be quelled.
|
||||
|
||||
More Lock definition examples
|
||||
=============================
|
||||
|
||||
::
|
||||
|
||||
examine: attr(eyesight, excellent) or perm(Builders)
|
||||
|
||||
You are only allowed to do *examine* on this object if you have
|
||||
'excellent' eyesight or is a Builder.
|
||||
|
||||
::
|
||||
|
||||
# lock for the tell command
|
||||
cmd: not perm(no_tell)
|
||||
|
||||
Locks can be used to implement highly specific bans. This will allow
|
||||
everyone *not* having the "permission" ``no_tell`` to use the ``tell``
|
||||
command. Just give a player the "permission" ``no_tell`` to disable
|
||||
their use of this particular command henceforth.
|
||||
|
||||
::
|
||||
|
||||
open: holds('the green key') or perm(Builder)
|
||||
|
||||
This could be called by the ``open`` command on a "door" object. The
|
||||
check is passed if you are a Builder or has the right key in your
|
||||
inventory.
|
||||
|
||||
::
|
||||
|
||||
# this limits what commands are visible to the user
|
||||
cmd: perm(Builders)
|
||||
|
||||
Evennia's command handler looks for a lock of type ``cmd`` to determine
|
||||
if a user is allowed to even call upon a particular command or not. When
|
||||
you define a command, this is the kind of lock you must set. See the
|
||||
default command set for lots of examples.
|
||||
|
||||
::
|
||||
|
||||
dbref = caller.id
|
||||
lockstring = "control:id(%s);examine:perm(Builders);delete:id(%s) or perm(Wizards);get:all()" % (dbref, dbref)
|
||||
new_obj.locks.add(lockstring)
|
||||
|
||||
This is how the ``@create`` command sets up new objects. In sequence,
|
||||
this permission string sets the owner of this object be the creator (the
|
||||
one running ``@create``). Builders may examine the object whereas only
|
||||
Wizards and the creator may delete it. Everyone can pick it up.
|
||||
|
||||
A complete example of setting locks on an object
|
||||
================================================
|
||||
|
||||
Assume we have two objects - one is ourselves (not superuser) and the
|
||||
other is an `Object <Objects.html>`_ called ``box``.
|
||||
|
||||
::
|
||||
|
||||
> @create/drop box
|
||||
> @desc box = "This is a very big and heavy box."
|
||||
|
||||
We want to limit which objects can pick up this heavy box. Let's say
|
||||
that to do that we require the would-be lifter to to have an attribute
|
||||
*strength* on themselves, with a value greater than 50. We assign it to
|
||||
ourselves to begin with.
|
||||
|
||||
::
|
||||
|
||||
> @set self/strength = 45
|
||||
|
||||
Ok, so for testing we made ourselves strong, but not strong enough. Now
|
||||
we need to look at what happens when someone tries to pick up the the
|
||||
box - they use the ``get`` command (in the default set). This is defined
|
||||
in ``game/gamesrc/commands/default/general.py``. In its code we find
|
||||
this snippet:
|
||||
|
||||
::
|
||||
|
||||
if not obj.access(caller, 'get'):
|
||||
if obj.db.get_err_msg:
|
||||
caller.msg(obj.db.get_err_msg)
|
||||
else:
|
||||
caller.msg("You can't get that.")
|
||||
return
|
||||
|
||||
So the ``get`` command looks for a lock with the type *get* (not so
|
||||
surprising). It also looks for an `Attribute <Attributes.html>`_ on the
|
||||
checked object called *get\_err\_msg* in order to return a customized
|
||||
error message. Sounds good! Let's start by setting that on the box:
|
||||
|
||||
::
|
||||
|
||||
> @set box/get_err_msg = You are not strong enough to lift this box.
|
||||
|
||||
Next we need to craft a Lock of type *get* on our box. We want it to
|
||||
only be passed if the accessing object has the attribute *strength* of
|
||||
the right value. For this we would need to create a lock function that
|
||||
checks if attributes have a value greater than a given value. Luckily
|
||||
there is already such a one included in evennia (see
|
||||
``src/permissions/lockfuncs.py``), called ``attr_gt``.
|
||||
|
||||
So the lock string will look like this: ``get:attr_gt(strength, 50)``.
|
||||
We put this on the box now:
|
||||
|
||||
::
|
||||
|
||||
@lock box = get:attr_gt(strength, 50)
|
||||
|
||||
Try to ``get`` the object and you should get the message that we are not
|
||||
strong enough. Increase your strength above 50 however and you'll pick
|
||||
it up no problem. Done! A very heavy box!
|
||||
|
||||
If you wanted to set this up in python code, it would look something
|
||||
like this:
|
||||
|
||||
::
|
||||
|
||||
from ev import create_object
|
||||
|
||||
box = create_object(None, key="box")
|
||||
box.locks.add("get:attr_gt(strength, 50)")
|
||||
|
||||
# or we can assign locks right away
|
||||
box = create_object(None, key="box", locks="get:attr_gt(strength, 50)")
|
||||
|
||||
# set the attributes
|
||||
box.db.desc = "This is a very big and heavy box."
|
||||
box.db.get_err_msg = "You are not strong enough to lift this box."
|
||||
|
||||
# one heavy box, ready to withstand all but the strongest...
|
||||
|
||||
On Django's permission system
|
||||
=============================
|
||||
|
||||
Django also implements a comprehensive permission/security system of its
|
||||
own. The reason we don't use that is because it is app-centric (app in
|
||||
the Django sense). Its permission strings are of the form
|
||||
``appname.permstring`` and it automatically adds three of them for each
|
||||
database model in the app - for the app src/object this would be for
|
||||
example 'object.create', 'object.admin' and 'object.edit'. This makes a
|
||||
lot of sense for a web application, not so much for a MUD, especially
|
||||
when we try to hide away as much of the underlying architecture as
|
||||
possible.
|
||||
|
||||
The django permissions are not completely gone however. We use it for
|
||||
logging in users (the ``User`` object tied to `Players <Players.html>`_
|
||||
is a part of Djangos's auth system). It is also used exclusively for
|
||||
managing Evennia's web-based admin site, which is a graphical front-end
|
||||
for the database of Evennia. You edit and assign such permissions
|
||||
directly from the web interface. It's stand-alone from the permissions
|
||||
described above.
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
Nicks
|
||||
=====
|
||||
|
||||
*Nicks*, short for *Nicknames* is a system allowing an object (usually a
|
||||
`Player Character <Players.html>`_) to assign custom replacement names
|
||||
for other game entities.
|
||||
|
||||
Nicks are not to be confused with *Aliases*. Setting an Alias on a game
|
||||
entity actually changes an inherent attribute on that entity, and
|
||||
everyone in the game will be able to use that alias to address the
|
||||
entity thereafter. A *Nick* on the other hand, is an alternate name *you
|
||||
alone* can use to refer to that entity. The nicknamed entitity is not
|
||||
changed in any way. The principle is very simple - Evennia simply scans
|
||||
your input looking for a defined nick and replaces it with the full,
|
||||
"real" name before passing it on. In the default system nicks are
|
||||
controlled with the simple ``nick`` command, but the system can be
|
||||
expanded for other uses too.
|
||||
|
||||
Default Evennia use Nicks in three flavours that determine when Evennia
|
||||
actually tries to do the substitution.
|
||||
|
||||
- inputline - replacement is attempted whenever you write anything on
|
||||
the command line. This is the default.
|
||||
- objects - replacement is only attempted when referring an object
|
||||
- players - replacement is only attempted when referring a player
|
||||
|
||||
Here's how to use it in the default command set (using the ``nick``
|
||||
command):
|
||||
|
||||
::
|
||||
|
||||
nick ls = look
|
||||
|
||||
This is a good one for unix/linux users who are accustomed to using the
|
||||
``ls`` command in their daily life. It is equivalent to
|
||||
``nick/inputline ls = look``.
|
||||
|
||||
::
|
||||
|
||||
nick/object mycar2 = The red sports car
|
||||
|
||||
With this example, substitutions will only be done specifically for
|
||||
commands expecting an object reference, such as
|
||||
|
||||
::
|
||||
|
||||
look mycar2
|
||||
|
||||
becomes equivalent to "``look The red sports car``\ ".
|
||||
|
||||
::
|
||||
|
||||
nick/players tom = Thomas Johnsson
|
||||
|
||||
This is useful for commands searching for players explicitly:
|
||||
|
||||
::
|
||||
|
||||
@find *tom
|
||||
|
||||
One can use nicks to speed up input. Below we add ourselves a quicker
|
||||
way to build red buttons. In the future just writing *rb* will be enough
|
||||
to execute that whole long string.
|
||||
|
||||
::
|
||||
|
||||
nick rb = @create button:examples.red_button.RedButton
|
||||
|
||||
Nicks could also be used as the start for building a "recog" system
|
||||
suitable for an RP mud.
|
||||
|
||||
::
|
||||
|
||||
nick/player Arnold = The mysterious hooded man
|
||||
|
||||
Coding with nicks
|
||||
-----------------
|
||||
|
||||
Nicks are are stored as the ``Nick`` database model and are referred
|
||||
from the normal Evennia `object <Objects.html>`_ through the ``nicks``
|
||||
property - this is known as the NickHandler. The NickHandler offers
|
||||
effective error checking, searches and conversion.
|
||||
|
||||
::
|
||||
|
||||
# A command/channel nick:
|
||||
object.nicks.add("greetjack", "tell Jack = Hello pal!")
|
||||
|
||||
# An object nick:
|
||||
obj.nicks.add("rose", "The red flower", nick_type="object")
|
||||
|
||||
# An player nick:
|
||||
obj.nicks.add("tom", "Tommy Hill", nick_type="player")
|
||||
|
||||
# My own custom nick type (handled by my own game code somehow):
|
||||
obj.nicks.add("hood", "The hooded man", nick_type="my_identsystem")
|
||||
|
||||
# get back the translated nick:
|
||||
full_name = obj.nicks.get("rose", nick_type="object")
|
||||
|
||||
# delete a previous set nick
|
||||
object.nicks.del("rose", nick_type="object")
|
||||
|
||||
In a command definition you can reach the nick handler through
|
||||
``self.caller.nicks``. See the ``nick`` command in
|
||||
``game/gamesrc/commands/default/general.py`` for more examples.
|
||||
|
||||
As a last note, The Evennia `channel <Communications.html>`_ alias
|
||||
systems are using nicks with the ``nick_type="channel"`` in order to
|
||||
allow users to create their own custom aliases to channels.
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,199 +0,0 @@
|
|||
Making your game available online
|
||||
=================================
|
||||
|
||||
Evennia development can be made also without any internet connection
|
||||
(except to download updates). At some point however, you are likely to
|
||||
want to make your game visible online, either as part of making it
|
||||
public or to allow other developers or beta testers access to it.
|
||||
|
||||
Using your own computer as a server
|
||||
-----------------------------------
|
||||
|
||||
By far the simplest and probably cheapest option. Evennia will run on
|
||||
your own, home computer. Moreover, since Evennia is its own web server,
|
||||
you don't need to install anything extra to also run its website.
|
||||
|
||||
**Advantages**
|
||||
|
||||
- Free (except for internet cost and electrical bill)
|
||||
- Full control over the server/hardware (it sits right there!)
|
||||
- Easy to set up.
|
||||
- Also suitable for quick setups - e.g. to briefly show off results to
|
||||
your collaborators.
|
||||
|
||||
**Disadvantages**
|
||||
|
||||
- You need a good internet connection, ideally without any
|
||||
upload/download limits/costs.
|
||||
- If you want to run a full game this way, your computer needs to
|
||||
always be on. It could be noisy, and as mentioned, the electrical
|
||||
bill is worth considering.
|
||||
- No support or safety - if your house burns down, so will your game.
|
||||
Also, you are yourself responsible for backups etc (some would
|
||||
consider this an advantage).
|
||||
- Home IP numbers are often dynamically allocated, so for permanent
|
||||
online time you need to set up a DNS to always re-point to the right
|
||||
place (see below).
|
||||
|
||||
Set up your own machine as a server
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Making Evennia available from your own machine is mainly a matter of
|
||||
configuring eventual firewalls to let Evennia's ports through. With
|
||||
Evennia running, note which ports it is using (defaults are 4000 for
|
||||
telnet and 8000 for webclient, we assume them below).
|
||||
|
||||
#. Go to `http://www.whatismyip.com/ <http://www.whatismyip.com/>`_ (or
|
||||
similar site). They should tell you which IP address you are
|
||||
connecting from, e.g. ``230.450.0.222``.
|
||||
#. In your web browser, go to ``http://230.450.0.222:8000``, where the
|
||||
last ``:8000`` is the webclient port Evennia uses. If you see
|
||||
Evennia's website and can connect to the webclient - -congrats,
|
||||
that's it! Try to connect with a traditional MUD-client to the telnet
|
||||
port too, just to make sure.
|
||||
#. Most likely you won't see the Evennia website right away though. This
|
||||
is probably because you have a firewall blocking the ports we need.
|
||||
There could also be a hardware-router between your computer and the
|
||||
Internet - in that case the IP address we see "from the outside" is
|
||||
actually the router's IP, not that of your computer on your local
|
||||
network.
|
||||
|
||||
- You need to let Evennia data out through your router/firewall. How
|
||||
you do that varies with manufacturer and software. But in
|
||||
principle you should look for something called "Port forwarding"
|
||||
or similar. You want to route port 8000/4000 from your computer to
|
||||
an "outgoing port" that the world can see. That latter port does
|
||||
*not* have to have the same number as the internal port! For
|
||||
example, you might want to connect port 8000 to an outgoing port
|
||||
80 - this is the port HTTP requests use and web browsers
|
||||
automatically look for. If you use port 80 you won't have to
|
||||
specify the port number in the url of your browser. If you run
|
||||
other web servers on your machine, that could be an issue though.
|
||||
- I found that I had to reboot my router for this to take effect, so
|
||||
worth trying if you still cannot get to the Evennia website
|
||||
through your browser.
|
||||
|
||||
#. At this point you should be able to invite people to play your game
|
||||
on ``http://230.450.0.222:8000`` or via telnet to ``230.450.0.222``
|
||||
on port ``4000``.
|
||||
|
||||
A complication with using a specific IP address like this is that your
|
||||
home IP might not remain the same. Many ISPs (Internet Service
|
||||
Providers) allocates a dynamic IP to you which could change at any time.
|
||||
When that happens, that IP you told people to go to will be worthless.
|
||||
Also, that long string of numbers is not very pretty, is it? It's hard
|
||||
to remember and not easy to use in marketing your game. What you need is
|
||||
to alias it to a more sensible domain name - an alias that follows you
|
||||
around also when the IP changes.
|
||||
|
||||
#. To set up a domain name alias, we recommend starting with a free
|
||||
domain name from `FreeDNS <http://freedns.afraid.org/>`_. Once you
|
||||
register there (it's free) you have access to tens of thousands
|
||||
domain names that people have "donated" to allow you to use for your
|
||||
own sub domain. For example, ``strangled.net`` is one of those
|
||||
available domains. So tying our IP address to ``strangled.net`` using
|
||||
the subdomain ``evennia`` would mean that one could henceforth direct
|
||||
people to
|
||||
`http://evennia.strangled.net:8000 <http://evennia.strangled.net:8000>`_
|
||||
for their gaming needs - far easier to remember!
|
||||
#. So how do we make this new, nice domain name follow us also if our IP
|
||||
changes? For this we need to set up a little program on our computer.
|
||||
It will check whenever our ISP decides to change our IP and tell
|
||||
FreeDNS that. There are many alternatives to be found from FreeDNS:s
|
||||
homepage, one that works on multiple platforms is
|
||||
`inadyn <http://www.inatech.eu/inadyn/>`_. Get it from their page or,
|
||||
in Linux, through something like
|
||||
|
||||
::
|
||||
|
||||
apt-get install inadyn
|
||||
|
||||
#. Next, you login to your account on FreeDNS and go to the
|
||||
`Dynamic <http://freedns.afraid.org/dynamic/>`_ page. You should have
|
||||
a list of your subdomains. Click the ``Direct URL`` link and you'll
|
||||
get a page with a text message. Ignore that and look at the URL of
|
||||
the page. It should be ending in a lot of random letters. Everything
|
||||
after the question mark is your unique "hash". Copy this string.
|
||||
#. You now start inadyn with the following command (Linux):
|
||||
|
||||
::
|
||||
|
||||
inadyn --dyndns_system default@freedns.afraid.org -a <my.domain>,<hash> &
|
||||
|
||||
where ``<my.domain>`` would be ``evennia.strangled.net`` and
|
||||
``<hash>`` the string of numbers we copied from FreeDNS. The ``&``
|
||||
means we run in the background (might not be valid in other
|
||||
operating systems). ``inadyn`` will henceforth check for changes
|
||||
every 60 seconds. You should put the ``inadyn`` command string in a
|
||||
startup script somewhere so it kicks into gear whenever your
|
||||
computer starts.
|
||||
|
||||
Remote hosting
|
||||
--------------
|
||||
|
||||
Your normal "web hotel" will probably not be enough to run Evennia. A
|
||||
web hotel is normally aimed at a very specific usage - delivering web
|
||||
pages, at the most with some dynamic content. The "Python scripts" they
|
||||
refer to on their home pages are usually only intended to be CGI-like
|
||||
scripts launched by their webserver. Even if they allow you shell access
|
||||
(so you can install the Evennia dependencies in the first place),
|
||||
resource usage will likely be very restricted. Running a full-fledged
|
||||
game server like Evennia will probably be shunned upon or be outright
|
||||
impossible. If you are unsure, contact your web hotel and ask about
|
||||
their policy on you running third-party servers that will want to open
|
||||
custom ports.
|
||||
|
||||
The options you probably need to look for are *shell account services*
|
||||
or *VPS:es*. A "Shell account" service means that you get a shell
|
||||
account on a server and can log in like any normal user. By contrast, a
|
||||
*VPS* (Virtual Private Server) service usually means that you get
|
||||
``root`` access, but in a virtual machine.
|
||||
|
||||
**Advantages**
|
||||
|
||||
- Shell accounts/VPS offer more flexibility than your average web hotel
|
||||
- it's the ability to log onto a shared computer away from home.
|
||||
- Usually runs a Linux flavor, making it easy to install Evennia.
|
||||
- Support. You don't need to maintain the server hardware. If your
|
||||
house burns down, at least your game stays online. Many services
|
||||
guarantee a certain level of up-time and might also do regular
|
||||
backups for you (this varies).
|
||||
- Gives a fixed domain name, so no need to mess with IP addresses.
|
||||
|
||||
**Disadvantages**
|
||||
|
||||
- Might be pretty expensive (more so than a web hotel)
|
||||
- Linux flavors might feel unfamiliar to users not used to ssh/PuTTy
|
||||
and the Linux command line.
|
||||
- You are probably sharing the server with many others, so you are not
|
||||
completely in charge. CPU usage might be limited. Also, if the server
|
||||
people decides to take the server down for maintenance, you have no
|
||||
choice but to sit it out (but you'll hopefully be warned ahead of
|
||||
time).
|
||||
|
||||
Set up Evennia on a remote shell account/VPS
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Assuming you know how to connect to your account over ssh/PuTTy you
|
||||
should be able to follow the `Getting Started <GettingStarted.html>`_
|
||||
instructions normally. Ports might be an issue, so make sure you know
|
||||
which ports are available to use.
|
||||
|
||||
If you don't have root access in a virtual machine (but just a normal
|
||||
user-shell account), you will probably *not* have all resources easily
|
||||
available. You need root to use ``apt-get`` for example. In that case
|
||||
you should be able set up a virtualenv install instead, see the last
|
||||
section of `Getting Started <GettingStarted.html>`_ for more info.
|
||||
|
||||
To find commercial solutions, just scour the web for shell access/VPS in
|
||||
your region. One user has for example reported some success with
|
||||
`Webfaction <http://www.webfaction.com/>`_.
|
||||
|
||||
Worth checking out is a free hosting offer especially aimed at MUDs
|
||||
`here <http://zeno.biyg.org/portal.php>`_. An account and some activity
|
||||
at `MUDbytes <http://www.mudbytes.net>`_ is required (that's a good
|
||||
forum to join anyway if you are interested in MUDs). On this
|
||||
mud-specific server you can reserve ports to use as well. From their
|
||||
page it's however unclear which resources are available (only gcc is
|
||||
listed), so one probably needs to use a virtualenv setup to get all
|
||||
dependencies.
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
Players
|
||||
=======
|
||||
|
||||
All *gamers* (real people) that opens a game `Session <Session.html>`_
|
||||
on Evennia are doing so through an object called *Player*. The Player
|
||||
object has no in-game representation, it represents the account the
|
||||
gamer has on the game. In order to actually get on the game the Player
|
||||
must *puppet* an `Object <Objects.html>`_ (normally a Character).
|
||||
|
||||
Just how this works depends on the configuration option
|
||||
``MULTISESSION_MODE``. There are three multisession modes, described in
|
||||
the diagram below:
|
||||
|
||||
|image0|
|
||||
|
||||
From left to right, these show ``MULTISESSION_MODE`` 0, 1 and 2. In all
|
||||
cases the gamer connects to the `Portal <PortalAndServer.html>`_ with
|
||||
one or more sessions - this could be a telnet connection, webclient, ssh
|
||||
or some of the other protocols Evennia supports.
|
||||
|
||||
- In mode 0 (leftmost), each Player can only hold one session at a
|
||||
time. This is the normal mode for many legacy muds.
|
||||
- In mode 1 (middle), each Player can hold any number of sessions but
|
||||
they are all treated equal. This means all giving a command in one
|
||||
client is doing exactly the same thing as doing so in any other
|
||||
connected client. All sessions will see the same output and e.g.
|
||||
giving the @quit command will kill all sessions.
|
||||
- In mode 2 (right) each Player can hold any number of sessions and
|
||||
they are kept separate from one another. This allows a single player
|
||||
to puppet any number of Characters and Objects.
|
||||
|
||||
Apart from storing login information and other account-specific data,
|
||||
the Player object is what is chatting on
|
||||
`Channels <Communications.html>`_. It is also a good place to store
|
||||
`Permissions <Locks.html>`_ to be consistent between different in-game
|
||||
characters as well as configuration options. Players are
|
||||
`TypeClassed <Typeclasses.html>`_ entities defaulting to use
|
||||
``settings.BASE_PLAYER_TYPECLASS``. They also hold a
|
||||
`CmdSet <Commands.html>`_ defaulting to the set defined by
|
||||
``settings.CMDSET_PLAYER``.
|
||||
|
||||
Logged into default Evennia, you can use the ``@ooc`` command to leave
|
||||
your current `Character <Objects.html>`_ and go into OOC mode. You are
|
||||
quite limited in this mode, basically it works like a simple chat
|
||||
program. It acts as a staging area for switching between Characters (if
|
||||
your game supports that) or as a safety mode if your Character gets
|
||||
deleted. Use ``@ic`` to attempt to puppet a Character.
|
||||
|
||||
Note that the Player object can and often do have a different set of
|
||||
[Locks#Permissions Permissions] from the Character they control.
|
||||
Normally you should put your permissions on the Player level - this will
|
||||
overrule permissions set on the Character level (unless ``@quell``-ing
|
||||
is used).
|
||||
|
||||
How to create your own Player types
|
||||
-----------------------------------
|
||||
|
||||
You will usually not want more than one Player typeclass for all new
|
||||
players (but you could in principle create a system that changes a
|
||||
player's typeclass dynamically).
|
||||
|
||||
An Evennia Player is, per definition, a Python class that includes
|
||||
``src.players.player.Player`` among its parents (if you are aware of how
|
||||
`Typeclasses <Typeclasses.html>`_ work, this is a typeclass linked to
|
||||
the ``PlayerDB`` database model). You can also inherit from
|
||||
``ev.Player`` which is a shortcut.
|
||||
|
||||
Here's how to define a new Player typeclass in code:
|
||||
|
||||
::
|
||||
|
||||
from ev import Player
|
||||
class ConfigPlayer(Player):
|
||||
"""
|
||||
This creates a Player with some configuration options
|
||||
"""
|
||||
at_player_creation(self):
|
||||
"this is called only once, when player is first created"
|
||||
self.db.real_name = None # this is set later
|
||||
self.db.real_address = None # "
|
||||
self.db.config_1 = True # default config
|
||||
self.db.config_2 = False # "
|
||||
self.db.config_3 = 1 # "
|
||||
# ... whatever else our game needs to know
|
||||
|
||||
There is no pre-made folder in ``game/gamesrc`` to store custom player
|
||||
typeclasses. Make your own folder or store it in ``gamesrc/objects``
|
||||
(remember that if you make your own folder you need to add an empty
|
||||
``__init__.py`` file so that you can import the file later). To change
|
||||
which object becomes the Player object for new players, set the variable
|
||||
``BASE_PLAYER_TYPECLASS`` in your ``settings.py`` file.
|
||||
|
||||
Properties on Players
|
||||
---------------------
|
||||
|
||||
Beyond those properties assigned to all typeclassed objects (see
|
||||
`Typeclasses <Typeclasses.html>`_), the Player also has the following
|
||||
custom properties:
|
||||
|
||||
- ``user`` - a unique link to a ``User`` Django object, representing
|
||||
the logged-in user.
|
||||
- ``obj`` - an alias for ``character``.
|
||||
- ``name`` - an alias for ``user.username``
|
||||
- ``sessions`` - a list of all connected Sessions (physical
|
||||
connections) this object listens to. The so-called session-id (used
|
||||
in many places) is found as a property ``sessid`` on each Session
|
||||
instance.
|
||||
- ``is_superuser`` (bool: True/False) - if this player is a superuser.
|
||||
|
||||
Special handlers:
|
||||
|
||||
- ``cmdset`` - This holds all the current `Commands <Commands.html>`_
|
||||
of this Player. By default these are the commands found in the cmdset
|
||||
defined by ``settings.CMDSET_PLAYER``.
|
||||
- ``nicks`` - This stores and handles `Nicks <Nicks.html>`_, in the
|
||||
same way as nicks it works on Objects. For Players, nicks are
|
||||
primarily used to store custom aliases for [Communications#Channels
|
||||
Channels].
|
||||
|
||||
Selection of special methods (see ``src.player.models`` for details):
|
||||
|
||||
- ``get_puppet`` - get a currently puppeted object connected to the
|
||||
Player and a given given session id, if any.
|
||||
- ``puppet_object`` - connect a session to a puppetable Object.
|
||||
- ``unpuppet_object`` - disconnect a session from a puppetable Object.
|
||||
- ``msg`` - send text to the Player
|
||||
- ``execute_cmd`` - runs a command as if this Player did it.
|
||||
- ``search`` - search for Players.
|
||||
|
||||
.. |image0| image:: https://lh5.googleusercontent.com/-9XuiTr2UAbo/UZDxNLFUobI/AAAAAAAAB3I/1wArg9P-KnQ/w898-h293-no/evennia_player_sessions2.png
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
Portal and Server layout
|
||||
========================
|
||||
|
||||
Evennia consists of two processes, known as *Portal* and *Server*. They
|
||||
can be controlled from inside the game or from the command line as
|
||||
described `here <StartStopReload.html>`_.
|
||||
|
||||
If you are new to the concept, the main purpose of separating the two is
|
||||
to have players connect to the Portal but keep the MUD running on the
|
||||
Server. This way one can restart/reload the game (the Server part)
|
||||
without Players getting disconnected.
|
||||
|
||||
|image0|
|
||||
|
||||
The Server and Portal are glued together via an AMP (Asynchronous
|
||||
Messaging Protocol) connection. This allows the two programs to
|
||||
communicate seamlessly.
|
||||
|
||||
Portal and Server Sessions
|
||||
--------------------------
|
||||
|
||||
*note: This is not really necessary to understand if you are new to
|
||||
Evennia.*
|
||||
|
||||
New Player connections are listened for and handled by the Portal using
|
||||
the `protocols <SessionProtocols.html>`_ it understands (such as telnet,
|
||||
ssh, webclient etc). When a new connection is established, a *Portal
|
||||
Session* is created on the Portal side. This session object looks
|
||||
different depending on which protocol is used to connect, but all still
|
||||
have a minimum set of attributes that are generic to all sessions.
|
||||
|
||||
These common properties are piped from the Portal, through AMP, to the
|
||||
*Server*, which is now informed a new connection has been established.
|
||||
On the Server side, a *Server Session* object is created to represent
|
||||
this. There is only one type of Server Session. It looks the same
|
||||
regardless of how the Player connects.
|
||||
|
||||
From now on, there is a one-to-one match between the Server Session on
|
||||
one side of the AMP connection and the Portal Session on the other. Data
|
||||
arriving to the Portal Session is sent on to its mirror Server session
|
||||
and vice versa.
|
||||
|
||||
During certain situations, the portal- and server-side sessions are
|
||||
"synced" with each other:
|
||||
|
||||
- The Player closes their client, killing the Portal Session. The
|
||||
Portal syncs with the Server to make sure the corresponding Server
|
||||
Session is also deleted.
|
||||
- The Player quits from inside the game, killing the Server Session.
|
||||
The Server then syncs with the Portal to make sure to close the
|
||||
Portal connection cleanly.
|
||||
- The Server is rebooted/reset/shutdown - The Server Sessions are
|
||||
copied over ("saved") to the Portal side. When the Server comes back
|
||||
up, this data is returned by the Portal so the two are again in sync.
|
||||
This way a Player's login status and other connection-critical things
|
||||
can survive a server reboot (assuming the Portal is not stopped at
|
||||
the same time, obviously).
|
||||
|
||||
Sessionhandlers
|
||||
---------------
|
||||
|
||||
Both the Portal and Server each have a *sessionhandler* to manage the
|
||||
connections. These handlers contain all methods for relaying data across
|
||||
the AMP bridge. All types of Sessions hold a reference to their
|
||||
respective Sessionhandler (the property is called ``sessionhandler``) so
|
||||
they can relay data. See `protocols <SessionProtocols.html>`_ for more
|
||||
info on building new protocols.
|
||||
|
||||
.. |image0| image:: https://2498159658166209538-a-1802744773732722657-s-sites.googlegroups.com/site/evenniaserver/file-cabinet/evennia_server_portal.png
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
Evennia Quirks
|
||||
==============
|
||||
|
||||
This is a list of various problems or stumbling blocks that people often
|
||||
ask about or report when using (or trying to use) Evennia. These are
|
||||
common stumbling blocks, non-intuitive behaviour and common newbie
|
||||
mistakes when working with Evennia. They are not Evennia bugs.
|
||||
|
||||
Actual Evennia bugs should be reported in
|
||||
`Issues <https://code.google.com/p/evennia/issues/list>`_.
|
||||
|
||||
Editing code directly in \`src/\`
|
||||
---------------------------------
|
||||
|
||||
Don't do this. Doing local changes to ``src`` will eventually conflict
|
||||
with changes done by the Evennia developers. Rather you should import
|
||||
``src``-modules into your own custom modules in ``game/gamesrc`` (or
|
||||
copy&paste them over if you want to expand on the defaults). Next you
|
||||
re-point the relevant links in your ``settings`` file to point to your
|
||||
custom modules instead of the default ones.
|
||||
|
||||
If you want to expand the web interface, copy the entire ``src/web``
|
||||
folder over to ``game/gamesrc`` and change the media links in your
|
||||
``settings`` file.
|
||||
|
||||
If you find that ``src`` lacks some functionality you need, make an
|
||||
`Issue <https://code.google.com/p/evennia/issues/list>`_ of the type
|
||||
*Feature Request*. Or become a part of the Evennia development and
|
||||
submit your own additions to the core.
|
||||
|
||||
Create typeclassed object by calling the typeclass
|
||||
--------------------------------------------------
|
||||
|
||||
Alas, you cannot create a new Typeclass by just initializing the
|
||||
classname. So ``obj = Object()`` won't do what you want. Whereas
|
||||
Evennia's Typeclasses behave *pretty much* like normal Python classes,
|
||||
this is one of the situations where they don't. You need to use
|
||||
Evennia's create functions to add new objects. So use e.g.
|
||||
``ev.create_object()``, ``ev.create_script()`` etc. (these are defined
|
||||
in ``src/utils/create.py``). This will set up the Typeclass database
|
||||
connection behind the scenes.
|
||||
|
||||
In the same vein, you cannot overload ``__init__()``, ``__setattr__`` or
|
||||
``__getattribute__`` on Typeclasses. Use the hook methods
|
||||
(``at_object_creation()`` etc) instead.
|
||||
|
||||
Web admin to create new Player
|
||||
------------------------------
|
||||
|
||||
If you use the default login system and is trying to use the Web admin
|
||||
to create a new Player account, you need to consider which
|
||||
MULTIPLAYER\_MODE you are in. If you are in MULTIPLAYER\_MODE 0 or 1,
|
||||
the login system expects each Player to also have a Character object
|
||||
named the same as the Player - there is no character creation screen by
|
||||
default. If using the normal mud login screen, a Character with the same
|
||||
name is automatically created and connected to your Player. From the web
|
||||
interface you must do this manually.
|
||||
|
||||
So, when creating the Player, make sure to also create the Character
|
||||
*from the same form* as you create the Player from. This should set
|
||||
everything up for you. Otherwise you need to manually set the "player"
|
||||
property on the Character and the "character" property on the Player to
|
||||
point to each other. You must also set the lockstring of the Character
|
||||
to allow the Player to "puppet" this particular character.
|
||||
|
||||
Mutable attributes and their connection to the database
|
||||
-------------------------------------------------------
|
||||
|
||||
When storing a mutable object (usually a list or a dictionary) in an
|
||||
Attribute
|
||||
|
||||
::
|
||||
|
||||
object.db.mylist = [1,2,3]
|
||||
|
||||
you should know that the connection to the database is retained also if
|
||||
you later extract that Attribute into another variable (what is stored
|
||||
and retrieved is actually a ``PackedList`` or a ``PackedDict`` that
|
||||
works just like their namesakes except they save themselves to the
|
||||
database when changed). So if you do
|
||||
|
||||
::
|
||||
|
||||
alist = object.db.mylist
|
||||
alist.append(4)
|
||||
|
||||
this updates the database behind the scenes, so both ``alist`` and
|
||||
``object.db.mylist`` are now ``[1,2,3,4]``. If you don't want this,
|
||||
convert the mutable object to its normal Python form.
|
||||
|
||||
::
|
||||
|
||||
blist = list(object.db.mylist)
|
||||
blist.append(4)
|
||||
|
||||
The property ``blist`` is now ``[1,2,3,4]`` whereas ``object.db.mylist``
|
||||
remains unchanged. You'd need to explicitly re-assign to the ``mylist``
|
||||
Attribute in order to update the database. If you store nested mutables
|
||||
you only need to convert the "outermost" one in order to "break" the
|
||||
connection to the database like this.
|
||||
|
||||
General shakiness of the web admin
|
||||
----------------------------------
|
||||
|
||||
Since focus has been on getting the underlying python core to work
|
||||
efficiently, the web admin is not quite as stable nor easy to use as
|
||||
we'd like at this point. Also, the web-based html code is, while
|
||||
working, not very pretty or clean. These are areas where we'd much
|
||||
appreciate getting more input and help.
|
||||
|
||||
Known upstream bugs
|
||||
===================
|
||||
|
||||
There are no known upstream bugs at this time.
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
RSS
|
||||
===
|
||||
|
||||
`RSS <http://en.wikipedia.org/wiki/RSS>`_ is a format for easily
|
||||
tracking updates on websites. The principle is simple - whenever a site
|
||||
is updated, a small text file is updated. An RSS reader can then
|
||||
regularly go online, check this file for updates and let the user know
|
||||
what's new.
|
||||
|
||||
Evennia allows for connecting any number of RSS feeds to any number of
|
||||
in-game channels. Updates to the feed will be conveniently echoed to the
|
||||
channel. There are many potential uses for this: For example the MUD
|
||||
might use a separate website to host its forums. Through RSS, the
|
||||
players can then be notified when new posts are made. Another example is
|
||||
to let everyone know you updated your dev blog. Admins might also want
|
||||
to track the latest Evennia updates through our own RSS feed
|
||||
`here <http://code.google.com/feeds/p/evennia/updates/basic>`_.
|
||||
|
||||
Configuring RSS
|
||||
---------------
|
||||
|
||||
To use RSS, you first need to install the
|
||||
`feedparser <http://code.google.com/p/feedparser/>`_ python module. It
|
||||
should be easily available through most distributions as
|
||||
*python-feedparser*, otherwise you can download it directly.
|
||||
|
||||
Next you activate RSS support in your config file by settting
|
||||
``RSS_ENABLED=True``.
|
||||
|
||||
Start/reload Evennia as a privileged user. You should now have a new
|
||||
command available, ``@rss2chan``:
|
||||
|
||||
::
|
||||
|
||||
@rss2chan <evennia_channel> = <rss_url>
|
||||
|
||||
Setting up RSS, step by step
|
||||
----------------------------
|
||||
|
||||
You can connect RSS to any Evennia channel, but for testing, let's set
|
||||
up a new channel "rss".
|
||||
|
||||
::
|
||||
|
||||
@ccreate rss = RSS feeds are echoed to this channel!
|
||||
|
||||
Let's connect Evennia's code-update feed to this channel. Its full url
|
||||
is ``http://code.google.com/feeds/p/evennia/updates/basic``.
|
||||
|
||||
::
|
||||
|
||||
@rss2chan rss = http://code.google.com/feeds/p/evennia/updates/basic
|
||||
|
||||
That's it, really. New Evennia updates will now show up as a one-line
|
||||
title and link in the channel. Give the ``@rss2chan`` command on its own
|
||||
to show all connections. To remove a feed from a channel, you specify
|
||||
the connection again (see the list) but add the ``/delete`` switch:
|
||||
|
||||
::
|
||||
|
||||
@rss2chan/delete rss = http://code.google.com/feeds/p/evennia/updates/basic
|
||||
|
||||
You can connect any number of RSS feeds to a channel this way. You could
|
||||
also connect them to the same channels as `IRC <IRC.html>`_ and/or
|
||||
`IMC2 <IMC2.html>`_ to have the feed echo to external chat channels as
|
||||
well.
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
Tutorial: Removing colour from your game
|
||||
========================================
|
||||
|
||||
This is a small tutorial for customizing your character objects, using
|
||||
the example of letting users turn on and off ansii colour parsing.
|
||||
|
||||
In the Building guide's `Colours <Colours.html>`_ page you can learn how
|
||||
to add Colour to your game by using special markup. Colours enhance the
|
||||
gaming experience, but not all users want colour. Examples would be
|
||||
users working from clients that don't support colour, or people with
|
||||
various seeing disabilities that rely on screen readers to play your
|
||||
game.
|
||||
|
||||
So here's how to allow those users to remove colour. It basically means
|
||||
you implementing a simple configuration system for your characters. This
|
||||
is the basic sequence:
|
||||
|
||||
#. Define your own default character typeclass, inheriting from
|
||||
Evennia's default.
|
||||
#. Set an attribute on the character to control markup on/off.
|
||||
#. Set your custom character class to be the default for new players.
|
||||
#. Overload the ``msg()`` method on the typeclass and change how it uses
|
||||
markup.
|
||||
#. Create a custom command to allow users to change their setting.
|
||||
|
||||
Setting up a custom Typeclass
|
||||
-----------------------------
|
||||
|
||||
Create a new module in ``game/gamesrc/objects`` named, for example,
|
||||
``mycharacter.py``.
|
||||
|
||||
In your new module, create a new `typeclass <Typeclasses.html>`_
|
||||
inheriting from ``ev.Character``.
|
||||
|
||||
::
|
||||
|
||||
from ev import Character
|
||||
|
||||
class ColourableCharacter(Character):
|
||||
at_object_creation(self):
|
||||
# set a colour config value
|
||||
self.db.config_colour = True
|
||||
|
||||
Above we set a simple config value as an `attribute <Attributes.html>`_.
|
||||
|
||||
Let's make sure that new characters are created of this type. Edit your
|
||||
``game/settings.py`` file and add/change ``BASE_CHARACTER_TYPECLASS`` to
|
||||
point to your new character class. Observe that this will only affect
|
||||
*new* characters, not those already created. You have to convert already
|
||||
created characters to the new typeclass by using the ``@typeclass``
|
||||
command (try on a secondary character first though, to test that
|
||||
everything works - you don't want to render your root user unusable!).
|
||||
|
||||
::
|
||||
|
||||
@typeclass/reset/force Bob = mycharacter.ColourableCharacter
|
||||
|
||||
``@typeclass`` changes Bob's typeclass and runs all its creation hooks
|
||||
all over again. The ``/reset`` switch clears all attributes and
|
||||
properties back to the default for the new typeclass - this is useful in
|
||||
this case to avoid ending up with an object having a "mixture" of
|
||||
properties from the old typeclass and the new one. ``/force`` might be
|
||||
needed if you edit the typeclass and want to update the object despite
|
||||
the actual typeclass name not having changed.
|
||||
|
||||
Overload the \`msg()\` method
|
||||
-----------------------------
|
||||
|
||||
Next we need to overload the ``msg()`` method. What we want is to check
|
||||
the configuration value before calling the main function. The ``msg``
|
||||
method call is found in
|
||||
``src/objects/objects.py' and is called like this: {{{ msg(message, from_obj=None, data=None) }}} As long as we define a method on our custom object with the same name and keep the same number of arguments/keywords we will overload the original. Here's how it could look: {{{ from ev import ansi msg(self, message, from_obj=None, data=None): "our custom msg()" if not self.db.config_colour: # if config_colour is False, strip ansi colour message = ansi.parse_ansi(message, strip_ansi=True) self.dbobj.msg(message, from_obj, data) }}} Above we create a custom version of the ``\ msg()\ `` method that cleans all ansi characters if the config value is not set to True. Once that's done, we pass it all on to the normal ``\ msg()\ `` on the database object (``\ ObjectDB\ ``) to do its thing. The colour strip is done by the ansi module itself by giving the ``\ strip\_ansi\ `` keyword to ``\ ansi.parse\_ansi\ ``. Since we put this custom ``\ msg()\ `` in our typeclass ``\ ColourableCharacter\ ``, it will be searched for and called rather than the default method on ``\ ObjectDB\ `` (which we now instead call manually). There we go! Just flip the attribute ``\ config\_colour\ `` to False and your users will not see any colour. As superuser (assuming you use the Typeclass ``\ ColourableCharacter\ ``) you can test this with the ``\ @py\ `` command: {{{ @py self.db.config_colour = False }}} ==Custom colour config command == For completeness, let's add a custom command so users can turn off their colour display themselves if they want. In ``\ game/gamesrc/commands\ ``, reate a new file, call it for example ``\ configcmds.py\ `` (it's likely that you'll want to add other commands for configuration down the line). You can also copy/rename the command template from ``\ game/gamesrc/commands/examples\ ``. {{{ from ev import default_cmds class ConfigColourCmd(default_cmds.MuxCommand): """ Configures your colour Usage: @setcolour on|off This turns ansii-colours on/off. Default is on. """ key = "@setcolour" aliases = ["@setcolor"] def func(self): "Implements the command" if not self.args or not self.args in ("on", "off"): self.caller.msg("Usage: @setcolour on|off") return if self.args == "on": self.caller.db.config_colour = True else: self.caller.db.config_colour = False self.caller.msg("Colour was turned %s." % self.args) }}} Lastly, we make this command available to the user by adding it to the default command set. Easiest is to add it to copy the template file from ``\ gamesrc/commands/examples\ ``, set ``\ settings.CMDSET\_DEFAULT\ `` to point to, and then add your module to the end of ``\ DefaultCmdSet\ `` in that new module. {{{ from game.gamesrc.commands import configcmds class DefaultCmdSet(cmdset_default.DefaultCmdSet): key = "DefaultMUX" def at_cmdset_creation(self): super(DefaultCmdSet, self).at_cmdset_creation() self.add(configcmds.ConfigColourCmd()) }}} When adding a new command to a cmdset like this you need to run the ``\ @reload\ ````
|
||||
command (or reboot the server). From here on out, your users should be
|
||||
able to turn on or off their colour as they please.
|
||||
|
|
@ -1,236 +0,0 @@
|
|||
Scripts
|
||||
=======
|
||||
|
||||
*Scripts* are the out-of-character siblings to the in-character
|
||||
`Objects <Objects.html>`_. The name "Script" might suggest that they can
|
||||
only be used to script the game but this is only part of their
|
||||
usefulness (in the end we had to pick a single name for them). Scripts
|
||||
are full Typeclassed database entities, just like Objects - with all the
|
||||
advantages this entails. Likewise they can also store arbitrary
|
||||
*Attributes*.
|
||||
|
||||
Scripts can be used for many different things in Evennia:
|
||||
|
||||
- They can attach to Objects to influence them in various ways - or
|
||||
exist independently of any one in-game entity.
|
||||
- They can work as timers and tickers - anything that may change with
|
||||
Time. But they can also have no time dependence at all.
|
||||
- They can describe State changes.
|
||||
- They can act as data stores for storing game data persistently in the
|
||||
database.
|
||||
- They can be used as OOC stores for sharing data between groups of
|
||||
objects.
|
||||
|
||||
The most obvious use of Scripts may be to use them as *timers* or
|
||||
*Events*. Consider a script running on the object ``Grandfather Clock``.
|
||||
The script has a timer that tells it to fire every hour. This allows the
|
||||
clock to chime regularly. The script might even regulate how often the
|
||||
clock must be rewound so it won't stop.
|
||||
|
||||
Scripts may act as changeable *States*. Consider for example creating a
|
||||
'dark' room. It has two scripts assigned on it - one ``DarkState``
|
||||
script, and one ``BrightState`` script. When characters enter the dark
|
||||
room, it assigns a custom `Cmdset <Commands.html>`_ to them. This
|
||||
command set defines the parameters of the state they describe. In this
|
||||
case it limits their actions because of the darkness. After the
|
||||
characters have stumbled around for a while, someone brings up a torch.
|
||||
As a light source is now in the room, ``DarkState`` reacts to this by
|
||||
shutting down itself and handing over control to the ``BrightState``
|
||||
script that restores normal commands. Finally, when the character with
|
||||
the torch leaves the room, the ``BrightState`` script detects this and
|
||||
obediently hands control back to the ``DarkState``, leaving the
|
||||
remaining poor characters in darkness once again.
|
||||
|
||||
By combining state-changes with timers one can make a room look
|
||||
different during nighttime than it does during the day. Weather and
|
||||
seasons might come and go. But one can also achieve more complex things
|
||||
such as state-AI systems that make mobs move around and possibly pursue
|
||||
characters between rooms.
|
||||
|
||||
Scripts are also excellent places to store game data in an OOC way. A
|
||||
groupd of objects may share date by use of a Script object they all hold
|
||||
references to.
|
||||
|
||||
In short, Scripts can be used for a lot of things.
|
||||
|
||||
How to create and test your own Script types
|
||||
--------------------------------------------
|
||||
|
||||
In-game you can try out scripts using the ``@script`` command. Try the
|
||||
following:
|
||||
|
||||
::
|
||||
|
||||
> @script self = examples.bodyfunctions.BodyFunctions
|
||||
|
||||
This should cause some random messages. Add the ``/stop`` switch to the
|
||||
above command to kill the script again. You can use the ``@scripts``
|
||||
command to list all active scripts in the game. Evennia creates a few
|
||||
default ones.
|
||||
|
||||
Custom script modules are usually stored in ``game/gamesrc/scripts``. As
|
||||
a convenience you can inherit sripts from ``ev.Script``.
|
||||
|
||||
If you add scripts to `Objects <Objects.html>`_ the script can then
|
||||
manipulate the object as desired. The script is added to the object's
|
||||
*script handler*, called simply ``scripts``. The handler takes care of
|
||||
all initialization and startup of the script for you.
|
||||
|
||||
::
|
||||
|
||||
# add script to myobj's scripthandler
|
||||
myobj.scripts.add("game.gamesrc.scripts.myscripts.CoolScript")
|
||||
# alternative way
|
||||
from ev import create_script
|
||||
create_script("game.gamesrc.scripts.myscripts.CoolScript", obj=myobj)
|
||||
|
||||
A script does not have to be connected to an in-game object. Such
|
||||
scripts are called *Global scripts*. You can create global scripts by
|
||||
simply not supplying an object to store it on:
|
||||
|
||||
::
|
||||
|
||||
# adding a global script
|
||||
from ev import create_script
|
||||
create_script("game.gamesrc.scripts.globals.MyGlobalEconomy", key="economy", obj=None)
|
||||
|
||||
Assuming the Script ``game.gamesrc.scripts.globals.MyGlobalEconomy``
|
||||
exists, this will create and start it as a global script.
|
||||
|
||||
Properties and functions defined on Scripts
|
||||
-------------------------------------------
|
||||
|
||||
A Script has all the properties of a typeclassed object, such as ``db``
|
||||
and ``ndb``\ (see `Typeclasses <Typeclasses.html>`_). Setting ``key`` is
|
||||
useful in order to manage scripts (delete them by name etc). These are
|
||||
usually set up in the Script's typeclass, but can also be assigned on
|
||||
the fly as keyword arguments to ``ev.create_script``.
|
||||
|
||||
- ``desc`` - an optional description of the script's function. Seen in
|
||||
script listings.
|
||||
- ``interval`` - how often the script should run. If ``interval == 0``
|
||||
(default), it runs forever, without any repeating (it will not accept
|
||||
a negative value).
|
||||
- ``start_delay`` - (bool), if we should wait ``interval`` seconds
|
||||
before firing for the first time or not.
|
||||
- ``repeats`` - How many times we should repeat, assuming
|
||||
``interval > 0``. If repeats is set to ``<= 0``, the script will
|
||||
repeat indefinitely.
|
||||
- ``persistent``- if this script should survive a server *reset* or
|
||||
server *shutdown*. (You don't need to set this for it to survive a
|
||||
normal reload - the script will be paused and seamlessly restart
|
||||
after the reload is complete).
|
||||
|
||||
There is one special property:
|
||||
|
||||
- ``obj`` - the `Object <Objects.html>`_ this script is attached to (if
|
||||
any). You should not need to set this manually. If you add the script
|
||||
to the Object with ``myobj.scripts.add(myscriptpath)`` or give
|
||||
``myobj`` as an argument to the ``utils.create.create_script``
|
||||
function, the ``obj`` property will be set to ``myobj`` for you.
|
||||
|
||||
It's also imperative to know the hook functions. Normally, overriding
|
||||
these are all the customization you'll need to do in Scripts. You can
|
||||
find longer descriptions of these in ``src/scripts/scripts.py``.
|
||||
|
||||
- ``at_script_creation()`` - this is usually where the script class
|
||||
sets things like ``interval`` and ``repeats``; things that control
|
||||
how the script runs. It is only called once - when the script is
|
||||
first created.
|
||||
- ``is_valid()`` - determines if the script should still be running or
|
||||
not. This is called when running ``obj.scripts.validate()``, which
|
||||
you can run manually, but which also Evennia calls during certain
|
||||
situations such as reloads. This is also useful for using scripts as
|
||||
state managers. If the method returns ``False``, the script is
|
||||
stopped and cleanly removed.
|
||||
- ``at_start()`` - this is called when the script starts or is
|
||||
unpaused. For persistent scripts this is at least once ever server
|
||||
startup. Note that this will *always* be called right away, also if
|
||||
``start_delay`` is ``True``.
|
||||
- ``at_repeat()`` - this is called every ``interval`` seconds, or not
|
||||
at all. It is called right away at startup, unless ``start_delay`` is
|
||||
``True``, in which case the system will wait ``interval`` seconds
|
||||
before calling.
|
||||
- ``at_stop()`` - this is called when the script stops for whatever
|
||||
reason. It's a good place to do custom cleanup.
|
||||
- ``at_server_reload()`` - this is called whenever the server is
|
||||
warm-rebooted (e.g. with the ``@reload`` command). It's a good place
|
||||
to save non-persistent data you might want to survive a reload.
|
||||
- ``at_server_shutdown()`` - this is called when a system reset or
|
||||
systems shutdown is invoked.
|
||||
|
||||
Running methods (usually called automatically by the engine, but
|
||||
possible to also invoke manually)
|
||||
|
||||
- ``start()`` - this will start the script. This is called
|
||||
automatically whenever you add a new script to a handler.
|
||||
``at_start()`` will be called.
|
||||
- ``stop()`` - this will stop the script and delete it. Removing a
|
||||
script from a handler will stop it automatically. ``at_stop()`` will
|
||||
be called.
|
||||
- ``pause()`` - this pauses a running script, rendering it inactive,
|
||||
but not deleting it. All properties are saved and timers can be
|
||||
resumed. This is called automatically when the server reloads. No
|
||||
hooks are called - as far as the script knows, it never stopped -
|
||||
this is a suspension of the script, not a change of state.
|
||||
- ``unpause()`` - resumes a previously paused script. The at\_start()
|
||||
hook will be called to allow it to reclaim its internal state. Timers
|
||||
etc are restored to what they were before pause. The server unpauses
|
||||
all paused scripts after a server reload.
|
||||
- ``time_until_next_repeat()`` - for timed scripts, this returns the
|
||||
time in seconds until it next fires. Returns ``None`` if
|
||||
``interval==0``.
|
||||
|
||||
Example script
|
||||
--------------
|
||||
|
||||
::
|
||||
|
||||
import random
|
||||
from ev import Script
|
||||
class Weather(Script):
|
||||
"Displays weather info. Meant to be attached to a room."
|
||||
def at_script_creation(self):
|
||||
"Called once, during initial creation"
|
||||
self.key = "weather_script"
|
||||
self.desc = "Gives random weather messages."
|
||||
self.interval = 60 * 5 # every 5 minutes
|
||||
self.persistent = True
|
||||
def at_repeat(self):
|
||||
"called every self.interval seconds."
|
||||
rand = random.random()
|
||||
if rand < 0.5:
|
||||
weather = "A faint breeze is felt."
|
||||
elif rand < 0.7:
|
||||
weather = "Clouds sweep across the sky."
|
||||
else:
|
||||
weather = "There is a light drizzle of rain."
|
||||
# send this message to everyone inside the object this
|
||||
# script is attached to (likely a room)
|
||||
self.obj.msg_contents(weather)
|
||||
|
||||
This is a simple weather script that we can put on an object. Every 5
|
||||
minutes it will tell everyone inside that object how the weather is.
|
||||
|
||||
To activate it, just add it to the script handler (``scripts``) on an
|
||||
`Room <Objects.html>`_. That object becomes ``self.obj`` in the example
|
||||
above. Here we put it on a room called ``myroom``:
|
||||
|
||||
::
|
||||
|
||||
myroom.scripts.add(weather.Weather)
|
||||
|
||||
In code you can also use the create function directly if you know how to
|
||||
locate the room you want:
|
||||
|
||||
::
|
||||
|
||||
from ev import create_script
|
||||
create_script('game.gamesrc.scripts.weather.Weather', obj=myroom)
|
||||
|
||||
Or, from in-game, use the ``@script`` command:
|
||||
|
||||
::
|
||||
|
||||
@script here = weather.Weather
|
||||
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
Evennia Server Configurations
|
||||
=============================
|
||||
|
||||
Evennia runs out of the box without any changes to its settings. But
|
||||
there are several important ways to customize the server and expand it
|
||||
with your own plugins.
|
||||
|
||||
Settings file
|
||||
-------------
|
||||
|
||||
The "Settings" file referenced throughout the documentation is the file
|
||||
``game/settings.py``. This is automatically created on the first run of
|
||||
``manage.py syncdb`` (see the `GettingStarted <GettingStarted.html>`_
|
||||
page). The settings file is actually a normal Python module. It's pretty
|
||||
much empty from the start, it just imports all default values from
|
||||
``src/settings_default.py`` into itself.
|
||||
|
||||
You should never edit ``src/settings_default.py``. Rather you should
|
||||
copy&paste the variables you want to change into ``settings.py`` and
|
||||
edit them there. This will overload the previously imported defaults.
|
||||
|
||||
In code, the settings is accessed through
|
||||
|
||||
::
|
||||
|
||||
from django.conf import settings
|
||||
# or (shorter):
|
||||
from ev import settings
|
||||
# example:
|
||||
servername = settings.SERVER_NAME
|
||||
|
||||
Each setting appears as a property on the imported ``settings`` object.
|
||||
You can also explore all possible options with ``ev.settings_full``
|
||||
(this also includes advanced Django defaults that are not not touched in
|
||||
default Evennia).
|
||||
|
||||
It should be pointed out that when importing ``settings`` into your code
|
||||
like this, it will be *read only*. You cannot edit your settings in
|
||||
code. The only way to change an Evennia setting is to edit
|
||||
``game/settings.py`` directly. You most often need to restart the server
|
||||
(possibly also the Portal) before a changed setting becomes available.
|
||||
|
||||
\`game/gamesrc/conf\` directory
|
||||
-------------------------------
|
||||
|
||||
The ``game/gamesrc/conf/examples/`` directory contains module templates
|
||||
for customizing Evennia. Common for all these is that you should *copy*
|
||||
the template up one level (to ``game/gamesrc/conf/``) and edit the copy,
|
||||
not the original. You then need to change your settings file to point
|
||||
the right variable at your new module. Each template header describes
|
||||
exactly how to use it and which settings variable needs to be changed
|
||||
for Evennia to be able to locate it.
|
||||
|
||||
- ``at_initial_setup.py`` - this allows you to add a custom startup
|
||||
method to be called (only) the very first time Evennia starts (at the
|
||||
same time as user #1 and Limbo is created). It can be made to start
|
||||
your own global scripts or set up other system/world-related things
|
||||
your game needs to have running from the start.
|
||||
- ``at_server_startstop.py`` - this module contains two functions that
|
||||
Evennia will call every time the Server starts and stops respectively
|
||||
- this includes stopping due to reloading and resetting as well as
|
||||
shutting down completely. It's a useful place to put custom startup
|
||||
code for handlers and other things that must run in your game but
|
||||
which has no database persistence.
|
||||
- ``connection_screens.py`` - all global string variables in this
|
||||
module are interpreted by Evennia as a greeting screen to show when a
|
||||
Player first connects. If more than one string variable is present in
|
||||
the module a random one will be picked.
|
||||
- ``lockfuncs.py`` - this is one of many possible modules to hold your
|
||||
own "safe" *lock functions* to make available to Evennia's `lock
|
||||
system <Locks.html>`_.
|
||||
- ``mssp.py`` - this holds meta information about your game. It is used
|
||||
by MUD search engines (which you often have to register with) in
|
||||
order to display what kind of game you are running along with
|
||||
statistics such as number of online players and online status.
|
||||
- ``portal_services_plugin.py`` - this allows for adding your own
|
||||
custom servies/protocols to the Portal. It must define one particular
|
||||
function that will be called by Evennia at startup. There can be any
|
||||
number of service plugin modules, all will be imported and used if
|
||||
defined. More info can be found
|
||||
`here <http://code.google.com/p/evennia/wiki/SessionProtocols#Adding_custom_Protocols>`_.
|
||||
- ``server_services_plugin.py`` - this is equivalent to the previous
|
||||
one, but used for adding new services to the Server instead. More
|
||||
info can be found
|
||||
`here <http://code.google.com/p/evennia/wiki/SessionProtocols#Adding_custom_Protocols>`_.
|
||||
|
||||
Some other Evennia systems can be customized by plugin modules but has
|
||||
no explicit template in ``conf/examples``:
|
||||
|
||||
- *command parser* - a custom module can be used to totally replace
|
||||
Evennia's default command parser. All this does is to split the
|
||||
incoming string into "command name" and "the rest". It also handles
|
||||
things like error messages for no-matches and multiple-matches among
|
||||
other things that makes this more complex than it sounds. The default
|
||||
parser is *very* generic, so you are most often best served by
|
||||
modifying things further down the line (on the command parse level)
|
||||
than here.
|
||||
- *search-return handler* - this can be used to replace how Evennia
|
||||
handles search results from most of Evennia's in-game searches (most
|
||||
importantly ``self.caller.search`` in commands). It handles the
|
||||
echoing of errors.
|
||||
- *multimatch handler* - this plugin replaces the handling of multiple
|
||||
match errors in searching. By default it allows for separating
|
||||
between same-named matches by use of numbers. Like understanding that
|
||||
"2-ball" should match the second "ball" object if there are two of
|
||||
them.
|
||||
|
||||
!ServerConf
|
||||
-----------
|
||||
|
||||
There is a special database model called ServerConf that stores server
|
||||
internal data and settings such as current player count (for interfacing
|
||||
with the webserver), startup status and many other things. It's rarely
|
||||
of use outside the server core itself but may be good to know about if
|
||||
you are an Evennia developer.
|
||||
|
|
@ -1,314 +0,0 @@
|
|||
Portal Sessions and Protocols
|
||||
=============================
|
||||
|
||||
*Note: This is considered an advanced topic and is mostly of interest to
|
||||
users planning to implement their own custom client protocol.*
|
||||
|
||||
A *Portal Session* is the basic data object representing an external
|
||||
connection to the Evennia `Portal <PortalAndServer.html>`_ -- usually a
|
||||
human player running a mud client of some kind. The way they connect -
|
||||
the language the player's client and Evennia use to talk to each other -
|
||||
is called the connection *Protocol*. The most common such protocol for
|
||||
MUD:s is the *Telnet* protocol. All Portal Sessions are stored and
|
||||
managed by the Portal's *sessionhandler*.
|
||||
|
||||
It's technically sometimes hard to separate the concept of *Session*
|
||||
from the concept of *Protocol* since both depend heavily on the other.
|
||||
|
||||
Protocols and Sessions both belong in ``src/server/``, so adding new
|
||||
protocols is one of the rare situations where development needs to
|
||||
happen in ``src/`` (in fact, if you do add a new useful protocol,
|
||||
consider contacting Evennia devs so we can include it in the main
|
||||
Evennia distribution!).
|
||||
|
||||
Protocols
|
||||
---------
|
||||
|
||||
Writing a stable communication protocol from scratch is not something
|
||||
we'll cover here, it's no trivial task. The good news is that Twisted
|
||||
offers implementations of many common protocols, ready for adapting.
|
||||
|
||||
Writing a protocol implementation in Twisted usually involves creating a
|
||||
class inheriting from a suitable Twisted parent, then overloading the
|
||||
methods that particular protocol requires so that it talks to Evennia.
|
||||
Whenever a new connection is made via this protocol, an instance of this
|
||||
class will be called. As various states change, specific-named methods
|
||||
on the class will be called (what they are named depends on the Twisted
|
||||
implementation).
|
||||
|
||||
A straight-forward protocol (like Telnet) is assumed to at least have
|
||||
the following components (although the actual method names might vary):
|
||||
|
||||
- ``connectionMade`` - called when a new connection is made. This must
|
||||
call ``self.init_session()`` with three arguments: an *identifier*
|
||||
for the protocol type (e.g. the string 'telnet' or 'ssh'), the *IP
|
||||
address* of the client connecting, and a reference to the
|
||||
*sessionhandler*. The sessionhandler is by convention made available
|
||||
by storing it on the protocol's *Factory* in
|
||||
``src/server/portal.py``, see that file for examples. Doing it this
|
||||
way avoids many possible recursive import issues.
|
||||
- ``connectionLost`` - called when connection is dropped for whatever
|
||||
reason. This must call ``self.sessionhandler.disconnect(self)`` so
|
||||
the handler can make sure the disconnect is reported to the rest of
|
||||
the system.
|
||||
- ``getData`` - data arriving from the player to Evennia. This should
|
||||
apply whatever custom formatting this protocol needs, then relay the
|
||||
data to ``self.sessionhandler.data_in(self, msg, data)``.
|
||||
- ``sendLine`` - data from Server to Player. This is called by hook
|
||||
``data_out()`` below.
|
||||
|
||||
See an example of this in
|
||||
`server/telnet.py <http://code.google.com/p/evennia/source/browse/trunk/src/server/telnet.py>`_.
|
||||
|
||||
These might not be as clear-cut in all protocols, but the principle is
|
||||
there. These four basic components - however they are accessed - links
|
||||
to the *Portal Session*, which is the actual common interface between
|
||||
the different low-level protocols and Evennia.
|
||||
|
||||
Portal Sessions
|
||||
---------------
|
||||
|
||||
A *Portal Session* is an Evennia-specific thing. It must be a class
|
||||
inheriting from ``src.server.session.Session``. If you read the Telnet
|
||||
example above, the Protocol and Session are infact sometimes
|
||||
conveniently implemented in the same class through multiple inheritance.
|
||||
At startup the Portal creates and adds the Portal Session to its
|
||||
*sessionhandler*. While doing so, the session also gets assigned a
|
||||
property ``sessionhandler`` that refers to that very handler. This is
|
||||
important since the handler holds all methods relevant for sending and
|
||||
receiving data to and from the Server.
|
||||
|
||||
Whereas we don't know the method names of a Twisted Protocol (this can
|
||||
vary from protocol to protocol), the Session has a strict naming scheme
|
||||
that may not change; it is the glue that connects the Protocol to
|
||||
Evennia (along with some other convenient features).
|
||||
|
||||
The Session class must implement the following method hooks (which must
|
||||
be named exactly like this):
|
||||
|
||||
- ``disconnect()`` - called when manually disconnecting. Must call the
|
||||
protocol-specific disconnect method (e.g. ``connectionLost`` above)
|
||||
- ``data_out(string="", data=None)`` - data from Evennia to player.
|
||||
This method should handle any protocol-specific processing before
|
||||
relaying data on to a send-method like ``self.sendLine()`` mentioned
|
||||
above. ``string`` is normally a raw text string with formatting.
|
||||
``data`` can be a collection of any extra information the server want
|
||||
to give to the protocol- it's completely up to the Protocol to handle
|
||||
this. To take an example, telnet assumes ``data`` to be either
|
||||
``None`` or a dictionary with flags for how the text should be
|
||||
parsed. From inside Evennia, ``data_out`` is often called with the
|
||||
alias ``msg`` instead.
|
||||
|
||||
Out-of-band communication
|
||||
-------------------------
|
||||
|
||||
Out-of-band communication (OOB) is data being sent to and fro the
|
||||
player's client and the server on the protocol level, often due to the
|
||||
request of the player's client software rather than any sort of active
|
||||
input by the player. There are two main types:
|
||||
|
||||
- Data requested by the client to which the server responds
|
||||
immediately. This could for example be data that should go into a
|
||||
window that the client just opened up.
|
||||
- Data the server sends to the client to keep it up-to-date. A common
|
||||
example of this is something like a graphical health bar - *whenever*
|
||||
the character's health status changes the server sends this data to
|
||||
the client so it can update the bar graphic. This sending could also
|
||||
be done on a timer, for example updating a weather map regularly.
|
||||
|
||||
To communicate to the client, there are a range of protocols available
|
||||
for MUDs, supported by different clients, such as MSDP and GMCP. They
|
||||
basically implements custom telnet negotiation sequences and goes into a
|
||||
custom Evennia Portal protocol so Evennia can understand it.
|
||||
|
||||
It then needs to translate each protocol-specific function into an
|
||||
Evennia function name - specifically a name of a module-level function
|
||||
you define in the module given by ``settings.OOB_FUNC_MODULE``. These
|
||||
function will get the session/character as first argument but is
|
||||
otherwise completely free of form. The portal packs all function names
|
||||
and eventual arguments they need in a dictionary and sends them off to
|
||||
the Server by use of the ``sessionhandler.oob_data_in()`` method. On the
|
||||
Server side, the dictionary is parsed, and the correct functions in
|
||||
``settings.OOB_FUNC_MODULE`` are called with the given arguments. The
|
||||
results from this function are again packed in a dictionary (keyed by
|
||||
function name) and sent back to the portal. It will appear in the Portal
|
||||
session's ``oob_data_out(data)`` method.
|
||||
|
||||
So to summarize: To implement a Portal protocol with OOB communication
|
||||
support, you need to first let your normal ``getData`` method somehow
|
||||
parse out the special protocol format format coming in from the client
|
||||
(MSDP, GMCP etc). It needs to translate what the client wants into
|
||||
function names matching that in the ``OOB_FUNC_MODULE`` - these
|
||||
functions need to be created to match too of course. The function name
|
||||
and arguments are packed in a dictionary and sent off to the server via
|
||||
``sessionhandler.oob_data_in()``. Finally, the portal session must
|
||||
implement ``oob_data_out(data)`` to handle the data coming back from
|
||||
Server. It will be a dictionary of return values keyed by the function
|
||||
names.
|
||||
|
||||
Example of out-of-band calling sequence
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Let's say we want our client to be able to request the character's
|
||||
current health, stamina and maybe some skill values. In our Portal
|
||||
protocol we somehow parse the incoming data stream and figure out what
|
||||
the request for health looks like. We map this to the Evennia
|
||||
``get_health`` function.
|
||||
|
||||
We point ``settings.OOB_FUNC_MODULE`` to someplace in ``game/`` and
|
||||
create a module there with the following functions:
|
||||
|
||||
::
|
||||
|
||||
# the caller is automatically added as first argument
|
||||
def get_health(character):
|
||||
"Get health, stored as simple attribute"
|
||||
return character.db.health
|
||||
def get_stamina(character):
|
||||
"Get stamina level, stored as simple attribute"
|
||||
return character.db.stamina
|
||||
def get_skill(character, skillname, master=False):
|
||||
"""we assume skills are stored as a dictionary
|
||||
stored in an attribute. Master skills are
|
||||
stored separately (for whatever reason)"""
|
||||
if master:
|
||||
return character.db.skills_master.get(skillname, "NoSkill")
|
||||
return character.db.skills.get(skillname, "NoSkill")
|
||||
|
||||
Done, the functions will return what we want assuming Characters do
|
||||
store this information in our game. Let's finish up the first part of
|
||||
the portal protocol:
|
||||
|
||||
::
|
||||
|
||||
# this method could be named differently depending on the
|
||||
# protocol you are using (this is telnet)
|
||||
def lineReceived(self, string):
|
||||
# (does stuff to analyze the incoming string)
|
||||
# ...
|
||||
outdict = {}
|
||||
if GET_HEALTH:
|
||||
# call get_health(char)
|
||||
outdict["get_health"] = ([], {})
|
||||
elif GET_STAMINA:
|
||||
# call get_mana(char)
|
||||
outdict["get_stamina"] = ([], {})
|
||||
elif GET_MASTER_SKILL_SMITH:
|
||||
# call get_skill(char, "smithing", master=True)
|
||||
outdict["get_skill"] = (["smithing"], {'master':True})
|
||||
|
||||
[...]
|
||||
|
||||
self.sessionhandler.oob_data_out(outdict)
|
||||
|
||||
The Server will properly accept this and call the relevant functions to
|
||||
get their return values for the health, stamina and skill. The return
|
||||
values will be packed in a dictionary keyed by function name before
|
||||
being passed back to the Portal. We need to define
|
||||
``oob_data_out(data)`` in our portal protocol to catch this:
|
||||
|
||||
::
|
||||
|
||||
def oob_data_out(self, data):
|
||||
# the indata is a dictionary {funcname:retval}
|
||||
|
||||
outstring = ""
|
||||
for funcname, retval in data.items():
|
||||
if funcname == 'get_health':
|
||||
# convert to the right format for sending back to client, store
|
||||
# in outstring ...
|
||||
[...]
|
||||
# send off using the protocols send method (this is telnet)
|
||||
sendLine(outstring)
|
||||
|
||||
As seen, ``oob_data`` takes the values and formats into a form the
|
||||
protocol understands before sending it off.
|
||||
|
||||
Implementing auto-sending
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To have the Server update the client regularly, simply create a global
|
||||
`Script <Scripts.html>`_ that upon each repeat creates the request
|
||||
dictionary (basically faking a request from the portal) and sends it
|
||||
directly to
|
||||
``src.server.sessionhandler.oob_data_in(session.sessid, datadict)``.
|
||||
Loop over all relevant sessions. The Server will treat this like a
|
||||
Portal call and data will be sent back to be handled by the portal as
|
||||
normal.
|
||||
|
||||
Adding custom Protocols
|
||||
=======================
|
||||
|
||||
Evennia has a plugin-system that allows you to add new custom Protocols
|
||||
without editing any files in ``src/``. To do this you need to add the
|
||||
protocol as a new "service" to the application.
|
||||
|
||||
Take a look at for example ``src/server/portal.py``, notably the
|
||||
sections towards the end of that file. These are where the various
|
||||
in-built services like telnet, ssh, webclient etc are added to the
|
||||
Portal (there is an equivalent but shorter list in
|
||||
``src/server.server.py``.
|
||||
|
||||
To add a new service of your own (for example your own custom client
|
||||
protocol) to e.g. the Portal, create a new module in
|
||||
``game/gamesrc/conf/``. Let's call it ``myproc_plugin.py``. We need to
|
||||
tell the Server or Portal that they need to import this module. In
|
||||
``game/settings.py``, add one of the following:
|
||||
|
||||
::
|
||||
|
||||
# add to the Server
|
||||
SERVER_SERVICES_PLUGIN_MODULES.append('game.gamesrc.conf.myproc_plugin')
|
||||
# or, if you want to add to the Portal
|
||||
PORTAL_SERVICES_PLUGIN_MODULES.append('game.gamesrc.conf.myproc_plugin')
|
||||
|
||||
This module can contain whatever you need to define your protocol, but
|
||||
it *must* contain a function ``start_plugin_services(app)``. This is
|
||||
called by the Portal as part of its upstart. The function
|
||||
``start_plugin_services`` must contain all startup code the server need.
|
||||
The ``app`` argument is a reference to the Portal application itself so
|
||||
the custom service can be added to it. The function should not return
|
||||
anything.
|
||||
|
||||
This is how it can look:
|
||||
|
||||
::
|
||||
|
||||
# game/gamesrc/conf/myproc_plugin.py
|
||||
|
||||
# here the new Portal Twisted protocol is defined
|
||||
class MyOwnFactory( ... ):
|
||||
[...]
|
||||
|
||||
# some configs
|
||||
MYPROC_ENABLED = True # convenient off-flag to avoid having to edit settings all the time
|
||||
MY_PORT = 6666
|
||||
|
||||
def start_plugin_services(portal):
|
||||
"This is called by the Portal during startup"
|
||||
if not MYPROC_ENABLED:
|
||||
return
|
||||
# output to list this with the other services at startup
|
||||
print " myproc: %s" % MY_PORT
|
||||
|
||||
# some setup (simple example)
|
||||
factory = MyOwnFactory()
|
||||
my_service = internet.TCPServer(MY_PORT, factory)
|
||||
# all Evennia services must be uniquely named
|
||||
my_service.setName("MyService")
|
||||
# add to the main portal application
|
||||
portal.services.addService(my_service)
|
||||
|
||||
One the module is defined and targeted in settings, just reload the
|
||||
server and your new protocol/services should start with the others.
|
||||
|
||||
Assorted notes
|
||||
==============
|
||||
|
||||
To take two examples, Evennia supports the *telnet* protocol as well as
|
||||
*webclient*, a custom ajax protocol. You'll find that whereas telnet is
|
||||
a textbook example of a Twisted protocol as seen above, the ajax client
|
||||
protocol looks quite different due to how it interacts with the
|
||||
webserver through long-polling (comet) style requests. All the necessary
|
||||
parts mentioned above are still there, but implemented in very different
|
||||
ways.
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
On MUX and Softcode: A brief overview
|
||||
=====================================
|
||||
|
||||
Evennia was originally created in order to provide a MUX/MUSH-style
|
||||
development environment without the kludgery of softcode. Although it
|
||||
has since grown to be adaptable to any style of MU\ ``*`` it still ships
|
||||
with 'MUX-like' default commands.
|
||||
|
||||
This document will provide a quick overview of what softcode is, why it
|
||||
drove us to write Evennia, and why we instead use Python.
|
||||
|
||||
Softcode is a very simple programming language that was created for
|
||||
in-game development on TinyMUD derivatives such as MUX, PennMUSH,
|
||||
TinyMUSH, and RhostMUSH. The idea is that by providing a stripped down,
|
||||
minimalistic language for in-game use, you can allow quick and easy
|
||||
building and game development to happen without having to learn C/C++.
|
||||
There is an added benefit of not having to have to hand out shell access
|
||||
to all developers, and permissions can be used to alleviate many
|
||||
security problems.
|
||||
|
||||
Writing and installing softcode is done through a MUD client. As is
|
||||
such, it is not a formatted language. Each softcode function is a single
|
||||
line of varying size. Some functions can be a half of a page long due to
|
||||
this, which is obviously not very readable. The lack of formatting is
|
||||
one of the big reasons Evennia exists today. Bigger projects tend to
|
||||
choke under their own weight after time unless your entire staff has a
|
||||
good understanding of functional programming practices.
|
||||
|
||||
Examples of Softcode
|
||||
--------------------
|
||||
|
||||
Here is a simple 'Hello World!' command:
|
||||
|
||||
::
|
||||
|
||||
@set me=HELLO_WORLD.C:$hello:@pemit %#=Hello World!
|
||||
|
||||
Pasting this into a MUX/MUSH and typing 'hello' will theoretically yield
|
||||
'Hello World!', assuming certain flags are not set on your player
|
||||
object.
|
||||
|
||||
Setting attributes is done via ``@set``. Softcode also allows the use of
|
||||
the ampersand (``&``) symbol. This shorter version looks like this:
|
||||
|
||||
::
|
||||
|
||||
&HELLO_WORLD.C me=$hello:@pemit %#=Hello World!
|
||||
|
||||
Perhaps I want to break the Hello World into an attribute which is
|
||||
retrieved when emitting:
|
||||
|
||||
::
|
||||
|
||||
&HELLO_VALUE.D me=Hello World
|
||||
&HELLO_WORLD.C me=$hello:@pemit %#=[v(HELLO_VALUE.D)]
|
||||
|
||||
The v() function returns the HELLO\_VALUE.D attribute on the object that
|
||||
the command resides (``me``, which is yourself in this case). This
|
||||
should yield the same output as the first example.
|
||||
|
||||
If you are still curious about how Softcode works, take a look at some
|
||||
external resources:
|
||||
|
||||
- `http://www.tinymux.com/wiki/index.php/Softcode <http://www.tinymux.com/wiki/index.php/Softcode>`_
|
||||
- `http://www.duh.com/discordia/mushman/man2x1 <http://www.duh.com/discordia/mushman/man2x1>`_
|
||||
|
||||
Problems with Softcode
|
||||
----------------------
|
||||
|
||||
Softcode is excellent at what it was intended for: simple things. It is
|
||||
an incredible tool if used for its intended purposes - an interactive
|
||||
object, a room with ambiance, simple global commands, simple economies
|
||||
and coded systems. However, once you start to try to write something
|
||||
like a complex combat system or a higher end economy, you're likely to
|
||||
find yourself buried under a mountain of functions that span various
|
||||
objects and are of various length.
|
||||
|
||||
Not to mention, softcode is not an inherently fast language. It is not
|
||||
compiled, it is parsed with each calling of a function. While MUX and
|
||||
MUSH parsers have jumped light years ahead of where they were even seven
|
||||
or eight years ago, they can still stutter under the weight of the more
|
||||
complex systems if not designed properly.
|
||||
|
||||
To further illustrate the lack of readability for building larger
|
||||
systems in softcode, here is another example, PennMush softcode this
|
||||
time, for implementing an "+info" command (it allows you to store pages
|
||||
of extra character info that is later confirmed by admins and can be
|
||||
viewed by other players):
|
||||
|
||||
::
|
||||
|
||||
&INC`SET u(ifo)=@include u(ifo)/INC`TARGET;@include \
|
||||
u(ifo)/INC`FILENAME;@assert strlen(%q<filename>)=@nspemit \
|
||||
%#=announce(INFO)%BERROR: Info file name empty.;@switch/inline \
|
||||
gt(strlen(setr(attr,u(u(ifo)/FUN`FINDFILE,%q<target>,%q<filename>))),0)=1,{@assert \
|
||||
or(isadmin(%#),strmatch(%q<target>,%#))=@nspemit \
|
||||
%#=announce(INFO)%BERROR: You may not change another's Info \
|
||||
files.;@switch/inline \
|
||||
or(getstat(%q<target>/%q<attr>`FLAGS,Hidden),getstat(%q<target>/%q<attr>`FLAGS,Approved))=1,{@assert \
|
||||
isadmin(%#)=@nspemit %#=announce(INFO)%BERROR: That Info File may not \
|
||||
be changed by you.}},0,{@break gt(strlen(%q<filename>),18)=@nspemit \
|
||||
%#=ERROR: Info names are limited to 18 characters or less.;@break \
|
||||
regmatchi(%q<filename>,\\|)=@nspemit %#=ERROR: Pipe symbols are not \
|
||||
allowed in info names.;@break regmatchi(%q<filename>,\/)=@nspemit \
|
||||
%#=ERROR: Slashes symbols are not allowed in info names.;@assert \
|
||||
strlen(%1)=ERROR: Text field empty. To delete an +info file, use \
|
||||
+info/delete.};&[strfirstof(%q<attr>,setr(attr,D`INFOFILE`[nextslot(%q<target>,D`INFOFILE)]))] \
|
||||
%q<target>=%q<filename>;&%q<attr>`CONTENTS %q<target>=%1;th \
|
||||
setstat(%q<target>/%q<attr>`FLAGS,SetBy,%#);th \
|
||||
setstat(%q<target>/%q<attr>`FLAGS,SetOn,secs());@switch/inline \
|
||||
strmatch(%#,%q<target>)=1,{@nspemit %#=announce(INFO)%BYou set your \
|
||||
%q<filename> Info File},{@nspemit %#=announce(INFO)%BYou set \
|
||||
[name(%q<target>)]'s %q<filename> Info File!;@nspemit \
|
||||
%q<target>=announce(INFO)%B%n set your %q<filename> Info File!}
|
||||
|
||||
(Note that the softcode is actually all one line, it was split to be
|
||||
viewable on this wiki). Below is the rough Evennia equivalent
|
||||
functionality as an Evennia command method, written by the same softcode
|
||||
author after a week of learning Evennia:
|
||||
|
||||
::
|
||||
|
||||
def switch_set(self,target,files,rhs,isadmin):
|
||||
if self.caller is not target and not isadmin:
|
||||
self.caller.msg("ERROR: You may not set that person's files.")
|
||||
return
|
||||
if not self.rhs:
|
||||
self.caller.msg("ERROR: No info file contents entered to set.")
|
||||
return
|
||||
for info in files:
|
||||
if not re.match('^[\w-]+$', info.lower().strip()):
|
||||
self.caller.msg("ERROR: File '" + info + \
|
||||
"' could not be set: may only use alphanumeric characters, -, and spaces in info names.")
|
||||
elif self.files.get(info.lower().strip(),{}).get("approved",None) is True:
|
||||
self.caller.msg("ERROR: File '" + info.strip() + "' could not be set: file is approved.")
|
||||
else:
|
||||
self.files[info.lower().strip()] = {"contents":rhs, "setby":self.caller,
|
||||
"seton":"timestamp", "displayname":info.strip()}
|
||||
if target is self.caller:
|
||||
self.caller.msg("Info File '" + info.strip() + "' set!")
|
||||
else:
|
||||
self.caller.msg("Info File '" + info.strip() + "' set!")
|
||||
target.caller.msg(self.caller.key + " set your '" + info.strip() + "' info file!")
|
||||
target.db.infofiles = dict(self.files)
|
||||
return
|
||||
|
||||
The details of the implementation are unimportant, the difference in
|
||||
readability is the main point here.
|
||||
|
||||
Changing Times
|
||||
--------------
|
||||
|
||||
Now that starting text-based games is easy and an option for even the
|
||||
most technically inarticulate, new projects are a dime a dozen. People
|
||||
are starting new MUDs every day with varying levels of commitment and
|
||||
ability. Because of this shift from fewer, larger, well-staffed games to
|
||||
a bunch of small, one or two developer games, some of the benefit of
|
||||
softcode fades.
|
||||
|
||||
Softcode is great in that it allows a mid to large sized staff all work
|
||||
on the same game without stepping on one another's toes. As mentioned
|
||||
before, shell access is not necessary to develop a MUX or a MUSH.
|
||||
However, now that we are seeing a lot more small, one or two-man shops,
|
||||
the issue of shell access and stepping on each other's toes is a lot
|
||||
less.
|
||||
|
||||
Our Solution
|
||||
============
|
||||
|
||||
For the hobbyist who would like the option to use a full-featured
|
||||
language, we created Evennia. We are no longer bound to single lines of
|
||||
softcode. Game developers now have access to the entire library of
|
||||
Python modules out there in the wild. Our complex systems may be
|
||||
organized neatly into modules, sub-modules, or even broken out into
|
||||
entire Python packages.
|
||||
|
||||
So what is *not* included in Evennia proper is a MUX/MOO-like online
|
||||
player building system. Advanced coding and building in Evennia is
|
||||
primarily intended to be done outside the game, in full-fledged Python
|
||||
modules. We feel that with a small development team you are better off
|
||||
using a professional source-control system (svn, git, bazaar, mercurial
|
||||
etc) anyway.
|
||||
|
||||
Your Solution
|
||||
=============
|
||||
|
||||
Adding very advanced and flexible building commands to your game will
|
||||
probably often be enough to satisfy most creative builders. However, if
|
||||
you really, *really* want to offer online coding there is of course
|
||||
nothing stopping you from adding that to Evennia, no matter our
|
||||
recommendations. You could even re-implement MUX' softcode in Python
|
||||
should you be very ambitious.
|
||||
|
||||
There is an experimental restricted python environment named *Evlang* to
|
||||
be found in our *contrib* folder. Being in this folder means it's not a
|
||||
part of the core server and is completely optional to use. Evlang could
|
||||
serve as a starting point if you want to go down the route of simple
|
||||
online player coding.
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
Running Evennia
|
||||
===============
|
||||
|
||||
You control Evennia from ``game/`` using the ``evennia.py`` program.
|
||||
Below are described the various management options. You can also start
|
||||
the program without any arguments or use the *menu* option to get a
|
||||
multiple-choice menu instead.
|
||||
|
||||
::
|
||||
|
||||
python evennia.py menu
|
||||
|
||||
Starting Evennia
|
||||
----------------
|
||||
|
||||
Evennia consists of two components, the Evennia `Server and
|
||||
Portal <PortalAndServer.html>`_. Briefly, the *Server* is what is
|
||||
running the mud. It handles all game-specific things but don't care
|
||||
exactly how players connect, only that they have. The *Portal* is a
|
||||
gateway to which players connect. It knows everything about telnet, ssh,
|
||||
webclient protocols etc but very little about the game. Both are
|
||||
required for a functioning mud.
|
||||
|
||||
::
|
||||
|
||||
python evennia.py start
|
||||
|
||||
The above command automatically starts both Portal and Server at the
|
||||
same time, logging to the log files in ``game/log``.
|
||||
|
||||
If you rather see error messages etc directly in the terminal (useful
|
||||
for quickly debugging your code), you use the -i (interactive) flag:
|
||||
|
||||
::
|
||||
|
||||
python evennia.py -i start
|
||||
|
||||
This will start the *Server* in interactive mode. The Portal will
|
||||
continue to log to its log file. This is normally what you want unless
|
||||
you are debugging the Portal.
|
||||
|
||||
You can also start the two components one at a time.
|
||||
|
||||
::
|
||||
|
||||
python evennia.py start server
|
||||
python evennia.py start portal
|
||||
|
||||
Adding -i to either of these explicit commands will start that component
|
||||
in interactive mode so it logs to the terminal rather than to log file.
|
||||
|
||||
Reloading
|
||||
---------
|
||||
|
||||
The act of *reloading* means the *Server* program is shut down and then
|
||||
restarted again. In the default cmdset you initiate a reload by using
|
||||
the ``@reload`` command from inside the game. The game will be briefly
|
||||
paused for all players as the server comes back up (since they are all
|
||||
connected to the *Portal*, their connections are not lost).
|
||||
|
||||
Reloading is as close to a "warm reboot" you can get. It reinitializes
|
||||
all code of Evennia, but doesn't kill "persistent" scripts. It also
|
||||
calls ``at_server_reload()`` hooks on all objects so you can save
|
||||
eventual temporary properties you want.
|
||||
|
||||
You can also reload the server from outside the game (not available in
|
||||
Windows):
|
||||
|
||||
::
|
||||
|
||||
python evennia.py reload
|
||||
|
||||
Resetting
|
||||
---------
|
||||
|
||||
*Resetting* is the equivalent of a "cold reboot" of the server - it will
|
||||
restart but will behave as if it was fully shut down. You initiate a
|
||||
reset using the ``@reset`` command from inside the game. As with a
|
||||
reload, no players will be disconnected during a shutdown. It will
|
||||
however purge all non-persistent scripts and will call
|
||||
``at_server_shutdown()`` hooks. It can be a good way to clean unsafe
|
||||
scripts during development, for example.
|
||||
|
||||
A reset is equivalent to
|
||||
|
||||
::
|
||||
|
||||
python evennia.py stop server
|
||||
python evennia.py start server
|
||||
|
||||
Shutting down
|
||||
-------------
|
||||
|
||||
A full shutdown closes Evennia completely, both Server and Portal. All
|
||||
players will be booted and systems saved and turned off cleanly. From
|
||||
inside the game you initiate a shutdown with the ``@shutdown`` command.
|
||||
|
||||
From command line you do
|
||||
|
||||
::
|
||||
|
||||
python.py evennia.py stop
|
||||
|
||||
You will see messages of both Server and Portal closing down.
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
Notes on text encodings
|
||||
=======================
|
||||
|
||||
Evennia is a text-based game server. This makes it important to
|
||||
understand how it actually deals with data in the form of text.
|
||||
|
||||
Text *byte encodings* describe how a string of text is actually stored
|
||||
in the computer - that is, the particular sequence of bytes used to
|
||||
represent the letters of your particular alphabet. A common encoding
|
||||
used in English-speaking languages is the *ASCII* encoding. This
|
||||
describes the letters in the English alphabet (Aa-Zz) as well as a bunch
|
||||
of special characters. For describing other character sets (such as that
|
||||
of other languages with other letters than English), sets with names
|
||||
such as *Latin-1*, *ISO-8859-3* and *ARMSCII-8* are used. There are
|
||||
hundreds of different byte encodings in use around the world.
|
||||
|
||||
In contrast to the byte encoding is the *unicode representation*. The
|
||||
unicode is an internationally agreed-upon table describing essentially
|
||||
all available letters you could ever want to print. Everything from
|
||||
English to Chinese alphabets and all in between. So what Evennia (as
|
||||
well as Python and Django) does is to store everything in Unicode
|
||||
internally, but then converts the data to one of the encodings whenever
|
||||
outputting data to the user.
|
||||
|
||||
The problem is that when receiving a string of bytes from a user it's
|
||||
impossible for Evennia to guess which encoding was used - it's just a
|
||||
bunch of bytes! Evennia must know the encoding in order to convert back
|
||||
and from the correct unicode representation.
|
||||
|
||||
How to customize encodings
|
||||
==========================
|
||||
|
||||
As long as you stick to the standard ASCII character set (which means
|
||||
the normal English characters, basically) you should not have to worry
|
||||
much about this section.
|
||||
|
||||
If you want to build your game in another language however, or expect
|
||||
your users to want to use special characters not in ASCII, you need to
|
||||
consider which encodings you want to support.
|
||||
|
||||
As mentioned, there are many, many byte-encodings used around the world.
|
||||
It should be clear at this point that Evennia can't guess but has to
|
||||
assume or somehow be told which encoding you want to use to communicate
|
||||
with the server. Basically the encoding used by your client must be the
|
||||
same encoding used by the server. This can be customized in two
|
||||
complementary ways.
|
||||
|
||||
#. Point users to the default ``@encoding`` command. This allows them to
|
||||
themselves set which encoding they (and their client of choice) uses.
|
||||
Whereas data will remain stored as unicode internally in Evennia, all
|
||||
data received from and sent to this particular player will be
|
||||
converted to the given format before transmitting.
|
||||
#. As a back-up, in case the user-set encoding translation is erroneous
|
||||
or fails in some other way, Evennia will fall back to trying with the
|
||||
names defined in the settings variable ``ENCODINGS``. This is a list
|
||||
of encoding names Evennia will try, in order, before giving up and
|
||||
giving an encoding error message.
|
||||
|
||||
Note that having to try several different encodings every input/output
|
||||
adds unneccesary overhead. Try to guess the most common encodings you
|
||||
players will use and make sure these are tried first. The International
|
||||
*UTF-8* encoding is what Evennia assumes by default (and also what
|
||||
Python/Django use normally). See the Wikipedia article
|
||||
`here <http://en.wikipedia.org/wiki/Text_encodings>`_ for more help.
|
||||
|
|
@ -1,232 +0,0 @@
|
|||
Ticker Scripts ("Heartbeats")
|
||||
=============================
|
||||
|
||||
A common way to implement a dynamic MUD is by using "tickers", also
|
||||
known as "heartbeats". A ticker is a timer that fires ("ticks" at a
|
||||
given interval. The tick triggers updates in various game systems.
|
||||
Tickers are very common or even unavoidable in other mud code bases,
|
||||
where many systems are hard coded to rely on the concept of the global
|
||||
'tick'. Evennia has no such notion - the use of tickers (or not) is very
|
||||
much up to your game and which requirements you have.
|
||||
`Scripts <Scripts.html>`_ are powerful enough to act as any type of
|
||||
counter you want and the "ticker recipe" is just one convenient (and
|
||||
occationally effective) way of cranking the wheels.
|
||||
|
||||
When \_not\_ to use tickers
|
||||
---------------------------
|
||||
|
||||
First, let's consider when *not* to use tickers. Even if you are used to
|
||||
habitually relying on tickers for everything in other code bases, stop
|
||||
and think about what you really need them for. Notably you should
|
||||
*never* try implement a ticker to *catch changes*. Think about it - you
|
||||
might have to run the ticker every second to react to the change fast
|
||||
enough. Most likely nothing will have changed most of the time. So you
|
||||
are doing pointless calls (since skipping the call gives the same result
|
||||
as doing it). Making sure nothing's changed might even be
|
||||
computationally expensive depending on the complexity of your system.
|
||||
Not to mention that you might need to run the check on every object in
|
||||
the database. Every second. Just to maintain status quo.
|
||||
|
||||
Rather than checking over and over on the off-chance that something
|
||||
changed, consider a more proactive approach. Can you maybe implement
|
||||
your rarely changing system to *itself* report its change *whenever* it
|
||||
happens? It's almost always much cheaper/efficient if you can do things
|
||||
"on demand". Evennia itself uses hook methods for this very reason. The
|
||||
only real "ticker"-like thing in the default set is the one that saves
|
||||
the uptime (which of course *always* changes every call).
|
||||
|
||||
So, in summary, if you consider a ticker script that will fire very
|
||||
often but which you expect to do nothing 99% of the time, ponder if you
|
||||
can handle things some other way. A self-reporting solution is usually
|
||||
cheaper also for fast-updating properties. The main reason you do need a
|
||||
ticker is rather when the timing itself is important.
|
||||
|
||||
Ticker example - night/day system
|
||||
=================================
|
||||
|
||||
Let's take the example of a night/day system. The way we want to use
|
||||
this is to have all outdoor rooms echo time-related messages to the
|
||||
room. Things like "The sun sets", "The church bells strike midnight" and
|
||||
so on.
|
||||
|
||||
One could imagine every `Room <Objects.html>`_ in the game having a
|
||||
script running on themselves that fire regularly. It's however much
|
||||
better (easier to handle and using a lot less computing resources) to
|
||||
use a single, global, ticker script. You create a "global" Script the
|
||||
way you would any Script except you don't assign it to any particular
|
||||
object.
|
||||
|
||||
To let objects use this global ticker, we will utilize a *subscription
|
||||
model*. In short this means that our Script holds an internal list of
|
||||
"subscribing" rooms. Whenever the Script fires it loops through this
|
||||
list and calls a given method on the subscribed object.
|
||||
|
||||
::
|
||||
|
||||
from ev import Script
|
||||
class TimeTicker(Script):
|
||||
"""
|
||||
This implements a subscription model
|
||||
"""
|
||||
def at_script_creation(self):
|
||||
"Called when script is created"
|
||||
self.key = "daynight_ticker"
|
||||
self.interval = 60 * 60 * 2 # every two hours
|
||||
self.persistent = True
|
||||
# storage of subscriptions
|
||||
self.db.subscriptions = []
|
||||
def subscribe(self, obj):
|
||||
"add object to subscription"
|
||||
if obj not in self.db.subscriptions:
|
||||
self.db.subscriptions.append(obj)
|
||||
def unsubscribe(self, obj):
|
||||
"remove object from subscription"
|
||||
try:
|
||||
del_ind = self.db.subscriptions.index(obj)
|
||||
del self.db.subscriptions[del_ind]
|
||||
except ValueError:
|
||||
pass
|
||||
def list_subscriptions(self):
|
||||
"echo all subscriptions"
|
||||
return self.db.subscriptions
|
||||
def at_repeat(self):
|
||||
"called every self.interval seconds"
|
||||
for obj in self.db.subscriptions:
|
||||
obj.echo_daynight()
|
||||
|
||||
This depends on your subscribing weather rooms defining the
|
||||
``echo_daynight()`` method (presumably outputting some sort of message).
|
||||
|
||||
It's worth noting that the simple recipe above can be used for all sorts
|
||||
of tickers. Rooms are maybe not likely to unsubscribe very often, but
|
||||
consider a mob that "deactivates" when Characters are not around for
|
||||
example.
|
||||
|
||||
This particular TimeTicker-example could be further optimized. All
|
||||
subscribed rooms are after all likely to echo the same time related
|
||||
text. So this text can be pre-set already at the Script level and echoed
|
||||
to each room directly. This way the subscribed objects won't need a
|
||||
custom ``echo_daynight()`` method at all.
|
||||
|
||||
Here's the more efficient example (only showing the new stuff).
|
||||
|
||||
::
|
||||
|
||||
...
|
||||
ECHOES = ["The sun rises in the east.",
|
||||
"It's mid-morning",
|
||||
"It's mid-day", ...]
|
||||
|
||||
class TimerTicker(Script):
|
||||
...
|
||||
def at_script_creation(self):
|
||||
...
|
||||
self.db.timeindex = 0
|
||||
...
|
||||
def at_repeat(self):
|
||||
"called every self.repeat seconds"
|
||||
echo = ECHOES[self.db.timeindex]
|
||||
# msg_contents() is a standard method, so this
|
||||
# ticker works with any object.
|
||||
for obj in self.db.subscriptions:
|
||||
obj.msg_contents(echo)
|
||||
# resetting/stepping the counter
|
||||
if self.db.timeindex == len(ECHOES) - 1:
|
||||
self.db.timeindex = 0
|
||||
else:
|
||||
self.db.timeindex += 1
|
||||
|
||||
Note that this ticker is unconnected to Evennia's default global in-game
|
||||
time script, and will thus be out of sync with that. A more advanced
|
||||
example would entail this script checking the current game time (in
|
||||
``at_script_creation()`` or in ``at_start()``) so it can pick a matching
|
||||
starting point in its cycle.
|
||||
|
||||
Testing the night/day ticker
|
||||
----------------------------
|
||||
|
||||
Tickers are really intended to be created and handled from your custom
|
||||
commands or in other coded systems. An "outdoor" room typeclass would
|
||||
probably subscribe to the ticker itself from its
|
||||
``at_object_creation()`` hook. Same would be true for mobs and other
|
||||
objects that could respond to outside stimuli (such as the presence of a
|
||||
player) in order to subscribe/unsubscribe.
|
||||
|
||||
There is no way to create a global script using non-superuser commands,
|
||||
and even if you could use ``@script`` to put it on an object just to
|
||||
test things out, you also need a way to subscribe objects to it.
|
||||
|
||||
With ``@py`` this would be something like this:
|
||||
|
||||
::
|
||||
|
||||
@py ev.create_script(TimeTicker) # if persistent=True, this only needs to be done once
|
||||
@py ev.search_script("daynight_ticker").subscribe(self.location)
|
||||
|
||||
|
||||
If you think you will use these kind of ticker scripts a lot, you might
|
||||
want to create your own command for adding/removing subscriptions to
|
||||
them. Here is a complete example:
|
||||
|
||||
::
|
||||
|
||||
import ev
|
||||
class CmdTicker(ev.default_cmds.MuxCommand):
|
||||
"""
|
||||
adds/remove an object to a given ticker
|
||||
|
||||
Usage:
|
||||
@ticker[/switches] tickerkey [= object]
|
||||
Switches:
|
||||
add (default) - subscribe object to ticker
|
||||
del - unsubscribe object from ticker
|
||||
|
||||
This adds an object to a named ticker Script,
|
||||
if such a script exists. Such a script must have
|
||||
subsribe/unsubscripe functionality. If no object is
|
||||
supplied, a list of subscribed objects for this ticker
|
||||
will be returned instead.
|
||||
"""
|
||||
key = "@ticker"
|
||||
locks = "cmd:all()"
|
||||
help_category = "Building"
|
||||
|
||||
def func(self):
|
||||
if not self.args:
|
||||
self.caller.msg("Usage: @ticker[/switches] tickerkey [= object]")
|
||||
return
|
||||
tickerkey = self.lhs
|
||||
# find script
|
||||
script = ev.search_scripts(tickerkey)
|
||||
if not script:
|
||||
self.caller.msg("Ticker %s could not be found." % tickerkey)
|
||||
return
|
||||
# all ev.search_* methods always return lists
|
||||
script = script[0]
|
||||
# check so the API is correct
|
||||
if not (hasattr(script, "subscribe")
|
||||
and hasattr(script, "unsubscribe")
|
||||
and hasattr(script, "list_subscriptions"):
|
||||
self.caller.msg("%s can not be subscribed to." % tickerkey)
|
||||
return
|
||||
if not self.rhs:
|
||||
# no '=' found, just show the subs
|
||||
subs = [o.key for o in script.list_subscripionts()]
|
||||
self.caller.msg(", ".join(subs))
|
||||
return
|
||||
# get the object to add
|
||||
obj = self.caller.search(self.rhs)
|
||||
if not obj:
|
||||
# caller.search handles error messages
|
||||
return
|
||||
elif 'del' in self.switches:
|
||||
# remove a sub
|
||||
script.unsubscribe(obj)
|
||||
self.caller.msg("Unsubscribed %s from %s." % (obj.key, tickerkey)
|
||||
else:
|
||||
# default - add subscription
|
||||
script.subscribe(obj)
|
||||
self.caller.msg("Subscribed %s to ticker %s." % (obj.key, tickerkey))
|
||||
|
||||
This looks longer than it is, most of the length comes from comments and
|
||||
the doc string.
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
Tutorial World Introduction
|
||||
===========================
|
||||
|
||||
The *Tutorial World* is, quite simply, a small example of Evennia usage
|
||||
for you to learn from. It's also a functioning (if small) game - a
|
||||
single-player quest area with some 20 rooms that you can explore on your
|
||||
quest to find a mythical weapon.
|
||||
|
||||
The source code is fully documented and you can find the whole thing in
|
||||
``contrib/tutorial_world``.
|
||||
|
||||
Some features exemplified by the tutorial world:
|
||||
|
||||
- Tutorial command, giving "behind-the-scenes" help for every room and
|
||||
some of the special objects
|
||||
- Hidden exits
|
||||
- Objects with multiple custom interactions
|
||||
- Large-area rooms
|
||||
- Outdoor weather rooms
|
||||
- Dark room, needing light source
|
||||
- Puzzle object
|
||||
- Multi-room puzzle
|
||||
- Aggressive mobile with roam, pursue and battle state-engine AI
|
||||
- Weapons, also used by mobs
|
||||
- Simple combat system with attack/defend commands
|
||||
- Object spawn
|
||||
- Teleporter trap rooms
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
The tutorial world consists of a a few modules in
|
||||
``contrib/tutorial_world/`` containing custom
|
||||
`Typeclasses <Typeclasses.html>`_ for `rooms and
|
||||
objects <Objects.html>`_, associated `commands <Commands.html>`_ and a
|
||||
few custom `scripts <Scripts.html>`_ to make things tick.
|
||||
|
||||
These reusable bits and pieces are then put together into a functioning
|
||||
game area ("world" is maybe too big a word for such a small zone) using
|
||||
a `batch script <BatchProcessors.html>`_ called ``build.ev``. To
|
||||
install, log into the server as the superuser (user #1) and run:
|
||||
|
||||
::
|
||||
|
||||
@batchcommand contrib.tutorial_world.build
|
||||
|
||||
The world will be built (this might take a while, so don't rerun the
|
||||
command even if it seems the system has frozen). After finishing you
|
||||
will end up back in Limbo with a new exit called ``tutorial``.
|
||||
|
||||
An alternative is
|
||||
|
||||
::
|
||||
|
||||
@batchcommand/interactive contrib.tutorial_world.build
|
||||
|
||||
with the /interactive switch you are able to step through the building
|
||||
process at your own pace to see what happens in detail.
|
||||
|
||||
To play the tutorial "correctly", you should *not* do so as superuser.
|
||||
The reason for this is that many game systems ignore the presence of a
|
||||
superuser and will thus not work as normal. Log out, then reconnect.
|
||||
From the login screen, create a new, non-superuser character for playing
|
||||
instead. As superuser you can of course examine things "under the hood"
|
||||
later if you want.
|
||||
|
||||
Gameplay
|
||||
--------
|
||||
|
||||
*To get into the mood of this miniature quest, imagine you are an
|
||||
adventurer out to find fame and fortune. You have heard rumours of an
|
||||
old castle ruin by the coast. In its depth a warrior princess was buried
|
||||
together with her powerful magical weapon - a valuable prize, if it's
|
||||
true. Of course this is a chance to adventure that you cannot turn
|
||||
down!*
|
||||
|
||||
*You reach the ocean in the midst of a raging thunderstorm. With wind
|
||||
and rain screaming in your face you stand where the moor meets the sea
|
||||
along a high, rocky coast ...*
|
||||
|
||||
- Look at everything.
|
||||
- Some objects are interactive in more than one way. Use the normal
|
||||
``help`` command to get a feel for which commands are available at
|
||||
any given time. (use the command ``tutorial`` to get insight behind
|
||||
the scenes of the tutorial).
|
||||
- In order to fight, you need to first find some type of weapon.
|
||||
|
||||
- *slash* is a normal attack
|
||||
- *stab* launches an attack that makes more damage but has a lower
|
||||
chance to hit.
|
||||
- *defend* will lower the chance to taking damage on your enemy's
|
||||
next attack.
|
||||
|
||||
- You *can* run from a fight that feels too deadly. Expect to be chased
|
||||
though.
|
||||
- Being defeated is a part of the experience ...
|
||||
|
||||
Uninstall
|
||||
---------
|
||||
|
||||
Uninstalling the tutorial world basically means deleting all the rooms
|
||||
and objects it consists of. First, move out of the tutorial area.
|
||||
|
||||
::
|
||||
|
||||
@find tut#01
|
||||
@find tut#17
|
||||
|
||||
This should locate the first and last rooms created by ``build.ev`` -
|
||||
*Intro* and *Outro*. If you installed normally, everything created
|
||||
between these two numbers should be part of the tutorial. Note their
|
||||
dbref numbers, for example 5 and 80. Next we just delete all objects in
|
||||
that range:
|
||||
|
||||
::
|
||||
|
||||
@del 5-80
|
||||
|
||||
You will see some errors since some objects are auto-deleted and so
|
||||
cannot be found when the delete mechanism gets to them. That's fine. You
|
||||
should have removed the tutorial completely once the command finishes.
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
When reading and learning from the code, keep in mind that *Tutorial
|
||||
World* was created with a very specific goal: to install easily and to
|
||||
not permanently modify the rest of the server. It therefore goes to some
|
||||
length to use only temporary solutions and to clean up after itself.
|
||||
None of the basic typeclasses are modified more than temporarily. This
|
||||
means the tutorial sometimes needs to solve things in a more complex
|
||||
fashion than really needed.
|
||||
|
||||
When coding your own game you'd of course not have such considerations -
|
||||
you'd just customize the base typeclasses to always work just the way
|
||||
you want and be done with it.
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
Evennia Tutorials
|
||||
=================
|
||||
|
||||
This is a summary of Evennia documentation available on a step-by-step
|
||||
or tutorial-like format.
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
More building details are found in the `Builder
|
||||
Docs <BuilderDocs.html>`_.
|
||||
|
||||
- `Tutorial: Building Quick-start <BuildingQuickstart.html>`_
|
||||
|
||||
Coding basics
|
||||
-------------
|
||||
|
||||
More details about coding with Evennia is found in the `Developer
|
||||
Central <DeveloperCentral.html>`_.
|
||||
|
||||
- `First Steps Coding with Evennia <FirstStepsCoding.html>`_ - this is
|
||||
partly duplicated in the following two tutorials using different
|
||||
words.
|
||||
- `Tutorial: Adding a new default
|
||||
command <AddingCommandTutorial.html>`_
|
||||
- `Tutorial: Adding new Object typeclasses and
|
||||
defaults <AddingObjectTypeclassTutorial.html>`_
|
||||
|
||||
Implementation ideas
|
||||
--------------------
|
||||
|
||||
Before starting to code your own game we recommend you read the
|
||||
`Planning Your own game <GamePlanning.html>`_ page for some ideas of
|
||||
organizing your workflow. There is also plenty of more information in
|
||||
the `Developer Central <DeveloperCentral.html>`_.
|
||||
|
||||
- `Tutorial: Removing Colour from your game (typeclass method
|
||||
overloading) <RemovingColour.html>`_
|
||||
- `Tutorial: Adding a Command prompt <CommandPrompt.html>`_
|
||||
- `Tutorial: Creating a Zoning system <Zones.html>`_
|
||||
- `Hints: Implementing cooldowns for commands <CommandCooldown.html>`_
|
||||
- `Hints: Designing commands that take time to
|
||||
finish <CommandDuration.html>`_
|
||||
- `Hints: Ticker Scripts <TickerScripts.html>`_
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
See also ``evennia/contrib/`` and the example directories under
|
||||
``game/gamesrc/``.
|
||||
|
||||
- `The Tutorial
|
||||
World <http://code.google.com/p/evennia/wiki/TutorialWorldIntroduction>`_
|
||||
|
||||
|
|
@ -1,307 +0,0 @@
|
|||
Typeclassed entities
|
||||
====================
|
||||
|
||||
How do you represent different objects in a game? What makes a bear
|
||||
different from a stone, a character different from a house or a AI
|
||||
script different from a script handling light and darkness? How do you
|
||||
store such differences in the database? One way would be to create new
|
||||
database tables for each type. So a bear would have a database field
|
||||
"claws" and the stone would have fields specifying its weight and colour
|
||||
... and you'd soon go crazy with making custom database manipulations
|
||||
for all infinite combinations.
|
||||
|
||||
Evennia instead uses very generic and simple database models and
|
||||
"decorates" these with normal Python classes that specify their
|
||||
functionality. Using Python classes means you get all the flexibility of
|
||||
Python object management for free.
|
||||
|
||||
There are four main game 'entities' in Evennia that are what we call
|
||||
*typeclassed*. They are `Players <Players.html>`_,
|
||||
`Objects <Objects.html>`_, `Scripts <Scripts.html>`_ and
|
||||
[Communications#Channels Channels]. This means that they are *almost*
|
||||
normal Python classes - they behave and can be inherited from etc just
|
||||
like normal Python classes. But whenever they store data they are infact
|
||||
transparently storing this data into the database.
|
||||
|
||||
|image0|
|
||||
|
||||
In the above diagram, each of the Typeclassed entities are connected to
|
||||
a *database model*. This handles all database interaction for you
|
||||
without you needing to worry. The database model class is not changing.
|
||||
But the Typeclass connected to it *can*. The typeclass roots (Object,
|
||||
Script and Player) can have any number of subclasses to describe various
|
||||
objects - above are some examples from the default distribution.
|
||||
|
||||
The good news is that you should only need to worry about the typeclass
|
||||
level, not about what happens behind the scenes with the database.
|
||||
|
||||
It's easy to work with Typeclasses - just create a new class inheriting
|
||||
from one of the base Typeclasses:
|
||||
|
||||
::
|
||||
|
||||
from ev import Object
|
||||
|
||||
class Furniture(Object):
|
||||
# this defines what 'furniture' is
|
||||
|
||||
Properties available to all typeclassed entities (Players, Objects, Scripts, Channels)
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
All typeclassed entities share a few very useful properties and methods.
|
||||
|
||||
- ``key`` - the main identifier for the entity, say 'Rose', 'myscript'
|
||||
or 'Paul'. ``name`` is an alias that can also be used.
|
||||
- ``date_created`` - time stamp when this object was created.
|
||||
- ``locks`` - the `lockhandler <Locks.html>`_ that manages access
|
||||
restrictsions. Use locks.add(), locks.get() etc.
|
||||
- ``dbref`` - the database id (database ref) of the object. This is a
|
||||
unique integer. You can usually also use ``id``.
|
||||
- ``is_typeclass(typeclass, exact=False)`` - Checks if this object has
|
||||
a typeclass mathing the one given. If exact is False, it will accept
|
||||
parents matching as well.
|
||||
- ``delete()`` - Deletes the object
|
||||
- ``swap_typeclass(new_typeclass, clean_attributes=False, no_default=True)``
|
||||
- this will swap the object to another typeclass. You may choose to
|
||||
clean out all attributes (this may be necessary if the new typeclass
|
||||
is very different from the old one). The ``no_default`` dictates what
|
||||
happens if the swap fails - if set it will revert back to the
|
||||
pre-swap typeclass, otherwise it will fall back to the default class
|
||||
as defined in your settings file.
|
||||
|
||||
There are three further properties that warrant special mention:
|
||||
|
||||
- ``db`` (DataBase) - this is the interface to the `Attribute
|
||||
system <Attributes.html>`_, allowing for *persistently* storing your
|
||||
own custom data on the entity (i.e. data that will survive a server
|
||||
restart).
|
||||
- ``ndb`` (NotDataBase) - this is equivalent to the functionality of
|
||||
``db`` but is used to store *non-peristent* data (i.e. data that will
|
||||
be lost on server restart).
|
||||
- ``dbobj`` - this is a reference to the *Database object* connected to
|
||||
this typeclass (reversely, ``dbobj.typeclass`` is a reference back to
|
||||
this very typeclass).
|
||||
|
||||
As said, each of the typeclassed entities then extend this list with
|
||||
their own properties. Go to the pages for `Objects <Objects.html>`_,
|
||||
`Scripts <Scripts.html>`_ and `Players <Players.html>`_ respectively for
|
||||
more info.
|
||||
|
||||
Things to remember when using !TypeClasses
|
||||
------------------------------------------
|
||||
|
||||
Typeclasses *mostly* behave like normal Python classes - you can
|
||||
add/overload custom methods and inherit your own classes from them -
|
||||
most things you'd expect to be able to do with a Python class. There are
|
||||
a few things that you need to remember however:
|
||||
|
||||
- Create new instances of typeclasses using ``ev.create_*`` instead of
|
||||
just initializing the typeclass. So use
|
||||
``ev.create_object(MyTypeclass, ...)`` to create a new object of the
|
||||
type ``MyTypeclass``. Doing ``obj = MyTypeclass()`` will not work.
|
||||
- Evennia will look for and call especially named *hook methods* on the
|
||||
typeclass in different situations. Just define a new method on the
|
||||
class named correctly and Evennia will call it appropriately. Hooks
|
||||
are your main way to interact with the server. Available hook methods
|
||||
are listed in the resepective base modules in ``game/gamesrc/``.
|
||||
- Don't use the normal ``__init__()`` to set up your typeclass. It is
|
||||
used by Evennia to set up the mechanics of the Typeclass system. Use
|
||||
the designated hook method instead, such as ``at_object_creation()``,
|
||||
``at_player_creation()`` or ``at_script_creation()``.
|
||||
- Don't re-implement the python special class methods
|
||||
``__setattr__()``, ``__getattribute__()`` and ``__delattr__()``.
|
||||
These are used extensively by the Typeclass system.
|
||||
- Some property names cannot be assigned to a Typeclassed entity due to
|
||||
being used for internal Typeclass operations. If you try, you will
|
||||
get an error. These property names are *id*, *dbobj*, *db*, *ndb*,
|
||||
*objects*, *typeclass*, *attr*, *save* and *delete*.
|
||||
- Even if they are not explicitly protected, you should not redefine
|
||||
the "type" of the default typeclass properties listed above and on
|
||||
each typeclassed entity (such as trying to store an integer in the
|
||||
``key`` property). These properties are often called by the engine
|
||||
expecting a certain type of return, and some are even tied directly
|
||||
to the database and will thus return errors if given a value of the
|
||||
wrong type.
|
||||
- *Advanced note*: If you are doing advanced coding you might (very
|
||||
rarely) find that overloading ``__init__``, ``_setattr__`` etc allows
|
||||
for some functionality not possible with hooks alone. You *can* do it
|
||||
if you know what you are doing, but you *must* then remember to use
|
||||
Python's built-in function ``super()`` to call the parent method too,
|
||||
or you *will* crash the server! You have been warned.
|
||||
|
||||
How typeclasses actually work
|
||||
=============================
|
||||
|
||||
*This is considered an advanced section. Skip it on your first
|
||||
read-through unless you are really interested in what's going on under
|
||||
the hood.*
|
||||
|
||||
All typeclassed entities actually consist of two (three) parts:
|
||||
|
||||
#. The *Typeclass* (a normal Python class with customized get/set
|
||||
behaviour)
|
||||
#. The *Database model* (Django model)
|
||||
#. (`Attributes <Attributes.html>`_)
|
||||
|
||||
The *Typeclass* is an almost normal Python class, and holds all the
|
||||
flexibility of such a class. This is what makes the class special, some
|
||||
of which was already mentioned above:
|
||||
|
||||
- It inherits from ``src.typeclasses.typeclass.TypeClass``.
|
||||
- ``__init__()`` is reserved for various custom startup procedures.
|
||||
- It always initiates a property ``dbobj`` that points to a *Database
|
||||
model*.
|
||||
- It redefines python's normal ``__getattribute__()``,
|
||||
``__setattr__()`` and ``__delattr__`` on itself to relay all data on
|
||||
itself to/from ``dbobj`` (i.e. to/from the database model).
|
||||
|
||||
The related *Database model* in turn communicates data in and out of the
|
||||
the database. The Database model holds the following (typeclass-related)
|
||||
features:
|
||||
|
||||
- It inherits from ``src.typeclasses.models.TypedObject`` (this
|
||||
actually implements a
|
||||
`idmapper <http://github.com/dcramer/django-idmapper>`_-type model.
|
||||
If that doesn't mean anything to you, never mind).
|
||||
- It has a field ``typelclass_path`` that gives the python path to the
|
||||
*Typeclass* associated with this particular model instance.
|
||||
- It has a property *typeclass* that dynamically imports and loads the
|
||||
*Typeclass* from ``typeclass_path``, and assigns itself to the
|
||||
Typeclass' ``dbobj`` property.
|
||||
- It redefines ``__getattribute__()`` to search its typeclass too,
|
||||
while avoiding loops. This means you can search either object and
|
||||
find also data stored on the other.
|
||||
|
||||
The *Attributes* are not really part of the typeclass scheme, but are
|
||||
very important for saving data without having to change the database
|
||||
object itself. They are covered in a separate entry
|
||||
`here <Attributes.html>`_.
|
||||
|
||||
Why split it like this?
|
||||
-----------------------
|
||||
|
||||
The *Database model* (Django model) allows for saving data to the
|
||||
database and is a great place for storing persistent data an object
|
||||
might need during and between sessions. But it is not suitable for
|
||||
representing all the various objects a game needs. You *don't* want to
|
||||
have to redefine a new database representation just because a
|
||||
``CarObject`` needs to look and behave differently than a
|
||||
``ChairObject``. So instead we keep the database model pretty "generic",
|
||||
and only put database Fields on it that we know that *all* objects would
|
||||
need (or that require fast and regular database searches). Examples of
|
||||
such fields are "key" and "location".
|
||||
|
||||
Enter the *Typeclass*. For lack of a better word, a typeclass
|
||||
"decorates" a Django database model. Through the re-definition of the
|
||||
class' get/set methods, the typeclass constantly communicates behind the
|
||||
scenes with the Django model. The beauty of it is that this is all
|
||||
hidden from you, the coder. As long as you don't overwrite the few magic
|
||||
methods listed above you can deal with the typeclass almost as you would
|
||||
any normal Python class. You can extend it, inherit from it, and so on,
|
||||
mostly without caring that it is infact hiding a full persistent
|
||||
database representation. So you can now create a typeclass-class
|
||||
*Flowers* and then inherit a bunch of other typeclass-classes from that
|
||||
one, like *Rose*, *Tulip*, *Sunflower*. As your classes are instantiated
|
||||
they will each secretly carry a reference to a database model to which
|
||||
all data *actually* goes. We, however, can treat the two as if they
|
||||
where one.
|
||||
|
||||
Below is a schematic of the database/typeclass structure.
|
||||
|
||||
|image1|
|
||||
|
||||
Let's see how object creation looks like in an example.
|
||||
|
||||
#. We have defined a Typeclass called *Rose* in
|
||||
``game.gamesrc.objects.flower.Rose``. It inherits from
|
||||
``game.gamesrc.objects.baseobjects.Object``, which is a grandchild of
|
||||
``src.typeclasses.typeclass.TypeClass``. So the rose a typeclassed
|
||||
object, just as it should be.
|
||||
#. Using a command we create a new *Rose* instance *RedRose* (e.g. with
|
||||
``@create redrose:flowers.Rose``).
|
||||
#. A new database model is created and given the key *RedRose*. Since
|
||||
this is an `Object <Objects.html>`_ typeclass (rather than a Script
|
||||
or Player), the database model used is
|
||||
``src.objects.models.ObjectDB``, which inherits directly from
|
||||
``src.typeclasses.models.TypedObject``).
|
||||
#. This new Django-model instance receives the python-path to the *Rose*
|
||||
typeclass and stores it as a string on itself (in a database field
|
||||
``typeclass_path``). When the server restarts in the future, the
|
||||
database model will restart from this point.
|
||||
#. The database model next *imports* the Typeclass from its stored path
|
||||
and creates a new instance of it in memory. It stores a reference to
|
||||
this instance of *Rose* (*RedRose*)in a property called
|
||||
``typeclass``.
|
||||
#. As *Rose* is instantiated, its ``__init__()`` method is called. What
|
||||
this does it to make sure to store the back-reference to the Django
|
||||
model on our new *Rose* instance. This back-reference is called
|
||||
``dbobj``.
|
||||
#. The creation method next runs the relevant startup hooks on the
|
||||
typeclass, such as ``at_object_creation()``.
|
||||
|
||||
Using the ``.db`` operator of Typeclasses will store Attributes of the
|
||||
right type in the database. So ``RedRose.db.thorns = True`` will create
|
||||
a new Attribute named "thorns" where the boolean value ``True`` will be
|
||||
stored.
|
||||
|
||||
On the other hand, storing RedRose.thorns will just store the data as a
|
||||
normal property (the Typeclass will actually transparently relay this so
|
||||
it's always stored on the database model). Due to caching reasons but
|
||||
also for the sake of clarity and readability, it's strongly recommended
|
||||
that you store temporary variables using the ``ndb`` operator, such as
|
||||
``RedRose.ndb.newly_planted=True``.
|
||||
|
||||
In the opposite direction, reading properties can also mean accessing
|
||||
methods that you want to overload. For example, the ``ObjectDB``
|
||||
database model holds a method ``msg`` that you might want to overload
|
||||
with your own version.
|
||||
|
||||
So accessing ``RedRose.msg`` will *first* search the RedRose typeclass
|
||||
to see if it holds a custom ``msg`` and only if it fails it will
|
||||
continue on to search the properties on the database object. An example
|
||||
of a Typeclass overloading ``msg`` is found
|
||||
[`CommandPrompt <CommandPrompt.html>`_\ #Prompt\_on\_the\_same\_line
|
||||
here]. This is another good reason for using ``db/ndb`` handlers - they
|
||||
make it clear if you are creating/reading an Attribute and is not trying
|
||||
to access a method on the class.
|
||||
|
||||
Here is a diagram exemplifying Attribute access:
|
||||
|
||||
|image2|
|
||||
|
||||
Caveats of the typeclass system
|
||||
-------------------------------
|
||||
|
||||
While there are many advantages to the typeclass system over working
|
||||
with Django models directly, there are also some caveats to remember.
|
||||
|
||||
Be careful when not using Evennia's search and create methods. Almost
|
||||
all code in evennia (including default commands) assume that what is
|
||||
returned from searches or creates are Typeclasses, not Django models
|
||||
(i.e. the first of the two in the pair). This is what you get if you use
|
||||
any of the model manager methods, and also the create/search functions
|
||||
in ``src.utils.create`` and ``src.utils.search``. Old Django-gurus will
|
||||
find it tempting to use Django's in-build database query methods, such
|
||||
as ``ObjectDB.objects.filter()`` to get data. This works, but the result
|
||||
will then of course *not* be a typeclass but a Django model object (a
|
||||
query). You can easily convert between them with ``dbobj.typeclass`` and
|
||||
``typeclass.dbobj``, but you should be aware of this distinction.
|
||||
|
||||
::
|
||||
|
||||
obj = ObjectDB.objects.get_id(1) # custom evennia manager method. This returns the typeclass.
|
||||
obj = ObjectDB.objects.get(1) # standard Django. Returns a Django model object.
|
||||
|
||||
Even more important to know for Django affectionados: Evennia's custom
|
||||
methods return *lists* where you with normal Django methods would expect
|
||||
``Query`` objects (e.g. from the ``filter()`` method). As long as you
|
||||
don't confuse what result type you are dealing with (for example you
|
||||
cannot 'link' ``list``\ s together the way you can ``Querysets``), you
|
||||
should be fine.
|
||||
|
||||
Read the ``manager.py`` files in each relevant folder under ``src/`` to
|
||||
see which database access methods are available.
|
||||
|
||||
.. |image0| image:: https://lh4.googleusercontent.com/-jMrRjLRQiHA/UZIKiDgGECI/AAAAAAAAB3Y/YUzHZlgVFTY/w480-h282-no/typeclasses_overview.png
|
||||
.. |image1| image:: https://lh4.googleusercontent.com/-HNUhh6xCYpY/UZISHoSucxI/AAAAAAAAB4I/2ThUbuAbosg/w865-h634-no/typeclasses2a.png
|
||||
.. |image2| image:: https://lh5.googleusercontent.com/-oCqy1f1ZFRA/UZIWeg0ll8I/AAAAAAAAB4g/-ewUvQ439y4/w681-h634-no/typeclasses2.png
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
Unit Testing
|
||||
============
|
||||
|
||||
*This topic is mainly of interest to people interested in helping to
|
||||
develop Evennia itself.*
|
||||
|
||||
Unit testing means testing components of a program in isolation from
|
||||
each other to make sure every part works on its own before using it with
|
||||
others. Extensive testing helps avoid new updates causing unexpected
|
||||
side effects as well as alleviates general code rot (a more
|
||||
comprehensive wikipedia article on unit testing can be found
|
||||
`here <http://en.wikipedia.org/wiki/Unit_test>`_).
|
||||
|
||||
A typical unit test calls some component of Evennia with a given input,
|
||||
looks at the result and makes sure that this result looks as expected.
|
||||
Rather than having lots of stand-alone test programs, Evennia makes use
|
||||
of a central *test runner*. This is a program that gathers all available
|
||||
tests all over the Evennia source code (called *test suites*) and runs
|
||||
them all in one go. Errors and tracebacks are reported.
|
||||
|
||||
Running the test suite
|
||||
----------------------
|
||||
|
||||
To run the Evennia test suite, go to the ``game/`` folder and issue the
|
||||
command
|
||||
|
||||
``python manage.py test``
|
||||
|
||||
A temporary database will be instantiated to manage the tests. If
|
||||
everything works out you will see how many tests were run and how long
|
||||
it took. If something went wrong you will get error messages.
|
||||
|
||||
Writing new tests
|
||||
-----------------
|
||||
|
||||
Evennia's test suite makes use of Django unit test system, which in turn
|
||||
relies on Python's *unittest* module. Evennia's test modules are always
|
||||
named ``tests.py`` and should be located in different sub folders of
|
||||
``src/`` depending on which system they are testing. You can find an
|
||||
example of a testing module in ``src/objects/tests.py``.
|
||||
|
||||
Inside the ``tests.py`` module you create classes inheriting from
|
||||
``django.test.TestCase`` (later versions of Django will use
|
||||
``django.utils.unittest.TestCase`` instead). A ``TestCase`` class is
|
||||
used to test a single aspect or component in various ways. Each test
|
||||
case contains one ore more *test methods* - these define the actual
|
||||
tests to run. You can name the test methods anything you want as long as
|
||||
the name starts with "``test_``\ ". Your ``TestCase`` class can also
|
||||
have a method SetUp(). This is run before each test, setting up whatever
|
||||
preparations the test methods need.
|
||||
|
||||
To test the results, you use special methods of the ``TestCase`` class.
|
||||
Many of those start with "``assert``\ ", such as ``assertEqual`` or
|
||||
``assertTrue``.
|
||||
|
||||
Example of a ``TestCase`` class (inside a file ``tests.py``):
|
||||
|
||||
::
|
||||
|
||||
|
||||
# testing a simple funcion
|
||||
|
||||
try:
|
||||
# this is an optimized version only available in later Django versions
|
||||
from django.utils.unittest import TestCase
|
||||
except ImportError:
|
||||
# if the first fail, we use the old version
|
||||
from django.test import TestCase
|
||||
|
||||
# the function we want to test
|
||||
from mypath import myfunc
|
||||
|
||||
TestObj(unittest.TestCase):
|
||||
"This tests a function myfunc."
|
||||
|
||||
def test_return_value(self):
|
||||
"test method. Makes sure return value is as expected."
|
||||
expected_return = "This is me being nice."
|
||||
actual_return = myfunc()
|
||||
# test
|
||||
self.assertEqual(expected_return, actual_return)
|
||||
def test_alternative_call(self):
|
||||
"test method. Calls with a keyword argument."
|
||||
expected_return = "This is me being baaaad."
|
||||
actual_return = myfunc(bad=True)
|
||||
# test
|
||||
self.assertEqual(expected_return, actual_return)
|
||||
|
||||
The above example is very simplistic, but you should get the idea. Look
|
||||
at ``src/objects/tests.py`` for more realistic examples of tests. You
|
||||
might also want to read the `documentation for the unittest
|
||||
module <http://docs.python.org/library/unittest.html>`_.
|
||||
|
||||
Testing in-game Commands
|
||||
------------------------
|
||||
|
||||
In-game Commands are a special case. Tests for the default commands are
|
||||
put in ``src/commands/default/tests.py``. This test suite is executed as
|
||||
part of running the ``objects`` test suite (since it lies outside
|
||||
Django's normal "app" structure). It also supplies a few convenience
|
||||
functions for executing commands (notably creating a "fake" player
|
||||
session so as to mimic an actual command call). It also makes several
|
||||
test characters and objects available. For example ``char1`` is a
|
||||
"logged in" Character object that acts as the one calling the command.
|
||||
|
||||
Each command tested should have its own ``TestCase`` class. Inherit this
|
||||
class from the ``CommandTest`` class in the same module to get access to
|
||||
the command-specific utilities mentioned.
|
||||
|
||||
::
|
||||
|
||||
class TestSet(CommandTest):
|
||||
"tests the @set command by simple call"
|
||||
def test_call(self):
|
||||
self.execute_command("@set self/testval = mytestvalue")
|
||||
# knowing what @set does, our test character (char1) should
|
||||
# by now have a new attribute 'testval' with the value 'mytestvalue'.
|
||||
self.assertEqual("mytestvalue", self.char1.db.testval)
|
||||
|
||||
A note on adding new tests
|
||||
--------------------------
|
||||
|
||||
Having an extensive tests suite is very important for avoiding code
|
||||
degradation as Evennia is developed. Only a small fraction of the
|
||||
Evennia codebase is covered by test suites at this point. Writing new
|
||||
tests is not hard, it's more a matter of finding the time to do so. So
|
||||
adding new tests is really an area where everyone can contribute, also
|
||||
with only limited Python skills.
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
Updating your Game
|
||||
==================
|
||||
|
||||
Fortunately, it's extremely easy to keep your Evennia server up-to-date
|
||||
via Mercurial. If you haven't already, see the `Getting Started
|
||||
guide <GettingStarted.html>`_ and get everything running. There are many
|
||||
ways to get told when to update: You can subscribe to the RSS feed or
|
||||
manually check up on the feeds from
|
||||
`http://www.evennia.com <http://www.evennia.com>`_. You can also join
|
||||
the `Evennia Commit
|
||||
Log <http://groups.google.com/group/evennia-commits/>`_ group, which
|
||||
will send you an email when the server repository changes.
|
||||
|
||||
When you're wanting to apply updates, simply ``cd`` to your ``evennia``
|
||||
root directory and type:
|
||||
|
||||
::
|
||||
|
||||
hg pull
|
||||
hg update
|
||||
|
||||
Assuming you've got the command line client. If you're using a graphical
|
||||
client, you will probably want to navigate to the ``evennia`` directory
|
||||
and either right click and find your client's pull function, or use one
|
||||
of the menus (if applicable).
|
||||
|
||||
You can review the latest changes with
|
||||
|
||||
::
|
||||
|
||||
hg log
|
||||
|
||||
or the equivalent in the graphical client. The log tends to scroll past
|
||||
quite quickly, so if you are in linux it might be an idea to *pipe* the
|
||||
output to a text reader like ``less``
|
||||
(`here <http://mercurial.selenic.com/wiki/PagerExtension>`_ is a more
|
||||
permanent solution):
|
||||
|
||||
::
|
||||
|
||||
hg log | less
|
||||
|
||||
You can also see the latest changes online
|
||||
`here <http://code.google.com/p/evennia/source/list>`_.
|
||||
|
||||
Resetting your database
|
||||
-----------------------
|
||||
|
||||
Should you ever want to start over completely from scratch, there is no
|
||||
need to re-download Evennia or anything like that. You just need to
|
||||
clear your database. Once you are done, you just rebuild it from scratch
|
||||
as described in step 2 of the `Getting Started
|
||||
guide <GettingStarted.html>`_.
|
||||
|
||||
First stop a running server with ``game/python evennia.py stop``.
|
||||
|
||||
If you run the default ``SQlite3`` database (to change this you need to
|
||||
edit your ``settings.py`` file), the database is actually just a normal
|
||||
file in ``game/`` called ``evennia.db3``. Simply delete that file -
|
||||
that's it.
|
||||
|
||||
Regardless of which database system you use, you can reset your database
|
||||
via ``game/manage.py``. Since Evennia consists of many separate
|
||||
components you need to clear the data from all of them:
|
||||
|
||||
::
|
||||
|
||||
python manage.py reset server objects players scripts comms help web auth
|
||||
|
||||
Django also offers an easy way to start the database's own management
|
||||
should we want more direct control:
|
||||
|
||||
::
|
||||
|
||||
python manage.py dbshell
|
||||
|
||||
In e.g. MySQL you can then do something like this (assuming your MySQL
|
||||
database is named "Evennia":
|
||||
|
||||
::
|
||||
|
||||
mysql> DROP DATABASE Evennia;
|
||||
mysql> exit
|
||||
|
||||
A Note on Schema Migration
|
||||
--------------------------
|
||||
|
||||
If and when an Evennia update modifies the database *schema* (that is,
|
||||
the under-the-hood details as to how data is stored in the database),
|
||||
you must update your existing database correspondingly to match the
|
||||
change. If you don't, the updated Evennia will complain that it cannot
|
||||
read the database properly. Whereas schema changes should become more
|
||||
and more rare as Evennia matures, it may still happen from time to time.
|
||||
|
||||
One way to handle this is to apply the changes manually to your database
|
||||
using the database's command line. This often means adding/removing new
|
||||
tables or fields as well as possibly convert existing data to match what
|
||||
the new Evennia version expects. It should be quite obvious that this
|
||||
quickly becomes cumbersome and error-prone. If your database doesn't
|
||||
contain anything critical yet it's probably easiest to simply reset it
|
||||
and start over rather than to bother converting.
|
||||
|
||||
Enter `South <http://south.aeracode.org/>`_. South keeps track of
|
||||
changes in the database schema and applies them automatically for you.
|
||||
Basically, whenever the schema changes we also distribute small files
|
||||
called "migrations" with the source. Those tell South exactly how to
|
||||
repeat that change so you don't have to do so manually.
|
||||
|
||||
Using South is optional, but if you do install it, Evennia *will* use
|
||||
South automatically. See the correct section of
|
||||
`GettingStarted <GettingStarted.html>`_ on how to install South and the
|
||||
slightly different way to start a clean database server when South is
|
||||
used (you have to give the ``mange.py migrate`` command as well as
|
||||
``manage.py syncdb``).
|
||||
|
||||
Once you have a database ready and using South, you work as normal.
|
||||
Whenever a new Evennia update tells you that the database schema has
|
||||
changed (check ``hg log`` after you pulled the latest stuff, or read the
|
||||
online list), you go to ``game/`` and run this command:
|
||||
|
||||
::
|
||||
|
||||
python manage.py migrate
|
||||
|
||||
This will convert your database to the new schema and you should be set
|
||||
to go.
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
The 'MUX-like' default of Evennia
|
||||
=================================
|
||||
|
||||
Evennia is a highly customizable codebase. Among many things, its
|
||||
command structure and indeed the very way that commands look can all be
|
||||
changed by you. If you like the way, say, DikuMUDs handle things, you
|
||||
could emulate that with Evennia. Or LPMuds, or MOOs. Or if you are
|
||||
ambitious you could design a whole new style, perfectly fitting your own
|
||||
dreams of the ideal MUD.
|
||||
|
||||
We do offer a default however. The default Evennia setup tend to
|
||||
resemble `MUX2 <http://www.tinymux.org/>`_, and its cousins
|
||||
`PennMUSH <http://www.pennmush.org>`_,
|
||||
`TinyMUSH <http://tinymush.sourceforge.net/>`_, and
|
||||
`RhostMUSH <http://www.rhostmush.org/>`_. By default we emulate these
|
||||
Tiny derivatives (MUX2, Penn, etc) in the user interface and building
|
||||
commands. We believe these codebases have found a good way to do things
|
||||
in terms of building and administration. We hope this will also make it
|
||||
more familiar for new users coming from those communities to start using
|
||||
Evennia.
|
||||
|
||||
However, Evennia has taken a completely different stance on how admins
|
||||
extend and improve their games. Instead of implementing a special
|
||||
in-game language (SoftCode), all game extension is done through Python
|
||||
modules, like the rest of Evennia. This gives the admin practically
|
||||
unlimited power to extend the game leveraging the full power of a mature
|
||||
high level programming language. You can find a more elaborate
|
||||
discussion about our take on MUX SoftCode `here <SoftCode.html>`_.
|
||||
|
||||
Documentation policy
|
||||
--------------------
|
||||
|
||||
All the commands in the default command sets have their doc-strings
|
||||
formatted on a similar form:
|
||||
|
||||
::
|
||||
|
||||
"""
|
||||
Short header
|
||||
|
||||
Usage:
|
||||
key[/switches, if any] <mandatory args> [<optional args or types>]
|
||||
|
||||
Switches:
|
||||
switch1 - description
|
||||
switch2 - description
|
||||
|
||||
Examples:
|
||||
usage example and output
|
||||
|
||||
Longer documentation detailing the command.
|
||||
|
||||
"""
|
||||
|
||||
The ``Switches`` and ``Examples`` headers can be skipped if not needed.
|
||||
Here is the ``nick`` command as an example:
|
||||
|
||||
::
|
||||
|
||||
"""
|
||||
Define a personal alias/nick
|
||||
|
||||
Usage:
|
||||
nick[/switches] <nickname> = [<string>]
|
||||
alias ''
|
||||
|
||||
Switches:
|
||||
object - alias an object
|
||||
player - alias a player
|
||||
clearall - clear all your aliases
|
||||
list - show all defined aliases (also "nicks" works)
|
||||
|
||||
Examples:
|
||||
nick hi = say Hello, I'm Sarah!
|
||||
nick/object tom = the tall man
|
||||
|
||||
A 'nick' is a personal shortcut you create for your own use [...]
|
||||
|
||||
"""
|
||||
|
||||
For commands that *require arguments*, the policy is for it to return a
|
||||
``Usage`` string if the command is entered without any arguments. So for
|
||||
such commands, the Command body should contain something to the effect
|
||||
of
|
||||
|
||||
::
|
||||
|
||||
if not self.args:
|
||||
self.caller.msg("Usage: nick[/switches] <nickname> = [<string>]")
|
||||
return
|
||||
|
||||
WWMD - What Would MUX Do?
|
||||
-------------------------
|
||||
|
||||
Our original policy for implementing the default commands was to look at
|
||||
MUX2's implementation and base our command syntax on that. This means
|
||||
that many default commands have roughly similar syntax and switches as
|
||||
MUX commands. There are however many differences between the systems and
|
||||
readability and usability has taken priority (frankly, the MUX syntax is
|
||||
outright arcane in places). So the default command sets can be
|
||||
considered to implement a "MUX-like" dialect - whereas the overall feel
|
||||
is familiar, the details may differ considerably.
|
||||
|
|
@ -1,257 +0,0 @@
|
|||
Setting up a coding environment with version control
|
||||
====================================================
|
||||
|
||||
Version control software allows you to easily backtrack changes to your
|
||||
code, help with sharing your development efforts and more. Even if you
|
||||
are not contributing to Evennia itself, but is "just" developing your
|
||||
own game, having a version control system in place is a good idea. If
|
||||
you want more info, start with the wikipedia article about it
|
||||
`here <http://en.wikipedia.org/wiki/Version_control>`_. Note that this
|
||||
page deals with commands in the Linux operating system. Details may vary
|
||||
for other systems.
|
||||
|
||||
Note: This is only one suggested way to use Mercurial by using separate
|
||||
local clones. You could set up any number of different workflows if it
|
||||
suited you better. See `here <http://mercurial.selenic.com/guide/>`_ for
|
||||
some more examples.
|
||||
|
||||
Using Mercurial
|
||||
===============
|
||||
|
||||
`Mercurial <http://mercurial.selenic.com/>`_ (abbreviated as ``hg``
|
||||
after the chemical symbol for mercury) is a version control system
|
||||
written mainly in Python. It's available for all major platforms.
|
||||
|
||||
First, identify to mercurial by creating a new file ``.hgrc`` in your
|
||||
home directory and put the following content in it:
|
||||
|
||||
::
|
||||
|
||||
[ui]
|
||||
username = MyName <myemail@mail.com>
|
||||
|
||||
You can put a nickname here too if you want. This is just so the system
|
||||
knows how to credit new revisions.
|
||||
|
||||
Setting up
|
||||
----------
|
||||
|
||||
We will here assume you are downloading Evennia for the first time. We
|
||||
will set up a simple environment for hacking your game in. In the end it
|
||||
will look like this:
|
||||
|
||||
::
|
||||
|
||||
evennia/
|
||||
evennia-main
|
||||
evennia-mygame
|
||||
|
||||
Create a new folder ``evennia`` and clone Evennia into it as
|
||||
``evennia-main``:
|
||||
|
||||
::
|
||||
|
||||
hg clone https://code.google.com/p/evennia/ evennia-main
|
||||
|
||||
A new folder ``evennia-main`` has appeared. In it you will find the
|
||||
entire Evennia source repository, including all its commit history -
|
||||
it's really a full copy of what's available on the web.
|
||||
|
||||
We'll let ``evennia-main`` only contain the "clean" Evennia install -
|
||||
it's a good debugging tool to tell you if a bug you find is due to your
|
||||
changes or also visible in the core server. We'll develop our game in
|
||||
another repository instead:
|
||||
|
||||
::
|
||||
|
||||
hg clone evennia-main evennia-mygame
|
||||
|
||||
This will create a new repository ``evennia-mygame`` on your machine. In
|
||||
this directory you now code away, adding and editing things to your
|
||||
heart's content to make your dream game.
|
||||
|
||||
Example work flow
|
||||
-----------------
|
||||
|
||||
First we make sure our copy of Evennia is up-to-date. Go to
|
||||
``evennia-main``:
|
||||
|
||||
::
|
||||
|
||||
cd evennia-main
|
||||
hg pull
|
||||
|
||||
Mercurial goes online and gets the latest Evennia revision from the
|
||||
central repository, merging it automatically into your repository. It
|
||||
will tell you that you need to ``update`` to incoorporate the latest
|
||||
changes. Do so.
|
||||
|
||||
::
|
||||
|
||||
hg update
|
||||
|
||||
So ``evennia-main`` is now up-to-date. If you want, you can review the
|
||||
changes and make sure things work as they should. Finally go to
|
||||
``evennia-mygame`` and pull the changes into that as well.
|
||||
|
||||
::
|
||||
|
||||
|
||||
cd ../evennia-mygame
|
||||
hg commit # (only if you had any changes)
|
||||
hg pull ../evennia-main
|
||||
hg update
|
||||
|
||||
You can now continue to hack away in ``evennia-mygame`` to build your
|
||||
game. Maybe you define new commands, economic systems, create batchfiles
|
||||
or what have you. If you create any new files, you must tell Mercurial
|
||||
to track them by using the ``add`` command:
|
||||
|
||||
::
|
||||
|
||||
hg add <filename(s)>
|
||||
|
||||
Check the current status of the version control with
|
||||
|
||||
::
|
||||
|
||||
hg status
|
||||
|
||||
If you don't get any return value, you haven't made any changes since
|
||||
last commit. Otherwise you will get a list of modified files.
|
||||
|
||||
It's usually a good idea to commit your changes often - it's fast and
|
||||
only local - you will never commit anything online. This gives you a
|
||||
"save" snapshot of your work that you can get back to.
|
||||
|
||||
::
|
||||
|
||||
hg commit
|
||||
|
||||
This will open a text editor where you can add a message detailing your
|
||||
changes. These are the messages you see in the Evennia update/log list.
|
||||
If you don't want to use the editor you can set the message right away
|
||||
with the ``-m`` flag:
|
||||
|
||||
::
|
||||
|
||||
hg commit -m "This should fix the bug Sarah talked about."
|
||||
|
||||
If you did changes that you wish you hadn't, you can easily get rid of
|
||||
everything since your latest commit:
|
||||
|
||||
::
|
||||
|
||||
hg revert --all
|
||||
|
||||
Instead of ``--all`` you can also choose to revert individual files.
|
||||
|
||||
You can view the full log of committed changes with
|
||||
|
||||
::
|
||||
|
||||
hg log
|
||||
|
||||
See the Mercurial manuals for learning more about useful day-to-day
|
||||
commands, and special situations such as dealing with text collisions
|
||||
etc.
|
||||
|
||||
Sharing your code with the world
|
||||
================================
|
||||
|
||||
The most common case of this is when you have fixed an Evennia bug and
|
||||
want to make the fix available to Evennia maintainers. But you can also
|
||||
share your work with other people on your game-development team if you
|
||||
aren't worried about the changes being publicly visible.
|
||||
|
||||
Let's take the example of debugging Evennia. Go online and create an
|
||||
"online clone" of Evennia as described `here <Contributing.html>`_. Pull
|
||||
this repo to your local machine -- so if your clone is named
|
||||
``my-evennia-fixes``, you do something like this:
|
||||
|
||||
::
|
||||
|
||||
hg clone https://<yourname>@code.google.com/r/my-evennia-fixes evennia-fixes
|
||||
|
||||
You will now have a new folder ``evennia-fixes``. Let's assume we want
|
||||
to use this to push bug fixes to Evennia. It works like any other
|
||||
mercurial repository except you also have push-rights to your online
|
||||
clone from it. When working, you'd first update it to the latest
|
||||
upstream Evennia version:
|
||||
|
||||
::
|
||||
|
||||
cd evennia-main
|
||||
hg pull
|
||||
hg update
|
||||
cd ../evennia-fixes
|
||||
hg pull ../evennia-main
|
||||
hg update
|
||||
|
||||
Now you fix things in ``evennia-fixes``. Commit your changes as
|
||||
described above. Make sure to make clear and descriptive commit messages
|
||||
so it's easy to see what you intended. You can do any number of commits
|
||||
as you work. Once you are at a stage where you want to show what you did
|
||||
to the world, you push all the so-far committed changes to your online
|
||||
clone:
|
||||
|
||||
::
|
||||
|
||||
hg push
|
||||
|
||||
(You'd next need to tell Evennia devs that they should merge your
|
||||
brilliant changes into Evennia proper. Create a new
|
||||
`Issue <https://code.google.com/p/evennia/issues/list>`_ of type *Merge
|
||||
Request*, informing them of this.)
|
||||
|
||||
Apart from supporting Evennia itself you can have any number of online
|
||||
clones for different purposes, such as sharing game code or collaborate
|
||||
on solutions. Just pull stuff from whichever relevant local repository
|
||||
you have (like ``evennia-mygame``) and push to a suitably named online
|
||||
clone so people can get to it.
|
||||
|
||||
Sharing your code only with a small coding team
|
||||
===============================================
|
||||
|
||||
Creating a publicly visible online clone might not be what you want for
|
||||
all parts of your development process - you may prefer a more private
|
||||
venue when sharing your revolutionary work with your team.
|
||||
|
||||
An online hosting provider offering private repositories is probably
|
||||
your best bet. For example, if all your contributors are registered on
|
||||
`BitBucket <https://bitbucket.org/>`_, that service offers free
|
||||
"private" repositories that you could use for this.
|
||||
|
||||
An alternative simple way to share your work with a limited number of
|
||||
people is to use mercurial's own simple webserver and let them connect
|
||||
directly to your machine:
|
||||
|
||||
::
|
||||
|
||||
cd evennia-mygame
|
||||
hg serve -p 8500
|
||||
|
||||
(the port was changed because the default port is 8000 and that is
|
||||
normally used by Evennia's own webserver). Find out the IP address of
|
||||
your machine visible to the net (make sure you know your firewall setup
|
||||
etc). Your collaborators will then be able to first review the changes
|
||||
in their browser:
|
||||
|
||||
::
|
||||
|
||||
firefox http://192.168.178.100:8500
|
||||
|
||||
and pull if they like what they see:
|
||||
|
||||
::
|
||||
|
||||
hg pull http://192.168.178.100:8500
|
||||
|
||||
See `here <http://mercurial.selenic.com/wiki/hgserve>`_ for more
|
||||
information on using ``hg serve``.
|
||||
|
||||
Mercurial's in-built webserver is *very* simplistic and not particularly
|
||||
robust. It only allows one connection at a time, lacks authorization and
|
||||
doesn't even allow your collaborators to ``push`` data to you (there is
|
||||
nothing stopping them to set up a server of their own so you can pull
|
||||
from them though).
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
Web Features
|
||||
============
|
||||
|
||||
Evennia is its own webserver and hosts a default website and browser
|
||||
webclient.
|
||||
|
||||
Editing the Web site
|
||||
--------------------
|
||||
|
||||
The Evennia website is a Django application that ties in with the MUD
|
||||
database. It allows you to, for example, tell website visitors how many
|
||||
players are logged into the game at the moment, how long the server has
|
||||
been up and any other database information you may want. The dynamic
|
||||
website application is located in ``src/web/website`` whereas you will
|
||||
find the html files in ``src/web/templates/prosimii``. Static media such
|
||||
as images, css and javascript files are served from ``src/web/media``.
|
||||
|
||||
You can access the website during development by going to
|
||||
``http://localhost:8000``.
|
||||
|
||||
Since it's not recommended to edit files in ``src/`` directly, we need
|
||||
to devise a way to allow website customization from ``game/gamesrc``.
|
||||
This is not really finalized at the current time (it will be easier to
|
||||
share media directories in Django 1.3) so for now, your easiest course
|
||||
of action is as follows:
|
||||
|
||||
#. Copy the entire ``src/web`` directory into ``game/gamesrc/`` and do
|
||||
your modifications to the copy. Make sure to retain permissions so
|
||||
the server can access the directory (in linux you can do this with
|
||||
something like ``cp -ra src/web game/gamesrc/``)
|
||||
#. Re-link all relevant path variables to the new location. In settings
|
||||
add the following lines:
|
||||
|
||||
::
|
||||
|
||||
ROOT_URLCONF = "game.gamesrc.web.urls"
|
||||
TEMPLATE_DIRS = (os.path.join(GAME_DIR, "gamesrc", "web", "templates", ACTIVE_TEMPLATE),)
|
||||
MEDIA_ROOT = os.path.join(GAME_DIR, "gamesrc", "web", "media"`).
|
||||
|
||||
#. Reload the server (you want to also restart the Portal at this point
|
||||
to make sure it picks up the new web location).
|
||||
|
||||
You should now have a separate website you can edit as you like. Be
|
||||
aware that updates we do to ``src/web`` will not transfer automatically
|
||||
to your copy, so you'll need to apply updates manually.
|
||||
|
||||
Web client
|
||||
----------
|
||||
|
||||
Evennia comes with a MUD client accessible from a normal web browser. It
|
||||
is technically a javascript client polling an asynchronous webserver
|
||||
through long-polling (this is also known as a *COMET* setup). The
|
||||
webclient server is defined in ``src/server/webclient`` and is not
|
||||
something that should normally need to be edited unless you are creating
|
||||
a custom client. The client javascript, html and css files are located
|
||||
under the respective folders of ``src/web/``.
|
||||
|
||||
The webclient uses the `jQuery <http://jquery.com/>`_ javascript
|
||||
library. This is imported automatically over the internet when running
|
||||
the server. If you want to run the client without an internet
|
||||
connection, you need to download the library from the jQuery homepage
|
||||
and put it in ``src/web/media/javascript``. Then edit
|
||||
``src/web/templates/prosimii/webclient.html`` and uncomment the line:
|
||||
|
||||
::
|
||||
|
||||
<script src="/media/javascript/jquery-1.4.4.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
(edit it to match the name of the ``*.js`` for the jQuery version you
|
||||
downloaded).
|
||||
|
||||
The webclient requires the webserver to be running and is then found on
|
||||
``http://localhost:8000/webclient``. For now it's best to follow the
|
||||
procedure suggested in the previous section if you want to customize it.
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
rtclient protocol
|
||||
=================
|
||||
|
||||
*Note: Most functionality of a webcliebnt implementation is already
|
||||
added to trunk as of Nov 2010. That implementation does however not use
|
||||
a custom protocol as suggested below. Rather it parses telnet-formatted
|
||||
ansi text and converts it to html. Custom client operations (such as
|
||||
opening windows or other features not relevant to telnet or other
|
||||
protocols) should instead eb handled by a second "data" object being
|
||||
passed to the server through the msg() method.*
|
||||
|
||||
rtclient is an extended and bastardized telnet protocol that processes
|
||||
html and javascript embedded in the telnet session.
|
||||
|
||||
rtclient is implemented by the Teltola client, a web-based html/js
|
||||
telnet client that is being integrated with Evennia and is written in
|
||||
twisted/python.
|
||||
|
||||
There are two principle aspects to the rtclient protocol, mode control
|
||||
and buffering.
|
||||
|
||||
Modes
|
||||
=====
|
||||
|
||||
Unencoded Mode
|
||||
--------------
|
||||
|
||||
All output is buffered until ascii char 10, 13, or 255 is encountered or
|
||||
the mode changes or no output has been added to the buffer in the last
|
||||
1/10th second and the buffer is not blank. When this occurs, the client
|
||||
interprets the entire buffer as plain text and flushes the buffer.
|
||||
|
||||
HTML Mode
|
||||
---------
|
||||
|
||||
All output is buffered. When the mode changes, the client then parses
|
||||
the entire buffer as HTML.
|
||||
|
||||
Javascript Mode
|
||||
---------------
|
||||
|
||||
All output is buffered. When the mode changes, the client then parses
|
||||
the entire buffer as Javascript.
|
||||
|
||||
Sample Sessions
|
||||
===============
|
||||
|
||||
# start html mode, send html, force buffer flush
|
||||
|
||||
::
|
||||
|
||||
session.msg(chr(240) + "<h1>Test</h1>" + chr(242) + chr(244))
|
||||
|
||||
# same as above, but realize that msg sends end-of-line # automatically
|
||||
thus sending the buffer via AUTO\_CHNK
|
||||
|
||||
::
|
||||
|
||||
session.msg(chr(240) + "<h1>Test</h1>" + chr(242))
|
||||
|
||||
# more elaborate example sending javascript, html, and unbuffered text #
|
||||
note we are using the tokens imported instead of the constants
|
||||
|
||||
::
|
||||
|
||||
from game.gamesrc.teltola.RTClient import HTML_TOKEN, JAVASCRIPT_TOKEN, UNENCODED_TOKEN
|
||||
hello_world_js = "alert('hello world');"
|
||||
welcome_html = "<h1>Hello World</h1>"
|
||||
session.msg("".join([JAVASCRIPT_TOKEN, hello_world_js, HTML_TOKEN, welcome_html, UNENCODED_TOKEN,"Hello there."]))
|
||||
|
||||
::
|
||||
|
||||
session.msg(chr(243))
|
||||
session.msg(my_text_with_line_breaks)
|
||||
session.msg(chr(244))
|
||||
|
||||
Values of Tokens
|
||||
================
|
||||
|
||||
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| chr() value \| name \| function |
|
||||
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| 240 \| HTML\_TOKEN \| lets client know it is about to receive HTML |
|
||||
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| 241 \| JAVASCRIPT\_TOKEN \| lets client know it is about to receive javascript |
|
||||
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| 242 \| UNENCODED\_TOKEN \| lets client know it is about to receive plain telnet text |
|
||||
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| 243 \| NO\_AUTOCHUNK\_TOKEN \| applies to unencoded mode only, prevents the chunking of text at end-of-line characters so that only mode changes force the buffer to be sent to the client |
|
||||
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| 244 \| AUTOCHUNK\_TOKEN \| applies to unencoded mode only, enables automatic chunking of text by end-of-line characters and by non-blank buffers not having been written to in the last 1/10th second |
|
||||
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
identifying as an rtclient
|
||||
--------------------------
|
||||
|
||||
rtclients send the text rtclient\\n immediately after connection so that
|
||||
the server may enable rtclient extensions
|
||||
|
||||
Buffering Control
|
||||
-----------------
|
||||
|
||||
Unbuffered output is not supported. There are two different buffering
|
||||
methods supported. The default method is called AUTOCHUNK and applies
|
||||
only to unencoded data (see data encoding section below). JAVASCRIPT and
|
||||
HTML data is always treated as NO\_AUTOCHUNK.
|
||||
|
||||
NO\_AUTOCHUNK
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Contents are never sent to the client until the encoding mode changes
|
||||
(for example, switching from HTML to UNENCODED will send the HTML
|
||||
buffer) or the buffering mode changes (for example, one could set
|
||||
NO\_AUTOCHUNK, send some text, and set NO\_AUTOCHUNK again to force a
|
||||
flush.
|
||||
|
||||
AUTOCHUNK
|
||||
~~~~~~~~~
|
||||
|
||||
It sends the buffer to the client as unencoded text whenever one of
|
||||
two things happen:
|
||||
|
||||
**the buffer is non-blank but hasn't had anything added to it very
|
||||
recently (about 1/10th of a second)** the buffer ends with an
|
||||
end-of-line character (10, 13, 255)
|
||||
|
||||
Autochunking strips end-of-line characters and the client adds in its
|
||||
own EOL! If you would like to preserve them, send them from within
|
||||
NO\_AUTOCHUNK.
|
||||
|
|
@ -1,297 +0,0 @@
|
|||
**Status Update**:*There does not seem to be any active development on
|
||||
this by the original initiator (rcaskey). As far as I know there is no
|
||||
active game code written apart from a Smaug area converter (how
|
||||
complete?). If anyone is willing to continue with this particular idea,
|
||||
they are welcome to do so. I will help out but I don't know anything
|
||||
about Smaug myself. In the interim I will chalk this one down as being a
|
||||
stalled project. /Griatch*
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
This is(was?) an initiative to create a "base" game system to be shipped
|
||||
with Evennia in a "contrib" folder. The game is an independent
|
||||
re-implementation of the basic stuff of the
|
||||
`SMAUG <http://www.smaug.org>`_ system. No code from the original will
|
||||
be used, and no licensed content will be included in the release. For
|
||||
easy testing of content, rcaskey's SMAUG importer will be used.
|
||||
|
||||
TODO, first prototype
|
||||
=====================
|
||||
|
||||
The first stage serves to establish a prototype implementation -
|
||||
something that shows the parts hanging together, but with only a subset
|
||||
of the functionality.
|
||||
|
||||
#. Create custom `TypeClasses <Objects.html>`_ supporting the SMAUG
|
||||
system:
|
||||
|
||||
- Object->SmaugObject->SmaugBeing->SmaugCharacter,Character
|
||||
- Object->SmaugObject->SmaugBeing->SmaugMob-> ...
|
||||
- Object->SmaugObject->SmaugThing-> ...
|
||||
|
||||
#. Create limited subclasses or attributes on objects
|
||||
|
||||
- Limited classes/races (1-2?)
|
||||
- Skills (<lvl 5?) - not too many!
|
||||
|
||||
#. Behind-the-scenes SMAUG engine
|
||||
|
||||
- Contest resolution
|
||||
- Mobs moving around, "AI"
|
||||
- Base combat system
|
||||
|
||||
#. Import of small data set, testing.
|
||||
|
||||
SMAUG specifics
|
||||
===============
|
||||
|
||||
Code Availability By Lvl
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
+-------+-----------------------------+
|
||||
| Lvl | Code Bit |
|
||||
+-------+-----------------------------+
|
||||
| 0 | spell\_disenchant\_weapon |
|
||||
+-------+-----------------------------+
|
||||
| 1 | spell\_cause\_light |
|
||||
+-------+-----------------------------+
|
||||
| 1 | do\_hide |
|
||||
+-------+-----------------------------+
|
||||
| 1 | spell\_ventriloquate |
|
||||
+-------+-----------------------------+
|
||||
| 1 | do\_cook |
|
||||
+-------+-----------------------------+
|
||||
| 1 | do\_climb |
|
||||
+-------+-----------------------------+
|
||||
| 1 | spell\_null |
|
||||
+-------+-----------------------------+
|
||||
| 1 | do\_pick |
|
||||
+-------+-----------------------------+
|
||||
| 1 | do\_steal |
|
||||
+-------+-----------------------------+
|
||||
| 1 | do\_backstab |
|
||||
+-------+-----------------------------+
|
||||
| 1 | spell\_smaug |
|
||||
+-------+-----------------------------+
|
||||
| 1 | do\_kick |
|
||||
+-------+-----------------------------+
|
||||
| 2 | do\_dig |
|
||||
+-------+-----------------------------+
|
||||
| 2 | do\_mount |
|
||||
+-------+-----------------------------+
|
||||
| 2 | spell\_faerie\_fire |
|
||||
+-------+-----------------------------+
|
||||
| 2 | spell\_create\_food |
|
||||
+-------+-----------------------------+
|
||||
| 2 | spell\_create\_water |
|
||||
+-------+-----------------------------+
|
||||
| 2 | spell\_weaken |
|
||||
+-------+-----------------------------+
|
||||
| 2 | spell\_black\_hand |
|
||||
+-------+-----------------------------+
|
||||
| 3 | do\_scan |
|
||||
+-------+-----------------------------+
|
||||
| 3 | do\_search |
|
||||
+-------+-----------------------------+
|
||||
| 3 | do\_feed |
|
||||
+-------+-----------------------------+
|
||||
| 3 | spell\_chill\_touch |
|
||||
+-------+-----------------------------+
|
||||
| 4 | do\_rescue |
|
||||
+-------+-----------------------------+
|
||||
| 4 | spell\_cure\_blindness |
|
||||
+-------+-----------------------------+
|
||||
| 4 | spell\_invis |
|
||||
+-------+-----------------------------+
|
||||
| 4 | do\_aid |
|
||||
+-------+-----------------------------+
|
||||
| 4 | spell\_galvanic\_whip |
|
||||
+-------+-----------------------------+
|
||||
| 5 | spell\_blindness |
|
||||
+-------+-----------------------------+
|
||||
| 5 | spell\_cause\_serious |
|
||||
+-------+-----------------------------+
|
||||
| 5 | spell\_detect\_poison |
|
||||
+-------+-----------------------------+
|
||||
| 5 | spell\_burning\_hands |
|
||||
+-------+-----------------------------+
|
||||
| 5 | spell\_know\_alignment |
|
||||
+-------+-----------------------------+
|
||||
| 6 | spell\_locate\_object |
|
||||
+-------+-----------------------------+
|
||||
| 6 | do\_track |
|
||||
+-------+-----------------------------+
|
||||
| 6 | spell\_remove\_invis |
|
||||
+-------+-----------------------------+
|
||||
| 6 | spell\_poison |
|
||||
+-------+-----------------------------+
|
||||
| 7 | spell\_earthquake |
|
||||
+-------+-----------------------------+
|
||||
| 7 | spell\_shocking\_grasp |
|
||||
+-------+-----------------------------+
|
||||
| 8 | spell\_teleport |
|
||||
+-------+-----------------------------+
|
||||
| 8 | do\_bashdoor |
|
||||
+-------+-----------------------------+
|
||||
| 8 | spell\_summon |
|
||||
+-------+-----------------------------+
|
||||
| 8 | spell\_cure\_poison |
|
||||
+-------+-----------------------------+
|
||||
| 8 | spell\_disruption |
|
||||
+-------+-----------------------------+
|
||||
| 9 | spell\_bethsaidean\_touch |
|
||||
+-------+-----------------------------+
|
||||
| 9 | spell\_cause\_critical |
|
||||
+-------+-----------------------------+
|
||||
| 9 | spell\_lightning\_bolt |
|
||||
+-------+-----------------------------+
|
||||
| 10 | spell\_identify |
|
||||
+-------+-----------------------------+
|
||||
| 10 | spell\_faerie\_fog |
|
||||
+-------+-----------------------------+
|
||||
| 10 | spell\_control\_weather |
|
||||
+-------+-----------------------------+
|
||||
| 10 | spell\_dispel\_evil |
|
||||
+-------+-----------------------------+
|
||||
| 10 | do\_disarm |
|
||||
+-------+-----------------------------+
|
||||
| 11 | spell\_colour\_spray |
|
||||
+-------+-----------------------------+
|
||||
| 11 | do\_bite |
|
||||
+-------+-----------------------------+
|
||||
| 11 | spell\_dispel\_magic |
|
||||
+-------+-----------------------------+
|
||||
| 11 | do\_bloodlet |
|
||||
+-------+-----------------------------+
|
||||
| 12 | spell\_sleep |
|
||||
+-------+-----------------------------+
|
||||
| 12 | spell\_curse |
|
||||
+-------+-----------------------------+
|
||||
| 12 | spell\_call\_lightning |
|
||||
+-------+-----------------------------+
|
||||
| 12 | spell\_remove\_curse |
|
||||
+-------+-----------------------------+
|
||||
| 12 | spell\_enchant\_weapon |
|
||||
+-------+-----------------------------+
|
||||
| 12 | spell\_word\_of\_recall |
|
||||
+-------+-----------------------------+
|
||||
| 13 | spell\_harm |
|
||||
+-------+-----------------------------+
|
||||
| 13 | spell\_fireball |
|
||||
+-------+-----------------------------+
|
||||
| 13 | spell\_expurgation |
|
||||
+-------+-----------------------------+
|
||||
| 13 | spell\_flamestrike |
|
||||
+-------+-----------------------------+
|
||||
| 13 | spell\_midas\_touch |
|
||||
+-------+-----------------------------+
|
||||
| 13 | spell\_energy\_drain |
|
||||
+-------+-----------------------------+
|
||||
| 14 | spell\_spectral\_furor |
|
||||
+-------+-----------------------------+
|
||||
| 14 | spell\_charm\_person |
|
||||
+-------+-----------------------------+
|
||||
| 15 | spell\_remove\_trap |
|
||||
+-------+-----------------------------+
|
||||
| 16 | spell\_farsight |
|
||||
+-------+-----------------------------+
|
||||
| 16 | do\_detrap |
|
||||
+-------+-----------------------------+
|
||||
| 17 | spell\_transport |
|
||||
+-------+-----------------------------+
|
||||
| 17 | spell\_dream |
|
||||
+-------+-----------------------------+
|
||||
| 18 | spell\_sulfurous\_spray |
|
||||
+-------+-----------------------------+
|
||||
| 18 | spell\_pass\_door |
|
||||
+-------+-----------------------------+
|
||||
| 19 | spell\_sonic\_resonance |
|
||||
+-------+-----------------------------+
|
||||
| 20 | do\_gouge |
|
||||
+-------+-----------------------------+
|
||||
| 20 | spell\_acid\_blast |
|
||||
+-------+-----------------------------+
|
||||
| 21 | spell\_portal |
|
||||
+-------+-----------------------------+
|
||||
| 23 | spell\_black\_fist |
|
||||
+-------+-----------------------------+
|
||||
| 25 | do\_punch |
|
||||
+-------+-----------------------------+
|
||||
| 25 | do\_circle |
|
||||
+-------+-----------------------------+
|
||||
| 25 | do\_brew |
|
||||
+-------+-----------------------------+
|
||||
| 27 | spell\_magnetic\_thrust |
|
||||
+-------+-----------------------------+
|
||||
| 27 | do\_poison\_weapon |
|
||||
+-------+-----------------------------+
|
||||
| 28 | spell\_scorching\_surge |
|
||||
+-------+-----------------------------+
|
||||
| 30 | do\_scribe |
|
||||
+-------+-----------------------------+
|
||||
| 30 | do\_bash |
|
||||
+-------+-----------------------------+
|
||||
| 30 | spell\_astral\_walk |
|
||||
+-------+-----------------------------+
|
||||
| 31 | do\_mistwalk |
|
||||
+-------+-----------------------------+
|
||||
| 32 | spell\_ethereal\_fist |
|
||||
+-------+-----------------------------+
|
||||
| 32 | spell\_knock |
|
||||
+-------+-----------------------------+
|
||||
| 33 | spell\_recharge |
|
||||
+-------+-----------------------------+
|
||||
| 34 | spell\_caustic\_fount |
|
||||
+-------+-----------------------------+
|
||||
| 35 | spell\_sacral\_divinity |
|
||||
+-------+-----------------------------+
|
||||
| 35 | spell\_plant\_pass |
|
||||
+-------+-----------------------------+
|
||||
| 37 | spell\_hand\_of\_chaos |
|
||||
+-------+-----------------------------+
|
||||
| 37 | spell\_acetum\_primus |
|
||||
+-------+-----------------------------+
|
||||
| 39 | spell\_solar\_flight |
|
||||
+-------+-----------------------------+
|
||||
| 41 | do\_broach |
|
||||
+-------+-----------------------------+
|
||||
| 41 | spell\_frost\_breath |
|
||||
+-------+-----------------------------+
|
||||
| 42 | spell\_helical\_flow |
|
||||
+-------+-----------------------------+
|
||||
| 42 | spell\_animate\_dead |
|
||||
+-------+-----------------------------+
|
||||
| 42 | spell\_lightning\_breath |
|
||||
+-------+-----------------------------+
|
||||
| 43 | spell\_acid\_breath |
|
||||
+-------+-----------------------------+
|
||||
| 44 | spell\_fire\_breath |
|
||||
+-------+-----------------------------+
|
||||
| 45 | spell\_gas\_breath |
|
||||
+-------+-----------------------------+
|
||||
| 46 | spell\_spiral\_blast |
|
||||
+-------+-----------------------------+
|
||||
| 46 | spell\_black\_lightning |
|
||||
+-------+-----------------------------+
|
||||
| 48 | do\_stun |
|
||||
+-------+-----------------------------+
|
||||
| 48 | spell\_quantum\_spike |
|
||||
+-------+-----------------------------+
|
||||
| 50 | do\_hitall |
|
||||
+-------+-----------------------------+
|
||||
| 51 | spell\_possess |
|
||||
+-------+-----------------------------+
|
||||
| 51 | spell\_change\_sex |
|
||||
+-------+-----------------------------+
|
||||
| 51 | spell\_gate |
|
||||
+-------+-----------------------------+
|
||||
| 51 | do\_slice |
|
||||
+-------+-----------------------------+
|
||||
| 51 | spell\_polymorph |
|
||||
+-------+-----------------------------+
|
||||
| 51 | do\_berserk |
|
||||
+-------+-----------------------------+
|
||||
|
||||
( + the affects they apply float, sneak, hide, detect invisibility,
|
||||
detect magic, detect evil, invisibility)
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
Zones
|
||||
=====
|
||||
|
||||
Problem
|
||||
-------
|
||||
|
||||
Say you create a room named *Meadow* in your nice big forest MUD. That's
|
||||
all nice and dandy, but what if you, in the other end of that forest
|
||||
want another *Meadow*? As a game creator, this can cause all sorts of
|
||||
confusion. For example, teleporting to *Meadow* will now give you a
|
||||
warning that there are two *Meadow* s and you have to select which one.
|
||||
It's no problem to do that, you just choose for example to go to
|
||||
``2-meadow``, but unless you examine them you couldn't be sure which of
|
||||
the two sat in the magical part of the forest and which didn't.
|
||||
|
||||
Another issue is if you want to group rooms in geographic regions for
|
||||
example. Let's say the "normal" part of the forest should have separate
|
||||
weather patterns from the magical part. Or maybe a magical disturbance
|
||||
echoes through all magical-forest rooms. It would then be convenient to
|
||||
be able to simply find all rooms that are "magical" so you could send
|
||||
messages to them.
|
||||
|
||||
Zones in Evennia
|
||||
----------------
|
||||
|
||||
*Zones* try to separate rooms by global location. In our example we
|
||||
would separate the forest into two parts - the magical and the
|
||||
non-magical part. Each have a *Meadow* and rooms belonging to each part
|
||||
should be easy to retrieve.
|
||||
|
||||
Many MUD codebases hardcode zones as part of the engine and database.
|
||||
Evennia does no such distinction due to the fact that rooms themselves
|
||||
are meant to be customized to any level anyway. Below are two
|
||||
suggestions for how zones could be implemented.
|
||||
|
||||
Zones using Tags
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
*OBS: Placeholder - this section is NOT fully supported by the code at
|
||||
the moment!*
|
||||
|
||||
All objects in Evennia can hold any number of `tags <Tag.html>`_. Tags
|
||||
are short labels that you attach to objects. They make it very easy to
|
||||
retrieve groups of objects. An object can have any number of different
|
||||
tags. So let's attach the relevant tag to our forest:
|
||||
|
||||
::
|
||||
|
||||
forestobj.tags.add("magicalforest", category="zone")
|
||||
|
||||
You could add this manually, or automatically during creation somehow
|
||||
(you'd need to modify your @dig command for this, most likely).
|
||||
|
||||
Henceforth you can then easily retrieve only objects with a given tag:
|
||||
|
||||
::
|
||||
|
||||
import ev
|
||||
rooms = ev.managers.Tags.get_objs_with_tag("magicalforest", category="zone") # (error here)
|
||||
|
||||
Zones using Aliases
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
All objects have a *key* property, stored in the database. This is the
|
||||
primary name of the object. But it can also have any number of *Aliases*
|
||||
connected to itself. This allows Players to refer to the object using
|
||||
both key and alias - a "big red door" can also be referred to as the
|
||||
alias "door". Aliases are actually separate database entities and are as
|
||||
such very fast to search for in the database, about as fast as searching
|
||||
for the object's primary key in fact.
|
||||
|
||||
This makes Aliases another candidate for implementing zones. All you
|
||||
need to do is to come up with a consistent aliasing scheme. Here's one
|
||||
suggestion:
|
||||
|
||||
::
|
||||
|
||||
#zonename|key
|
||||
|
||||
There is nothing special about this format; it's just a string we store
|
||||
- a way to clump the zone-name and the room-key together in a "tag" we
|
||||
can easily find and match against later. We could have used a format
|
||||
like ``"zonename.key"`` or ``"ZONE:zonename,ROOMNAME:key"`` or some
|
||||
other combination if we liked that better. So, using our suggested
|
||||
format we assume we (arbitrarily) divide our forest example into the
|
||||
zones ``magicforest`` and ``normalforest``. These are the added aliases
|
||||
we use for the respective *Meadow* 's:
|
||||
|
||||
::
|
||||
|
||||
#magicforest|meadow
|
||||
#normalforest|meadow
|
||||
|
||||
The primary key of each will still be *Meadow*, and players will still
|
||||
see that name. We can also add any number of other Aliases to each
|
||||
meadow if we want. But we will also henceforth always be able to
|
||||
uniquely identify the right meadow by prepending its primary key name
|
||||
with ``#zonename|``.
|
||||
|
||||
Enforcing zones
|
||||
---------------
|
||||
|
||||
Maybe you feel that this usage of aliases for zones is loose and ad-hoc.
|
||||
It is indeed, and there is no guarantee that a builder would follow the
|
||||
naming convention - unless you force them. And you can do that easily by
|
||||
changing for example the ``@dig`` `Command <Commands.html>`_ to require
|
||||
the zone to be given:
|
||||
|
||||
::
|
||||
|
||||
@dig zone|roomname:typeclass = north;n, south;s
|
||||
|
||||
Just have the ``@dig`` command auto-add an alias of the correct format
|
||||
and hey-presto! A functioning zone system! An even more convenient way
|
||||
to enforce zones would be for the new room to inherit the zone from the
|
||||
room we are building from.
|
||||
|
||||
Overload the default ``search`` method on a typeclass for further
|
||||
functionality:
|
||||
|
||||
::
|
||||
|
||||
def search(self, ostring, zone=None, *args, **kwargs):
|
||||
if zone:
|
||||
ostring = "#%s|%s" % (ostring, zone)
|
||||
return self.dbobj.search(ostring, *args, **kwargs)
|
||||
|
||||
You will then be able to do, from commands:
|
||||
|
||||
::
|
||||
|
||||
meadow_obj = self.caller.search("meadow", zone="magicforest")
|
||||
|
||||
and be sure you are getting the magical meadow, not the normal one.
|
||||
|
||||
You could also easily build search queries searching only for rooms with
|
||||
aliases starting with ``#magicforest|``. This would allow for easy
|
||||
grouping and retrieving of all rooms in a zone for whatever need to
|
||||
have.
|
||||
|
||||
Evennia's open solution to zones means that you have much more power
|
||||
than in most MUD systems. There is for example no reason why you have to
|
||||
group and organize only rooms with this scheme.
|
||||
|
||||
Using typeclasses and inheritance for zoning
|
||||
--------------------------------------------
|
||||
|
||||
The aliasing system above doesn't instill any sort of functional
|
||||
difference between a magical forest room and a normal one - it's just an
|
||||
abitrary way to attach tags to objects for quick retrieval later. To
|
||||
enforce differences you will need to use
|
||||
`Typeclasses <Typeclasses.html>`_. If you know that a certain typeclass
|
||||
of room will always be in a certain zone you could even hard-code the
|
||||
zone in the typeclass rather than enforce the ``@dig`` command to do it:
|
||||
|
||||
::
|
||||
|
||||
class MagicalForestRoom(Room)
|
||||
def at_object_creation(self):
|
||||
...
|
||||
self.aliases.add("#magicforest|%s" % self.key)
|
||||
...
|
||||
class NormalForestRoom(Room)
|
||||
def at_object_creation(self):
|
||||
...
|
||||
self.aliases.add("#normalforest|%s" % self.key)
|
||||
...
|
||||
|
||||
Of course, an alternative way to implement zones themselves is to have
|
||||
all rooms/objects in a zone inherit from a given typeclass parent - and
|
||||
then limit your searches to objects inheriting from that given parent.
|
||||
The effect would be the same and you wouldn't need to implement any
|
||||
ad-hoc aliasing scheme; but you'd need to expand the search
|
||||
functionality to properly search the inheritance tree.
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
\`ev\` - Evennia's flat API
|
||||
===========================
|
||||
|
||||
Evennia consists of many components, some of which interact in rather
|
||||
complex ways. One such example is the Typeclass system which is
|
||||
implemented across four different folders in ``src/``. This is for
|
||||
efficiency reasons and to avoid code duplication, but it means that it
|
||||
can be a bit of a hurdle to understand just what connects to what and
|
||||
which properties are actually available/inherited on a particular game
|
||||
entity you want to use.
|
||||
|
||||
Evennia's ``ev`` API (Application Programming Interface) tries to help
|
||||
with this. ``ev.py`` sits in evennia's root directory which means you
|
||||
can import it from your code simply with ``import ev``. The ``ev``
|
||||
module basically implements shortcuts to the innards of ``src/``. The
|
||||
goal is to give a very "flat" structure (as opposed to a deeply nested
|
||||
one). Not only is this a Python recommendation, it also makes it much
|
||||
easier to see what you have.
|
||||
|
||||
Exploring \`ev\`
|
||||
----------------
|
||||
|
||||
To check out evennia interactively, it's recommended you use a more
|
||||
advanced Python interpreter, like `ipython <http://ipython.org/>`_. With
|
||||
ipython you can easily read module headers and help texts as well as
|
||||
list possible completions.
|
||||
|
||||
Start a python interactive shell, then get ``ev``:
|
||||
|
||||
::
|
||||
|
||||
import ev
|
||||
|
||||
In ipython we can now do for example ``ev?`` to read the API's help
|
||||
text. Using eg. ``ev.Object?`` will read the documentation for the
|
||||
``Object`` typeclass. Use ``??`` to see source code. Tab on ``ev.`` to
|
||||
see what ``ev`` contains.
|
||||
|
||||
Some highlights
|
||||
---------------
|
||||
|
||||
- ``Object, Player, Script, Room, Character, Exit`` - direct links to
|
||||
the most common base classes in Evennia.
|
||||
- ``search_*`` - shortcuts to the search functions in
|
||||
``src.utils.search``, such as ``search_object()`` or
|
||||
``search_script()``
|
||||
- ``create_*`` - are convenient links to all object-creation functions.
|
||||
Note that all Typeclassed objects *must* be created with methods such
|
||||
as these (or their parents in ``src.utils.create``) to make
|
||||
Typeclasses work.
|
||||
- ``managers`` - this is a container object that groups shortcuts to
|
||||
initiated versions of Evennia's django *managers*. So
|
||||
``managers.objects`` is in fact equivalent to ``ObjectDB.objects``
|
||||
and you can do ``managers.objects.all()`` to get a list of all
|
||||
database objects. The managers allows to explore the database in
|
||||
various ways. To use, do ``from ev import manager`` and access the
|
||||
desired manager on the imported ``managers`` object.
|
||||
- default\_cmds - this is a container on ``ev`` that groups all default
|
||||
commands and command sets under itself. Do
|
||||
``from ev import default_cmds`` and you can then access any default
|
||||
command from the imported ``default_cmds`` object.
|
||||
- ``utils, logger, gametime, ansi`` are various utilities. Especially
|
||||
utils contains many useful functions described
|
||||
`here <CodingUtils.html>`_.
|
||||
- ``syscmdkeys`` is a container that holds all the system-command keys
|
||||
needed to define system commands. Similar to the ``managers``
|
||||
container, you import this and can then access the keys on the
|
||||
imported ``syscmdkeys`` object.
|
||||
|
||||
To remember when importing from \`ev\`
|
||||
--------------------------------------
|
||||
|
||||
Properties on ``ev`` are *not* modules in their own right. They are just
|
||||
shortcut properties stored in the ``ev.py`` module. That means that you
|
||||
cannot use dot-notation to ``import`` nested module-names over ``ev``.
|
||||
The rule of thumb is that you cannot use ``import`` for more than one
|
||||
level down. Hence you can do
|
||||
|
||||
::
|
||||
|
||||
import ev
|
||||
print ev.default_cmds.CmdLook
|
||||
|
||||
or import one level down
|
||||
|
||||
::
|
||||
|
||||
from ev import default_cmds
|
||||
print default_cmds.CmdLook
|
||||
|
||||
but you *cannot* import two levels down
|
||||
|
||||
::
|
||||
|
||||
from ev.default_cmds import CmdLook # error!
|
||||
|
||||
This will give you an ``ImportError`` telling you that the module
|
||||
``default_cmds`` cannot be found. This is not so strange -
|
||||
``default_cmds`` is just a variable name in the ``ev.py`` module, it
|
||||
does not exist outside of it.
|
||||
|
||||
As long as you keep this in mind, you should be fine. If you really want
|
||||
full control over which level of package you import you can always
|
||||
bypass ``ev`` and import directly from ``src/``. If so, look at
|
||||
``ev.py`` to see where it imports from.
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
# /usr/bin/python
|
||||
#
|
||||
# Auto-generate reST documentation for Sphinx from Evennia source
|
||||
# code.
|
||||
#
|
||||
# Uses etinenned's sphinx autopackage script. Install it to folder
|
||||
# "autogen" in this same directory:
|
||||
#
|
||||
# hg clone https://bitbucket.org/etienned/sphinx-autopackage-script autogen
|
||||
#
|
||||
# Create a directory tree "code/" containing one directory for every
|
||||
# package in the PACKAGE dictionary below. Make sure EVENNIA_DIR
|
||||
# points to an Evennia root dir. Then just run this script. A new
|
||||
# folder sphinx/source/code will be created with the reST sources.
|
||||
#
|
||||
# Note - this is not working very well at the moment, not all sources
|
||||
# seems to be properly detected and you get lots of errors when
|
||||
# compiling. To nevertheless make a link to the code from the doc
|
||||
# front page, edit docs/sphinx/sources/index.rst to reference
|
||||
# code/modules.
|
||||
#
|
||||
|
||||
|
||||
import os, subprocess, shutil
|
||||
|
||||
EVENNIA_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
SPHINX_DIR = os.path.join(os.path.join(EVENNIA_DIR, "docs"), "sphinx")
|
||||
SPHINX_SRC_DIR = os.path.join(SPHINX_DIR, "source")
|
||||
SPHINX_CODE_DIR = os.path.join(SPHINX_SRC_DIR, "code")
|
||||
|
||||
CONVERT_DIR = os.path.join(SPHINX_DIR, 'src2rest')
|
||||
AUTOGEN_EXE = os.path.join(CONVERT_DIR, os.path.join("autogen", "generate_modules.py"))
|
||||
|
||||
def src2rest():
|
||||
"""
|
||||
Run import
|
||||
"""
|
||||
try:
|
||||
shutil.rmtree(SPHINX_CODE_DIR)
|
||||
print "Emptied old %s." % SPHINX_CODE_DIR
|
||||
except OSError:
|
||||
pass
|
||||
os.mkdir(SPHINX_CODE_DIR)
|
||||
|
||||
inpath = EVENNIA_DIR
|
||||
outpath = SPHINX_CODE_DIR
|
||||
excludes = [r".*/migrations/.*", r"evennia\.py$", r"manage\.py$",
|
||||
r"runner\.py$", r"server.py$", r"portal.py$"]
|
||||
|
||||
subprocess.call(["python", AUTOGEN_EXE,
|
||||
"-n", "Evennia",
|
||||
"-d", outpath,
|
||||
"-s", "rst",
|
||||
"-f",
|
||||
inpath] + excludes)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
try:
|
||||
src2rest()
|
||||
except Exception, e:
|
||||
print e
|
||||
print "Make sure to read the header of this file so that it's properly set up."
|
||||
|
|
@ -1,208 +0,0 @@
|
|||
#! /usr/bin/python
|
||||
#
|
||||
# Converts Evennia's google-style wiki pages to reST documents
|
||||
#
|
||||
# Setting up to run:
|
||||
#
|
||||
# 1) Install pandoc (converts from html to reST):
|
||||
#
|
||||
# apt-get install pandoc (debian)
|
||||
# or download from
|
||||
# http://johnmacfarlane.net/pandoc/
|
||||
#
|
||||
# 2) Retrieve wiki files (*.wiki) from Google code by mercurial. Make sure
|
||||
# to retrieve them into a subdirectory wiki here:
|
||||
#
|
||||
# hg clone https://code.google.com/p/evennia.wiki wiki
|
||||
#
|
||||
# Regular Usage:
|
||||
#
|
||||
# 1) Make sure to pull/update the wiki files into wiki/ so you have the latest.
|
||||
# 2) Run wiki2rest.py. Temporary work folders html and rest will be created, so make sure you
|
||||
# have the rights to create directories here. The contents
|
||||
# of rest/ will automatically be copied over to docs/sphinx/source/wiki.
|
||||
# 3) From docs/sphinx, run e.g. "make html" to build the documentation from the reST sources.
|
||||
#
|
||||
|
||||
import sys, os, subprocess, re, urllib, shutil
|
||||
|
||||
# Setup
|
||||
|
||||
EVENNIA_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
|
||||
SPHINX_DIR = os.path.join(os.path.join(EVENNIA_DIR, "docs"), "sphinx")
|
||||
SPHINX_SRC_DIR = os.path.join(SPHINX_DIR, "source")
|
||||
SPHINX_WIKI_DIR = os.path.join(SPHINX_SRC_DIR, "wiki")
|
||||
CONVERT_DIR = os.path.join(SPHINX_DIR, "wiki2rest")
|
||||
|
||||
WIKI_DIR = os.path.join(CONVERT_DIR, "wiki")
|
||||
HTML_DIR = os.path.join(CONVERT_DIR, "html")
|
||||
REST_DIR = os.path.join(CONVERT_DIR, "rest")
|
||||
WIKI2HTML_DIR = os.path.join(CONVERT_DIR, "wiki2html")
|
||||
PANDOC_EXE = "pandoc"
|
||||
RUBY_EXE = "ruby"
|
||||
|
||||
WIKI_ROOT_URL = "http://code.google.com/p/evennia/wiki/"
|
||||
WIKI_CRUMB_URL = "/p/evennia/wiki/"
|
||||
|
||||
# files to not convert (no file ending)
|
||||
NO_CONVERT = ["SideBar", "Screenshot"]
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# This is a version of the importer that imports Google html pages
|
||||
# directly instead of going through the ruby converter. Alas, while
|
||||
# being a lot cleaner in implementation, this seems to produce worse
|
||||
# results in the end (both visually and with broken-link issues), so
|
||||
# not using it at this time.
|
||||
#
|
||||
# See the wiki2html at the bottom for the ruby-version.
|
||||
#------------------------------------------------------------
|
||||
|
||||
def fetch_google_wiki_html_files():
|
||||
"""
|
||||
Acquire wiki html pages from google code
|
||||
"""
|
||||
# use wiki repo to find html filenames
|
||||
html_urls = dict([(re.sub(r"\.wiki", "", fn), WIKI_ROOT_URL + re.sub(r"\.wiki", "?show=content", fn))
|
||||
for fn in os.listdir(WIKI_DIR) if fn.endswith(".wiki")])
|
||||
|
||||
#html_urls = {"Index":html_urls["Index"]} #SR!
|
||||
|
||||
html_pages = {}
|
||||
for name, html_url in html_urls.items():
|
||||
print "urllib: fetching %s ..." % html_url
|
||||
f = urllib.urlopen(html_url)
|
||||
s = f.read()
|
||||
s = clean_html(s)
|
||||
html_pages[name] = s #clean_html(f.read())
|
||||
f.close()
|
||||
|
||||
# saving html file for debugging
|
||||
f = open(os.path.join(HTML_DIR, "%s.html" % name), 'w')
|
||||
f.write(s)
|
||||
f.close()
|
||||
|
||||
return html_pages
|
||||
|
||||
def clean_html(htmlstring):
|
||||
"""
|
||||
Clean up html properties special to google code and not known by pandoc
|
||||
"""
|
||||
# remove wikiheader tag (searches over many lines). Unfortunately python <2.7 don't support
|
||||
# DOTALL flag in re.sub ...
|
||||
matches = re.findall(r'<div id="wikiheader">.*?</div>.*?</div>.*?</div>', htmlstring, re.DOTALL)
|
||||
for match in matches:
|
||||
htmlstring = htmlstring.replace(match, "")
|
||||
#htmlstring = re.sub(r'<div id="wikiheader">.*?</div>.*?</div>.*?</div>', "", htmlstring, re.DOTALL)
|
||||
# remove prefix from urls
|
||||
htmlstring = re.sub('href="' + WIKI_CRUMB_URL, 'href="', htmlstring)
|
||||
# remove #links from headers
|
||||
htmlstring = re.sub(r'(<h[0-9]>.*?)(<a href="#.*?</a>)(.*?</h[0-9]>)', r"\1\3", htmlstring)
|
||||
return htmlstring
|
||||
|
||||
def html2rest(name, htmlstring):
|
||||
"""
|
||||
Convert html data to reST with pandoc
|
||||
"""
|
||||
print " pandoc: Converting %s ..." % name
|
||||
p = subprocess.Popen([PANDOC_EXE, '--from=html', '--to=rst', '--reference-links'],
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
return p.communicate(htmlstring)[0]
|
||||
|
||||
|
||||
def wiki2rest_ver2():
|
||||
"""
|
||||
Convert Google wiki pages to reST.
|
||||
"""
|
||||
# obtain all html data from google code
|
||||
html_pages = fetch_google_wiki_html_files()
|
||||
|
||||
# convert to output files
|
||||
for name, htmldata in html_pages.items():
|
||||
restfilename = os.path.join(REST_DIR, "%s.rst" % name)
|
||||
f = open(restfilename, 'w')
|
||||
f.write(html2rest(name, htmldata))
|
||||
f.close()
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# This converter uses the 3rd party ruby script to convert wiki pages
|
||||
# to html, seems to produce a better final result than downloading html
|
||||
# directly from google code.
|
||||
#------------------------------------------------------------
|
||||
|
||||
def wiki2rest():
|
||||
"""
|
||||
Convert from wikifile to rst file, going through html
|
||||
"""
|
||||
|
||||
# convert from wikifile to html with wiki2html
|
||||
#subprocess.call([RUBY_EXE, "wiki_convertor.rb", WIKI_DIR, HTML_DIR], cwd=WIKI2HTML_DIR)
|
||||
# use google html output directly (really bad)
|
||||
#subprocess.call(["python", "get_wiki_as_html.py"])
|
||||
# use wikify importer
|
||||
print " wikify: converting wiki -> html ..."
|
||||
subprocess.call(["python", "wikify.py", "-r", "-e", "-m", "-c", "-a", "-s", "wiki", "-d", "html"])
|
||||
|
||||
# convert from html to rest with pandoc
|
||||
htmlfilenames = [fn for fn in os.listdir(HTML_DIR)
|
||||
if fn.endswith(".html") and not re.sub(r".html", "", fn) in NO_CONVERT]
|
||||
|
||||
print " pandoc: converting html -> ReST ..."
|
||||
for filename in htmlfilenames:
|
||||
|
||||
htmlfilename = os.path.join(HTML_DIR, filename)
|
||||
|
||||
# cleanup of code
|
||||
string = "".join(open(htmlfilename, 'r').readlines())
|
||||
string = re.sub(r'<p class="summary">[A-Za-z0-9 .-\:]*</p>', "", string)
|
||||
string = re.sub(r"<wiki:toc max_depth="[0-9]*" />", "", string)
|
||||
string = re.sub(r"<wiki:toc max_depth<h1>"[0-9]*" /></h1>", "", string)
|
||||
string = re.sub(r"<p>#settings Featured</p>", "", string)
|
||||
string = re.sub(r'<p class="labels">Featured</p>', "", string)
|
||||
string = re.sub(r'<wiki:comment>', "", string)
|
||||
string = re.sub(r'</wiki:comment>', "", string)
|
||||
string = re.sub(r'<wiki:comment>[<>;a-zA\/\n-&Z0-9 ]*</wiki:comment>', "", string)
|
||||
f = open(htmlfilename, 'w')
|
||||
f.write(string)
|
||||
f.close()
|
||||
|
||||
rstfilename = os.path.join(REST_DIR, re.sub(r".html$", ".rst", filename))
|
||||
#print "pandoc: converting %s -> %s" % (htmlfilename, rstfilename)
|
||||
subprocess.call([PANDOC_EXE, "--from=html", "--to=rst", "-o", rstfilename, htmlfilename])
|
||||
|
||||
# main program
|
||||
if __name__ == "__main__":
|
||||
|
||||
print "creating/cleaning output dirs ...",
|
||||
try:
|
||||
shutil.rmtree(REST_DIR)
|
||||
os.mkdir(REST_DIR)
|
||||
except OSError:
|
||||
os.mkdir(REST_DIR)
|
||||
try:
|
||||
shutil.rmtree(HTML_DIR)
|
||||
os.mkdir(HTML_DIR)
|
||||
except Exception:
|
||||
os.mkdir(HTML_DIR)
|
||||
try:
|
||||
shutil.rmtree(SPHINX_WIKI_DIR)
|
||||
except Exception:
|
||||
# this is created by copy mechanism.
|
||||
pass
|
||||
print "done."
|
||||
print "running conversions ..."
|
||||
|
||||
try:
|
||||
wiki2rest()
|
||||
except Exception, e:
|
||||
print e
|
||||
print "Make sure to read this file's header to make sure everything is correctly set up. "
|
||||
sys.exit()
|
||||
|
||||
print "... conversions finished (make sure there are no error messages above)."
|
||||
print "copying rest data to %s ..." % SPHINX_WIKI_DIR
|
||||
shutil.copytree(REST_DIR, SPHINX_WIKI_DIR)
|
||||
print "... done. You can now build the docs from the sphinx directory with e.g. 'make html'."
|
||||
|
|
@ -1,962 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
#
|
||||
# wikify.py - Convert from wikitext to HTML
|
||||
# Based on large portions of JeremyRuston's TiddlyWiki JS Wikifier
|
||||
# Changed to GoogleCode wiki syntax, python by Michael Crawford <mike@dataunity.com>
|
||||
""" Convert wikitext to HTML """
|
||||
|
||||
# Jeremy's license:
|
||||
# Copyright (c) UnaMesa Association 2004-2007
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice, this
|
||||
# list of conditions and the following disclaimer in the documentation and/or other
|
||||
# materials provided with the distribution.
|
||||
#
|
||||
# Neither the name of the UnaMesa Association nor the names of its contributors may be
|
||||
# used to endorse or promote products derived from this software without specific
|
||||
# prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
# My license:
|
||||
# Copyright (c) Data Unity 2007
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice, this
|
||||
# list of conditions and the following disclaimer in the documentation and/or other
|
||||
# materials provided with the distribution.
|
||||
#
|
||||
# Neither the name of the Data Unity nor the names of its contributors may be
|
||||
# used to endorse or promote products derived from this software without
|
||||
# specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import re, os, os.path, htmlentitydefs, urllib
|
||||
|
||||
|
||||
class _HTML:
|
||||
""" An HTML node factory factory. """
|
||||
|
||||
class Node:
|
||||
""" An HTML element. """
|
||||
def __init__(self, parent, tagname, text="", attribs={}, empty=False, **kwargs):
|
||||
self.tagname = tagname
|
||||
self.attribs = dict(attribs)
|
||||
self.children = list()
|
||||
self.empty = empty
|
||||
if text != "":
|
||||
self.appendText(text)
|
||||
if parent is not None:
|
||||
parent.children.append(self)
|
||||
self.parent = parent
|
||||
|
||||
def appendText(self, text):
|
||||
if text == "": return
|
||||
_HTML.Text(self, text)
|
||||
|
||||
def __str__(self):
|
||||
attrs = " ".join([ '%s="%s"' % i for i in self.attribs.iteritems() ])
|
||||
if attrs: attrs = " " + attrs
|
||||
if self.empty:
|
||||
return "<%s%s/>" % (self.tagname, attrs)
|
||||
|
||||
children = "".join([str(c) for c in self.children])
|
||||
return "<%s%s>%s</%s>" % (self.tagname, attrs, children, self.tagname)
|
||||
|
||||
def isInside(self, tagname):
|
||||
k = self
|
||||
while k is not None:
|
||||
if k.tagname == tagname:
|
||||
return True
|
||||
k = k.parent
|
||||
return False
|
||||
|
||||
class Text:
|
||||
""" Simple text node. """
|
||||
entities = [ (k,v)
|
||||
for k,v in htmlentitydefs.entitydefs.iteritems()
|
||||
if k != "amp" and k[0] != "#" ]
|
||||
|
||||
def __init__(self, parent, text=""):
|
||||
self.text = self._clean(text)
|
||||
if parent is not None:
|
||||
parent.children.append(self)
|
||||
|
||||
def _clean(self, text):
|
||||
text = text.replace("&", "&")
|
||||
for k,v in self.entities:
|
||||
text = text.replace(v, "&%s;" % k)
|
||||
return text
|
||||
|
||||
def __str__(self):
|
||||
return self.text
|
||||
|
||||
|
||||
def __getattr__(self, attr):
|
||||
""" Return an element constructor using the attribute as the tagname """
|
||||
def factory(parent=None, **kwargs):
|
||||
return self.Node(parent, attr, **kwargs)
|
||||
return factory
|
||||
|
||||
HTML = _HTML()
|
||||
|
||||
URLSTR = r"(?:file|http|https|mailto|ftp|irc|news|data):[^\s'\"]+(?:/|\b)"
|
||||
URL = re.compile(URLSTR, re.M)
|
||||
IMGURLSTR = r".+((\.[Pp][Nn][Gg])|(\.[Gg][Ii][Ff])|(\.[Jj][Pp][Ee]?[Gg]))"
|
||||
IMGURL = re.compile(IMGURLSTR, re.M)
|
||||
YOUTUBESTR = r"http://www.youtube.com/watch\?v=([A-Za-z0-9_-]+)"
|
||||
YOUTUBEURL = re.compile(YOUTUBESTR, re.M)
|
||||
YOUTUBEREPL = r'<object width="425" height="355"><param name="movie" value="http://www.youtube.com/v/%s&rel=1"></param><param name="wmode" value="transparent"></param><embed src="http://www.youtube.com/v/hQPHf_8J8Eg&rel=1" type="application/x-shockwave-flash" wmode="transparent" width="425" height="355"></embed></object>'
|
||||
VIDEOURLSTR = r".+((\.[Aa][Vv][Ii])|(\.[Mm][Oo][Vv])|(\.[Mm][Pp][Ee]?[Gg]))"
|
||||
VIDEOURL = re.compile(VIDEOURLSTR, re.M)
|
||||
VIDEOREPL = r'<embed src = "%s" width="400" height="350" hidden=false autostart=true loop=1>'
|
||||
CODEURLSTR = r"http://([^\.]+).googlecode.com/svn/trunk/([^#]+)#((?:(?:(?:[\d]+)?\-)?[\d]+)|(?:[\d]+\-?))((?:\:(?:[\:]|[^\W])+))?"
|
||||
CODEURL = re.compile(CODEURLSTR, re.M)
|
||||
CODEREPL = r'<a href="%(url)s">svn://%(site)s/trunk/%(file)s</a><pre name="code" class="%(class)s">%(lines)s</pre>'
|
||||
|
||||
def GoogleCode_ReadSVNFile(wikifier, domain, path, start, end):
|
||||
""" Try to read a file from subversion for inclusion in the wiki. """
|
||||
|
||||
gcurl = "http://%s.googlecode.com/svn/trunk/%s" % (domain,path)
|
||||
fdata = urllib.urlopen(gcurl).readlines()
|
||||
return gcurl, fdata[start-1:end]
|
||||
|
||||
|
||||
def GoogleCode_IsExternalLink(wikifier, link):
|
||||
""" See if the link points outside of the wiki. """
|
||||
|
||||
if GoogleCode_Exists(wikifier, link):
|
||||
return False;
|
||||
|
||||
if URL.match(link):
|
||||
return True
|
||||
|
||||
if '.' in link or '\\' in link or '/' in link or '#' in link:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def GoogleCode_Exists(wikifier, wikipage):
|
||||
""" See if a wiki page exists inside this wiki. """
|
||||
path = os.path.join(wikifier.srcdir, "%s.wiki" % wikipage)
|
||||
if os.path.exists(path):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def GoogleCode_Heading(wikifier, termRegExp=None, **kwargs):
|
||||
termMatch = termRegExp.search(wikifier.source, wikifier.nextMatch)
|
||||
if termMatch is None: return
|
||||
if (len(wikifier.output.children) and
|
||||
"br" == getattr(wikifier.output.children[-1], 'tagname', '')):
|
||||
wikifier.output.children.pop(-1)
|
||||
if (len(wikifier.output.children) and
|
||||
"br" == getattr(wikifier.output.children[-1], 'tagname', '')):
|
||||
wikifier.output.children.pop(-1)
|
||||
output = HTML.Node(wikifier.output, "h%i" % wikifier.matchLength)
|
||||
wikifier.outputText(output, wikifier.nextMatch, termMatch.start())
|
||||
wikifier.nextMatch = termMatch.end()
|
||||
|
||||
def GoogleCode_SimpleElement(wikifier, termRegExp=None, tagName=None, **kwargs):
|
||||
if wikifier.output.isInside(tagName):
|
||||
wikifier.outputText(wikifier.output, wikifier.matchStart, wikifier.nextMatch)
|
||||
return
|
||||
elif wikifier.source[wikifier.nextMatch-1] == "_":
|
||||
wikifier.outputText(wikifier.output, wikifier.matchStart, wikifier.nextMatch-1)
|
||||
|
||||
if termRegExp.search(wikifier.source, wikifier.nextMatch) is None: return
|
||||
output = HTML.Node(wikifier.output, tagName, **kwargs)
|
||||
wikifier.subWikifyTerm(output, termRegExp)
|
||||
#if wikifier.source[wikifer.nextMatch-2] == "_":
|
||||
# wikifier.nextMatch -= 1
|
||||
|
||||
def GoogleCode_Blockquote(wikifier, termRegExp=None, **kwargs):
|
||||
sibs = wikifier.output.children
|
||||
if len(sibs) and getattr(sibs[-1], 'tagname', None) == "blockquote":
|
||||
wikifier.subWikifyTerm(sibs[-1], termRegExp)
|
||||
else:
|
||||
output = HTML.blockquote(wikifier.output, **kwargs)
|
||||
wikifier.subWikifyTerm(output, termRegExp)
|
||||
|
||||
def GoogleCode_Codeblock(wikifier, tagName=None, termRegExp=None, initRegExp=None, **kwargs):
|
||||
if 'attribs' not in kwargs:
|
||||
kwargs['attribs'] = {}
|
||||
|
||||
kwargs['attribs']['name'] = 'code'
|
||||
if 'class' not in kwargs['attribs']:
|
||||
kwargs['attribs']['class'] = wikifier.defaultHiLang.lower()
|
||||
else:
|
||||
kwargs['attribs']['class'] += " " + wikifier.defaultHiLang.lower()
|
||||
|
||||
output = HTML.Node(wikifier.output, tagName, **kwargs)
|
||||
tcount = 1
|
||||
matchStart = wikifier.nextMatch
|
||||
# Find the matching terminator
|
||||
while tcount > 0:
|
||||
nextTermMatch = termRegExp.search(wikifier.source, wikifier.nextMatch)
|
||||
nextInitMatch = initRegExp.search(wikifier.source, wikifier.nextMatch)
|
||||
|
||||
if not nextTermMatch:
|
||||
# No terminator. Syntax error, just ignore it.
|
||||
matchEnd = matchStart
|
||||
tcount = 0
|
||||
break
|
||||
elif not nextInitMatch or nextTermMatch.start() <= nextInitMatch.start():
|
||||
# Terminator goes first.
|
||||
nextMatch = nextTermMatch
|
||||
tcount -= 1
|
||||
if tcount > 0:
|
||||
matchEnd = nextMatch.end()
|
||||
else:
|
||||
matchEnd = nextMatch.start()
|
||||
else:
|
||||
nextMatch = nextInitMatch
|
||||
tcount += 1
|
||||
matchEnd = nextMatch.end()
|
||||
|
||||
wikifier.nextMatch = nextMatch.end()
|
||||
|
||||
# Copy the content
|
||||
wikifier.outputText(output, matchStart, matchEnd)
|
||||
|
||||
if "\n" not in wikifier.source[matchStart:matchEnd]:
|
||||
output.tagname = "code"
|
||||
|
||||
def GoogleCode_WikiWord(wikifier, **kwargs):
|
||||
if wikifier.matchStart > 0:
|
||||
# Make sure we're at the start of a word?
|
||||
preRegExp = re.compile("[!A-Za-z0-9]", re.M)
|
||||
preMatch = preRegExp.search(wikifier.source, wikifier.matchStart-1)
|
||||
if (preMatch is not None and
|
||||
preMatch.start() == wikifier.matchStart-1):
|
||||
wikifier.outputText(wikifier.output,wikifier.matchStart,wikifier.nextMatch)
|
||||
return
|
||||
|
||||
if wikifier.source[wikifier.matchStart] == "!":
|
||||
wikifier.outputText(wikifier.output,wikifier.matchStart+1,wikifier.nextMatch)
|
||||
elif GoogleCode_Exists(wikifier, wikifier.matchText):
|
||||
# Full link, everybody sees it
|
||||
HTML.a(wikifier.output, text=wikifier.matchText, attribs={"href": wikifier.matchText + wikifier.suffix})
|
||||
elif wikifier.autolink:
|
||||
# Partial link - only authorized users
|
||||
wikifier.outputText(wikifier.output,wikifier.matchStart,wikifier.nextMatch)
|
||||
link = HTML.a(wikifier.output, text="?", attribs={"href": wikifier.matchText + wikifier.suffix})
|
||||
else:
|
||||
wikifier.outputText(wikifier.output,wikifier.matchStart,wikifier.nextMatch)
|
||||
|
||||
def GoogleCode_LineBreak(wikifier, **kwargs):
|
||||
sibs = wikifier.output.children
|
||||
if wikifier.multibreak:
|
||||
HTML.p(wikifier.output, **kwargs)
|
||||
elif len(sibs) and (not hasattr(sibs[-1], 'tagname') or
|
||||
sibs[-1].tagname == "img"):
|
||||
# Only after an inline or header block.
|
||||
HTML.p(wikifier.output, **kwargs)
|
||||
HTML.p(wikifier.output, **kwargs)
|
||||
|
||||
def GoogleCode_PrettyLink(wikifier, lookaheadRegExp=None, **kwargs):
|
||||
lookMatch = lookaheadRegExp.search(wikifier.source, wikifier.matchStart)
|
||||
if lookMatch and lookMatch.start() == wikifier.matchStart:
|
||||
text = lookMatch.group(1)
|
||||
link = text
|
||||
if lookMatch.group(2):
|
||||
# Pretty bracketted link
|
||||
text = lookMatch.group(2)
|
||||
if GoogleCode_IsExternalLink(wikifier, link):
|
||||
# External link
|
||||
attribs={"href":link, "target": "_blank" }
|
||||
else:
|
||||
# Internal link
|
||||
attribs={"href":link + wikifier.suffix}
|
||||
|
||||
e = HTML.a(wikifier.output, attribs=attribs)
|
||||
|
||||
if URL.match(text):
|
||||
HTML.img(e, attribs={'src':text,
|
||||
'border': '0'})
|
||||
HTML.br(wikifier.output)
|
||||
else:
|
||||
HTML.Text(e, text)
|
||||
else:
|
||||
if GoogleCode_IsExternalLink(wikifier, text):
|
||||
# External link
|
||||
attribs={"href":link, "target": "_blank" }
|
||||
else:
|
||||
# Internal link
|
||||
attribs={"href":text + wikifier.suffix}
|
||||
|
||||
# Simple bracketted link
|
||||
e = HTML.a(wikifier.output, text=text, attribs=attribs)
|
||||
wikifier.nextMatch = lookMatch.end()
|
||||
|
||||
def GoogleCode_UrlLink(wikifier, **kwargs):
|
||||
attribs = {"href": wikifier.matchText}
|
||||
if GoogleCode_IsExternalLink(wikifier, wikifier.matchText):
|
||||
attribs["target"] = "_blank"
|
||||
|
||||
if IMGURL.match(wikifier.matchText):
|
||||
HTML.img(wikifier.output, attribs={'src':wikifier.matchText})
|
||||
HTML.br(wikifier.output)
|
||||
elif YOUTUBEURL.match(wikifier.matchText):
|
||||
match = YOUTUBEURL.match(wikifier.matchText)
|
||||
# Raw html ;)
|
||||
wikifier.output.children.append(YOUTUBEREPL % match.group(1))
|
||||
elif VIDEOURL.match(wikifier.matchText):
|
||||
# Raw html ;)
|
||||
wikifier.output.children.append(VIDEOREPL % wikifier.matchText)
|
||||
elif CODEURL.match(wikifier.matchText):
|
||||
# Raw html ;)
|
||||
# http://([^\.]+).googlecode.com/svn/trunk/([^\#]+)#([^\:]+)(?:\:([^\W]+))?
|
||||
|
||||
codeMatch = CODEURL.match(wikifier.matchText)
|
||||
parts = { "class": (codeMatch.group(4) or "").lower()[1:],
|
||||
"file": codeMatch.group(2),
|
||||
"site": codeMatch.group(1)}
|
||||
|
||||
lines = codeMatch.group(3)
|
||||
if '-' in lines:
|
||||
lines = lines.split('-')
|
||||
lines[0] = int(lines[0])
|
||||
lines[1] = int(lines[1])
|
||||
else:
|
||||
lines = [int(lines), int(lines)]
|
||||
|
||||
parts['class'] += ":firstline[%i]" % lines[0]
|
||||
url, parts['lines'] = GoogleCode_ReadSVNFile(wikifier, parts['site'],
|
||||
parts['file'], *lines)
|
||||
parts['url'] = url
|
||||
parts['lines'] = "".join(parts['lines'])
|
||||
|
||||
wikifier.output.children.append(CODEREPL % parts)
|
||||
else:
|
||||
HTML.a(wikifier.output, text=wikifier.matchText, attribs=attribs)
|
||||
|
||||
|
||||
def GoogleCode_Table(wikifier, sepRegExp=None, termRegExp=None, **kwargs):
|
||||
sibs = wikifier.output.children
|
||||
if len(sibs) and getattr(sibs[-1], 'tagname', None) == "table":
|
||||
table = sibs[-1]
|
||||
else:
|
||||
table = HTML.table(wikifier.output)
|
||||
row = HTML.tr(table)
|
||||
|
||||
termMatch = termRegExp.search(wikifier.source, wikifier.matchStart)
|
||||
if termMatch is None:
|
||||
termEnd = termStart = len(wikifier.source)
|
||||
else:
|
||||
termStart, termEnd = termMatch.start(), termMatch.end()
|
||||
|
||||
# Skip over the leading separator
|
||||
sepMatch = sepRegExp.search(wikifier.source, wikifier.matchStart)
|
||||
wikifier.nextMatch = wikifier.matchStart = sepMatch.end()
|
||||
sepMatch = sepRegExp.search(wikifier.source, wikifier.matchStart)
|
||||
attribs = { "style": "border: 1px solid #aaa; padding: 5px;" }
|
||||
|
||||
while sepMatch and sepMatch.end() <= termStart:
|
||||
cell = HTML.td(row, attribs=attribs)
|
||||
wikifier.subWikifyTerm(cell, sepRegExp)
|
||||
wikifier.nextMatch = sepMatch.end()
|
||||
sepMatch = sepRegExp.search(wikifier.source, wikifier.nextMatch)
|
||||
|
||||
wikifier.nextMatch = termEnd
|
||||
|
||||
|
||||
def GoogleCode_List(wikifier, lookaheadRegExp=None, termRegExp=None, **kwargs):
|
||||
currLevel = 0
|
||||
currType = None
|
||||
stack = [wikifier.output]
|
||||
indents = [currLevel]
|
||||
wikifier.nextMatch = wikifier.matchStart
|
||||
|
||||
lookMatch = lookaheadRegExp.search(wikifier.source, wikifier.nextMatch)
|
||||
while lookMatch and lookMatch.start() == wikifier.nextMatch:
|
||||
# See what kind of list it is
|
||||
if lookMatch.group(1):
|
||||
listType = "ul"
|
||||
itemType = "li"
|
||||
elif lookMatch.group(2):
|
||||
listType = "ol"
|
||||
itemType = "li"
|
||||
|
||||
listLevel = len(lookMatch.group(0))
|
||||
wikifier.nextMatch += len(lookMatch.group(0))
|
||||
|
||||
# Check for any changes in list type or indentation
|
||||
if listLevel > currLevel:
|
||||
# Indent further
|
||||
indents.append(listLevel)
|
||||
if currLevel == 0:
|
||||
target = stack[-1]
|
||||
else:
|
||||
target = stack[-1].children[-1]
|
||||
|
||||
stack.append(HTML.Node(target, listType))
|
||||
|
||||
elif listLevel < currLevel:
|
||||
# Indent less
|
||||
while indents[-1] > listLevel:
|
||||
stack.pop(-1)
|
||||
indents.pop(-1)
|
||||
|
||||
elif listLevel == currLevel and listType != currType:
|
||||
# Same level, different kind of list
|
||||
stack.pop(-1)
|
||||
stack.append(HTML.Node(stack[-1].children[-1], listType))
|
||||
|
||||
currLevel = listLevel
|
||||
currType = listType
|
||||
|
||||
# Output the item
|
||||
output = HTML.Node(stack[-1],itemType)
|
||||
wikifier.subWikifyTerm(output,termRegExp)
|
||||
|
||||
# Roll again
|
||||
lookMatch = lookaheadRegExp.search(wikifier.source, wikifier.nextMatch)
|
||||
|
||||
|
||||
|
||||
GoogleCodeWikiFormat = [
|
||||
{
|
||||
"name": "tablerow",
|
||||
"match": r"^(?:\|\|.+\|\|)",
|
||||
"termRegExp": re.compile(r"(\n)", re.M),
|
||||
"sepRegExp": re.compile(r"(\|\|)", re.M),
|
||||
"handler": GoogleCode_Table
|
||||
},
|
||||
|
||||
{ "name": "heading",
|
||||
"match": r"^={1,6}",
|
||||
"termRegExp": re.compile(r"([=]+)", re.M),
|
||||
"handler": GoogleCode_Heading
|
||||
},
|
||||
|
||||
{ "name": "list",
|
||||
"match": r"^(?:[ ]+)(?:[\*#])",
|
||||
"lookaheadRegExp": re.compile(r"^(?:[ ]+)(?:(\*)|(#))",re.M),
|
||||
"termRegExp": re.compile(r"(\n)", re.M),
|
||||
"handler": GoogleCode_List
|
||||
},
|
||||
|
||||
|
||||
|
||||
{ "name": "blockquote",
|
||||
"match": r"^(?:[ ]+)",
|
||||
"termRegExp": re.compile(r"(\n)", re.M),
|
||||
"handler": GoogleCode_Blockquote,
|
||||
"tagName": "blockquote"
|
||||
},
|
||||
|
||||
{ "name": "codeword",
|
||||
"match": r"\`",
|
||||
"initRegExp": re.compile(r"(\`)", re.M),
|
||||
"termRegExp": re.compile(r"(\`)", re.M),
|
||||
"handler": GoogleCode_Codeblock,
|
||||
"tagName": "code"
|
||||
},
|
||||
|
||||
{ "name": "codeblock",
|
||||
"match": r"\{\{\{",
|
||||
"initRegExp": re.compile(r"(\{\{\{)", re.M),
|
||||
"termRegExp": re.compile(r"(\}\}\})", re.M),
|
||||
"handler": GoogleCode_Codeblock,
|
||||
"tagName": "pre",
|
||||
"attribs": { "class": "codeblock" }
|
||||
},
|
||||
|
||||
{ "name": "bold",
|
||||
"match": r"[\*]",
|
||||
"termRegExp": re.compile(r"([\*])", re.M),
|
||||
"handler": GoogleCode_SimpleElement,
|
||||
"tagName": "b"
|
||||
},
|
||||
|
||||
{ "name": "italic",
|
||||
"match": r"(?:[^\w\b]|^)[\_]",
|
||||
"termRegExp": re.compile(r"([\_])[^\w\b]", re.M),
|
||||
"handler": GoogleCode_SimpleElement,
|
||||
"tagName": "i"
|
||||
},
|
||||
|
||||
{ "name": "strike",
|
||||
"match": r"\~\~",
|
||||
"termRegExp": re.compile(r"(\~\~)", re.M),
|
||||
"handler": GoogleCode_SimpleElement,
|
||||
"tagName": "strike"
|
||||
},
|
||||
|
||||
{ "name": "superscript",
|
||||
"match": r"\^",
|
||||
"termRegExp": re.compile(r"(\^)", re.M),
|
||||
"handler": GoogleCode_SimpleElement,
|
||||
"tagName": "sup"
|
||||
},
|
||||
|
||||
{ "name": "subscript",
|
||||
"match": r",,",
|
||||
"termRegExp": re.compile(r"(,,)", re.M),
|
||||
"handler": GoogleCode_SimpleElement,
|
||||
"tagName": "sub"
|
||||
},
|
||||
|
||||
{ "name": "prettyLink",
|
||||
"match": r"\[(?:(?:[A-Za-z][A-Za-z0-9\_\-]+)|(?:(?:file|http|https|mailto|ftp|irc|news|data):[^\s'\"]+(?:/|\b)))(?: .*?)?\]",
|
||||
"lookaheadRegExp": re.compile(r'\[(.*?)(?: (.*?))?\]', re.M),
|
||||
"handler": GoogleCode_PrettyLink
|
||||
},
|
||||
|
||||
{ "name": "wikiword",
|
||||
"match": r"(?:\!?(?:[A-Z]+[a-z]+[A-Z][A-Za-z]*)|(?:[A-Z]{2,}[a-z]+))",
|
||||
"handler": GoogleCode_WikiWord
|
||||
},
|
||||
|
||||
{ "name": "urlLink",
|
||||
"match": URLSTR,
|
||||
"handler": GoogleCode_UrlLink
|
||||
},
|
||||
|
||||
{ "name": "linebreak",
|
||||
"match": r"\n\n",
|
||||
"handler": GoogleCode_LineBreak,
|
||||
"empty": True
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
||||
class Wikifier:
|
||||
|
||||
def __init__(self, formatters, autolink=False, srcdir=os.getcwd(),
|
||||
multibreak=False, tabwidth=8, suffix=".html",
|
||||
hiLang="Python"):
|
||||
# Create the master regex
|
||||
forms = [ "(%s)" % r['match'] for r in formatters ]
|
||||
self.formatterRegExp = re.compile("|".join(forms), re.M)
|
||||
# Save the individual format handlers
|
||||
self.formatters = formatters
|
||||
self.autolink = autolink
|
||||
self.srcdir = srcdir
|
||||
self.multibreak = multibreak and True or False
|
||||
self.tabwidth = tabwidth
|
||||
self.suffix = suffix
|
||||
self.defaultHiLang = hiLang
|
||||
|
||||
def _clean(self, text):
|
||||
text = text.replace("\r\n", "\n")
|
||||
|
||||
# Out, out, damned tabs
|
||||
text = text.replace("\t", " " * self.tabwidth)
|
||||
|
||||
if not self.multibreak:
|
||||
# Remove redundant line breaks
|
||||
tlen = len(text) + 1
|
||||
while tlen > len(text):
|
||||
tlen = len(text)
|
||||
text = text.replace("\n\n\n", "\n\n")
|
||||
|
||||
while text.startswith("#"):
|
||||
# Process any wiki-headers
|
||||
line, text = text.split("\n", 1)
|
||||
self._header(line)
|
||||
|
||||
return text
|
||||
|
||||
def _header(self, line):
|
||||
tagname, content = line.split(" ", 1)
|
||||
if tagname == "#summary":
|
||||
self.summary = content
|
||||
elif tagname == "#labels":
|
||||
self.labels = tuple(content.split(","))
|
||||
|
||||
def wikify(self, source, labels=None, summary=None):
|
||||
self.labels = labels
|
||||
self.summary = summary
|
||||
# Clean up the content
|
||||
self.source = self._clean(source)
|
||||
self.nextMatch = 0
|
||||
# Do it
|
||||
self.output = HTML.div(None)
|
||||
self.subWikifyUnterm()
|
||||
|
||||
return "".join([str(c) for c in self.output.children])
|
||||
|
||||
def findMatch(self, source, start):
|
||||
return self.formatterRegExp.search(source, start)
|
||||
|
||||
def subWikifyUnterm(self, output=None):
|
||||
oldOutput = self.output
|
||||
if output is not None:
|
||||
self.output = output
|
||||
|
||||
match = self.findMatch(self.source, self.nextMatch)
|
||||
while match:
|
||||
# Output any text before the match
|
||||
if match.start() > self.nextMatch:
|
||||
self.outputText(self.output, self.nextMatch, match.start())
|
||||
|
||||
# Set the match parameters for the handler
|
||||
self.matchStart = match.start()
|
||||
self.matchLength = len(match.group(0))
|
||||
self.matchText = match.group(0)
|
||||
self.nextMatch = match.end()
|
||||
|
||||
# Figure out which sub-group matched (zero-indexed)
|
||||
t,submatch = [ (t,s) for t, s in enumerate(match.groups()) if s ][0]
|
||||
|
||||
# Handle it
|
||||
self.formatters[t]['handler'](self, **self.formatters[t])
|
||||
|
||||
# Go back for more matches
|
||||
match = self.findMatch(self.source, self.nextMatch)
|
||||
|
||||
if self.nextMatch < len(self.source):
|
||||
self.outputText(self.output, self.nextMatch, len(self.source))
|
||||
self.nextMatch = len(self.source)
|
||||
|
||||
# Restore the destination node
|
||||
self.output = oldOutput
|
||||
|
||||
def subWikifyTerm(self, output, termRegExp):
|
||||
oldOutput = self.output
|
||||
if output is not None:
|
||||
self.output = output
|
||||
|
||||
# Get the first matches for the formatter and terminator RegExps
|
||||
termMatch = termRegExp.search(self.source, self.nextMatch)
|
||||
if termMatch:
|
||||
match = self.findMatch(self.source[:termMatch.start()], self.nextMatch)
|
||||
else:
|
||||
match = self.findMatch(self.source, self.nextMatch)
|
||||
|
||||
while termMatch or match:
|
||||
# If the terminator comes before the next formatter match, we're done
|
||||
if termMatch and (not match or termMatch.start() <= match.start()):
|
||||
if termMatch.start() > self.nextMatch:
|
||||
self.outputText(self.output,self.nextMatch,termMatch.start())
|
||||
self.matchText = termMatch.group(1)
|
||||
self.matchLength = len(self.matchText)
|
||||
self.matchStart = termMatch.start()
|
||||
self.nextMatch = self.matchStart + self.matchLength
|
||||
self.output = oldOutput
|
||||
return
|
||||
|
||||
# Output any text before the match
|
||||
if match.start() > self.nextMatch:
|
||||
self.outputText(self.output, self.nextMatch, match.start())
|
||||
|
||||
# Set the match parameters for the handler
|
||||
self.matchStart = match.start()
|
||||
self.matchLength = len(match.group(0))
|
||||
self.matchText = match.group(0)
|
||||
self.nextMatch = match.end()
|
||||
|
||||
# Figure out which sub-group matched (zero-indexed)
|
||||
t,submatch = [ (t,s) for t, s in enumerate(match.groups()) if s ][0]
|
||||
|
||||
# Handle it
|
||||
self.formatters[t]['handler'](self, **self.formatters[t])
|
||||
|
||||
termMatch = termRegExp.search(self.source, self.nextMatch)
|
||||
if termMatch:
|
||||
match = self.findMatch(self.source[:termMatch.start()], self.nextMatch)
|
||||
else:
|
||||
match = self.findMatch(self.source, self.nextMatch)
|
||||
|
||||
if self.nextMatch < len(self.source):
|
||||
self.outputText(self.output, self.nextMatch,len(self.source))
|
||||
self.nextMatch = len(self.source)
|
||||
|
||||
self.output = oldOutput
|
||||
|
||||
|
||||
def outputText(self, output, startPos, endPos):
|
||||
HTML.Text(output, self.source[startPos:endPos])
|
||||
|
||||
|
||||
DEFAULT_TEMPLATE = '''
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<br style="clear: both" /><br/>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
%(toc)s
|
||||
</div>
|
||||
|
||||
<i>%(title)s</i>
|
||||
|
||||
<div class="summary">
|
||||
%(summary)s
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
%(wiki)s
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
DEFAULT_TEMPLATE = '''
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div class="summary">
|
||||
%(summary)s
|
||||
</div>
|
||||
<div class="narrow">
|
||||
%(wiki)s
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
|
||||
def wikify(pages, options=None):
|
||||
# See options definition below.
|
||||
# Pass any object with those (potential) attributes
|
||||
srcdir = getattr(options, 'srcdir', os.getcwd())
|
||||
destdir = getattr(options, 'destdir', None)
|
||||
|
||||
# Find all requested files
|
||||
onlyStale = False
|
||||
if getattr(options, 'all', False):
|
||||
pages = [ k for k in os.listdir(srcdir)
|
||||
if k.endswith(".wiki") ]
|
||||
onlyStale = True
|
||||
if destdir is None:
|
||||
destdir = os.getcwd()
|
||||
|
||||
# Create the magic 8-ball
|
||||
w = Wikifier(GoogleCodeWikiFormat,
|
||||
autolink=getattr(options, 'autolink', False),
|
||||
tabwidth=getattr(options, 'tabwidth', 8),
|
||||
multibreak=getattr(options, 'multibreak', False),
|
||||
srcdir=srcdir,
|
||||
suffix=".html")
|
||||
|
||||
rets = []
|
||||
for wikiname in pages:
|
||||
# Clean up the page name
|
||||
if wikiname.endswith(".wiki"):
|
||||
wikiname = wikiname[:-5]
|
||||
|
||||
wikifilename = os.path.join(srcdir, "%s.wiki" % wikiname)
|
||||
if onlyStale:
|
||||
# See if the output is fresh, and if so, skip it
|
||||
wikidestname = os.path.join(destdir, "%s.html" % wikiname)
|
||||
try:
|
||||
sstat = os.stat(wikifilename)
|
||||
except:
|
||||
continue
|
||||
try:
|
||||
dstat = os.stat(wikidestname)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
if dstat.st_mtime > sstat.st_mtime:
|
||||
continue
|
||||
|
||||
# Load the wiki content
|
||||
wikifilename = os.path.join(srcdir, "%s.wiki" % wikiname)
|
||||
wikisrc = file(wikifilename).read()
|
||||
|
||||
# Ask a question
|
||||
wikified = w.wikify(wikisrc)
|
||||
|
||||
reFind = re.compile(r'<h(\d)>\s*([^\<]*[\S])\s*</h\d>')
|
||||
strRepl = r'<h\g<1>><a name="\g<2>">\g<2></a></h\g<1>>'
|
||||
|
||||
# Number the sections
|
||||
if getattr(options, 'number', True):
|
||||
sectstack = []
|
||||
matches = []
|
||||
curLevel = 0
|
||||
match = reFind.search(wikified)
|
||||
while match is not None:
|
||||
level = int(match.group(1))
|
||||
|
||||
while level > len(sectstack):
|
||||
sectstack.append(1)
|
||||
|
||||
while len(sectstack) > level:
|
||||
sectstack.pop(-1)
|
||||
|
||||
if curLevel >= level:
|
||||
sectstack[-1] += 1
|
||||
curLevel = len(sectstack)
|
||||
|
||||
sectnum = ".".join([str(n) for n in sectstack]) + "."
|
||||
matches.append((sectnum, match))
|
||||
match = reFind.search(wikified, match.end())
|
||||
|
||||
matches.reverse()
|
||||
for sectnum, match in matches:
|
||||
wikified = wikified[:match.start()+4] + sectnum + " " + wikified[match.start()+4:]
|
||||
|
||||
|
||||
# Generate the TOC
|
||||
if getattr(options, 'toc', True):
|
||||
matches = [ '<b>%s: Contents</b>' % wikiname ]
|
||||
for match in reFind.findall(wikified):
|
||||
if int(match[0]) > getattr(options, 'levels', 3): continue
|
||||
indent = " " * ((int(match[0])) * 2)
|
||||
|
||||
href = "#" + match[1]
|
||||
anchor = '%s<a href="%s">%s</a>' % (indent, href, match[1])
|
||||
matches.append(anchor)
|
||||
toc = "<br>".join(matches)
|
||||
else:
|
||||
toc = "" #-e -d /home/adam/src/CSpaceWiki/
|
||||
|
||||
# Generate the body links
|
||||
if getattr(options, 'links', True):
|
||||
wikified = reFind.sub(strRepl, wikified)
|
||||
|
||||
# Find a summary
|
||||
summary = ""
|
||||
if w.summary is not None:
|
||||
summary = w.summary
|
||||
|
||||
if not getattr(options, 'raw', False):
|
||||
# Fill the template
|
||||
wikified = options.template % {
|
||||
"toc": toc,
|
||||
"title": wikiname,
|
||||
"wiki": wikified,
|
||||
"summary": summary }
|
||||
|
||||
# Save it or write it
|
||||
if destdir is not None:
|
||||
outputname = os.path.join(destdir, "%s.html" % wikiname)
|
||||
file(outputname,"w").write(wikified)
|
||||
|
||||
mainpage = getattr(options, 'mainpage', 'MainPage')
|
||||
if wikiname == mainpage:
|
||||
rets.append((wikiname, outputname))
|
||||
outputname = os.path.join(destdir, "index.html")
|
||||
file(outputname,"w").write(wikified)
|
||||
|
||||
wikified = outputname
|
||||
rets.append((wikiname, wikified))
|
||||
return rets
|
||||
|
||||
if __name__ == "__main__":
|
||||
from optparse import OptionParser
|
||||
import sys
|
||||
|
||||
parser = OptionParser()
|
||||
|
||||
# Output format options
|
||||
parser.add_option("-t", "--template", dest="template",
|
||||
help="use TPLTFILE to wrap wiki output", metavar="TPLTFILE")
|
||||
parser.add_option("-n", "--number", dest="number", metavar="NUMSTART",
|
||||
help="number the headings in the body and table of contents starting with level NUMSTART")
|
||||
parser.add_option("-l", "--levels", dest="levels", type="int",
|
||||
help="create toc to depth LEVELS", metavar="LEVELS")
|
||||
parser.add_option("-c", "--skiptoc", dest="toc", action="store_false",
|
||||
help="leave toc out, even if template has slot")
|
||||
parser.add_option("-u", "--unlink", dest="links", action="store_false",
|
||||
help="don't create named anchors for toc links")
|
||||
parser.add_option("-a", "--autolink", dest="autolink", action="store_false",
|
||||
help="autolink wiki words that don't exist")
|
||||
parser.add_option("-w", "--tabwidth", dest="tabwidth", type="int",
|
||||
help="replace tabs by WIDTH spaces", metavar="WIDTH")
|
||||
parser.add_option("-m", "--multibreak", dest="multibreak", action="store_true",
|
||||
help="don't collapse multiple line breaks")
|
||||
parser.add_option("-r", "--raw", dest="raw", action="store_true",
|
||||
help="raw wiki translation -- no wrapping, no toc, no links")
|
||||
parser.add_option("-p", "--mainpage", dest="mainpage", metavar="PAGENAME",
|
||||
help="set main page to PAGENAME")
|
||||
|
||||
# Batch / Location options
|
||||
parser.add_option("-s", "--srcdir", dest="srcdir",
|
||||
help="wiki format sources in SRCDIR", metavar="SRCDIR")
|
||||
parser.add_option("-d", "--destdir", dest="destdir",
|
||||
help="write html output into DESTDIR", metavar="DESTDIR")
|
||||
parser.add_option("-e", "--stale", dest="all", action="store_true",
|
||||
help="convert all wiki files that are stale or missing from DESTDIR")
|
||||
|
||||
|
||||
parser.set_default('toc', True)
|
||||
parser.set_default('links', True)
|
||||
parser.set_default('template', None)
|
||||
parser.set_default('number', False)
|
||||
parser.set_default('levels', 3)
|
||||
parser.set_default('tabwidth', 8)
|
||||
parser.set_default('multibreak', False)
|
||||
parser.set_default('mainpage', "MainPage") # Identity of index
|
||||
|
||||
parser.set_default('srcdir', os.getcwd())
|
||||
parser.set_default('destdir', None)
|
||||
parser.set_default('all', False)
|
||||
|
||||
# Parse the command line
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if options.template is None:
|
||||
options.template = DEFAULT_TEMPLATE
|
||||
elif os.path.exists(options.template):
|
||||
options.template = file(options.template).read()
|
||||
else:
|
||||
print "Template not found: %s" % options.template
|
||||
parser.print_usage()
|
||||
sys.exit()
|
||||
#sys.exit()
|
||||
for wikiname, htmldata in wikify(args, options):
|
||||
if options.destdir:
|
||||
#print wikiname + ":",
|
||||
if htmldata is not None:
|
||||
pass
|
||||
#print htmldata
|
||||
else:
|
||||
print "Complete."
|
||||
elif htmldata is not None:
|
||||
print htmldata
|
||||
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue