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:
Griatch 2014-02-02 23:26:11 +01:00
parent 399f896b03
commit 2306449ee1
81 changed files with 359 additions and 16545 deletions

View file

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

View file

@ -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."

View file

@ -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.

View file

@ -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

View file

@ -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)
]

View file

@ -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

View file

@ -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>

View file

@ -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.

View file

@ -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.

View file

@ -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>`_

View file

@ -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>

View file

@ -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.

View file

@ -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.

View file

@ -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.*

View file

@ -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).

View file

@ -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.

View file

@ -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.

View file

@ -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>`_

View file

@ -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.

View file

@ -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>`_.

View file

@ -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.

View file

@ -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.

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

View file

@ -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.

View file

@ -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``.

View file

@ -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.

View file

@ -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

View file

@ -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.

View file

@ -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``.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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>, ...]

View file

@ -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!

View file

@ -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!

View file

@ -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.

View file

@ -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 ...

View file

@ -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.

View file

@ -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>`_.

View file

@ -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>`_.

View file

@ -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.

View file

@ -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!

View file

@ -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.

View file

@ -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>`_

View file

@ -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.

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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>`_

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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).

View file

@ -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.

View file

@ -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.

View file

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

View file

@ -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.

View file

@ -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.

View file

@ -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."

View file

@ -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"&lt;wiki:toc max_depth=&quot;[0-9]*&quot; /&gt;", "", string)
string = re.sub(r"&lt;wiki:toc max_depth<h1>&quot;[0-9]*&quot; /&gt;</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'&lt;wiki:comment&gt;', "", string)
string = re.sub(r'&lt;/wiki:comment&gt;', "", string)
string = re.sub(r'&lt;wiki:comment&gt;[<>;a-zA\/\n-&Z0-9 ]*&lt;/wiki:comment&gt;', "", 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'."

View file

@ -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("&", "&amp;")
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 = "&nbsp;" * ((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