Trunk: Merged the Devel-branch (branches/griatch) into /trunk. This constitutes a major refactoring of Evennia. Development will now continue in trunk. See the wiki and the past posts to the mailing list for info. /Griatch

This commit is contained in:
Griatch 2010-08-29 18:46:58 +00:00
parent df29defbcd
commit f83c2bddf8
222 changed files with 22304 additions and 14371 deletions

71
ABOUT
View file

@ -1,71 +0,0 @@
Evennia Proof-of-Concept
------------------------
Evennia is a proof-of-concept MUD server written entirely in Python, backed
by SQL. The project rises from a general dissatisfaction with the limitations
of softcode in MUX and MUSH, and the generally inflexible Diku-derivatives and
relatives.
Evennia represents a combination of several technologies, and most importantly
of all, my first venture into codebase design. You may find things within
the source that look strange to you, perhaps not ideally designed. I'm open
to suggestions, but this really is largely an experiment and a learning
experience.
Design Objectives
-----------------
1) To create a MU* server that serves as a great foundation for capable admins
to craft into their respective games. It is not my intention to provide a
full-fledged, ready-to-run base, I'm releasing the means to make such games.
2) Development of games on Evennia must be easy for anyone with some degree
of Python experience. Building needs to be easy, and per-room, per-object,
and environmental customizations need to be simple to do.
3) The server must utilize SQL as a storage back-end to allow for web->game
integration. See the details on Django later on in the document for more
details.
4) Any and all game-specific configuration must reside in SQL, not
external configuration files. The only exception is the settings.py file
containing the SQL information.
How it all Works
----------------
Python (Including the SQL driver of your choice)
|-Twisted (http://twistedmatrix.com)
|-SQL (MySQL, SQLite, Postgresql)
|-Django (http://djangoproject.com)
Evennia is built on top of Twisted, a networking engine that handles a lot
of the guts and lower-level socket stuff for us.
Serving as our storage medium, SQL is one of the more important and unique
features of the codebase. It allows for very simple code in many cases, and
can lead to a game being a lot more scalable due to the inherent speed of
most modern SQL servers. Another extremely important benefit is that by
storing everything in SQL, we make the entire game accessible from other
means, such as a website. Which leads us to the next component.
Django is perhaps one of the most interesting introductions to the codebase,
since I'm not aware of any other server using it to run MU*'s. Django is
technically a Python web framework, but it also includes a great data modeling
and database abstraction module. This means that things like Players or
Objects can be represented by a very short class, then related to one another.
This allows us to add, remove, delete, and manipulate things in our database
very easily. Another huge benefit is the admin interface that Django more
or less automatically generates for us. Instead of a bunch of clunky admin
commands, you can fire up your web browser and administer pretty much
everything from there, although equivalent in-game commands may be offered.
The possibilities for developing your game's website are nearly endless with
this tandem of MU* server, SQL, and Django.
Support
-------
The best place for support is the Evennia website, located at:
http://evennia.com
Reporting Bugs
--------------
Make sure to report any bugs you encounter on the issue tracker on our Google
Code project. This is easily reached at:
http://code.evennia.com

View file

@ -1,3 +1,4 @@
Evennia Code Style
------------------
All code submitted or committed to the Evennia project needs to follow the
@ -5,21 +6,48 @@ guidelines outlined in Python PEP 8, which may be found at:
http://www.python.org/dev/peps/pep-0008/
A quick list of other stuff
---------------------------
* 4-space indention, NO TABS!
* Unix line endings
* CamelCase is only used for classes, nothing else.
* All variables and functions are to be lowercase, and words separated by
underscores.
A quick list of code style points
---------------------------------
* 4-space indendation, NO TABS!
* Unix line endings.
* CamelCase is only used for classes, nothing else.
* All non-global variable names and all function names are to be lowercase,
words separated by underscores. Variable names should always be more than
two letters long.
* Module-level global variables (only) are to be in CAPITAL letters.
* Imports are to be done in this order:
- Python modules (Global, outside of Evennia)
- Python modules (builtins and modules otherwise unrelated to Evennia)
- Twisted
- Django
- Project modules
- Evennia src/ modules
- Evennia game/ modules
Pylint
------
The program 'pylint' (http://www.logilab.org/857) is a useful tool for checking
your Python code for errors. It will also check how well your code adheres to
the PEP 8 guidelines and tells you what can be improved.
Since pylint cannot catch dynamically created variables used in commands and
elsewhere in Evennia, one needs to reduce some checks to avoid false errors and
warnings. For best results, run pylint like this:
> pylint --disable=E1101,E0102,F0401,W0232,R0903 filename.py
To avoid entering the options every time, you can auto-create a pylintrc file by
using the option --generate-rcfile. You need to dump this output into a
file .pylintrc, for example like this (linux):
> pylint --disable=E1101,E0102,F0401,W0232,R0903 --generate-rcfile >& ~/.pylintrc
From now on you can then just run
> pylint filename.py
Ask Questions!
--------------
If any of this outlined in PEP 8 or the list above doesn't make sense, please
If any of the rules outlined in PEP 8 or in the list above doesn't make sense, please
don't hesitate to ask on the Evennia mailing list at http://evennia.com.
Keeping our code style uniform makes this project much easier for a wider group
of people to participate in.
of people to participate in.

129
INSTALL
View file

@ -1,37 +1,114 @@
The most detailed and up-to-date instructions can always be found at
http://groups.google.com/group/evennia/web/getting-started?hl=en
It is recommended that you refer to there. The instructions below
should be fine, but are terse and may be slightly out of date.
Evennia install
---------------
Requirements
------------
* Python 2.5 strongly recommended, although 2.3 or 2.4 may work just fine.
* Twisted -- http://twistedmatrix.com/
* PySqlite2 (If you're using the default SQLite driver)
* Django (Latest trunk from Subversion recommended)
* Optional: Apache2 or equivalent webserver with a Python interpreter
module. Required for web interface.
* Python (www.python.org)
Version 2.5+ strongly recommended, although 2.3 or 2.4 *may* work just fine.
* Twisted (http://twistedmatrix.com)
Version 10.0+
+ ZopeInterface 3.0+ (www.zope.org/Products/ZopeInterface)
+ (pywin32 (sourceforge.net/projects/pywin32) - needed for Windows only)
* Django (www.djangoproject.com)
Version 1.1+ or latest subversion trunk recommended.
+ PIL library (www.pythonware.com/products/pil)
* PySqlite2 (http://code.google.com/p/pysqlite)
Needed if you want to use the sqlite default database. Otherwise you need to
check which databases are supported by Django and install/setup that instead.
* Apache2 (http://httpd.apache.org)
Optional. Other equivalent webservers with a Python interpreter module can also
be used. Required for serving final production web interface (not needed for
web development, django has a test server that's good enough for that).
* Subversion (subversion.apache.org)
This is needed to download Evennia itself.
Users of most decent Linux distros should be able to install all the above through
their normal package managers. Windows users will need to visit the various homepages
and install the programs manually.
Installation
------------
At this point in time, the codebase is changing so rapidly that writing
installation instructions is pretty much pointless. When we get to that stage
in development, we'll make sure to update this. But for the really determined
(or stubborn), here's a rough outline:
* Install Django.
* Get a copy of the Evennia source.
* Optional: Set up your apache2.conf to point mod-python to the settings.py
file if you want the web features.
* Change to the evennia/game directory and run something like:
python manage.py
* Make sure you have/install the prerequsites listed above.
* Get a copy of the Evennia source through subversion (SVN):
> svn checkout http://code.google.com/p/evennia/source/checkout evennia
Once you have downloaded, this is as much internet connectivity you need
for trying out Evennia; you don't need to run any web server or to make
anything visible online (that's for when you have a game created and
want people to join it). For now it runs just fine locally on your machine.
* Change to the evennia/game directory and run
> python manage.py
This will create a settings.py file. You may override any of the default
values in src/config_defaults.py by pasting them into settings.py and
changing the values.
changing the values. Never edit src/config_defaults.py directly!
* If you want to use anything other than the default SQLite setup, copy and
modify the DATABASE_* variables from src/config_defaults.py.
* Run 'python manage.py syncdb'
* Run 'python evennia.py -i start'. This will start the MU* server on port 4000
by default. You may change this via the web interface or by editing the
config table in SQL.
modify the database-related variables from src/config_defaults.py.
* Run
> python manage.py syncdb
This sets up the database. Answer 'yes' to create an admin account. Supply
a name, e-mail and password when prompted. Remember what you enter since
they are used when you log into the server as admin. The name given will
be the name of your admin character.
* Run
> python evennia.py -i start
This will start the MU* server on port 4000 by default. You may change
this in the settings.py file by changing the variable GAMEPORTS to one
or more port numbers you want to use.
Note: Using -i starts the server in 'interactive mode' - it will print
messages to standard output and you can shut it down with (on most systems)
Ctrl-C. To start the server as a background process (suitable for production
environments), just skip the -i flag. A server running as a process is
stopped with 'python evennia.py stop'.
* Start up your MUD client of choice and point it to your server and port 4000.
If you are just running locally the server name is most likely 'localhost'.
* Login with the email address and password you provided to the syncdb script.
Welcome to Evennia!
Web features (Optional)
-----------------------
If you want to test web features you can also start Django's
test web server. You should start this as a separate process, e.g.
in a separate terminal. Go to Evennia's game/ directory and enter
> python manage.py runserver
(obs, not to be confused with 'testserver').
Django's test webserver starts up locally on port 8000. Point your webbrowser
to 'localhost:8000' and you should see Evennia's nice default page,
graphics and all (gasp!). You cannot play the game from here, but you can
view and edit the database extensively using the powerful admin interface,
courtesy of Django.
Note: You should never use the django testserver for anything more than local
tests. If you have a full-fledged web server (like Apache) running you should use
that for production environments. Set up your apache2.conf to point mod-python
to your newly created settings.py file (see online documentation for details).

View file

@ -8,7 +8,7 @@ Definitions
-----------
* "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification.
* "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder as specified below.
* "Copyright Holder" is Gregory Taylor.
* "Copyright Holder" is Gregory Taylor and Griatch (griatch AT gmail DOT com).
* "You" is you, if you're thinking about copying or distributing this Package.
* "Distribution fee" is a fee you charge for providing a copy of this Package to another party.
* "Freely Available" means that no fee is charged for the right to use the item, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it.

161
README
View file

@ -1,10 +1,34 @@
Evennia README http://evennia.com
--------------
- < 2010 (earlier revisions)
- May 2010 - merged ABOUT and README. Added Current status /Griatch
- Aug 2010 - evennia devel merged into trunk /Griatch
Contents:
---------
- Version
- About Evennia
- Current Status
- Contact, Support and Development
- Directory structure
- Design Objectives
- The Components
Version
-------
Evennia Alpha SVN version
About Evennia
-------------
Evennia is a proof-of-concept MU* server that aims to provide a functional
base for developers. While there are quite a few codebases that do the same
bare-bones base for developers. While there are quite a few codebases that do the same
(and very well in many cases), we are taking a unique spin on the problem.
Some of our flagship features include (or will one day include):
* Coded fully in Python using Django and Twisted
* Extensive web integration.
* The ability to build/administer through a web browser.
* Shared accounts between the website and the game.
@ -13,6 +37,7 @@ Some of our flagship features include (or will one day include):
(djangoproject.com)
* Simple and easily extensible design.
* Very granular permissions. Individual and group based.
* Powerful an extremely extendable base system
The essential points here are the web integration and the SQL backing via
Django. The Django framework has database abstraction abilities that give us
@ -24,19 +49,135 @@ many features free, such as:
simple yet very powerful.
* For any model we outline for the server's use, we have the ability to
more or less automatically generate a web-based admin interface for it with
two lines of code. This lets you Create, Update, or Delete entries.
two lines of code. This lets you Create, Update, or Delete entries, as well
limit permissions for those abilities.
* On the web-based side of things, features such as automatic form validation,
abstraction of sessions and cookies, and access to whatever game data you
desire are all attractive.
Support and Development
desire are all attractive.
See the INSTALL file for help on setting up and running Evennia.
Current Status
--------------
Aug 2010:
Evennia-griatch-branch is ready for merging with trunk. This marks
a rather big change in the inner workings of the server, but should
hopefully bring everything together into one consistent package as
code development continues.
May 2010:
Evennia is currently being heavily revised and cleaned from
the years of gradual piecemeal development. It is thus in a very
'Alpha' stage at the moment. This means that old code snippets
will not be backwards compatabile. Changes touch almost all
parts of Evennia's innards, from the way Objects are handled
to Events, Commands and Permissions.
Contact, Support and Development
-----------------------
Since we're so early in development, we really can't hope to offer much support.
However, if you'd like to report bugs, make suggestions, or help with the
code work, visit either or both of the following links:
We are early in development, but we try to give support best we can
if you feel daring enough to play with the codebase. Make a post to
the mailing list or chat us up on IRC if you have questions. We also
have a bug tracker if you want to report bugs. Finally, if
you are willing to help with the code work, we much appreciate all help!
Visit either of the following resources:
* Evennia Webpage
http://evennia.com
* Evennia wiki (documentation)
http://code.google.com/p/evennia/wiki/Index
* Evennia Code Page
http://code.evennia.com
* Evennia Code Page (See INSTALL text for installation)
http://code.google.com/p/evennia/source/checkout
* Bug tracker
http://code.google.com/p/evennia/issues/list
* IRC channel
visit channel #evennia on the Freenode IRC network
Directory structure
-------------------
evennia
|_______src
| |___(engine-related dirs)
|
|_______game (start the server)
|___gamesrc
|___(game-related dirs)
The two main directories you will spend most of your time in
are src/ and game/ (probably mostly game/).
Basically src contains everything related to
running the gritty stuff behind the scenes. Unless you are an
Evennia developer you should normally make sure never to edit
things in src/, since this is where we push new revisions that
may overwrite your changes when you update. You will however
need to have a good feeling for the resources supplied by
the functions in src, since accessing them correctly is the key
to making your dream game come true.
If src/ is the Evennia developer's domain, the game/ directory
on the other hand contains YOUR game. This is where you will
define and extend the commands, objects and systems of Evennia
to make your dream game. game/ contains the main server settings
and the actual evennia executable to start things. game/gamesrc/
holds all the templates for creating objects in your virtual world.
With this little first orientation, you should head into the online
Evennia wiki documentation to get going with the codebase.
Design Objectives
-----------------
1) To create a barebones MU* server that serves as a great foundation
for capable admins to craft their own respective games. It is not the
intention to provide a full-fledged, ready-to-run base, rather Evennia
is offering the means to make such games.
2) Development of games on Evennia must be easy for anyone with some degree
of Python experience. Building needs to be easy, and per-room, per-object,
and environmental customizations need to be simple to do.
3) The server must utilize SQL as a storage back-end to allow for web->game
integration. See the details on Django later on in the document for more
details.
4) Any and all game-specific configuration must reside in SQL, not
external configuration files. The only exception is the settings.py file
containing the SQL information.
The Components
--------------
Python (Including the SQL driver of your choice)
|-Twisted (http://twistedmatrix.com)
|-SQL (MySQL, SQLite, Postgresql)
|-Django (http://djangoproject.com)
Evennia is built on top of Twisted, a networking engine that handles a lot
of the guts and lower-level socket stuff for us.
Serving as our storage medium, SQL allows for very simple code in many cases, and
can lead to a game being a lot more scalable due to the inherent speed of
most modern SQL servers. Another extremely important benefit is that by
storing everything in SQL, we make the entire game accessible from other
means, such as a website. Which leads us to the next component.
Django is perhaps one of the most interesting introductions to the codebase.
Django is technically a Python web framework, but it also includes a great
data modeling and database abstraction module. This means that things like
Players or Objects can be represented by a very short class, then related to one
another. This allows us to add, remove, delete, and manipulate things in our database
very easily. Another huge benefit is the admin interface that Django more
or less automatically generates for us. Instead of a bunch of clunky admin
commands, you can fire up your web browser and administer pretty much
everything from there, although equivalent in-game commands may be offered.
The possibilities for developing your game's website are nearly endless with
this tandem of MU* server, SQL, and Django.

9
TODO
View file

@ -1,9 +0,0 @@
TODO List
---------
The TODO list has all of the things currently needing attention the most in it.
If you are feeling ambitious, tackle as much as you can and send patches
to the project site or via email to gtaylor@clemson.edu.
To see what is currently in need of work, visit the issue tracker at:
http://code.google.com/p/evennia/issues/list

1
VERSION Normal file
View file

@ -0,0 +1 @@
SVN-griatch-branch

1630
docs/Doxyfile Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,57 @@
* Evennia documentation
DOCS README
-----------
To build the auto-documentation (overview of all the sources, with comments), read the
instructions in ./doxygen.
* Evennia docs and manual
The most updated 'manual' is currently the online wiki, at
- The most updated documentation is found in the online wiki,
http://code.google.com/p/evennia/wiki/Index?tm=6
http://code.google.com/p/evennia/wiki/Index
- You can also ask for help from the evennia community,
You can ask for help from the evennia community here:
http://groups.google.com/group/evennia
http://groups.google.com/group/evennia
- Or by visiting our irc channel,
#evennia on the Freenode network
* Evennia source auto-docs
In this folder 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.
- 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 Doxyfile
This will create the auto-docs in a folder 'html'.
- Start your web browser and point it to
<evenniadir>/docs/html/index.html
- If you prefer a pdf version for printing, use LaTeX by
activating the relevant section in Doxyfile. Run the
doxygen command again as above and a new folder 'latex'
will be created with the latex sources. With the latex
processing system installed, then run
> 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.

View file

@ -1,275 +0,0 @@
# Doxyfile 1.4.7
#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------
PROJECT_NAME = Evennia
PROJECT_NUMBER = Pre-Alpha
OUTPUT_DIRECTORY = ./output/
CREATE_SUBDIRS = NO
OUTPUT_LANGUAGE = English
USE_WINDOWS_ENCODING = NO
BRIEF_MEMBER_DESC = YES
REPEAT_BRIEF = YES
ABBREVIATE_BRIEF = "The $name class" \
"The $name widget" \
"The $name file" \
is \
provides \
specifies \
contains \
represents \
a \
an \
the
ALWAYS_DETAILED_SEC = NO
INLINE_INHERITED_MEMB = NO
FULL_PATH_NAMES = YES
STRIP_FROM_PATH = ./doxygen/
STRIP_FROM_INC_PATH =
SHORT_NAMES = NO
JAVADOC_AUTOBRIEF = NO
MULTILINE_CPP_IS_BRIEF = NO
DETAILS_AT_TOP = NO
INHERIT_DOCS = YES
SEPARATE_MEMBER_PAGES = NO
TAB_SIZE = 8
ALIASES =
OPTIMIZE_OUTPUT_FOR_C = NO
OPTIMIZE_OUTPUT_JAVA = YES
BUILTIN_STL_SUPPORT = NO
DISTRIBUTE_GROUP_DOC = NO
SUBGROUPING = YES
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
EXTRACT_ALL = NO
EXTRACT_PRIVATE = NO
EXTRACT_STATIC = NO
EXTRACT_LOCAL_CLASSES = YES
EXTRACT_LOCAL_METHODS = NO
HIDE_UNDOC_MEMBERS = YES
HIDE_UNDOC_CLASSES = YES
HIDE_FRIEND_COMPOUNDS = NO
HIDE_IN_BODY_DOCS = NO
INTERNAL_DOCS = NO
CASE_SENSE_NAMES = YES
HIDE_SCOPE_NAMES = NO
SHOW_INCLUDE_FILES = YES
INLINE_INFO = YES
SORT_MEMBER_DOCS = YES
SORT_BRIEF_DOCS = NO
SORT_BY_SCOPE_NAME = NO
GENERATE_TODOLIST = YES
GENERATE_TESTLIST = YES
GENERATE_BUGLIST = YES
GENERATE_DEPRECATEDLIST= YES
ENABLED_SECTIONS =
MAX_INITIALIZER_LINES = 30
SHOW_USED_FILES = YES
SHOW_DIRECTORIES = NO
FILE_VERSION_FILTER =
#---------------------------------------------------------------------------
# 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 = ../../
FILE_PATTERNS = *.c \
*.cc \
*.cxx \
*.cpp \
*.c++ \
*.d \
*.java \
*.ii \
*.ixx \
*.ipp \
*.i++ \
*.inl \
*.h \
*.hh \
*.hxx \
*.hpp \
*.h++ \
*.idl \
*.odl \
*.cs \
*.php \
*.php3 \
*.inc \
*.m \
*.mm \
*.dox \
*.py \
*.C \
*.CC \
*.C++ \
*.II \
*.I++ \
*.H \
*.HH \
*.H++ \
*.CS \
*.PHP \
*.PHP3 \
*.M \
*.MM \
*.PY
RECURSIVE = YES
EXCLUDE =
EXCLUDE_SYMLINKS = NO
EXCLUDE_PATTERNS =
EXAMPLE_PATH =
EXAMPLE_PATTERNS = *
EXAMPLE_RECURSIVE = NO
IMAGE_PATH =
INPUT_FILTER =
FILTER_PATTERNS =
FILTER_SOURCE_FILES = NO
#---------------------------------------------------------------------------
# configuration options related to source browsing
#---------------------------------------------------------------------------
SOURCE_BROWSER = YES
INLINE_SOURCES = NO
STRIP_CODE_COMMENTS = YES
REFERENCED_BY_RELATION = YES
REFERENCES_RELATION = YES
REFERENCES_LINK_SOURCE = YES
USE_HTAGS = NO
VERBATIM_HEADERS = YES
#---------------------------------------------------------------------------
# configuration options related to the alphabetical class index
#---------------------------------------------------------------------------
ALPHABETICAL_INDEX = NO
COLS_IN_ALPHA_INDEX = 5
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_ALIGN_MEMBERS = YES
GENERATE_HTMLHELP = NO
CHM_FILE =
HHC_LOCATION =
GENERATE_CHI = NO
BINARY_TOC = NO
TOC_EXPAND = NO
DISABLE_INDEX = NO
ENUM_VALUES_PER_LINE = 4
GENERATE_TREEVIEW = YES
TREEVIEW_WIDTH = 250
#---------------------------------------------------------------------------
# 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 = a4wide
EXTRA_PACKAGES =
LATEX_HEADER =
PDF_HYPERLINKS = NO
USE_PDFLATEX = NO
LATEX_BATCHMODE = NO
LATEX_HIDE_INDICES = NO
#---------------------------------------------------------------------------
# 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
HIDE_UNDOC_RELATIONS = YES
HAVE_DOT = NO
CLASS_GRAPH = YES
COLLABORATION_GRAPH = YES
GROUP_GRAPHS = YES
UML_LOOK = NO
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
DOT_PATH =
DOTFILE_DIRS =
MAX_DOT_GRAPH_WIDTH = 1024
MAX_DOT_GRAPH_HEIGHT = 1024
MAX_DOT_GRAPH_DEPTH = 1000
DOT_TRANSPARENT = NO
DOT_MULTI_TARGETS = NO
GENERATE_LEGEND = YES
DOT_CLEANUP = YES
#---------------------------------------------------------------------------
# Configuration::additions related to the search engine
#---------------------------------------------------------------------------
SEARCHENGINE = YES

View file

@ -1,8 +0,0 @@
To create the developer auto-docs, install doxygen (it's available for most platforms here:
http://www.stack.nl/~dimitri/doxygen/ ), and run
> doxygen Doxyfile
in this directory. This will create the auto-docs in ./output/html. To read, point your web browser to
evennia/docs/doxygen/output/html/index.html. If you prefer a pdf / paper hardcopy, install the LaTeX
system and activate the relevant section in Doxyfile.

View file

@ -5,49 +5,104 @@ EVENNIA SERVER STARTUP SCRIPT
Sets the appropriate environmental variables and launches the server
process. Run the script with the -h flag to see usage information.
"""
from optparse import OptionParser
from subprocess import Popen, call
import os
import sys
import signal
from optparse import OptionParser
from subprocess import Popen, call
# Set the Python path up so we can get to settings.py from here.
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
from django.conf import settings
SERVER_PY_FILE = os.path.join(settings.SRC_DIR, 'server.py')
# Determine what the twistd binary name is. Eventually may want to have a
# setting in settings.py to specify the path to the containing directory.
if not os.path.exists('settings.py'):
# make sure we have a settings.py file.
print " No settings.py file found. Launching manage.py ..."
import game.manage
print """
Now configure Evennia by editing your new settings.py file.
If you haven't already, you should also create/configure the
database with 'python manage.py syncdb' before continuing.
When you are ready, run this program again to start the server."""
sys.exit()
# Get the settings
from django.conf import settings
# Setup the launch of twisted depending on which operating system we use
if os.name == 'nt':
TWISTED_BINARY = 'twistd.bat'
try:
# Test for for win32api
import win32api
except ImportError:
print "=" * 78
print """ERROR: Unable to import win32api, which Twisted requires to run. You may
download it from:
print """
ERROR: Unable to import win32api, which Twisted requires to run.
You may download it from:
http://starship.python.net/crew/mhammond/win32/Downloads.html"""
print "=" * 78
http://sourceforge.net/projects/pywin32
or
http://starship.python.net/crew/mhammond/win32/Downloads.html"""
sys.exit()
if not os.path.exists('twistd.bat'):
# Test for executable twisted batch file. This calls the twistd.py
# executable that is usually not found on the path in Windows.
# It's not enough to locate scripts.twistd, what we want is the
# executable script C:\PythonXX/Scripts/twistd.py. Alas we cannot
# hardcode this location since we don't know if user has Python
# in a non-standard location, so we try to figure it out.
from twisted.scripts import twistd
twistd_path = os.path.abspath(
os.path.join(os.path.dirname(twistd.__file__),
os.pardir, os.pardir, os.pardir, os.pardir,
'scripts', 'twistd.py'))
bat_file = open('twistd.bat','w')
bat_file.write("@%s %%*" % twistd_path)
bat_file.close()
print """
INFO: Since you are running Windows, a twistd.bat file was created for you.
The twistd.bat is a simple batch file that tries to call the twisted
executable. The system has determined this to be:
%s
If you should run into errors you might need to edit twistd.bat to point to
the correct location of the Twisted executable (usually called twistd.py).
When you are ready, run this program again to retry the server restart.""" % twistd_path
sys.exit()
TWISTED_BINARY = 'twistd.bat'
else:
TWISTED_BINARY = 'twistd'
# Setup access of the evennia server itself
SERVER_PY_FILE = os.path.join(settings.SRC_DIR, 'server/server.py')
# Add this to the environmental variable for the 'twistd' command.
tmppath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
thispath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if 'PYTHONPATH' in os.environ:
os.environ['PYTHONPATH'] += (":%s" % tmppath)
os.environ['PYTHONPATH'] += (":%s" % thispath)
else:
os.environ['PYTHONPATH'] = tmppath
os.environ['PYTHONPATH'] = thispath
def cycle_logfile():
"""
Move the old log file to evennia.log (by default).
"""
if os.path.exists(settings.DEFAULT_LOG_FILE):
os.rename(settings.DEFAULT_LOG_FILE,
settings.DEFAULT_LOG_FILE+'.old')
Move the old log file to evennia.log.old (by default).
"""
logfile = settings.DEFAULT_LOG_FILE.strip()
logfile_old = logfile + '.old'
if os.path.exists(logfile):
# Cycle the old logfiles to *.old
if os.path.exists(logfile_old):
# E.g. Windows don't support rename-replace
os.remove(logfile_old)
os.rename(logfile, logfile_old)
def start_daemon(parser, options, args):
"""
@ -59,7 +114,8 @@ def start_daemon(parser, options, args):
print "A twistd.pid file exists in the current directory, which suggests that the server is already running."
sys.exit()
print 'Starting in daemon mode...'
print '\nStarting Evennia server in daemon mode ...'
print 'Logging to: %s.' % settings.DEFAULT_LOG_FILE
# Move the old evennia.log file out of the way.
cycle_logfile()
@ -74,7 +130,9 @@ def start_interactive(parser, options, args):
Start in interactive mode, which means the process is foregrounded and
all logging output is directed to stdout.
"""
print 'Starting in interactive mode...'
print '\nStarting Evennia server in interactive mode (stop with keyboard interrupt) ...'
print 'Logging to: Standard output.'
try:
call([TWISTED_BINARY,
'-n',
@ -88,7 +146,7 @@ def stop_server(parser, options, args):
"""
if os.name == 'posix':
if os.path.exists('twistd.pid'):
print 'Stoping the server...'
print 'Stopping the Evennia server...'
f = open('twistd.pid', 'r')
pid = f.read()
os.kill(int(pid), signal.SIGINT)
@ -96,16 +154,18 @@ def stop_server(parser, options, args):
else:
print "No twistd.pid file exists, the server doesn't appear to be running."
elif os.name == 'nt':
print 'TODO not implented'
print '\n\rStopping cannot be done safely under this operating system.'
print 'Kill server using the task manager or shut it down from inside the game.'
else:
print 'Unknown OS delected, can not kill'
print '\n\rUnknown OS detected, can not stop. '
def main():
"""
Beginning of the program logic.
"""
parser = OptionParser(usage="%prog [options] <start|stop>",
description="This command starts or stops the Evennia game server.")
description="This command starts or stops the Evennia game server. Note that you have to setup the database by running 'manage.py syncdb' before starting the server for the first time.")
parser.add_option('-i', '--interactive', action='store_true',
dest='interactive', default=False,
help='Start in interactive mode')

View file

@ -0,0 +1,78 @@
"""
This is the parent class for all Commands in Evennia. Inherit from this and
overload the member functions to define your own commands.
See commands/default/muxcommand.py for an example.
"""
from src.commands.command import Command as BaseCommand
from src.permissions import permissions
from src.utils import utils
class Command(BaseCommand):
"""
Note that the class's __doc__ string (this text) is
used by Evennia to create the automatic help entry for
the command, so make sure to document consistently here.
"""
def has_perm(self, srcobj):
"""
This is called by the cmdhandler to determine
if srcobj is allowed to execute this command. This
also determines if the command appears in help etc.
By default, We use checks of the 'c' type of lock to determine
if the command should be run.
"""
return permissions.has_perm(srcobj, self, 'cmd')
def parse(self):
"""
This method is called by the cmdhandler once the command name
has been identified. It creates a new set of member variables
that can be later accessed from self.func() (see below)
The following variables are available for our use when entering this
method (from the command definition, and assigned on the fly by the
cmdhandler):
self.key - the name of this command ('look')
self.aliases - the aliases of this cmd ('l')
self.permissions - permission string for this command
self.help_category - overall category of command
self.caller - the object calling this command
self.cmdstring - the actual command name used to call this
(this allows you to know which alias was used,
for example)
self.args - the raw input; everything following self.cmdstring.
self.cmdset - the cmdset from which this command was picked. Not
often used (useful for commands like 'help' or to
list all available commands etc)
self.obj - the object on which this command was defined. It is often
the same as self.caller.
"""
pass
def func(self):
"""
This is the hook function that actually does all the work. It is called
by the cmdhandler right after self.parser() finishes, and so has access
to all the variables defined therein.
"""
# a simple test command to show the available properties
string = "-" * 50
string += "\n{w%s{n - Command variables from evennia:\n" % self.key
string += "-" * 50
string += "\nname of cmd (self.key): {w%s{n\n" % self.key
string += "cmd aliases (self.aliases): {w%s{n\n" % self.aliases
string += "cmd perms (self.permissions): {w%s{n\n" % self.permissions
string += "help category (self.help_category): {w%s{n\n" % self.help_category
string += "object calling (self.caller): {w%s{n\n" % self.caller
string += "object storing cmdset (self.obj): {w%s{n\n" % self.obj
string += "command string given (self.cmdstring): {w%s{n\n" % self.cmdstring
# show cmdset.key instead of cmdset to shorten output
string += utils.fill("current cmdset (self.cmdset): {w%s{n\n" % self.cmdset)
self.caller.msg(string)

View file

@ -0,0 +1,689 @@
"""
Batch processors
These commands implements the 'batch-command' and 'batch-code'
processors, using the functionality in src.utils.batchprocessors.
They allow for offline world-building.
Batch-command is the simpler system. This reads a file (*.ev)
containing a list of in-game commands and executes them in sequence as
if they had been entered in the game (including permission checks
etc).
Example batch-command file: game/gamesrc/commands/examples/example_batch_cmd.ev
Batch-code is a full-fledged python code interpreter that reads blocks
of python code (*.py) and executes them in sequence. This allows for
much more power than Batch-command, but requires knowing Python and
the Evennia API. It is also a severe security risk and should
therefore always be limited to superusers only.
Example batch-code file: game/gamesrc/commands/examples/example_batch_code.py
"""
from traceback import format_exc
from django.conf import settings
from src.utils import batchprocessors
from game.gamesrc.commands.default.muxcommand import MuxCommand
from src.commands.cmdset import CmdSet
#global defines for storage
CWHITE = r"%cn%ch%cw"
CRED = r"%cn%ch%cr"
CGREEN = r"%cn%ci%cg"
CYELLOW = r"%cn%ch%cy"
CNORM = r"%cn"
#------------------------------------------------------------
# Helper functions
#------------------------------------------------------------
def batch_cmd_exec(caller):
"""
Helper function for executing a single batch-command entry
"""
ptr = caller.ndb.batch_stackptr
stack = caller.ndb.batch_stack
command = stack[ptr]
cmdname = command[:command.find(" ")]
caller.msg("%s %02i/%02i: %s %s%s" % (CGREEN, ptr+1,
len(stack),
cmdname,
CGREEN, " "*(50-len(cmdname))))
try:
caller.execute_cmd(command)
except Exception:
caller.msg(format_exc())
return False
return True
def batch_code_exec(caller):
"""
Helper function for executing a single batch-code entry
"""
ptr = caller.ndb.batch_stackptr
stack = caller.ndb.batch_stack
debug = caller.ndb.batch_debug
codedict = stack[ptr]
caller.msg("%s %02i/%02i: %s %s%s" % (CGREEN, ptr + 1,
len(stack),
codedict["firstline"],
CGREEN, " "*(50-len(codedict["firstline"]))))
err = batchprocessors.batch_code_exec(codedict,
extra_environ={"caller":caller}, debug=debug)
if err:
caller.msg(err)
return False
return True
def step_pointer(caller, step=1):
"""
Step in stack, returning the item located.
stackptr - current position in stack
stack - the stack of units
step - how many steps to move from stackptr
"""
ptr = caller.ndb.batch_stackptr
stack = caller.ndb.batch_stack
nstack = len(stack)
if ptr + step <= 0:
caller.msg("Beginning of batch file.")
if ptr + step >= nstack:
caller.msg("End of batch file.")
caller.ndb.batch_stackptr = max(0, min(nstack-1, ptr + step))
def show_curr(caller, showall=False):
"Show the current position in stack."
stackptr = caller.ndb.batch_stackptr
stack = caller.ndb.batch_stack
if stackptr >= len(stack):
caller.ndb.batch_stackptr = len(stack) - 1
show_curr(caller, showall)
return
entry = stack[stackptr]
if type(entry) == dict:
# we first try the batch-code syntax
firstline = entry['code'][:min(35, len(entry['code'])-1)]
codeall = entry['code']
else:
# we try the batch-cmd syntax instead
firstline = entry[:min(35, len(entry)-1)]
codeall = entry
string = "%s %02i/%02i: %s %s %s %s%s" % (CGREEN,
stackptr+1, len(stack),
firstline, CGREEN,
"(hh for help)",
" "*(35-len(firstline.strip())),
CNORM)
if showall:
string += "\n%s" % codeall
caller.msg(string)
#------------------------------------------------------------
# main access commands
#------------------------------------------------------------
class CmdBatchCommands(MuxCommand):
"""
Build from batch-command file
Usage:
@batchcommands[/interactive] <python path to file>
Switch:
interactive - this mode will offer more control when
executing the batch file, like stepping,
skipping, reloading etc.
Runs batches of commands from a batch-cmd text file (*.ev).
"""
key = "@batchcommands"
aliases = ["@batchcommand", "@batchcmd"]
permissions = "cmd:batchcommands"
help_category = "Building"
def func(self):
"Starts the processor."
caller = self.caller
args = self.args
if not args:
caller.msg("Usage: @batchcommands[/interactive] <path.to.file>")
return
python_path = self.args
#parse indata file
commands = batchprocessors.parse_batchcommand_file(python_path)
if not commands:
string = "'%s' not found.\nYou have to supply the python path "
string += "of the file relative to \nyour batch-file directory (%s)."
caller.msg(string % (python_path, settings.BASE_BATCHPROCESS_PATH))
return
switches = self.switches
# Store work data in cache
caller.ndb.batch_stack = commands
caller.ndb.batch_stackptr = 0
caller.ndb.batch_pythonpath = python_path
caller.ndb.batch_batchmode = "batch_commands"
caller.cmdset.add(BatchSafeCmdSet)
if 'inter' in switches or 'interactive' in switches:
# Allow more control over how batch file is executed
# Set interactive state directly
caller.cmdset.add(BatchInteractiveCmdSet)
caller.msg("\nBatch-command processor - Interactive mode for %s ..." % python_path)
show_curr(caller)
else:
caller.msg("Running Batch-command processor - Automatic mode for %s ..." % python_path)
# add the 'safety' cmdset in case the batch processing adds cmdsets to us
for inum in range(len(commands)):
# loop through the batch file
if not batch_cmd_exec(caller):
return
step_pointer(caller, 1)
# clean out the safety cmdset and clean out all other temporary attrs.
caller.cmdset.delete(BatchSafeCmdSet)
del caller.ndb.batch_stack
del caller.ndb.batch_stackptr
del caller.ndb.batch_pythonpath
del caller.ndb.batch_batchmode
string = " Batchfile '%s' applied." % python_path
caller.msg("%s%s%s" % (CGREEN, string, " "*(60-len(string))))
class CmdBatchCode(MuxCommand):
"""
Build from batch-code file
Usage:
@batchcode[/interactive] <python path to file>
Switch:
interactive - this mode will offer more control when
executing the batch file, like stepping,
skipping, reloading etc.
debug - auto-delete all objects that has been marked as
deletable in the script file (see example files for
syntax). This is useful so as to to not leave multiple
object copies behind when testing out the script.
Runs batches of commands from a batch-code text file (*.py).
"""
key = "@batchcode"
aliases = ["@batchcodes"]
permissions = "cmd:batchcodes"
help_category = "Building"
def func(self):
"Starts the processor."
caller = self.caller
args = self.args
if not args:
caller.msg("Usage: @batchcode[/interactive/debug] <path.to.file>")
return
python_path = self.args
#parse indata file
codes = batchprocessors.parse_batchcode_file(python_path)
if not codes:
string = "'%s' not found.\nYou have to supply the python path "
string += "of the file relative to \nyour batch-file directory (%s)."
caller.msg(string % (python_path, settings.BASE_BATCHPROCESS_PATH))
return
switches = self.switches
debug = False
if 'debug' in switches:
debug = True
# Store work data in cache
caller.ndb.batch_stack = codes
caller.ndb.batch_stackptr = 0
caller.ndb.batch_pythonpath = python_path
caller.ndb.batch_batchmode = "batch_code"
caller.ndb.batch_debug = debug
caller.cmdset.add(BatchSafeCmdSet)
if 'inter' in switches or 'interactive'in switches:
# Allow more control over how batch file is executed
# Set interactive state directly
caller.cmdset.add(BatchInteractiveCmdSet)
caller.msg("\nBatch-code processor - Interactive mode for %s ..." % python_path)
show_curr(caller)
else:
caller.msg("Running Batch-code processor - Automatic mode for %s ..." % python_path)
# add the 'safety' cmdset in case the batch processing adds cmdsets to us
for inum in range(len(codes)):
# loop through the batch file
if not batch_code_exec(caller):
return
step_pointer(caller, 1)
# clean out the safety cmdset and clean out all other temporary attrs.
caller.cmdset.delete(BatchSafeCmdSet)
del caller.ndb.batch_stack
del caller.ndb.batch_stackptr
del caller.ndb.batch_pythonpath
del caller.ndb.batch_batchmode
string = " Batchfile '%s' applied." % python_path
caller.msg("%s%s%s" % (CGREEN, string, " "*(60-len(string))))
#------------------------------------------------------------
# State-commands for the interactive batch processor modes
# (these are the same for both processors)
#------------------------------------------------------------
class CmdStateAbort(MuxCommand):
"""
@abort
Exits back the default cmdset, regardless of what state
we are currently in.
"""
key = "@abort"
help_category = "BatchProcess"
def func(self):
"Exit back to default."
caller = self.caller
del caller.ndb.batch_stack
del caller.ndb.batch_stackptr
del caller.ndb.batch_pythonpath
del caller.ndb.batch_batchmode
# clear everything but the default cmdset.
caller.cmdset.delete(BatchSafeCmdSet)
caller.cmdset.clear()
caller.msg("Exit: Cleared back to default state.")
class CmdStateLL(MuxCommand):
"""
ll
Look at the full source for the current
command definition.
"""
key = "ll"
help_category = "BatchProcess"
def func(self):
show_curr(self.caller, showall=True)
class CmdStatePP(MuxCommand):
"""
pp
Process the currently shown command definition.
"""
key = "pp"
help_category = "BatchProcess"
def func(self):
"""
This checks which type of processor we are running.
"""
caller = self.caller
if caller.ndb.batch_batchmode == "batch_code":
batch_code_exec(caller)
else:
batch_cmd_exec(caller)
class CmdStateRR(MuxCommand):
"""
rr
Reload the batch file, keeping the current
position in it.
"""
key = "rr"
help_category = "BatchProcess"
def func(self):
caller = self.caller
if caller.ndb.batch_batchmode == "batch_code":
batchprocessors.read_batchcommand_file(caller.ndb.batch_pythonpath)
else:
batchprocessors.read_batchcode_file(caller.ndb.batch_pythonpath)
caller.msg("\nFile reloaded. Staying on same command.\n")
show_curr(caller)
class CmdStateRRR(MuxCommand):
"""
rrr
Reload the batch file, starting over
from the beginning.
"""
key = "rrr"
help_category = "BatchProcess"
def func(self):
caller = self.caller
if caller.ndb.batch_batchmode == "batch_code":
batchprocessors.read_batchcommand_file(caller.ndb.batch_pythonpath)
else:
batchprocessors.read_batchcode_file(caller.ndb.batch_pythonpath)
caller.ndb.batch_stackptr = 0
caller.msg("\nFile reloaded. Restarting from top.\n")
show_curr(caller)
class CmdStateNN(MuxCommand):
"""
nn
Go to next command. No commands are executed.
"""
key = "nn"
help_category = "BatchProcess"
def func(self):
caller = self.caller
arg = self.args
if arg and arg.isdigit():
step = int(self.args)
else:
step = 1
step_pointer(caller, step)
show_curr(caller)
class CmdStateNL(MuxCommand):
"""
nl
Go to next command, viewing its full source.
No commands are executed.
"""
key = "nl"
help_category = "BatchProcess"
def func(self):
caller = self.caller
arg = self.args
if arg and arg.isdigit():
step = int(self.args)
else:
step = 1
step_pointer(caller, step)
show_curr(caller, showall=True)
class CmdStateBB(MuxCommand):
"""
bb
Backwards to previous command. No commands
are executed.
"""
key = "bb"
help_category = "BatchProcess"
def func(self):
caller = self.caller
arg = self.args
if arg and arg.isdigit():
step = -int(self.args)
else:
step = -1
step_pointer(caller, step)
show_curr(caller)
class CmdStateBL(MuxCommand):
"""
bl
Backwards to previous command, viewing its full
source. No commands are executed.
"""
key = "bl"
help_category = "BatchProcess"
def func(self):
caller = self.caller
arg = self.args
if arg and arg.isdigit():
step = -int(self.args)
else:
step = -1
step_pointer(caller, step)
show_curr(caller, showall=True)
class CmdStateSS(MuxCommand):
"""
ss [steps]
Process current command, then step to the next
one. If steps is given,
process this many commands.
"""
key = "ss"
help_category = "BatchProcess"
def func(self):
caller = self.caller
arg = self.args
if arg and arg.isdigit():
step = int(self.args)
else:
step = 1
for istep in range(step):
if caller.ndb.batch_batchmode == "batch_code":
batch_code_exec(caller)
else:
batch_cmd_exec(caller)
step_pointer(caller, 1)
show_curr(caller)
class CmdStateSL(MuxCommand):
"""
sl [steps]
Process current command, then step to the next
one, viewing its full source. If steps is given,
process this many commands.
"""
key = "sl"
help_category = "BatchProcess"
def func(self):
caller = self.caller
arg = self.args
if arg and arg.isdigit():
step = int(self.args)
else:
step = 1
for istep in range(step):
if caller.ndb.batch_batchmode == "batch_code":
batch_code_exec(caller)
else:
batch_cmd_exec(caller)
step_pointer(caller, 1)
show_curr(caller)
class CmdStateCC(MuxCommand):
"""
cc
Continue to process all remaining
commands.
"""
key = "cc"
help_category = "BatchProcess"
def func(self):
caller = self.caller
nstack = len(caller.ndb.batch_stack)
ptr = caller.ndb.batch_stackptr
step = nstack - ptr
for istep in range(step):
if caller.ndb.batch_batchmode == "batch_code":
batch_code_exec(caller)
else:
batch_cmd_exec(caller)
step_pointer(caller, 1)
show_curr(caller)
del caller.ndb.batch_stack
del caller.ndb.batch_stackptr
del caller.ndb.batch_pythonpath
del caller.ndb.batch_batchmode
caller.msg("Finished processing batch file.")
class CmdStateJJ(MuxCommand):
"""
j <command number>
Jump to specific command number
"""
key = "j"
help_category = "BatchProcess"
def func(self):
caller = self.caller
arg = self.args
if arg and arg.isdigit():
number = int(self.args)-1
else:
caller.msg("You must give a number index.")
return
ptr = caller.ndb.batch_stackptr
step = number - ptr
step_pointer(caller, step)
show_curr(caller)
class CmdStateJL(MuxCommand):
"""
jl <command number>
Jump to specific command number and view its full source.
"""
key = "jl"
help_category = "BatchProcess"
def func(self):
caller = self.caller
arg = self.args
if arg and arg.isdigit():
number = int(self.args)-1
else:
caller.msg("You must give a number index.")
return
ptr = caller.ndb.batch_stackptr
step = number - ptr
step_pointer(caller, step)
show_curr(caller, showall=True)
class CmdStateQQ(MuxCommand):
"""
qq
Quit the batchprocessor.
"""
key = "qq"
help_category = "BatchProcess"
def func(self):
caller = self.caller
del caller.ndb.batch_stack
del caller.ndb.batch_stackptr
del caller.ndb.batch_pythonpath
del caller.ndb.batch_batchmode
caller.cmdset.delete(BatchSafeCmdSet)
caller.cmdset.delete(BatchInteractiveCmdSet)
caller.scripts.validate() # this will clear interactive mode.
caller.msg("Aborted interactive batch mode.")
class CmdStateHH(MuxCommand):
"Help command"
key = "help"
aliases = "hh"
help_category = "BatchProcess"
def func(self):
string = """
Interactive batch processing commands:
nn [steps] - next command (no processing)
nl [steps] - next & look
bb [steps] - back to previous command (no processing)
bl [steps] - back & look
jj <N> - jump to command nr N (no processing)
jl <N> - jump & look
pp - process currently shown command (no step)
ss [steps] - process & step
sl [steps] - process & step & look
ll - look at full definition of current command
rr - reload batch file (stay on current)
rrr - reload batch file (start from first)
hh - this help list
cc - continue processing to end, then quit.
qq - quit (abort all remaining commands)
"""
self.caller.msg(string)
#------------------------------------------------------------
#
# Defining the cmdsets for the interactive batchprocessor
# mode (same for both processors)
#
#------------------------------------------------------------
class BatchSafeCmdSet(CmdSet):
"""
The base cmdset for the batch processor.
This sets a 'safe' @abort command that will
always be available to get out of everything.
"""
key = "Batch_default"
priority = 104 # override other cmdsets.
def at_cmdset_creation(self):
"Init the cmdset"
self.add(CmdStateAbort())
class BatchInteractiveCmdSet(CmdSet):
"""
The cmdset for the interactive batch processor mode.
"""
key = "Batch_interactive"
priority = 104
def at_cmdset_creation(self):
"init the cmdset"
self.add(CmdStateAbort())
self.add(CmdStateLL())
self.add(CmdStatePP())
self.add(CmdStateRR())
self.add(CmdStateRRR())
self.add(CmdStateNN())
self.add(CmdStateNL())
self.add(CmdStateBB())
self.add(CmdStateBL())
self.add(CmdStateSS())
self.add(CmdStateSL())
self.add(CmdStateCC())
self.add(CmdStateJJ())
self.add(CmdStateJL())
self.add(CmdStateQQ())
self.add(CmdStateHH())

View file

@ -0,0 +1,98 @@
"""
This module ties together all the commands of the default command set.
"""
from src.commands.cmdset import CmdSet
from game.gamesrc.commands.default import general, help, privileged
from game.gamesrc.commands.default import tests, comms, objmanip
from game.gamesrc.commands.default import info, batchprocess
class DefaultCmdSet(CmdSet):
"""
Implements the default command set.
"""
key = "DefaultMUX"
def at_cmdset_creation(self):
"Populates the cmdset"
# The general commands
self.add(general.CmdLook())
self.add(general.CmdPassword())
self.add(general.CmdWall())
self.add(general.CmdInventory())
self.add(general.CmdQuit())
self.add(general.CmdPose())
self.add(general.CmdNick())
self.add(general.CmdEmit())
self.add(general.CmdGet())
self.add(general.CmdDrop())
self.add(general.CmdWho())
self.add(general.CmdSay())
self.add(general.CmdGroup())
# The help system
self.add(help.CmdHelp())
self.add(help.CmdSetHelp())
# Privileged commands
self.add(privileged.CmdReload())
self.add(privileged.CmdPy())
self.add(privileged.CmdListScripts())
self.add(privileged.CmdListCmdSets())
self.add(privileged.CmdListObjects())
self.add(privileged.CmdBoot())
self.add(privileged.CmdDelPlayer())
self.add(privileged.CmdNewPassword())
self.add(privileged.CmdHome())
self.add(privileged.CmdService())
self.add(privileged.CmdShutdown())
self.add(privileged.CmdPerm())
# Info commands
self.add(info.CmdVersion())
self.add(info.CmdTime())
self.add(info.CmdList())
self.add(info.CmdPs())
self.add(info.CmdStats())
# Object manipulation commands
self.add(objmanip.CmdTeleport())
self.add(objmanip.CmdSetObjAlias())
self.add(objmanip.CmdWipe())
self.add(objmanip.CmdSetAttribute())
self.add(objmanip.CmdName())
self.add(objmanip.CmdDesc())
#self.add(objmanip.CmdCpAttr()) #TODO - need testing/debugging
#self.add(objmanip.CmdMvAttr()) #TODO - need testing/debugging
self.add(objmanip.CmdFind())
self.add(objmanip.CmdCopy()) #TODO - need testing/debugging
self.add(objmanip.CmdOpen())
self.add(objmanip.CmdLink())
self.add(objmanip.CmdUnLink())
self.add(objmanip.CmdCreate())
self.add(objmanip.CmdDig())
self.add(objmanip.CmdDestroy())
self.add(objmanip.CmdExamine())
self.add(objmanip.CmdTypeclass())
self.add(objmanip.CmdDebug())
# Comm commands
self.add(comms.CmdAddCom())
self.add(comms.CmdDelCom())
self.add(comms.CmdComlist())
self.add(comms.CmdClist())
self.add(comms.CmdCdestroy())
self.add(comms.CmdChannelCreate())
self.add(comms.CmdCdesc())
self.add(comms.CmdPage())
# Batchprocessor commands
self.add(batchprocess.CmdBatchCommands())
self.add(batchprocess.CmdBatchCode())
# Testing commands
self.add(tests.CmdTest())
self.add(tests.CmdTestState())
self.add(tests.CmdTestPerms())
self.add(tests.TestCom())

View file

@ -0,0 +1,21 @@
"""
This module describes the unlogged state of the default game.
The setting STATE_UNLOGGED should be set to the python path
of the state instance in this module.
"""
from src.commands.cmdset import CmdSet
from game.gamesrc.commands.default import unloggedin
class UnloggedinCmdSet(CmdSet):
"""
Sets up the unlogged cmdset.
"""
key = "Unloggedin"
def at_cmdset_creation(self):
"Populate the cmdset"
self.add(unloggedin.CmdConnect())
self.add(unloggedin.CmdCreate())
self.add(unloggedin.CmdQuit())
self.add(unloggedin.CmdUnconnectedLook())
self.add(unloggedin.CmdUnconnectedHelp())

View file

@ -0,0 +1,792 @@
"""
Comsys command module.
"""
from src.comms.models import Channel, Msg, ChannelConnection
from game.gamesrc.commands.default.muxcommand import MuxCommand
from src.utils import create
from src.permissions.permissions import has_perm
def find_channel(caller, channelname):
"""
Helper function for searching for a single channel with
some error handling.
"""
channels = Channel.objects.channel_search(channelname)
if not channels:
caller.msg("Channel '%s' not found." % channelname)
return None
elif len(channels) > 1:
matches = ", ".join(["%s(%s)" % (chan.key, chan.id) for chan in channels])
caller.msg("Multiple channels match (be more specific): \n%s" % matches)
return None
return channels[0]
class CmdAddCom(MuxCommand):
"""
addcom - join a channel with alias
Usage:
addcom [alias=] <channel>
Allows adding an alias for a channel to make is easier and
faster to use. Subsequent calls of this command can
be used to add multiple aliases.
"""
key = "addcom"
help_category = "Comms"
def func(self):
"Implement the command"
caller = self.caller
args = self.args
player = caller.player
if not args:
caller.msg("Usage: addcom [alias =] channelname.")
return
if self.rhs:
# rhs holds the channelname
channelname = self.rhs
alias = self.lhs
else:
channelname = self.args
alias = None
channel = find_channel(caller, channelname)
if not channel:
# we use the custom search method to handle errors.
return
# check permissions
if not has_perm(player, channel, 'chan_listen'):
caller.msg("You are not allowed to listen to this channel.")
return
string = ""
if not channel.has_connection(player):
# we want to connect as well.
if not channel.connect_to(player):
# if this would have returned True, the player is connected
caller.msg("You are not allowed to join this channel.")
return
else:
string += "You now listen to the channel %s. " % channel.key
if alias:
# create a nick and add it to the caller.
nicks = caller.nicks
nicks[alias.strip()] = channel.key
caller.nicks = nicks # nicks auto-save to database.
string += "You can now refer to the channel %s with the alias '%s'."
caller.msg(string % (channel.key, alias))
else:
string += "No alias added."
caller.msg(string)
class CmdDelCom(MuxCommand):
"""
delcom - remove a channel alias
Usage:
delcom <alias>
Removes the specified alias to a channel. If this is the last alias,
the user is effectively removed from the channel.
"""
key = "delcom"
help_category = "Comms"
def func(self):
"Implementing the command. "
caller = self.caller
if not self.args:
caller.msg("Usage: delcom <alias>")
return
#find all the nicks defining this channel
searchnick = self.args.lower()
nicks = caller.nicks
channicks = [nick for nick in nicks.keys()
if nick == searchnick]
if not channicks:
caller.msg("You don't have any such alias defined.")
return
#if there are possible nick matches, look if they match a channel.
channel = None
for nick in channicks:
channel = find_channel(caller, nicks[nick])
if channel:
break
if not channel:
caller.msg("No channel with alias '%s' found." % searchnick)
return
player = caller.player
if not channel.has_connection(player):
caller.msg("You are not on that channel.")
else:
if len(channicks) > 1:
del nicks[searchnick]
caller.msg("Your alias '%s' for channel %s was cleared." % (searchnick,
channel.key))
else:
del nicks[searchnick]
channel.disconnect_from(player)
caller.msg("You stop listening to channel '%s'." % channel.key)
# have to save nicks back too
caller.nicks = nicks
class CmdComlist(MuxCommand):
"""
comlist - list channel memberships
Usage:
comlist
Lists the channels a user is subscribed to.
"""
key = "comlist"
aliases = ["channels"]
help_category = "Comms"
def func(self):
"Implement the command"
caller = self.caller
player = caller.player
connections = ChannelConnection.objects.get_all_player_connections(player)
if not connections:
caller.msg("You don't listen to any channels.")
return
# get aliases:
nicks = caller.nicks
channicks = {}
for connection in connections:
channame = connection.channel.key.lower()
channicks[channame] = ", ".join([nick for nick in nicks
if nicks[nick].lower() == channame])
string = "Your subscribed channels (use @clist for full chan list)\n"
string += "** Alias Channel Status\n"
for connection in connections:
string += " %s%s %-15.14s%-22.15s\n" % ('-', '-',
channicks[connection.channel.key.lower()],
connection.channel.key)
string = string[:-1]
caller.msg(string)
# def cmd_allcom(command):
# """
# allcom - operate on all channels
# Usage:
# allcom [on | off | who | clear]
# Allows the user to universally turn off or on all channels they are on,
# as well as perform a 'who' for all channels they are on. Clear deletes
# all channels.
# Without argument, works like comlist.
# """
# caller = self.caller
# arg = self.args
# if not arg:
# cmd_comlist(command)
# caller.msg("(allcom arguments: 'on', 'off', 'who' and 'clear'.)")
# return
# arg = arg.strip()
# if arg == 'clear':
# cmd_clearcom(command)
# return
# #get names and alias of all subscribed channels
# chandict = comsys.plr_get_cdict(self.session)
# aliaslist = chandict.keys()
# aliaslist.sort()
# if arg == "on":
# for alias in aliaslist:
# comsys.plr_chan_on(self.session, alias)
# elif arg == "off":
# for alias in aliaslist:
# comsys.plr_chan_off(self.session, alias)
# elif arg == "who":
# s = ""
# if not aliaslist:
# s += " (No channels) "
# for alias in aliaslist:
# s += "-- %s (alias: %s)\n" % (chandict[alias][0],alias)
# sess_list = comsys.get_cwho_list(chandict[alias][0])
# objlist = [sess.get_pobject() for sess in sess_list]
# plist = [p.get_name(show_dbref=caller.sees_dbrefs())
# for p in filter(lambda o: o.is_player(), objlist)]
# olist = [o.get_name(show_dbref=caller.sees_dbrefs())
# for o in filter(lambda o: not o.is_player(), objlist)]
# plist.sort()
# olist.sort()
# if plist:
# s += " Players:\n "
# for pname in plist:
# s += "%s, " % pname
# s = s[:-2] + "\n"
# if olist:
# s += " Objects:\n "
# for oname in olist:
# s += "%s, " % oname
# s = s[:-2] + "\n"
# s = s[:-1]
# caller.msg(s)
# GLOBAL_CMD_TABLE.add_self("allcom", cmd_allcom, help_category="Comms")
## def cmd_clearcom(self):
## """
## clearcom - removes all channels
## Usage:
## clearcom
## Effectively runs delcom on all channels the user is on. It will remove
## their aliases, remove them from the channel, and clear any titles they
## have set.
## """
## caller = self.caller
## #get aall subscribed channel memberships
## memberships = caller.channel_membership_set.all()
## if not memberships:
## s = "No channels to delete. "
## else:
## s = "Deleting all channels in your subscriptions ...\n"
## for membership in memberships:
## chan_name = membership.channel.get_name()
## s += "You have left %s.\n" % chan_name
## comsys.plr_del_channel(caller, membership.user_alias)
## comsys.send_cmessage(chan_name, "%s has left the channel." % caller.get_name(show_dbref=False))
## s = s[:-1]
## caller.msg(s)
## GLOBAL_CMD_TABLE.add_self("clearcom", cmd_clearcom)
class CmdClist(MuxCommand):
"""
@clist
Usage:
@clist
list channels
all channels
Lists all available channels in the game.
"""
key = "@clist"
aliases = ["channellist", "all channels"]
help_category = "Comms"
def func(self):
"Implement function"
caller = self.caller
string = "All channels (use comlist to see your subscriptions)\n"
string += "** Channel Perms Description\n"
channels = Channel.objects.get_all_channels()
if not channels:
string += "(No channels) "
for chan in channels:
if has_perm(caller, chan, 'can_listen'):
string += " %s%s %-15.14s%-22.15s%s\n" % \
('-',
'-',
chan.key,
chan.permissions,
#chan.get_owner().get_name(show_dbref=False),
chan.desc)
string = string[:-1]
#s += "** End of Channel List **"
caller.msg(string)
class CmdCdestroy(MuxCommand):
"""
@cdestroy
Usage:
@cdestroy <channel>
Destroys a channel that you control.
"""
key = "@cdestroy"
help_category = "Comms"
def func(self):
"Destroy objects cleanly."
caller = self.caller
if not self.args:
caller.msg("Usage: @cdestroy <channelname>")
return
channel = find_channel(caller, self.args)
if not channel:
caller.msg("Could not find channel %s." % self.args)
return
if not has_perm(caller, channel, 'chan_admin', default_deny=True):
caller.msg("You are not allowed to do that.")
return
message = "Channel %s is being destroyed. Make sure to change your aliases." % channel.key
msgobj = create.create_message(caller, message, channel)
channel.msg(msgobj)
channel.delete()
caller.msg("Channel %s was destroyed." % channel)
## def cmd_cset(self):
## """
## @cset
## Sets various flags on a channel.
## """
## # TODO: Implement cmd_cset
## pass
## def cmd_ccharge(self):
## """
## @ccharge
## Sets the cost to transmit over a channel. Default is free.
## """
## # TODO: Implement cmd_ccharge
## pass
## def cmd_cboot(self):
## """
## @cboot
## Usage:
## @cboot[/quiet] <channel> = <player or object>
## Kicks a player or object from a channel you control.
## """
## caller = self.caller
## args = self.args
## switches = self.self_switches
## if not args or not "=" in args:
## caller.msg("Usage: @cboot[/quiet] <channel> = <object>")
## return
## cname, objname = args.split("=",1)
## cname, objname = cname.strip(), objname.strip()
## if not cname or not objname:
## caller.msg("You must supply both channel and object.")
## return
## try:
## channel = CommChannel.objects.get(name__iexact=cname)
## except CommChannel.DoesNotExist:
## caller.msg("Could not find channel %s." % cname)
## return
## #do we have power over this channel?
## if not channel.controlled_by(caller) or caller.has_perm("channels.channel_admin"):
## caller.msg("You don't have that power in channel '%s'." % cname)
## return
## #mux specification requires an * before player objects.
## player_boot = False
## if objname[0] == '*':
## player_boot = True
## objname = objname[1:]
## bootobj = Object.objects.player_name_search(objname)
## if not bootobj:
## caller.msg("Object '%s' not found." % objname)
## return
## if bootobj.is_player() and not player_boot:
## caller.msg("To boot players you need to start their name with an '*'. ")
## return
## #check so that this object really is on the channel in the first place
## membership = bootobj.channel_membership_set.filter(channel__name__iexact=cname)
## if not membership:
## caller.msg("'%s' is not on channel '%s'." % (objname,cname))
## return
## #announce to channel
## if not 'quiet' in switches:
## comsys.send_cmessage(cname, "%s boots %s from channel." % \
## (caller.get_name(show_dbref=False), objname))
## #all is set, boot the object by removing all its aliases from the channel.
## for mship in membership:
## comsys.plr_del_channel(bootobj, mship.user_alias)
## GLOBAL_CMD_TABLE.add_self("@cboot", cmd_cboot, help_category="Comms")
## def cmd_cemit(self):
## """
## @cemit - send a message to channel
## Usage:
## @cemit <channel>=<message>
## @cemit/noheader <channel>=<message>
## @cemit/sendername <channel>=<message>
## Allows the user to send a message over a channel as long as
## they own or control it. It does not show the user's name unless they
## provide the /sendername switch.
## [[channel_selfs]]
## Useful channel selfs
## (see their help pages for detailed help and options)
## - Listing channels
## clist - show all channels available to you
## comlist - show channels you listen to
## - Joining/parting channels
## addcom - add your alias for a channel
## delcom - remove alias for channel
## (leave channel if no more aliases)
## allcom - view, on/off or remove all your channels
## clearcom - removes all channels
## - Other
## who - list who's online
## <chanalias> off - silence channel temporarily
## <chanalias> on - turn silenced channel back on
## """
## caller = self.caller
## if not self.args:
## caller.msg("@cemit[/switches] <channel> = <message>")
## return
## eq_args = self.args.split('=', 1)
## if len(eq_args) != 2:
## caller.msg("You must provide a channel name and a message to emit.")
## return
## cname = eq_args[0].strip()
## cmessage = eq_args[1].strip()
## final_cmessage = cmessage
## if len(cname) == 0:
## caller.msg("You must provide a channel name to emit to.")
## return
## if len(cmessage) == 0:
## caller.msg("You must provide a message to emit.")
## return
## name_matches = comsys.cname_search(cname, exact=True)
## if name_matches:
## cname_parsed = name_matches[0].get_name()
## else:
## caller.msg("Could not find channel %s." % (cname,))
## return
## # If this is False, don't show the channel header before
## # the message. For example: [Public] Woohoo!
## show_channel_header = True
## if "noheader" in self.self_switches:
## if not caller.has_perm("objects.emit_commchannel"):
## caller.msg(defines_global.NOPERMS_MSG)
## return
## final_cmessage = cmessage
## show_channel_header = False
## else:
## if "sendername" in self.self_switches:
## if not comsys.plr_has_channel(self.session, cname_parsed,
## return_muted=False):
## caller.msg("You must be on %s to do that." % (cname_parsed,))
## return
## final_cmessage = "%s: %s" % (caller.get_name(show_dbref=False),
## cmessage)
## else:
## if not caller.has_perm("objects.emit_commchannel"):
## caller.msg(defines_global.NOPERMS_MSG)
## return
## final_cmessage = cmessage
## if not "quiet" in self.self_switches:
## caller.msg("Sent - %s" % (name_matches[0],))
## comsys.send_cmessage(cname_parsed, final_cmessage,
## show_header=show_channel_header)
## #pipe to external channels (IRC, IMC) eventually mapped to this channel
## comsys.send_cexternal(cname_parsed, cmessage, caller=caller)
## GLOBAL_CMD_TABLE.add_self("@cemit", cmd_cemit,priv_tuple=("channels.emit_commchannel",),
## help_category="Comms")
## def cmd_cwho(self):
## """
## @cwho
## Usage:
## @cwho channel[/all]
## Displays the name, status and object type for a given channel.
## Adding /all after the channel name will list disconnected players
## as well.
## """
## session = self.session
## caller = self.caller
## if not self.args:
## cmd_clist(self)
## caller.msg("Usage: @cwho <channel>[/all]")
## return
## channel_name = self.args
## if channel_name.strip() == '':
## caller.msg("You must specify a channel name.")
## return
## name_matches = comsys.cname_search(channel_name, exact=True)
## if name_matches:
## # Check to make sure the user has permission to use @cwho.
## is_channel_admin = caller.has_perm("objects.channel_admin")
## is_controlled_by_plr = name_matches[0].controlled_by(caller)
## if is_controlled_by_plr or is_channel_admin:
## comsys.msg_cwho(caller, channel_name)
## else:
## caller.msg("Permission denied.")
## return
## else:
## caller.msg("No channel with that name was found.")
## return
## GLOBAL_CMD_TABLE.add_self("@cwho", cmd_cwho, help_category="Comms")
class CmdChannelCreate(MuxCommand):
"""
@ccreate
channelcreate
Usage:
@ccreate <new channel>[;alias;alias...] = description
Creates a new channel owned by you.
"""
key = "@ccreate"
aliases = "channelcreate"
permissions = "cmd:ccreate"
help_category = "Comms"
def func(self):
"Implement the command"
caller = self.caller
if not self.args:
caller.msg("Usage @ccreate <channelname>[;alias;alias..] = description")
return
description = ""
if self.rhs:
description = self.rhs
lhs = self.lhs
channame = lhs
aliases = None
if ';' in lhs:
channame, aliases = [part.strip().lower()
for part in lhs.split(';', 1) if part.strip()]
aliases = [alias.strip().lower()
for alias in aliases.split(';') if alias.strip()]
channel = Channel.objects.channel_search(channame)
if channel:
caller.msg("A channel with that name already exists.")
return
# Create and set the channel up
permissions = "chan_send:%s,chan_listen:%s,chan_admin:has_id(%s)" % \
("Players","Players",caller.id)
new_chan = create.create_channel(channame, aliases, description, permissions)
new_chan.connect_to(caller)
caller.msg("Created channel %s and connected to it." % new_chan.key)
## def cmd_cchown(self):
## """
## @cchown
## Usage:
## @cchown <channel> = <player>
## Changes the owner of a channel.
## """
## caller = self.caller
## args = self.args
## if not args or "=" not in args:
## caller.msg("Usage: @cchown <channel> = <player>")
## return
## cname, pname = args.split("=",1)
## cname, pname = cname.strip(), pname.strip()
## #locate channel
## try:
## channel = CommChannel.objects.get(name__iexact=cname)
## except CommChannel.DoesNotExist:
## caller.msg("Channel '%s' not found." % cname)
## return
## #check so we have ownership to give away.
## if not channel.controlled_by(caller) and not caller.has_perm("channels.channel_admin"):
## caller.msg("You don't control this channel.")
## return
## #find the new owner
## new_owner = Object.objects.player_name_search(pname)
## if not new_owner:
## caller.msg("New owner '%s' not found." % pname)
## return
## old_owner = channel.get_owner()
## old_pname = old_owner.get_name(show_dbref=False)
## if old_owner == new_owner:
## caller.msg("Owner unchanged.")
## return
## #all is set, change owner
## channel.set_owner(new_owner)
## caller.msg("Owner of %s changed from %s to %s." % (cname, old_pname, pname))
## new_owner.msg("%s transfered ownership of channel '%s' to you." % (old_pname, cname))
## GLOBAL_CMD_TABLE.add_self("@cchown", cmd_cchown, help_category="Comms")
class CmdCdesc(MuxCommand):
"""
@cdesc - set channel description
Usage:
@cdesc <channel> = <description>
Changes the description of the channel as shown in
channel lists.
"""
key = "@cdesc"
permissions = "cmd:cdesc"
help_category = "Comms"
def func(self):
"Implement command"
caller = self.caller
if not self.rhs:
caller.msg("Usage: @cdesc <channel> = <description>")
return
channel = find_channel(caller, self.lhs)
if not channel:
caller.msg("Channel '%s' not found." % self.lhs)
return
#check permissions
if not has_perm(caller, channel, 'channel_admin'):
caller.msg("You cant admin this channel.")
return
# set the description
channel.desc = self.rhs
channel.save()
caller.msg("Description of channel '%s' set to '%s'." % (channel.key, self.rhs))
class CmdPage(MuxCommand):
"""
page - send private message
Usage:
page[/switches] [<player>,<player>,... = <message>]
tell ''
Switch:
list - show your last 10 tells/pages.
Send a message to target user (if online). If no
argument is given, you will instead see who was the last
person you paged to.
"""
key = "page"
aliases = ['tell']
permissions = "cmd:tell"
help_category = "Comms"
def func(self):
"Implement function using the Msg methods"
caller = self.caller
player = caller.player
# get the last message we sent
messages = list(Msg.objects.get_messages_by_sender(player))
pages = [msg for msg in messages
if msg.receivers]
if pages:
lastpage = pages[-1]
if 'list' in self.switches:
if len(messages) > 10:
lastpages = messages[-10:]
else:
lastpages = messages
lastpages = "\n ".join(["%s to %s: %s" % (mess.date_sent, mess.receivers.all(),
mess.message)
for mess in messages])
caller.msg("Your latest pages:\n %s" % lastpages )
return
if not self.args or not self.rhs:
if pages:
string = "You last paged %s." % (", ".join([obj.name
for obj in lastpage.receivers.all()]))
caller.msg(string)
return
else:
string = "You haven't paged anyone yet."
caller.msg(string)
return
# Build a list of targets
if not self.lhs:
# If there are no targets, then set the targets
# to the last person they paged.
receivers = lastpage.receivers
else:
receivers = self.lhslist
recobjs = []
for receiver in receivers:
obj = caller.search("*%s" % (receiver.lstrip('*')), global_search=True)
if not obj:
return
recobjs.append(obj)
header = "{wPlayer{n {c%s{n {wpages:{n" % caller.key
message = self.rhs
# create the persistent message object
msg = create.create_message(caller, message,
receivers=recobjs)
# tell the players they got a message.
for obj in recobjs:
obj.msg("%s %s" % (header, message))
caller.msg("You paged %s with '%s'." % (recobjs, message))

View file

@ -0,0 +1,703 @@
"""
Generic command module. Pretty much every command should go here for
now.
"""
import time
from src.server import sessionhandler
from src.permissions.models import PermissionGroup
from src.permissions.permissions import has_perm, has_perm_string
from src.objects.models import HANDLE_SEARCH_ERRORS
from src.utils import utils
from game.gamesrc.commands.default.muxcommand import MuxCommand
class CmdLook(MuxCommand):
"""
look
Usage:
look
look <obj>
Observes your location or objects in your vicinity.
"""
key = "look"
aliases = ["l"]
def func(self):
"""
Handle the looking.
"""
caller = self.caller
args = self.args # caller.msg(inp)
if args:
# Use search to handle duplicate/nonexistant results.
looking_at_obj = caller.search(args)
if not looking_at_obj:
return
else:
looking_at_obj = caller.location
if not looking_at_obj:
caller.msg("Location: None")
return
# get object's appearance
caller.msg(looking_at_obj.return_appearance(caller))
# the object's at_desc() method.
looking_at_obj.at_desc(looker=caller)
class CmdPassword(MuxCommand):
"""
@password - set your password
Usage:
@password <old password> = <new password>
Changes your password. Make sure to pick a safe one.
"""
key = "@password"
def func(self):
"hook function."
caller = self.caller
if not self.rhs:
caller.msg("Usage: @password <oldpass> = <newpass>")
return
oldpass = self.lhslist[0] # this is already stripped by parse()
newpass = self.rhslist[0] # ''
try:
uaccount = caller.user
except AttributeError:
caller.msg("This is only applicable for players.")
return
if not uaccount.check_password(oldpass):
caller.msg("The specified old password isn't correct.")
elif len(newpass) < 3:
caller.msg("Passwords must be at least three characters long.")
else:
uaccount.set_password(newpass)
uaccount.save()
caller.msg("Password changed.")
class CmdNick(MuxCommand):
"""
Define a personal alias/nick
Usage:
alias[/switches] <alias> = [<string>]
nick ''
Switches:
obj - alias an object
player - alias a player
clearall - clear all your aliases
list - show all defined aliases
If no switch is given, a command/channel alias is created, used
to replace strings before sending the command.
Creates a personal nick for some in-game object or
string. When you enter that string, it will be replaced
with the alternate string. The switches dictate in what
situations the nick is checked and substituted. If string
is None, the alias (if it exists) will be cleared.
Obs - no objects are actually changed with this command,
if you want to change the inherent aliases of an object,
use the @alias command instead.
"""
key = "alias"
aliases = ["nick"]
def func(self):
"Create the nickname"
caller = self.caller
switches = self.switches
if 'list' in switches:
string = "{wAliases:{n \n"
string = string + "\n\r".join(["%s = %s" % (alias, replace)
for alias, replace
in caller.nicks.items()])
caller.msg(string)
return
if 'clearall' in switches:
del caller.nicks
caller.msg("Cleared all aliases.")
return
if not self.args or not self.lhs:
caller.msg("Usage: alias[/switches] string = [alias]")
return
alias = self.lhs
rstring = self.rhs
err = None
if rstring == alias:
err = "No point in setting alias same as the string to replace..."
caller.msg(err)
return
elif 'obj' in switches:
# object alias, for adressing objects
# (including user-controlled ones)
err = caller.set_nick("_obj:%s" % alias, rstring)
atype = "Object"
elif 'player' in switches:
# player alias, used for messaging
err = caller.set_nick("_player:%s" % alias, rstring)
atype = "Player "
else:
# a command/channel alias - these are replaced if
# they begin a command string.
caller.msg(rstring)
caller.msg("going in: %s %s" % (alias, rstring))
err = caller.set_nick(alias, rstring)
atype = "Command/channel "
if err:
if rstring:
err = "%salias %s changed from '%s' to '%s'." % (atype, alias, err, rstring)
else:
err = "Cleared %salias '%s'(='%s')." % (atype, alias, err)
else:
err = "Set %salias '%s' = '%s'" % (atype, alias, rstring)
caller.msg(err.capitalize())
class CmdEmit(MuxCommand):
"""
@emit
Usage:
@emit[/switches] [<obj>, <obj>, ... =] <message>
@remit [<obj>, <obj>, ... =] <message>
@pemit [<obj>, <obj>, ... =] <message>
Switches:
room : limit emits to rooms only
players : limit emits to players only
contents : send to the contents of matched objects too
Emits a message to the selected objects or to
your immediate surroundings. If the object is a room,
send to its contents. @remit and @pemit are just
limited forms of @emit, for sending to rooms and
to players respectively.
"""
key = "@emit"
aliases = ["@pemit", "@remit"]
permissions = "cmd:emit"
help_category = "Comms"
def func(self):
"Implement the command"
caller = self.caller
args = self.args
if not args:
string = "Usage: "
string += "\n@emit[/switches] [<obj>, <obj>, ... =] <message>"
string += "\n@remit [<obj>, <obj>, ... =] <message>"
string += "\n@pemit [<obj>, <obj>, ... =] <message>"
caller.msg(string)
return
rooms_only = 'rooms' in self.switches
players_only = 'players' in self.switches
send_to_contents = 'contents' in self.switches
# we check which command was used to force the switches
if self.cmdstring == '@remit':
rooms_only = True
elif self.cmdstring == '@pemit':
players_only = True
if not self.rhs:
message = self.args
objnames = [caller.location.key]
else:
message = self.rhs
objnames = self.lhslist
# send to all objects
for objname in objnames:
obj = caller.search(objname, global_search=True)
if not obj:
return
if rooms_only and not obj.location == None:
caller.msg("%s is not a room. Ignored." % objname)
continue
if players_only and not obj.has_player:
caller.msg("%s has no active player. Ignored." % objname)
continue
if has_perm(caller, obj, 'send_to'):
obj.msg(message)
if send_to_contents:
for content in obj.contents:
content.msg(message)
caller.msg("Emitted to %s and its contents." % objname)
else:
caller.msg("Emitted to %s." % objname)
else:
caller.msg("You are not allowed to send to %s." % objname)
class CmdWall(MuxCommand):
"""
@wall
Usage:
@wall <message>
Announces a message to all connected players.
"""
key = "@wall"
permissions = "cmd:wall"
def func(self):
"Implements command"
if not self.args:
self.caller.msg("Usage: @wall <message>")
return
message = "%s shouts \"%s\"" % (self.caller.name, self.args)
sessionhandler.announce_all(message)
class CmdInventory(MuxCommand):
"""
inventory
Usage:
inventory
inv
Shows a player's inventory.
"""
key = "inventory"
aliases = ["inv", "i"]
def func(self):
"hook function"
string = "You are carrying:"
for item in self.caller.contents:
string += "\n %s" % item.name
self.caller.msg(string)
## money = int(caller.MONEY)
## if money == 1:
## money_name = ConfigValue.objects.get_configvalue("MONEY_NAME_SINGULAR")
## else:
## money_name = ConfigValue.objects.get_configvalue("MONEY_NAME_PLURAL")
##caller.msg("You have %d %s." % (money, money_name))
class CmdGet(MuxCommand):
"""
get
Usage:
get <obj>
Picks up an object from your location and puts it in
your inventory.
"""
key = "get"
aliases = "grab"
def func(self):
"implements the command."
caller = self.caller
if not self.args:
caller.msg("Get what?")
return
obj = caller.search(self.args)
if not obj:
return
if caller == obj:
caller.msg("You can't get yourself.")
return
if obj.player or obj.db._destination:
# don't allow picking up player objects, nor exits.
caller.msg("You can't get that.")
return
if not has_perm(caller, obj, 'get'):
#TODO - have the object store fail messages?
caller.msg("You can't get that.")
return
obj.move_to(caller, quiet=True)
caller.msg("You pick up %s." % obj.name)
caller.location.msg_contents("%s picks up %s." %
(caller.name,
obj.name),
exclude=caller)
# calling hook method
obj.at_get(caller)
class CmdDrop(MuxCommand):
"""
drop
Usage:
drop <obj>
Lets you drop an object from your inventory into the
location you are currently in.
"""
key = "drop"
def func(self):
"Implement command"
caller = self.caller
if not self.args:
caller.msg("Drop what?")
return
results = caller.search(self.args, ignore_errors=True)
# we process the results ourselves since we want to sift out only
# those in our inventory.
results = [obj for obj in results if obj in caller.contents]
# now we send it into the handler.
obj = HANDLE_SEARCH_ERRORS(caller, self.args, results, False)
if not obj:
return
obj.move_to(caller.location, quiet=True)
caller.msg("You drop %s." % (obj.name,))
caller.location.msg_contents("%s drops %s." %
(caller.name, obj.name),
exclude=caller)
# Call the object script's at_drop() method.
obj.at_drop(caller)
class CmdQuit(MuxCommand):
"""
quit
Usage:
quit
Gracefully disconnect from the game.
"""
key = "quit"
def func(self):
"hook function"
sessions = self.caller.sessions
for session in sessions:
session.msg("Quitting. Hope to see you soon again.")
session.handle_close()
class CmdWho(MuxCommand):
"""
who
Usage:
who
doing
Shows who is currently online. Doing is an
alias that limits info also for those with
all permissions.
"""
key = "who"
aliases = "doing"
def func(self):
"""
Get all connected players by polling session.
"""
caller = self.caller
session_list = sessionhandler.get_sessions()
if self.cmdstring == "doing":
show_session_data = False
else:
show_session_data = has_perm_string(caller, "Immortals,Wizards")
if show_session_data:
retval = "Player Name On For Idle Room Cmds Host\n\r"
else:
retval = "Player Name On For Idle\n\r"
for session in session_list:
if not session.logged_in:
continue
delta_cmd = time.time() - session.cmd_last_visible
delta_conn = time.time() - session.conn_time
plr_pobject = session.get_character()
if show_session_data:
retval += '%-31s%9s %4s%-3s#%-6d%5d%3s%-25s\r\n' % \
(plr_pobject.name[:25], \
# On-time
utils.time_format(delta_conn,0), \
# Idle time
utils.time_format(delta_cmd,1), \
# Flags
'', \
# Location
plr_pobject.location.id, \
session.cmd_total, \
# More flags?
'', \
session.address[0])
else:
retval += '%-31s%9s %4s%-3s\r\n' % \
(plr_pobject.name[:25], \
# On-time
utils.time_format(delta_conn,0), \
# Idle time
utils.time_format(delta_cmd,1), \
# Flags
'')
retval += '%d Players logged in.' % (len(session_list),)
caller.msg(retval)
class CmdSay(MuxCommand):
"""
say
Usage:
say <message>
Talk to those in your current location.
"""
key = "say"
def func(self):
"Run the say command"
caller = self.caller
if not self.args:
caller.msg("Say what?")
return
speech = self.args
# calling the speech hook on the location
speech = caller.location.at_say(caller, speech)
# Feedback for the object doing the talking.
caller.msg("You say, '%s'" % speech)
# Build the string to emit to neighbors.
emit_string = "{c%s{n says, '%s'" % (caller.name,
speech)
caller.location.msg_contents(emit_string,
exclude=caller)
## def cmd_fsay(command):
## """
## @fsay - make an object say something
## Usage:
## @fsay <obj> = <text to say>
## Make an object talk to its current location.
## """
## caller = command.caller
## args = command.command_argument
## if not args or not "=" in args:
## caller.msg("Usage: @fsay <obj> = <text to say>")
## return
## target, speech = [arg.strip() for arg in args.split("=",1)]
## # find object
## if target in ['here']:
## results = [caller.location]
## elif target in ['me','my']:
## results = [caller]
## else:
## results = Object.objects.global_object_name_search(target)
## if not results:
## caller.msg("No matches found for '%s'." % target)
## return
## if len(results) > 1:
## string = "There are multiple matches. Please use #dbref to be more specific."
## for result in results:
## string += "\n %s" % results.name
## caller.msg(string)
## return
## target = results[0]
## # permission check
## if not caller.controls_other(target):
## caller.msg("Cannot pose %s (you don's control it)" % target.name)
## return
## # Feedback for the object doing the talking.
## caller.msg("%s says, '%s%s'" % (target.name,
## speech,
## ANSITable.ansi['normal']))
## # Build the string to emit to neighbors.
## emit_string = "%s says, '%s'" % (target.name,
## speech)
## target.location.msg_contents(emit_string,
## exclude=caller)
## GLOBAL_CMD_TABLE.add_command("@fsay", cmd_fsay)
class CmdPose(MuxCommand):
"""
pose - strike a pose
Usage:
pose <pose text>
pose's <pose text>
Example:
pose is standing by the wall, smiling.
-> others will see:
Tom is standing by the wall, smiling.
Describe an script being taken. The pose text will
automatically begin with your name.
"""
key = "pose"
aliases = [":", "emote"]
def parse(self):
"""
Custom parse the cases where the emote
starts with some special letter, such
as 's, at which we don't want to separate
the caller's name and the emote with a
space.
"""
args = self.args
if args and not args[0] in ["'", ",", ":"]:
args = " %s" % args
self.args = args
def func(self):
"Hook function"
if not self.args:
msg = "Do what?"
else:
msg = "%s%s" % (self.caller.name, self.args)
self.caller.location.msg_contents(msg)
## def cmd_fpose(command):
## """
## @fpose - force an object to pose
## Usage:
## @fpose[/switches] <obj> = <pose text>
## Switches:
## nospace : put no text between the object's name
## and the start of the pose.
## Describe an action being taken as performed by obj.
## The pose text will automatically begin with the name
## of the object.
## """
## caller = command.caller
## args = command.command_argument
## if not args or not "=" in args:
## caller.msg("Usage: @fpose <obj> = <pose text>")
## return
## target, pose_string = [arg.strip() for arg in args.split("=",1)]
## # find object
## if target in ['here']:
## results = [caller.location]
## elif target in ['me','my']:
## results = [caller]
## else:
## results = Object.objects.global_object_name_search(target)
## if not results:
## caller.msg("No matches found for '%s'." % target)
## return
## if len(results) > 1:
## string = "There are multiple matches. Please use #dbref to be more specific."
## for result in results:
## string += "\n %s" % results.name
## caller.msg(string)
## return
## target = results[0]
## # permission check
## if not caller.controls_other(target):
## caller.msg("Cannot pose %s (you don's control it)" % target.name)
## return
## if "nospace" in command.command_switches:
## # Output without a space between the player name and the emote.
## sent_msg = "%s%s" % (target.name,
## pose_string)
## else:
## # No switches, default.
## sent_msg = "%s %s" % (target.name,
## pose_string)
## caller.location.msg_contents(sent_msg)
## GLOBAL_CMD_TABLE.add_command("@fpose", cmd_fpose)
class CmdGroup(MuxCommand):
"""
group - show your groups
Usage:
group
This command shows you which user permission groups
you are a member of, if any.
"""
key = "group"
aliases = "groups"
def func(self):
"Load the permission groups"
caller = self.caller
string = ""
if caller.player and caller.player.is_superuser:
string += "\n This is a SUPERUSER account! Group membership does not matter."
else:
# get permissions and determine if they are groups
perms = [perm.strip().lower() for perm in caller.player.permissions
if perm.strip()]
for group in [group for group in PermissionGroup.objects.all()
if group.key.lower() in perms]:
string += "\n %s\t\t%s" % (group.key, [str(perm) for perm in group.group_permissions])
if string:
string = "\nYour (%s's) group memberships: %s" % (caller.name, string)
else:
string = "\nYou are not not a member of any groups."
caller.msg(string)
## def cmd_apropos(command):
## """
## apropos - show rough help matches
## Usage:
## apropos <text>
## or
## suggest <text>
## This presents a list of topics very loosely matching your
## search text. Use this command when you are searching for
## help on a certain concept but don't know any exact
## command names. You can also use the normal help command
## with the /apropos switch to get the same functionality.
## """
## arg = command.command_argument
## command.caller.execute_cmd("help/apropos %s" % arg)
## GLOBAL_CMD_TABLE.add_command("apropos", cmd_apropos)
## GLOBAL_CMD_TABLE.add_command("suggest", cmd_apropos)

View file

@ -0,0 +1,281 @@
"""
The help command. The basic idea is that help texts for commands
are best written by those that write the commands - the admins. So
command-help is all auto-loaded and searched from the current command
set. The normal, database-tied help system is used for collaborative
creation of other help topics such as RP help or game-world aides.
"""
from src.utils.utils import fill, dedent
from src.commands.command import Command
from src.help.models import HelpEntry
from src.permissions.permissions import has_perm
from src.utils import create
from game.gamesrc.commands.default.muxcommand import MuxCommand
LIST_ARGS = ["list", "all"]
def format_help_entry(title, help_text, aliases=None,
suggested=None):
"This visually formats the help entry."
string = "-"*70 + "\n"
if title:
string += "Help topic for {w%s{n" % (title.capitalize())
if aliases:
string += " (aliases: %s)" % (", ".join(aliases))
if help_text:
string += "\n%s" % dedent(help_text.rstrip())
if suggested:
string += "\nSuggested:\n"
string += fill(", ".join(suggested))
string.strip()
string += "\n" + "-"*70
return string
def format_help_list(hdict_cmds, hdict_db):
"Output a category-ordered list"
string = "\n\r" + "-"*70 + "\n\r {gCommand help entries{n\n" + "-"*70
for category in sorted(hdict_cmds.keys()):
string += "\n {w%s{n:\n" % \
(str(category).capitalize())
string += fill(", ".join(sorted(hdict_cmds[category])))
if hdict_db:
string += "\n\r\n\r" + "-"*70 + "\n\r {gOther help entries{n\n" + '-'*70
for category in sorted(hdict_db.keys()):
string += "\n\r {w%s{n:\n" % (str(category).capitalize())
string += fill(", ".join(sorted([str(topic) for topic in hdict_db[category]])))
return string
class CmdHelp(Command):
"""
The main help command
Usage:
help <topic or command>
help list
help all
This will search for help on commands and other
topics related to the game.
"""
key = "help"
# this is a special cmdhandler flag that makes the cmdhandler also pack
# the current cmdset with the call to self.func().
return_cmdset = True
def parse(self):
"""
inp is a string containing the command or topic match.
"""
self.args = self.args.strip().lower()
def func(self):
"""
Run the dynamic help entry creator.
"""
query, cmdset = self.args, self.cmdset
caller = self.caller
if not query:
query = "all"
# Listing help entries
if query in LIST_ARGS:
# we want to list all available help entries
hdict_cmd = {}
for cmd in (cmd for cmd in cmdset if has_perm(caller, cmd, 'cmd')
if not cmd.key.startswith('__')
and not (hasattr(cmd, 'is_exit') and cmd.is_exit)):
if hdict_cmd.has_key(cmd.help_category):
hdict_cmd[cmd.help_category].append(cmd.key)
else:
hdict_cmd[cmd.help_category] = [cmd.key]
hdict_db = {}
for topic in (topic for topic in HelpEntry.objects.get_all_topics()
if has_perm(caller, topic, 'view')):
if hdict_db.has_key(topic.help_category):
hdict_db[topic.help_category].append(topic.key)
else:
hdict_db[topic.help_category] = [topic.key]
help_entry = format_help_list(hdict_cmd, hdict_db)
caller.msg(help_entry)
return
# Look for a particular help entry
# Cmd auto-help dynamic entries
cmdmatches = [cmd for cmd in cmdset
if query in cmd and has_perm(caller, cmd, 'cmd')]
if len(cmdmatches) > 1:
# multiple matches. Try to limit it down to exact match
exactmatches = [cmd for cmd in cmdmatches if cmd == query]
if exactmatches:
cmdmatches = exactmatches
# Help-database static entries
dbmatches = \
[topic for topic in
HelpEntry.objects.find_topicmatch(query, exact=False)
if has_perm(caller, topic, 'view')]
if len(dbmatches) > 1:
exactmatches = \
[topic for topic in
HelpEntry.objects.find_topicmatch(query, exact=True)
if has_perm(caller, topic, 'view')]
if exactmatches:
dbmatches = exactmatches
# Handle result
if (not cmdmatches) and (not dbmatches):
# no normal match. Check if this is a category match instead
categ_cmdmatches = [cmd for cmd in cmdset
if query == cmd.help_category and has_perm(caller, cmd, 'cmd')]
categ_dbmatches = \
[topic for topic in
HelpEntry.objects.find_topics_with_category(query)
if has_perm(caller, topic, 'view')]
if categ_cmdmatches or categ_dbmatches:
help_entry = format_help_list({query:categ_cmdmatches},
{query:categ_dbmatches})
else:
help_entry = "No help entry found for '%s'" % query
elif len(cmdmatches) == 1:
# we matched against a command name or alias. Show its help entry.
suggested = []
if dbmatches:
suggested = [entry.key for entry in dbmatches]
cmd = cmdmatches[0]
help_entry = format_help_entry(cmd.key, cmd.__doc__,
aliases=cmd.aliases,
suggested=suggested)
elif len(dbmatches) == 1:
# matched against a database entry
entry = dbmatches[0]
help_entry = format_help_entry(entry.key, entry.entrytext)
else:
# multiple matches of either type
cmdalts = [cmd.key for cmd in cmdmatches]
dbalts = [entry.key for entry in dbmatches]
helptext = "Multiple help entries match your search ..."
help_entry = format_help_entry("", helptext, None, cmdalts + dbalts)
# send result to user
caller.msg(help_entry)
class CmdSetHelp(MuxCommand):
"""
@sethelp - edit the help database
Usage:
@sethelp[/switches] <topic>[,category[,permission,permission,...]] = <text>
Switches:
add - add or replace a new topic with text.
append - add text to the end of topic with a newline between.
merge - As append, but don't add a newline between the old
text and the appended text.
delete - remove help topic.
force - (used with add) create help topic also if the topic
already exists.
Examples:
@sethelp/add throw = This throws something at ...
@sethelp/append pickpocketing,Thievery,is_thief, is_staff) = This steals ...
@sethelp/append pickpocketing, ,is_thief, is_staff) = This steals ...
"""
key = "@sethelp"
permissions = "cmd:sethelp"
help_category = "Building"
def func(self):
"Implement the function"
caller = self.caller
switches = self.switches
lhslist = self.lhslist
rhs = self.rhs
if not self.rhs:
caller.msg("Usage: @sethelp/[add|del|append|merge] <topic>[,category[,permission,..] = <text>]")
return
topicstr = ""
category = ""
permissions = ""
try:
topicstr = lhslist[0]
category = lhslist[1]
permissions = ",".join(lhslist[2:])
except Exception:
pass
if not topicstr:
caller.msg("You have to define a topic!")
return
string = ""
print topicstr, category, permissions
if switches and switches[0] in ('append', 'app','merge'):
# add text to the end of a help topic
# find the topic to append to
old_entry = None
try:
old_entry = HelpEntry.objects.get(key=topicstr)
except Exception:
pass
if not old_entry:
string = "Could not find topic '%s'. You must give an exact name." % topicstr
else:
entrytext = old_entry.entrytext
if switches[0] == 'merge':
old_entry.entrytext = "%s %s" % (entrytext, self.rhs)
string = "Added the new text right after the old one (merge)."
else:
old_entry.entrytext = "%s\n\n%s" % (entrytext, self.rhs)
string = "Added the new text as a new paragraph after the old one (append)"
old_entry.save()
elif switches and switches[0] in ('delete','del'):
#delete a help entry
old_entry = None
try:
old_entry = HelpEntry.objects.get(key=topicstr)
except Exception:
pass
if not old_entry:
string = "Could not find topic. You must give an exact name."
else:
old_entry.delete()
string = "Deleted the help entry '%s'." % topicstr
else:
# add a new help entry.
force_create = ('for' in switches) or ('force' in switches)
old_entry = None
try:
old_entry = HelpEntry.objects.get(key=topicstr)
except Exception:
pass
if old_entry:
if force_create:
old_entry.key = topicstr
old_entry.entrytext = self.rhs
old_entry.help_category = category
old_entry.permissions = permissions
old_entry.save()
string = "Overwrote the old topic '%s' with a new one." % topicstr
else:
string = "Topic '%s' already exists. Use /force to overwrite it." % topicstr
else:
# no old entry. Create a new one.
new_entry = create.create_help_entry(topicstr,
rhs, category, permissions)
if new_entry:
string = "Topic '%s' was successfully created." % topicstr
else:
string = "Error when creating topic '%s'! Maybe it already exists?" % topicstr
# give feedback
caller.msg(string)

View file

@ -0,0 +1,204 @@
"""
Commands that are generally staff-oriented that show information regarding
the server instance.
"""
import os
import django, twisted
from django.contrib.auth.models import User
from src.objects.models import ObjectDB
from src.scripts.models import ScriptDB
from src.utils import utils
from src.utils import gametime
from game.gamesrc.commands.default.muxcommand import MuxCommand
from src.commands import cmdsethandler
class CmdVersion(MuxCommand):
"""
@version - game version
Usage:
@version
Display the game version info.
"""
key = "@version"
help_category = "System"
def func(self):
"Show the version"
version = utils.get_evennia_version()
string = "-"*50 +"\n\r"
string += " Evennia %s\n\r" % version
string += " (Django %s, " % (django.get_version())
string += " Twisted %s)\n\r" % (twisted.version.short())
string += "-"*50
self.caller.msg(string)
class CmdTime(MuxCommand):
"""
@time
Usage:
@time
Server local time.
"""
key = "@time"
aliases = "@uptime"
permissions = "cmd:time"
help_category = "System"
def func(self):
"Show times."
string2 = "\nCurrent server uptime:\n %i yrs, %i months, "
string2 += "%i weeks, %i days, %i hours, %i minutes and %i secs."
string2 = string2 % gametime.uptime(format=True)
string3 = "\nTotal running time (gametime x %g):" % (1.0/gametime.TIMEFACTOR)
string3 += "\n %i yrs, %i months, %i weeks, %i days, "
string3 += "%i hours, %i minutes and %i secs."
string3 = string3 % gametime.runtime(format=True)
#print "runtime:", gametime.runtime()
string1 = "\nTotal game time (realtime x %g):" % (gametime.TIMEFACTOR)
string1 += "\n %i yrs, %i months, %i weeks, %i days, "
string1 += "%i hours, %i minutes and %i secs."
string1 = string1 % (gametime.gametime(format=True))
#print "gametime:", gametime.gametime()
string4 = ""
if not utils.host_os_is('nt'):
# os.getloadavg() is not available on Windows.
loadavg = os.getloadavg()
string4 = "\n Server load (1 min) : %g%%" % (100 * loadavg[0])
string = "%s%s%s%s" % (string2, string3, string1, string4)
self.caller.msg(string)
class CmdList(MuxCommand):
"""
@list - list info
Usage:
@list commands | process
Shows game related information depending
on which argument is given.
"""
key = "@list"
permissions = "cmd:list"
help_category = "System"
def func(self):
"Show list."
caller = self.caller
if not self.args:
caller.msg("Usage: @list commands|process")
return
string = ""
if self.arglist[0] in ["com", "command", "commands"]:
string = "Command sets currently in cache:"
for cmdset in cmdsethandler.get_cached_cmdsets():
string += "\n %s" % cmdset
elif self.arglist[0] in ["proc","process"]:
if utils.host_os_is('nt'):
string = "Feature not available on Windows."
else:
import resource
loadavg = os.getloadavg()
string = "\n Server load (1 min) : %.2f " % loadavg[0]
psize = resource.getpagesize()
rusage = resource.getrusage(resource.RUSAGE_SELF)
string += "\n Process ID: %10d" % os.getpid()
string += "\n Bytes per page: %10d" % psize
string += "\n Time used: %10d, user: %g" % (rusage[0], rusage[1])
string += "\n Integral mem: %10d shared, %10d, private, %10d stack " % \
(rusage[3], rusage[4], rusage[5])
string += "\n Max res mem: %10d pages %10d bytes" % \
(rusage[2],rusage[2] * psize)
string += "\n Page faults: %10d hard %10d soft %10d swapouts " % \
(rusage[7], rusage[6], rusage[8])
string += "\n Disk I/O: %10d reads %10d writes " % \
(rusage[9], rusage[10])
string += "\n Network I/O: %10d in %10d out " % \
(rusage[12], rusage[11])
string += "\n Context swi: %10d vol %10d forced %10d sigs " % \
(rusage[14], rusage[15], rusage[13])
else:
string = "Not a valid option."
# send info
caller.msg(string)
class CmdPs(MuxCommand):
"""
@ps - list processes
Usage
@ps
Shows the process/event table.
"""
key = "@ps"
permissions = "cmd:ps"
help_category = "System"
def func(self):
"run the function."
string = "Processes Scheduled:\n-- PID [time/interval] [repeats] description --"
all_scripts = ScriptDB.objects.get_all_scripts()
repeat_scripts = [script for script in all_scripts if script.interval]
nrepeat_scripts = [script for script in all_scripts if script not in repeat_scripts]
string = "\nNon-timed scripts:"
for script in nrepeat_scripts:
string += "\n %i %s %s" % (script.id, script.key, script.desc)
string += "\n\nTimed scripts:"
for script in repeat_scripts:
repeats = "[inf] "
if script.repeats:
repeats = "[%i] " % script.repeats
string += "\n %i %s [%d/%d] %s%s" % (script.id, script.key,
script.time_until_next_repeat(),
script.interval,
repeats,
script.desc)
string += "\nTotals: %d interval scripts" % len(all_scripts)
self.caller.msg(string)
class CmdStats(MuxCommand):
"""
@stats - show object stats
Usage:
@stats
Shows stats about the database.
"""
key = "@stats"
aliases = "@db"
permissions = "cmd:stats"
help_category = "System"
def func(self):
"Show all stats"
# get counts for all typeclasses
stats_dict = ObjectDB.objects.object_totals()
# get all objects
stats_allobj = ObjectDB.objects.all().count()
# get all rooms
stats_room = ObjectDB.objects.filter(obj_location=None).count()
# get all players
stats_users = User.objects.all().count()
string = "-"*60
string += "\n Number of users: %i" % stats_users
string += "\n Total number of objects: %i" % stats_allobj
string += "\n Number of rooms (location==None): %i" % stats_room
string += "\n Object type statistics:"
for path, num in stats_dict.items():
string += "\n %i - %s" % (num, path)
self.caller.msg(string)

View file

@ -0,0 +1,147 @@
"""
The command template for the default MUX-style command set
"""
from src.utils import utils
from game.gamesrc.commands.basecommand import Command
class MuxCommand(Command):
"""
This sets up the basis for a MUX command. The idea
is that most other Mux-related commands should just
inherit from this and don't have to implement much
parsing of their own unless they do something particularly
advanced.
Note that the class's __doc__ string (this text) is
used by Evennia to create the automatic help entry for
the command, so make sure to document consistently here.
"""
def has_perm(self, srcobj):
"""
This is called by the cmdhandler to determine
if srcobj is allowed to execute this command.
We just show it here for completeness - we
are satisfied using the default check in Command.
"""
return super(MuxCommand, self).has_perm(srcobj)
def parse(self):
"""
This method is called by the cmdhandler once the command name
has been identified. It creates a new set of member variables
that can be later accessed from self.func() (see below)
The following variables are available for our use when entering this
method (from the command definition, and assigned on the fly by the
cmdhandler):
self.key - the name of this command ('look')
self.aliases - the aliases of this cmd ('l')
self.permissions - permission string for this command
self.help_category - overall category of command
self.caller - the object calling this command
self.cmdstring - the actual command name used to call this
(this allows you to know which alias was used,
for example)
self.args - the raw input; everything following self.cmdstring.
self.cmdset - the cmdset from which this command was picked. Not
often used (useful for commands like 'help' or to
list all available commands etc)
self.obj - the object on which this command was defined. It is often
the same as self.caller.
A MUX command has the following possible syntax:
name[ with several words][/switch[/switch..]] arg1[,arg2,...] [[=|,] arg[,..]]
The 'name[ with several words]' part is already dealt with by the
cmdhandler at this point, and stored in self.cmdname (we don't use
it here). The rest of the command is stored in self.args, which can start
with the switch indicator /.
This parser breaks self.args into its constituents and stores them in the
following variables:
self.switches = [list of /switches (without the /)]
self.raw = This is the raw argument input, including switches
self.args = This is re-defined to be everything *except* the switches
self.lhs = Everything to the left of = (lhs:'left-hand side'). If
no = is found, this is identical to self.args.
self.rhs: Everything to the right of = (rhs:'right-hand side').
If no '=' is found, this is None.
self.lhslist - [self.lhs split into a list by comma]
self.rhslist - [list of self.rhs split into a list by comma]
self.arglist = [list of space-separated args (stripped, including '=' if it exists)]
All args and list members are stripped of excess whitespace around the
strings, but case is preserved.
"""
raw = self.args
args = raw.strip()
# split out switches
switches = []
if args and len(args) >1 and args[0] == "/":
# we have a switch, or a set of switches. These end with a space.
#print "'%s'" % args
switches = args[1:].split(None, 1)
if len(switches) > 1:
switches, args = switches
switches = switches.split('/')
else:
args = ""
switches = switches[0].split('/')
arglist = [arg.strip() for arg in args.split(None)]
# check for arg1, arg2, ... = argA, argB, ... constructs
lhs, rhs = args, None
lhslist, rhslist = [arg.strip() for arg in args.split(',')], []
if args and '=' in args:
lhs, rhs = [arg.strip() for arg in args.split('=', 1)]
lhslist = [arg.strip() for arg in lhs.split(',')]
rhslist = [arg.strip() for arg in rhs.split(',')]
# save to object properties:
self.raw = raw
self.switches = switches
self.args = args.strip()
self.arglist = arglist
self.lhs = lhs
self.lhslist = lhslist
self.rhs = rhs
self.rhslist = rhslist
def func(self):
"""
This is the hook function that actually does all the work. It is called
by the cmdhandler right after self.parser() finishes, and so has access
to all the variables defined therein.
"""
# a simple test command to show the available properties
string = "-" * 50
string += "\n{w%s{n - Command variables from evennia:\n" % self.key
string += "-" * 50
string += "\nname of cmd (self.key): {w%s{n\n" % self.key
string += "cmd aliases (self.aliases): {w%s{n\n" % self.aliases
string += "cmd perms (self.permissions): {w%s{n\n" % self.permissions
string += "help category (self.help_category): {w%s{n\n" % self.help_category
string += "object calling (self.caller): {w%s{n\n" % self.caller
string += "object storing cmdset (self.obj): {w%s{n\n" % self.obj
string += "command string given (self.cmdstring): {w%s{n\n" % self.cmdstring
# show cmdset.key instead of cmdset to shorten output
string += utils.fill("current cmdset (self.cmdset): {w%s{n\n" % self.cmdset)
string += "\n" + "-" * 50
string += "\nVariables from MuxCommand baseclass\n"
string += "-" * 50
string += "\nraw argument (self.raw): {w%s{n \n" % self.raw
string += "cmd args (self.args): {w%s{n\n" % self.args
string += "cmd switches (self.switches): {w%s{n\n" % self.switches
string += "space-separated arg list (self.arglist): {w%s{n\n" % self.arglist
string += "lhs, left-hand side of '=' (self.lhs): {w%s{n\n" % self.lhs
string += "lhs, comma separated (self.lhslist): {w%s{n\n" % self.lhslist
string += "rhs, right-hand side of '=' (self.rhs): {w%s{n\n" % self.rhs
string += "rhs, comma separated (self.rhslist): {w%s{n\n" % self.rhslist
string += "-" * 50
self.caller.msg(string)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,670 @@
"""
This file contains commands that require special permissions to
use. These are generally @-prefixed commands, but there are
exceptions.
"""
import traceback
from django.contrib.auth.models import User
from src.server import sessionhandler
from src.scripts.models import ScriptDB
from src.objects.models import ObjectDB
from src.permissions.models import PermissionGroup
from src.scripts.scripthandler import format_script_list
from src.utils import reloads, create, logger, utils
from src.permissions.permissions import has_perm
from game.gamesrc.commands.default.muxcommand import MuxCommand
class CmdReload(MuxCommand):
"""
Reload the system
Usage:
@reload
This reloads the system modules and
re-validates all scripts.
"""
key = "@reload"
permissions = "cmd:reload"
help_category = "System"
def func(self):
"""
reload the system.
"""
caller = self.caller
reloads.reload_modules()
reloads.reload_scripts()
reloads.reload_commands()
class CmdPy(MuxCommand):
"""
Execute a snippet of python code
Usage:
@py <cmd>
In this limited python environment, only two
variables are defined: 'self'/'me' which refers to one's
own object, and 'here' which refers to the current
location.
"""
key = "@py"
aliases = ["!"]
permissions = "cmd:py"
help_category = "Admin"
def func(self):
"hook function"
caller = self.caller
pycode = self.args
if not pycode:
string = "Usage: @py <code>"
caller.msg(string)
return
# create temporary test objects for playing with
script = create.create_script("src.scripts.scripts.DoNothing",
'testscript')
obj = create.create_object("src.objects.objects.Object",
'testobject')
available_vars = {'self':caller,
'me':caller,
'here':caller.location,
'obj':obj,
'script':script}
caller.msg(">>> %s" % pycode)
try:
ret = eval(pycode, {}, available_vars)
ret = "<<< %s" % str(ret)
except Exception:
try:
exec(pycode, {}, available_vars)
ret = "<<< Done."
except Exception:
errlist = traceback.format_exc().split('\n')
if len(errlist) > 4:
errlist = errlist[4:]
ret = "\n".join("<<< %s" % line for line in errlist if line)
caller.msg(ret)
obj.delete()
script.delete()
class CmdListScripts(MuxCommand):
"""
List all scripts.
Usage:
@scripts[/switches] [<obj or scriptid>]
Switches:
stop - stops an existing script
validate - run a validation on the script(s)
If no switches are given, this command just views all active
scripts. The argument can be either an object, at which point it
will be searched for all scripts defined on it, or an script name
or dbref. For using the /stop switch, a unique script dbref is
required since whole classes of scripts often have the same name.
"""
key = "@scripts"
aliases = "@listscripts"
permissions = "cmd:listscripts"
help_category = "Admin"
def func(self):
"implement method"
caller = self.caller
args = self.args
string = ""
if args:
# test first if this is an script match
scripts = ScriptDB.objects.get_all_scripts(key=args)
if not scripts:
# try to find an object instead.
objects = ObjectDB.objects.pobject_search(caller,
args,
global_search=True)
if objects:
scripts = []
for obj in objects:
# get all scripts on the object(s)
scripts.extend(ScriptDB.objects.get_all_scripts_on_obj(obj))
else:
# we want all scripts.
scripts = ScriptDB.objects.get_all_scripts()
if not scripts:
return
#caller.msg(scripts)
if self.switches and self.switches[0] in ('stop', 'del', 'delete'):
# we want to delete something
if not scripts:
string = "No scripts/objects matching '%s'. " % args
string += "Be more specific."
elif len(scripts) == 1:
# we have a unique match!
string = "Stopping script '%s'." % scripts[0].key
scripts[0].stop()
ScriptDB.objects.validate() #just to be sure all is synced
else:
# multiple matches.
string = "Multiple script matches. Please refine your search:\n"
string += ", ".join([str(script.key) for script in scripts])
elif self.switches and self.switches[0] in ("validate", "valid", "val"):
# run validation on all found scripts
nr_started, nr_stopped = ScriptDB.objects.validate(scripts=scripts)
string = "Validated %s scripts. " % ScriptDB.objects.all().count()
string += "Started %s and stopped %s scripts." % (nr_started,
nr_stopped)
else:
# No stopping or validation. We just want to view things.
string = format_script_list(scripts)
caller.msg(string)
class CmdListCmdSets(MuxCommand):
"""
list command sets on an object
Usage:
@listcmdsets [obj]
This displays all cmdsets assigned
to a user. Defaults to yourself.
"""
key = "@listcmdsets"
permissions = "cmd:listcmdsets"
def func(self):
"list the cmdsets"
caller = self.caller
if self.arglist:
obj = caller.search(self.arglist[0])
if not obj:
return
else:
obj = caller
string = "%s" % obj.cmdset
caller.msg(string)
class CmdListObjects(MuxCommand):
"""
List all objects in database
Usage:
@listobjects [nr]
Gives a list of nr latest objects in database ang give
statistics. If not given, nr defaults to 10.
"""
key = "@listobjects"
aliases = ["@listobj", "@listobjs"]
permissions = "cmd:listobjects"
help_category = "Building"
def func(self):
"Implement the command"
caller = self.caller
if self.args and self.args.isdigit():
nlim = int(self.args)
else:
nlim = 10
dbtotals = ObjectDB.objects.object_totals()
#print dbtotals
string = "\nObjects in database:\n"
string += "Count\tTypeclass"
for path, count in dbtotals.items():
string += "\n %s\t%s" % (count, path)
string += "\nLast %s Objects created:" % nlim
objs = list(ObjectDB.objects.all())
for i, obj in enumerate(objs):
if i <= nlim:
string += "\n %s\t%s(#%i) (%s)" % \
(obj.date_created, obj.name, obj.id, str(obj.typeclass))
else:
break
caller.msg(string)
class CmdBoot(MuxCommand):
"""
@boot
Usage
@boot[/switches] <player obj> [: reason]
Switches:
quiet - Silently boot without informing player
port - boot by port number instead of name or dbref
Boot a player object from the server. If a reason is
supplied it will be echoed to the user unless /quiet is set.
"""
key = "@boot"
permissions = "cmd:boot"
help_category = "Admin"
def func(self):
"Implementing the function"
caller = self.caller
args = self.args
if not args:
caller.msg("Usage: @boot[/switches] <player> [:reason]")
return
if ':' in args:
args, reason = [a.strip() for a in args.split(':', 1)]
boot_list = []
reason = ""
if 'port' in self.switches:
# Boot a particular port.
sessions = sessionhandler.get_session_list(True)
for sess in sessions:
# Find the session with the matching port number.
if sess.getClientAddress()[1] == int(args):
boot_list.append(sess)
break
else:
# Boot by player object
pobj = caller.search("*%s" % args, global_search=True)
if not pobj:
return
pobj = pobj[0]
if pobj.has_player:
if not has_perm(caller, pobj, 'can_boot'):
string = "You don't have the permission to boot %s."
pobj.msg(string)
return
# we have a bootable object with a connected user
matches = sessionhandler.sessions_from_object(pobj)
for match in matches:
boot_list.append(match)
else:
caller.msg("That object has no connected player.")
return
if not boot_list:
caller.msg("No matches found.")
return
# Carry out the booting of the sessions in the boot list.
feedback = None
if not 'quiet' in self.switches:
feedback = "You have been disconnected by %s.\n" % caller.name
if reason:
feedback += "\nReason given: %s" % reason
for session in boot_list:
name = session.name
if feedback:
session.msg(feedback)
session.disconnectClient()
sessionhandler.remove_session(session)
caller.msg("You booted %s." % name)
class CmdDelPlayer(MuxCommand):
"""
delplayer - delete player from server
Usage:
@delplayer[/switch] <name> [: reason]
Switch:
delobj - also delete the player's currently
assigned in-game object.
Completely deletes a user from the server database,
making their nick and e-mail again available.
"""
key = "@delplayer"
permissions = "cmd:delplayer"
help_category = "Admin"
def func(self):
"Implements the command."
caller = self.caller
args = self.args
if not args:
caller.msg("Usage: @delplayer[/delobj] <player/user name or #id>")
return
reason = ""
if ':' in args:
args, reason = [arg.strip() for arg in args.split(':', 1)]
# Search for the object connected to this user (this is done by
# adding a * to the beginning of the search criterion)
pobj = caller.search("*%s" % args, global_search=True)
if not pobj:
# if we cannot find an object connected to this user,
# try a more direct approach
try:
user = User.objects.get(id=args)
except Exception:
try:
user = User.objects.get(name__iexact=args)
except Exception:
caller.msg("Could not find user/id '%s'." % args)
return
uprofile = user.get_profile
else:
user = pobj.user
uprofile = pobj.user_profile
if not has_perm(caller, uprofile, 'manage_players'):
string = "You don't have the permissions to delete that player."
caller.msg(string)
return
uname = user.username
# boot the player then delete
if pobj and pobj.has_user:
caller.msg("Booting and informing player ...")
msg = "\nYour account '%s' is being *permanently* deleted.\n" % uname
if reason:
msg += " Reason given:\n '%s'" % reason
pobj.msg(msg)
caller.execute_cmd("@boot %s" % uname)
uprofile.delete()
user.delete()
caller.msg("Player %s was successfully deleted." % uname)
class CmdNewPassword(MuxCommand):
"""
@newpassword
Usage:
@newpassword <user obj> = <new password>
Set a player's password.
"""
key = "@newpassword"
permissions = "cmd:newpassword"
help_category = "Admin"
def func(self):
"Implement the function."
caller = self.caller
if not self.rhs:
caller.msg("Usage: @newpassword <user obj> = <new password>")
return
pobj = caller.search("*%s" % self.lhs, global_search=True)
if not pobj:
# if we cannot find an object connected to this user,
# try a more direct approach
try:
user = User.objects.get(id=self.lhs)
except Exception:
try:
user = User.objects.get(name__iexact=self.lhs)
except Exception:
caller.msg("Could not find user/id '%s'." % self.lhs)
return
else:
user = pobj.user
user.set_password(self.rhs)
user.save()
caller.msg("%s - new password set to '%s'." % (user.username, self.rhs))
if pobj:
pobj.msg("%s has changed your password to '%s'." % (caller.name, self.rhs))
class CmdHome(MuxCommand):
"""
home
Usage:
home
Teleport the player to their home.
"""
key = "home"
permissions = "cmd:home"
def func(self):
"Implement the command"
caller = self.caller
home = caller.home
if not home:
caller.msg("You have no home set.")
else:
caller.move_to(home)
caller.msg("There's no place like home ...")
class CmdService(MuxCommand):
"""
@service - manage services
Usage:
@service[/switch] <service>
Switches:
start - activates a service
stop - stops a service
list - shows all available services
Service management system. Allows for the listing,
starting, and stopping of services.
"""
key = "@service"
permissions = "cmd:service"
help_category = "Admin"
def func(self):
"Implement command"
caller = self.caller
switches = self.switches
if not switches or \
switches[0] not in ["list","start","stop"]:
caller.msg("Usage: @servive/<start|stop|list> [service]")
return
switch = switches[0]
# get all services
sessions = caller.sessions
if not sessions:
return
service_collection = sessions[0].server.service_collection
if switch == "list":
# Just display the list of installed services and their
# status, then exit.
string = "-" * 40
string += "\nService Listing"
for service in service_collection.services:
if service.running:
status = 'Running'
else:
status = 'Inactive'
string += '\n * %s (%s)' % (service.name, status)
string += "\n" + "-" * 40
caller.msg(string)
return
# Get the service to start / stop
try:
service = service_collection.getServiceNamed(self.args)
except Exception:
string = 'Invalid service name. This command is case-sensitive. '
string += 'See @service/list.'
caller.msg(string)
return
if switch == "stop":
# Stopping a service gracefully closes it and disconnects
# any connections (if applicable).
if not service.running:
caller.msg('That service is not currently running.')
return
# We don't want to kill the main Evennia TCPServer services
# here. If wanting to kill a listening port, one needs to
# do it through settings.py and a restart.
if service.name[:7] == 'Evennia':
string = "You can not stop Evennia TCPServer services this way."
string += "\nTo e.g. remove a listening port, change settings file and restart."
caller.msg(string)
return
#comsys.cemit_mudinfo("%s is *Stopping* the service '%s'." % (sname, service.name)) #TODO!
service.stopService()
return
if switch == "start":
#Starts a service.
if service.running:
caller.msg('That service is already running.')
return
#comsys.cemit_mudinfo("%s is *Starting* the service '%s'." % (sname,service.name)) #TODO!
service.startService()
class CmdShutdown(MuxCommand):
"""
@shutdown
Usage:
@shutdown [announcement]
Shut the game server down gracefully.
"""
key = "@shutdown"
permissions = "cmd:shutdown"
help_category = "System"
def func(self):
"Define function"
try:
session = self.caller.sessions[0]
except Exception:
return
self.caller.msg('Shutting down server ...')
announcement = "\nServer is being SHUT DOWN!\n"
if self.args:
announcement += "%s\n" % self.args
sessionhandler.announce_all(announcement)
logger.log_infomsg('Server shutdown by %s.' % self.caller.name)
# access server through session so we don't call server directly
# (importing it directly would restart it...)
session.server.shutdown()
class CmdPerm(MuxCommand):
"""
@perm - set permissions
Usage:
@perm[/switch] [<user>] = [<permission>]
Switches:
del : delete the given permission from <user>.
list : list all permissions, or those set on <user>
This command sets/clears individual permission strings on a user.
Use /list without any arguments to see all available permissions
or those defined on the <user> argument.
"""
key = "@perm"
permissions = "cmd:perm"
help_category = "Admin"
def func(self):
"Implement function"
caller = self.caller
switches = self.switches
lhs, rhs = self.lhs, self.rhs
if not self.args and "list" not in switches:
caller.msg("Usage: @setperm[/switch] [user = permission]")
return
if "list" in switches:
#just print all available permissions
string = "\nAll currently available permissions (i.e. not locks):"
pgroups = PermissionGroup.objects.all()
for pgroup in pgroups:
string += "\n\n - %s (%s):" % (pgroup.key, pgroup.desc)
string += "\n%s" % \
utils.fill(", ".join(pgroup.group_permissions.split(',')))
caller.msg(string)
return
pobj = caller.search("*%s" % self.lhs, global_search=True)
if not pobj:
# if we cannot find an object connected to this user,
# try a more direct approach
try:
user = User.objects.get(id=self.lhs)
except Exception:
try:
user = User.objects.get(name__iexact=self.lhs)
except Exception:
caller.msg("Could not find user/id '%s'." % self.lhs)
return
else:
pobj = pobj
user = pobj.user
uprofile = user.get_profile()
if not rhs:
#if we didn't have any =, we list the permissions set on <object>.
if user.is_superuser:
string = "\n This is a SUPERUSER account! "
string += "All permissions are automatically set."
else:
string = "Permissions set on this object:\n"
string += uprofile.permissions
caller.msg(string)
return
# we supplied an argument on the form obj = perm
if 'del' in switches:
# delete the given permission from object.
uprofile.del_perm(rhs)
caller.msg("Permission '%s' removed (if it existed)." % rhs)
if pobj:
pobj.msg("%s revokes the permission '%s' from you." % (caller.name, rhs))
else:
# As an extra check, we warn the user if they customize the
# permission string (which is okay, and is used by the lock system)
uprofile.set_perm(rhs)
string = "Permission '%s' given to %s." % (rhs, uprofile.user.username)
if not any(group.contains(rhs)
for group in PermissionGroup.objects.all()):
string += "Note: The given permission is not found in any permission groups."
string += "\nThis is not an error, it just shows that it will work only as a lock."
caller.msg(string)
if pobj:
pobj.msg("%s granted you the permission '%s'." % (caller.name, rhs))

View file

@ -0,0 +1,173 @@
"""
System commands
These are the default commands called by the system commandhandler
when various exceptions occur. If one of these commands are not
implemented and part of the current cmdset, the engine falls back
to a default solution instead.
Some system commands are shown in this module
as a REFERENCE only (they are not all added to Evennia's
default cmdset since they don't currently do anything differently from the
default backup systems hard-wired in the engine).
Overloading these commands in a cmdset can be used to create
interesting effects. An example is using the NoMatch system command
to implement a line-editor where you don't have to start each
line with a command (if there is no match to a known command,
the line is just added to the editor buffer).
"""
from game.gamesrc.commands.default.muxcommand import MuxCommand
from src.comms.models import Channel
from src.utils import create
from src.permissions.permissions import has_perm
# The command keys the engine is calling
# (the actual names all start with __)
from src.commands.cmdhandler import CMD_NOINPUT
from src.commands.cmdhandler import CMD_NOMATCH
from src.commands.cmdhandler import CMD_MULTIMATCH
from src.commands.cmdhandler import CMD_NOPERM
from src.commands.cmdhandler import CMD_CHANNEL
from src.commands.cmdhandler import CMD_EXIT
# Command called when there is no input at line
# (i.e. an lone return key)
class SystemNoInput(MuxCommand):
"""
This is called when there is no input given
"""
key = CMD_NOINPUT
def func(self):
"Do nothing."
pass
#
# Command called when there was no match to the
# command name
#
class SystemNoMatch(MuxCommand):
"""
No command was found matching the given input.
"""
key = CMD_NOMATCH
def func(self):
"""
This is given the failed raw string as input.
"""
self.caller.msg("Huh?")
#
# Command called when there were mulitple matches to the command.
#
class SystemMultimatch(MuxCommand):
"""
Multiple command matches
"""
key = CMD_MULTIMATCH
def func(self):
"""
argument to cmd is a comma-separated string of
all the clashing matches.
"""
self.caller.msg("Multiple matches found:\n %s" % self.args)
class SystemNoPerm(MuxCommand):
"""
This is called when the user does not have the
correct permissions to use a particular command.
"""
key = CMD_NOPERM
def func(self):
"""
This receives the original raw
input string (the one whose command failed to validate)
as argument.
"""
self.caller.msg("You are not allowed to do that.")
# Command called when the comman given at the command line
# was identified as a channel name, like there existing a
# channel named 'ooc' and the user wrote
# > ooc Hello!
class SystemSendToChannel(MuxCommand):
"""
This is a special command that the cmdhandler calls
when it detects that the command given matches
an existing Channel object key (or alias).
"""
key = CMD_CHANNEL
permissions = "cmd:use_channels"
def parse(self):
channelname, msg = self.args.split(':', 1)
self.args = channelname.strip(), msg.strip()
def func(self):
"""
Create a new message and send it to channel, using
the already formatted input.
"""
caller = self.caller
channelkey, msg = self.args
if not msg:
caller.msg("Say what?")
return
channel = Channel.objects.get_channel(channelkey)
if not channel:
caller.msg("Channel '%s' not found." % channelkey)
return
if not channel.has_connection(caller):
string = "You are not connected to channel '%s'."
caller.msg(string % channelkey)
return
if not has_perm(caller, channel, 'chan_send'):
string = "You are not permitted to send to channel '%s'."
caller.msg(string % channelkey)
return
msg = "[%s] %s: %s" % (channel.key, caller.name, msg)
msgobj = create.create_message(caller, msg, channels=[channel])
channel.msg(msgobj)
#
# Command called when the system recognizes the command given
# as matching an exit from the room. E.g. if there is an exit called 'door'
# and the user gives the command
# > door
# the exit 'door' should be traversed to its destination.
class SystemUseExit(MuxCommand):
"""
Handles what happens when user gives a valid exit
as a command. It receives the raw string as input.
"""
key = CMD_EXIT
def func(self):
"""
Handle traversing an exit
"""
caller = self.caller
if not self.args:
return
exit_name = self.args
exi = caller.search(exit_name)
if not exi:
return
destination = exi.attr('_destination')
if not destination:
return
if has_perm(caller, exit, 'traverse'):
caller.move_to(destination)
else:
caller.msg("You cannot enter")

View file

@ -0,0 +1,190 @@
"""
This defines some test commands for use while testing the MUD.
Just remove these commands from the default state when they
are not needed anymore.
"""
from django.db import IntegrityError
from src.comms.models import Msg
from game.gamesrc.commands.default.muxcommand import MuxCommand
from src.permissions import permissions
from src.utils import create
# Test permissions
class CmdTest(MuxCommand):
"""
test the command system
Usage:
@test <any argument or switch>
This command will echo back all argument or switches
given to it, showcasing the muxcommand style.
"""
key = "@test"
aliases = ["@te", "@test all"]
permissions = "cmd:Immortals Wizards"
# the muxcommand class itself handles the display
# so we just defer to it by not adding any function.
pass
class CmdTestPerms(MuxCommand):
"""
Test command - test permissions
Usage:
@testperm [[lockstring] [=permstring]]
With no arguments, runs a sequence of tests for the
permission system using the calling player's permissions.
If <lockstring> is given, match caller's permissions
against these locks. If also <permstring> is given,
match this against the given locks instead.
"""
key = "@testperm"
permissions = "cmd:Immortals Wizards"
def func(self, srcobj, inp):
"""
Run tests
"""
if srcobj.user.is_superuser:
srcobj.msg("You are a superuser. Permission tests are pointless.")
return
# create a test object
obj = create.create_object(None, "accessed_object") # this will use default typeclass
obj_id = obj.id
srcobj.msg("obj_attr: %s" % obj.attr("testattr"))
# perms = ["has_permission", "has permission", "skey:has_permission",
# "has_id(%s)" % obj_id, "has_attr(testattr)",
# "has_attr(testattr, testattr_value)"]
# test setting permissions
uprofile = srcobj.user.get_profile()
# do testing
srcobj.msg("----------------")
permissions.set_perm(obj, "has_permission")
permissions.add_perm(obj, "skey:has_permission")
srcobj.msg(" keys:[%s] locks:[%s]" % (uprofile.permissions, obj.permissions))
srcobj.msg("normal permtest: %s" % permissions.has_perm(uprofile, obj))
srcobj.msg("skey permtest: %s" % permissions.has_perm(uprofile, obj, 'skey'))
permissions.set_perm(uprofile, "has_permission")
srcobj.msg(" keys:[%s] locks:[%s]" % (uprofile.permissions, obj.permissions))
srcobj.msg("normal permtest: %s" % permissions.has_perm(uprofile, obj))
srcobj.msg("skey permtest: %s" % permissions.has_perm(uprofile, obj, 'skey'))
# function tests
permissions.set_perm(obj, "has_id(%s)" % (uprofile.id))
srcobj.msg(" keys:[%s] locks:[%s]" % (uprofile.permissions, obj.permissions))
srcobj.msg("functest: %s" % permissions.has_perm(uprofile, obj))
uprofile.attr("testattr", "testattr_value")
permissions.set_perm(obj, "has_attr(testattr, testattr_value)")
srcobj.msg(" keys:[%s] locks:[%s]" % (uprofile.permissions, obj.permissions))
srcobj.msg("functest: %s" % permissions.has_perm(uprofile, obj))
# cleanup of test permissions
permissions.del_perm(uprofile, "has_permission")
srcobj.msg(" cleanup: keys:[%s] locks:[%s]" % (uprofile.permissions, obj.permissions))
obj.delete()
uprofile.attr("testattr", delete=True)
# Add/remove states
EXAMPLE_STATE="game.gamesrc.commands.examples.example.EXAMPLESTATE"
class CmdTestState(MuxCommand):
"""
Test command - add a state.
Usage:
@teststate[/switch] [<python path to state instance>]
Switches:
add - add a state
clear - remove all added states.
list - view current state stack
reload - reload current state stack
If no python path is given, an example state will be added.
You will know it worked if you can use the commands '@testcommand'
and 'smile'.
"""
key = "@teststate"
alias = "@testingstate"
permissions = "cmd:Immortals Wizards"
def func(self, source_object, inp):
"""
inp is the dict returned from MuxCommand's parser.
"""
switches = inp["switches"]
if not switches or switches[0] not in ["add", "clear", "list", "reload"]:
string = "Usage: @teststate[/add|clear|list|reload] [<python path>]"
source_object.msg(string)
elif "clear" in switches:
source_object.statehandler.clear()
source_object.msg("All states cleared.")
return
elif "list" in switches:
string = "%s" % source_object.statehandler
source_object.msg(string)
elif "reload" in switches:
source_object.statehandler.load()
source_object.msg("States reloaded.")
else: #add
arg = inp["raw"]
if not arg:
arg = EXAMPLE_STATE
source_object.statehandler.add(arg)
string = "Added state '%s'." % source_object.statehandler.state.key
source_object.msg(string)
class TestCom(MuxCommand):
"""
Test the command system
Usage:
@testcom/create/list [channel]
"""
key = "@testcom"
permissions = "cmd:Immortals Wizards"
def func(self):
"Run the test program"
caller = self.caller
if 'create' in self.switches:
if self.args:
chankey = self.args
try:
channel = create.create_channel(chankey)
except IntegrityError:
caller.msg("Channel '%s' already exists." % chankey)
return
channel.connect_to(caller)
caller.msg("Created new channel %s" % chankey)
msgobj = create.create_message(caller.player,
"First post to new channel!")
channel.msg(msgobj)
return
elif 'list' in self.switches:
msgresults = Msg.objects.get_messages_by_sender(caller)
string = "\n".join(["%s %s: %s" % (msg.date_sent,
[str(chan.key) for chan in msg.channels.all()],
msg.message)
for msg in msgresults])
caller.msg(string)
return
caller.msg("Usage: @testcom/create channel")

View file

@ -2,7 +2,8 @@
Implementation of the @search command that resembles MUX2.
"""
from django.db.models import Q
from src.objects.models import Object
#from src.objects.models import Object
from src.utils import OBJECT as Object
from src import defines_global
from src.cmdtable import GLOBAL_CMD_TABLE
@ -18,13 +19,13 @@ def _parse_restriction_split(source_object, restriction_split, search_low_dbnum,
try:
search_low_dbnum = int(restriction_split[1].strip())
except ValueError:
source_object.emit_to("Invalid value for low dbref limit.")
source_object.msg("Invalid value for low dbref limit.")
return False
if restriction_size >= 3:
try:
search_high_dbnum = int(restriction_split[2].strip())
except ValueError:
source_object.emit_to("Invalid value for high dbref limit.")
source_object.msg("Invalid value for high dbref limit.")
return False
return search_low_dbnum, search_high_dbnum
@ -38,40 +39,33 @@ def display_results(source_object, search_query):
room_list = []
exit_list = []
player_list = []
# this bits gotta get totally redone
for obj in search_query:
if obj.is_thing():
thing_list.append(obj)
elif obj.is_room():
room_list.append(obj)
elif obj.is_exit():
exit_list.append(obj)
elif obj.is_player():
player_list.append(obj)
thing_list.append(obj)
# Render each section for different object types
if thing_list:
source_object.emit_to("\n\rTHINGS:")
source_object.msg("\n\rTHINGS:")
for thing in thing_list:
source_object.emit_to(thing.get_name(show_dbref=True, show_flags=True))
source_object.msg(thing.name)
if exit_list:
source_object.emit_to("\n\rEXITS:")
source_object.msg("\n\rEXITS:")
for exit in exit_list:
source_object.emit_to(exit.get_name(show_dbref=True, show_flags=True))
source_object.msg(exit.name)
if room_list:
source_object.emit_to("\n\rROOMS:")
source_object.msg("\n\rROOMS:")
for room in room_list:
source_object.emit_to(room.get_name(show_dbref=True, show_flags=True))
source_object.msg(room.name)
if player_list:
source_object.emit_to("\n\rPLAYER:")
source_object.msg("\n\rPLAYER:")
for player in player_list:
source_object.emit_to(player.get_name(show_dbref=True, show_flags=True))
source_object.msg(player.name)
# Show the total counts by type
source_object.emit_to("\n\rFound: Rooms...%d Exits...%d Things...%d Players...%d" % (
source_object.msg("\n\rFound: Rooms...%d Exits...%d Things...%d Players...%d" % (
len(room_list),
len(exit_list),
len(thing_list),
@ -104,7 +98,7 @@ def build_query(source_object, search_query, search_player, search_type,
elif search_restriction == "player":
search_query = search_query.filter(type=defines_global.OTYPE_PLAYER)
else:
source_object.emit_to("Invalid class. See 'help SEARCH CLASSES'.")
source_object.msg("Invalid class. See 'help SEARCH CLASSES'.")
return None
elif search_type == "parent":
search_query = search_query.filter(script_parent__iexact=search_restriction)
@ -128,11 +122,11 @@ def build_query(source_object, search_query, search_player, search_type,
search_query = search_query.filter(zone=zone_obj)
elif search_type == "power":
# TODO: Need this once we have powers implemented.
source_object.emit_to("To be implemented...")
source_object.msg("To be implemented...")
return None
elif search_type == "flags":
flag_list = search_restriction.split()
#source_object.emit_to("restriction: %s" % flag_list)
#source_object.msg("restriction: %s" % flag_list)
for flag in flag_list:
search_query = search_query.filter(Q(flags__icontains=flag) | Q(nosave_flags__icontains=flag))
@ -171,9 +165,9 @@ def cmd_search(command):
search_type = eq_split[0]
restriction_split = eq_split[1].split(',')
search_restriction = restriction_split[0].strip()
#source_object.emit_to("@search class=restriction")
#source_object.emit_to("eq_split: %s" % eq_split)
#source_object.emit_to("restriction_split: %s" % restriction_split)
#source_object.msg("@search class=restriction")
#source_object.msg("eq_split: %s" % eq_split)
#source_object.msg("restriction_split: %s" % restriction_split)
try:
search_low_dbnum, search_high_dbnum = _parse_restriction_split(source_object,
@ -186,19 +180,19 @@ def cmd_search(command):
else:
# @search player
if len(first_check_split) == 1:
#source_object.emit_to("@search player")
#source_object.emit_to(first_check_split)
#source_object.msg("@search player")
#source_object.msg(first_check_split)
search_player = first_check_split[0]
else:
#source_object.emit_to("@search player class=restriction")
#source_object.emit_to(first_check_split)
#source_object.msg("@search player class=restriction")
#source_object.msg(first_check_split)
search_player = first_check_split[0]
eq_split = first_check_split[1].split('=', 1)
search_type = eq_split[0]
#source_object.emit_to("eq_split: %s" % eq_split)
#source_object.msg("eq_split: %s" % eq_split)
restriction_split = eq_split[1].split(',')
search_restriction = restriction_split[0]
#source_object.emit_to("restriction_split: %s" % restriction_split)
#source_object.msg("restriction_split: %s" % restriction_split)
try:
search_low_dbnum, search_high_dbnum = _parse_restriction_split(source_object,
@ -210,11 +204,11 @@ def cmd_search(command):
search_query = Object.objects.all()
#source_object.emit_to("search_player: %s" % search_player)
#source_object.emit_to("search_type: %s" % search_type)
#source_object.emit_to("search_restriction: %s" % search_restriction)
#source_object.emit_to("search_lowdb: %s" % search_low_dbnum)
#source_object.emit_to("search_highdb: %s" % search_high_dbnum)
#source_object.msg("search_player: %s" % search_player)
#source_object.msg("search_type: %s" % search_type)
#source_object.msg("search_restriction: %s" % search_restriction)
#source_object.msg("search_lowdb: %s" % search_low_dbnum)
#source_object.msg("search_highdb: %s" % search_high_dbnum)
# Clean up these variables for comparisons.
try:

View file

@ -0,0 +1,295 @@
"""
Commands that are available from the connect screen.
"""
import traceback
#from django.contrib.auth.models import User
from django.conf import settings
from django.contrib.auth.models import User
from src.players.models import PlayerDB
from src.objects.models import ObjectDB
from src.config.models import ConfigValue
from src.comms.models import Channel
from src.utils import create, logger, utils
from game.gamesrc.commands.default.muxcommand import MuxCommand
class CmdConnect(MuxCommand):
"""
Connect to the game.
Usage (at login screen):
connect <email> <password>
Use the create command to first create an account before logging in.
"""
key = "connect"
aliases = ["conn", "con", "co"]
def func(self):
"""
Uses the Django admin api. Note that unlogged-in commands
have a unique position in that their func() receives
a session object instead of a source_object like all
other types of logged-in commands (this is because
there is no object yet before the player has logged in)
"""
session = self.caller
arglist = self.arglist
if not arglist or len(arglist) < 2:
session.msg("\n\r Usage (without <>): connect <email> <password>")
return
uemail = arglist[0]
password = arglist[1]
# Match an email address to an account.
email_match = PlayerDB.objects.get_player_from_email(uemail)
# No playername match
if not email_match:
string = "The email '%s' does not match any accounts." % uemail
string += "\n\r\n\rIf you are new you should first create a new account "
string += "using the 'create' command."
session.msg(string)
return
# We have at least one result, so we can check the password.
player = email_match
if not player.user.check_password(password):
session.msg("Incorrect password.")
return
# We are logging in, get/setup the player object controlled by player
character = player.character
if not character:
# Create a new character object to tie the player to. This should
# usually not be needed unless the old character object was manually
# deleted.
default_home_id = ConfigValue.objects.conf(db_key="default_home")
default_home = ObjectDB.objects.get_id(default_home_id)
typeclass = settings.BASE_CHARACTER_TYPECLASS
character = create.create_object(typeclass=typeclass,
key=player.name,
location=default_home,
home=default_home,
player=player)
character.db.FIRST_LOGIN = "True"
# Getting ready to log the player in.
# Check if this is the first time the
# *player* connects
if player.db.FIRST_LOGIN:
player.at_first_login()
del player.db.FIRST_LOGIN
# check if this is the first time the *character*
# character (needs not be the first time the player
# does so, e.g. if the player has several characters)
if character.db.FIRST_LOGIN:
character.at_first_login()
del character.db.FIRST_LOGIN
# actually do the login, calling
# customization hooks before and after.
player.at_pre_login()
character.at_pre_login()
session.login(player)
player.at_post_login()
character.at_post_login()
# run look
#print "character:", character, character.scripts.all(), character.cmdset.current
character.execute_cmd('look')
class CmdCreate(MuxCommand):
"""
Create a new account.
Usage (at login screen):
create \"playername\" <email> <password>
This creates a new player account.
"""
key = "create"
aliases = ["cre", "cr"]
def parse(self):
"""
The parser must handle the multiple-word player
name enclosed in quotes:
connect "Long name with many words" my@myserv.com mypassw
"""
super(CmdCreate, self).parse()
self.playerinfo = []
if len(self.arglist) < 3:
return
if len(self.arglist) > 3:
# this means we have a multi_word playername. pop from the back.
password = self.arglist.pop()
email = self.arglist.pop()
# what remains is the playername.
playername = " ".join(self.arglist)
else:
playername, email, password = self.arglist
playername = playername.replace('"', '') # remove "
playername = playername.replace("'", "")
self.playerinfo = (playername, email, password)
def func(self):
"Do checks and create account"
session = self.caller
try:
playername, email, password = self.playerinfo
except ValueError:
string = "\n\r Usage (without <>): create \"<playername>\" <email> <password>"
session.msg(string)
return
if not playername:
# entered an empty string
session.msg("\n\r You have to supply a longer playername, surrounded by quotes.")
return
if not email or not password:
session.msg("\n\r You have to supply an e-mail address followed by a password." )
return
if not utils.validate_email_address(email):
# check so the email at least looks ok.
session.msg("'%s' is not a valid e-mail address." % email)
return
# Run sanity and security checks
if PlayerDB.objects.get_player_from_name(playername) or User.objects.filter(username=playername):
# player already exists
session.msg("Sorry, there is already a player with the name '%s'." % playername)
elif PlayerDB.objects.get_player_from_email(email):
# email already set on a player
session.msg("Sorry, there is already a player with that email address.")
elif len(password) < 3:
# too short password
string = "Your password must be at least 3 characters or longer."
string += "\n\rFor best security, make it at least 8 characters long, "
string += "avoid making it a real word and mix numbers into it."
session.msg(string)
else:
# everything's ok. Create the new player account
try:
default_home_id = ConfigValue.objects.conf(db_key="default_home")
default_home = ObjectDB.objects.get_id(default_home_id)
typeclass = settings.BASE_CHARACTER_TYPECLASS
permissions = settings.PERMISSION_PLAYER_DEFAULT
new_character = create.create_player(playername, email, password,
permissions=permissions,
location=default_home,
typeclass=typeclass,
home=default_home)
new_character.db.FIRST_LOGIN = True
new_player = new_character.player
new_player.db.FIRST_LOGIN = True
# join the new player to the public channel
pchanneldef = settings.CHANNEL_PUBLIC
if pchanneldef:
pchannel = Channel.objects.get_channel(pchanneldef[0])
if not pchannel.connect_to(new_player):
string = "New player '%s' could not connect to public channel!" % new_player.key
logger.log_errmsg(string)
string = "A new account '%s' was created with the email address %s. Welcome!"
string += "\n\nYou can now log with the command 'connect %s <your password>'."
session.msg(string % (playername, email, email))
except Exception:
# we have to handle traceback ourselves at this point, if
# we don't, errors will give no feedback.
string = "%s\nThis is a bug. Please e-mail an admin if the problem persists."
session.msg(string % (traceback.format_exc()))
logger.log_errmsg(traceback.format_exc())
class CmdQuit(MuxCommand):
"""
We maintain a different version of the quit command
here for unconnected players for the sake of simplicity. The logged in
version is a bit more complicated.
"""
key = "quit"
aliases = ["q", "qu"]
def func(self):
"Simply close the connection."
session = self.caller
session.msg("Good bye! Disconnecting ...")
session.handle_close()
class CmdUnconnectedLook(MuxCommand):
"""
This is an unconnected version of the look command for simplicity.
All it does is re-show the connect screen.
"""
key = "look"
aliases = "l"
def func(self):
"Show the connect screen."
try:
self.caller.game_connect_screen()
except Exception:
self.caller.msg("Connect screen not found. Enter 'help' for aid.")
class CmdUnconnectedHelp(MuxCommand):
"""
This is an unconnected version of the help command,
for simplicity. It shows a pane or info.
"""
key = "help"
aliases = ["h", "?"]
def func(self):
"Shows help"
string = \
"""Welcome to Evennia!
Commands available at this point:
create - create a new account
connect - login with an existing account
look - re-show the connect screen
help - this help
quit - leave
To login to the system, you need to do one of the following:
1) If you have no previous account, you need to use the 'create'
command followed by your desired character name (in quotes), your
e-mail address and finally a password of your choice. Like
this:
> create "Anna the Barbarian" anna@myemail.com tuK3221mP
It's always a good idea (not only here, but everywhere on the net)
to not use a regular word for your password. Make it longer than
3 characters (ideally 6 or more) and mix numbers and capitalization
into it. Now proceed to 2).
2) If you have an account already, either because you just created
one in 1) above, or you are returning, use the 'connect' command
followed by the e-mail and password you previously set.
Example:
> connect anna@myemail.com tuK3221mP
This should log you in. Run 'help' again once you're logged in
to get more aid. Welcome to Evennia!
You can use the 'look' command if you want to see the connect screen again.
"""
self.caller.msg(string)

View file

@ -0,0 +1,292 @@
"""
This defines the cmdset for the red_button. Here we have defined
the commands and the cmdset in the same module, but if you
have many different commands to merge it if often better
to define the cmdset separately, picking and choosing from
among the available commands as to what should be included in the
cmdset - this way you can often re-use the commands too.
"""
import random
from src.commands.cmdset import CmdSet
from game.gamesrc.commands.basecommand import Command
# Some simple commands for the red button
#------------------------------------------------------------
# Commands defined for the red button
#------------------------------------------------------------
class CmdNudge(Command):
"""
Try to nudge the button's lid
Usage:
nudge lid
This command will have you try to
push the lid of the button away.
"""
key = "nudge lid" # two-word command name!
alias = ["nudge"]
def func(self):
"""
nudge the lid.
"""
rand = random.random()
if rand < 0.5:
string = "You nudge at the lid. It seems stuck."
elif 0.5 <= 0.5 < 0.7:
string = "You move the lid back and forth. It won't budge."
else:
string = "You manage to get a nail under the lid. It pops open."
self.obj.open_lid()
self.caller.msg(string)
class CmdPush(Command):
"""
Push the red button
Usage:
push button
"""
key = "push button"
aliases = ["push", "press button", "press"]
def func(self):
"""
Note that we choose to implement this with checking for
if the lid is open/closed. This is because this command
is likely to be tries regardless of the state of the lid.
An alternative would be to make two versions of this command
and tuck them into the cmdset linked to the Open and Closed
lid-state respectively.
"""
if self.obj.db.lid_open:
string = "You reach out to press the big red button ..."
string += "\n\nA BOOM! A bright light blinds you!"
string += "\nThe world goes dark ..."
self.caller.msg(string)
self.obj.press_button(self.caller)
self.caller.location.msg_contents("%s presses the button. BOOM! %s is blinded by a flash!" %
(self.caller.name, self.caller.name), exclude=self.caller)
else:
string = "You cannot push the button - there is a glass lid covering it."
self.caller.msg(string)
class CmdSmashGlass(Command):
"""
smash glass
Usage:
smash glass
Try to smash the glass of the button.
"""
key = "smash glass"
aliases = ["smash lid", "break lid", "smash"]
def func(self):
"""
The lid won't open, but there is a small chance
of causing the lamp to break.
"""
rand = random.random()
if rand < 0.2:
string = "You smash your hand against the glass"
string += " with all your might. The lid won't budge"
string += " but you cause quite the tremor through the button's mount."
self.caller.msg(string) # have to be called before breakage since that
# also gives a return feedback to the room.
self.obj.break_lamp()
return
elif rand < 0.6:
string = "You hit the lid hard. It doesn't move an inch."
else:
string = "You place a well-aimed fist against the glass of the lid."
string += "Unfortunately all you get is a pain in your hand. Maybe"
string += " you should just try to open the lid instead?"
self.caller.msg(string)
self.caller.location.msg_contents("%s tries to smash the glass of the button." %
(self.caller.name), exclude=self.caller)
class CmdOpenLid(Command):
"""
open lid
Usage:
open lid
"""
key = "open lid"
aliases = ["open button", 'open']
def func(self):
"simply call the right function."
if self.obj.db.lid_locked:
self.caller.msg("This lid seems locked in place for the moment.")
return
self.caller.location.msg_contents("%s opens the lid of the button." %
(self.caller.name), exclude=self.caller)
self.obj.open_lid()
class CmdCloseLid(Command):
"""
close the lid
Usage:
close lid
Closes the lid of the red button.
"""
key = "close lid"
aliases = ["close"]
def func(self):
"Close the lid"
self.obj.close_lid()
self.caller.location.msg_contents("%s closes the button's lid." %
(self.caller.name), exclude=self.caller)
class CmdBlindLook(Command):
"""
Looking around in darkness
Usage:
look <obj>
... not that there's much to see in the dark.
"""
key = "look"
aliases = ["l", "get", "examine", "ex", "feel", "listen"]
def func(self):
"This replaces all the senses when blinded."
# we decide what to reply based on which command was
# actually tried
if self.cmdstring == "get":
string = "You fumble around blindly without finding anything."
elif self.cmdstring == "examine":
string = "You try to examine your surroundings, but can't see a thing."
elif self.cmdstring == "listen":
string = "You are deafened by the boom."
elif self.cmdstring == "feel":
string = "You fumble around, hands outstretched. You bump your knee."
else:
# trying to look
string = "You are temporarily blinded by the flash. "
string += "Until it wears off, all you can do is feel around blindly."
self.caller.msg(string)
self.caller.location.msg_contents("%s stumbles around, blinded." %
(self.caller.name), exclude=self.caller)
class CmdBlindHelp(Command):
"""
Help function while in the blinded state
Usage:
help
"""
key = "help"
aliases = "h"
def func(self):
"Give a message."
self.caller.msg("You are beyond help ... until you can see again.")
#---------------------------------------------------------------
# Command sets for the red button
#---------------------------------------------------------------
# We next tuck these commands into their respective command sets.
# (note that we are overdoing the cdmset separation a bit here
# to show how it works).
class DefaultCmdSet(CmdSet):
"""
The default cmdset always sits
on the button object and whereas other
command sets may be added/merge onto it
and hide it, removing them will always
bring it back. It's added to the object
using obj.cmdset.add_default().
"""
key = "RedButtonDefault"
mergetype = "Union" # this is default, we don't really need to put it here.
def at_cmdset_creation(self):
"Init the cmdset"
self.add(CmdPush())
class LidClosedCmdSet(CmdSet):
"""
A simple cmdset tied to the redbutton object.
It contains the commands that launches the other
command sets, making the red button a self-contained
item (i.e. you don't have to manually add any
scripts etc to it when creating it).
"""
key = "LidClosedCmdSet"
# default Union is used *except* if we are adding to a
# cmdset named RedButtonOpen - this one we replace
# completely.
key_mergetype = {"LidOpenCmdSet": "Replace"}
def at_cmdset_creation(self):
"Populates the cmdset when it is instantiated."
self.add(CmdNudge())
self.add(CmdSmashGlass())
self.add(CmdOpenLid())
class LidOpenCmdSet(CmdSet):
"""
This is the opposite of the Closed cmdset.
"""
key = "LidOpenCmdSet"
# default Union is used *except* if we are adding to a
# cmdset named RedButtonClose - this one we replace
# completely.
key_mergetype = {"LidClosedCmdSet": "Replace"}
def at_cmdset_creation(self):
"setup the cmdset (just one command)"
self.add(CmdCloseLid())
class BlindCmdSet(CmdSet):
"""
This is the cmdset added to the *player* when
the button is pushed.
"""
key = "BlindCmdSet"
# we want it to completely replace all normal commands
# until the timed script removes it again.
mergetype = "Replace"
# we want to stop the player from walking around
# in this blinded state, so we hide all exits too.
# (channel commands will still work).
no_exits = True # keep player in the same room
no_objs = True # don't allow object commands
def at_cmdset_creation(self):
"Setup the blind cmdset"
self.add(CmdBlindLook())
self.add(CmdBlindHelp())

View file

@ -1,129 +0,0 @@
"""
This is an example command module for showing the pluggable command
system in action.
You'll need to make sure that this or any new modules you create are
added to game/settings.py under CUSTOM_COMMAND_MODULES or
CUSTOM_UNLOGGED_COMMAND_MODULES, which are tuples of module import
path strings. See src/config_defaults.py for more details.
E.g. to add this example command for testing, your entry in
game/settings.py would look like this:
CUSTOM_COMMAND_MODULES = ('game.gamesrc.commands.examples.example',)
(note the extra comma at the end to make this into a Python
tuple. It's only needed if you have only one entry.) You need to
restart the Evennia server before new files are recognized. Once this
is done once, you don't have to restart again, just use
@reload/commands to use the changes you make to your modules.
"""
# This is the common global CommandTable object which we'll be adding the
# example command(s) to.
from src.cmdtable import GLOBAL_CMD_TABLE
# The main command definition. We can add any number of commands this way in the
# same file.
def cmd_example(command):
"""
example - example command
Usage:
@testcommand[/switches] <text>
switches:
(can be any string, e.g. /test1 or /tom/sarah/peter)
This is the help text for the 'example' command, a command to
show how the pluggable command system works.
For testing, you can try calling this with different switches and
arguments, like
> example/test/test2 Hello
and see what is returned.
[[example_auto_help]]
This is a subtopic to the main example command help entry. It is
done by the help system splitting the text by markup of the
form [ [title ] ] (with no spaces between the square brackets)
Note that this help entry is auto-added as long as HELP_AUTO
is not set to False in your game/settings.py file.
Any number of subtopics like this one can be added on the fly
using the auto-help system. See help topics on 'help' and
'help_markup' for more information and options.
"""
# By building one big string and passing it at once, we cut down on a lot
# of emit_to() calls, which is generally a good idea.
retval = "----- Example Command -----\n\r"
# source_object is the object executing the command
retval += " Source object: %s\n\r" % command.source_object
# session points to a user Session (session.py) object (if applicable)
retval += " Session: %s\n\r" % command.session
# The raw, un-parsed input
retval += " Raw input: %s\n\r" % command.raw_input
# The command name being executed
retval += " Command: %s\n\r" % command.command_string
# A list of switches provided (if any)
retval += " Switches: %s\n\r" % command.command_switches
# A string with any arguments provided with the command
retval += " Arguments: %s\n\r" % command.command_argument
# The function that was looked up via cmdtable.py
retval += " Function: %s\n\r" % command.command_function
# Extra variables passed with cmdtable.py's add_command().
retval += " Extra vars: %s\n\r" % command.extra_vars
# Some more info for more advanced commands.
if not command.command_switches and \
command.command_argument:
retval += "\n Obs: When no switches, also multi-word\n"
retval += " command names are possible. Max allowed\n"
retval += " length is set in game/settings.py.\n"
retval += " So if there exist a matching command in the\n"
retval += " command table, Evennia would also allow\n"
retval += " the following as valid commands (and the\n"
retval += " argument list would shrink accordingly):\n"
multi = ""
for arg in command.command_argument.split():
multi += " %s" % arg
retval += " %s%s\n" % (command.command_string, multi)
# send string to player
command.source_object.emit_to(retval)
# Add the command to the common global command table. Note that
# this will auto-create help entries 'example' and
# "example_auto_help" for us.
GLOBAL_CMD_TABLE.add_command("@testcommand", cmd_example)
#
# another simple example
#
def cmd_emote_smile(command):
"""
smile - break a smile
Usage:
smile
A 'smile' emote.
"""
#get the source object (that is, the player using the command)
source_object = command.source_object
#find name of caller
name = source_object.get_name(show_dbref=False)
#get the location caller is at
location = source_object.get_location()
#build the emote
text = "%s smiles." % name
#emit the emote to everyone at the current location
location.emit_to_contents(text)
# add to global command table (we probably want an auto-help entry
# for this, but we are turning auto-help off anyway, to show
# how it works)
GLOBAL_CMD_TABLE.add_command('smile', cmd_emote_smile,
auto_help_override=False)

View file

@ -1,136 +0,0 @@
"""
This module contains various commands for testing some
of Evennia's subsystems. They were used for initial testing
but are also instructive for playing around with to learn
how different systems work. See also state_example.py.
To make these commands available in-game, add this module
to the CUSTOM_COMMAND_MODULES tuple in game/settings.py
as 'game.gamesrc.commands.examples.misc_tests'.
None of these commands are auto-added to the help database
(they have no docstrings) in order to help make it clean.
"""
from src.cmdtable import GLOBAL_CMD_TABLE
#------------------------------------------------------------
# Tests of the event system
#------------------------------------------------------------
def cmd_testevent(command):
#
# This test allows testing the event system
#
# Usage:
# @testevent [pid]
#
# Without argument, this command creates
# a dummy event in the process table.
# Use @ps to see it. Give the equivalent
# pid to remove it again (careful though,
# this command can also remove useful
# events if you give the wrong pid).
#
from src import events
from src import scheduler
source_object = command.source_object
if not source_object.is_superuser():
# To avoid accidental access to process table
source_object.emit_to("This command is superuser only.")
return
if not command.command_argument:
# No argument given; create a new test event.
event = events.IntervalEvent()
event.description = "Test event created with @testevent."
event.repeats = 3
event.interval = 5
pid = scheduler.add_event(event)
string = "Event with pid %s added. " % pid
string += "It repeats %i times and waits " % event.repeats
string += "for %i seconds between each repeat." % event.interval
string += "After all repeats, it will delete itself."
string += "\nUse @ps to see it and give this "
string += "command with the pid as argument to delete it."
source_object.emit_to(string)
else:
# An argument given; assume this is a pid.
try:
pid = int(command.command_argument)
except:
source_object.emit_to("Not a valid argument. You must give a number.")
return
if pid < 3:
string = "This low pid might belong to a system process, \n"
string += "so as a safety measure you cannot delete it using \n"
string += "this test command. Use @delevent instead."
source_object.emit_to(string)
return
pid = command.command_argument
scheduler.del_event(pid)
string = "Event with pid %s removed (if it existed)." % pid
string += " Confirm this worked using @ps."
source_object.emit_to(string)
GLOBAL_CMD_TABLE.add_command("@testevent", cmd_testevent,
auto_help_override=False)
#------------------------------------------------------------
# Test of Cache system
#------------------------------------------------------------
def cmd_testcache(command):
#
# Tests the cache system by writing to it
# back and forth several times.
#
# Usage:
# @testcache [get]
#
# Use without 'get' to store test data in
# caches and with 'get' to read them back
# and make sure they all saved as they
# should. You might also want to
# try shut down the server between
# calls to make sure the persistent
# cache does survive the shutdown.
from src.cache import cache
from src import gametime
source_object = command.source_object
switches = command.command_switches
s1 = "Value: Cache: OK"
s2 = "Value: PCache 1 (set using property assignment): OK"
s3 = "Value: PCache 2 (set using function call): OK"
if switches and "get" in switches:
# Reading from cache
source_object.emit_to("Reading from cache ...")
cache.load_pcache()
cache_vol = source_object.cache.testcache
source_object.emit_to("< volatile cache:\n %s" % cache_vol)
cache_perm = source_object.pcache.testcache_perm
source_object.emit_to("< persistent cache 1/2:\n %s" % cache_perm)
cache_perm2 = cache.get_pcache("permtest2")
source_object.emit_to("< persistent cache 2/2:\n %s" % cache_perm2)
else:
# Saving to cache
source_object.emit_to("Save to cache ...")
source_object.cache.testcache = s1
# using two different ways to set pcache
source_object.pcache.testcache_perm = s2
cache.set_pcache("permtest2", s3)
source_object.emit_to("> volatile cache:\n %s" % s1)
source_object.emit_to("> persistent cache 1/2:\n %s" % s2)
source_object.emit_to("> persistent cache 2/2:\n %s" % s3)
cache.save_pcache()
string = "Caches saved. Use /get as a switch to read them back."
source_object.emit_to(string)
source_object.emit_to("Running Gametime: %i" % gametime.time())
GLOBAL_CMD_TABLE.add_command("@testcache", cmd_testcache,
auto_help_override=False)

View file

@ -1,333 +0,0 @@
"""
Example of using the state system. The State system allows a player
object to be 'trapped' in a special environment where different
commands are available than normal. This is very useful in order to
implement anything from menus and combat states to npc-conversational
choices and inline text-editors.
This example uses the State system to create a simple menu.
To test out this example, add this module to the
CUSTOM_COMMAND_MODULES tuple in your game/settings.py as
'game.gamesrc.commands.examples.state_example' (see ./example.py for
another example). You need to restart the Evennia server before new
files are recognized.
Next enter the mud and give the command
> @testmenu
Note that the help entries related to this little menu are not part of
the normal help database, they are stored with the state and only
accessible from inside it. Try 'help entermenu' from outside the state
and 'help' and 'info' from inside the menu to see the auto-help system
in action.
To further test the state system, try the command
> @teststate
This takes arguments between 1-6 to set up various states with varying
access to different global commands.
See also misc_tests.py for other tests.
"""
# This is the normal command table, accessible by default
from src.cmdtable import GLOBAL_CMD_TABLE
# The statetable contains sets of cmdtables that is made available
# only when we are in a particular state (possibly overriding
# same-named commands in GLOBAL_CMD_TABLE).
from src.statetable import GLOBAL_STATE_TABLE
#
# Implementing a simple 'menu' state
#
#the name of our state, to make sure it's the same everywhere
STATENAME = 'menu'
#
# 'entry' command. This takes the player from the normal game
# mode into the menu state. This must be added to the
# GLOBAL_CMD_TABLE like any other command.
#
def cmd_entermenu(command):
"""
entermenu - enter the example menu
Usage:
entermenu
This is the 'entry' command that takes the player from the normal
gameplay mode into the 'menu' state.
"""
# get the player object calling the command
source_object = command.source_object
# this is important: we use the set_state() command
# to shift the player into a state named 'menu'. Other useful
# access functions on source_object are get_state()
# and clear_state(), the latter returns the player to
# the normal mode of gameplay.
source_object.set_state(STATENAME)
#show a welcome text .
string = """
Welcome to the Demo menu! In this small demo all you can do is
select one of the two options so it changes colour. This is just
intended to show off the possibilities of the state system. More
interesting things should of course happen in a real menu.
Use @exit to leave the menu.
"""
source_object.emit_to(string)
# show the menu
source_object.execute_cmd('menu')
#
# Below are commands only available while in the 'menu' state.
# Note that the _doc__ strings of the functions
# can be read as help entries when in the menu.
#
def menu_cmd_option1(command):
"""
option1
This command, obviously, selects the first option.
"""
source_object = command.source_object
print_menu(source_object, 1)
def menu_cmd_option2(command):
"""
option2
This command selects the second option. Duh.
"""
source_object = command.source_object
print_menu(source_object, 2)
def menu_cmd_menu(command):
"""
menu
Clears the options and redraws the menu.
[[autohelp]]
Auto-help
This is an extra topic to test the auto-help functionality. The state-help
system supports nested ('related') topics using [ [subtopic] ] markup,
just like the normal help index does.
"""
source_object = command.source_object
print_menu(source_object)
#
# helper function
#
def print_menu(source_obj, choice=None):
"""
Utility function to print the menu. More interesting things
would happen here in a real menu.
"""
if choice == 1:
#ansi colouring; see src.ansi
chtext = "%s> option1\n %soption2" % ('%ch%cy','%cn%cy')
elif choice == 2:
chtext = " %soption1\n%s> option2" % ('%cn%cy','%ch%cy')
else:
chtext = " %soption1\n option2" % ('%cn%cy')
string ="\n%sMenu: \n%s\n %shelp \n @exit" % ('%ch%cr', chtext, '%cn%cy')
source_obj.emit_to(string)
# Add the 'entry' command to the normal command table
GLOBAL_CMD_TABLE.add_command("@testmenu", cmd_entermenu,
auto_help_override=False)
# create the state. We make sure the player can exit it at
# any time by @exit.
GLOBAL_STATE_TABLE.add_state(STATENAME, exit_command=True)
# Add the menu commands to the state table by tying them to the 'menu'
# state. It is important that the name of the state matches what we
# set the player-object to in the 'entry' command.
GLOBAL_STATE_TABLE.add_command(STATENAME, "option1", menu_cmd_option1)
GLOBAL_STATE_TABLE.add_command(STATENAME, "option2", menu_cmd_option2)
GLOBAL_STATE_TABLE.add_command(STATENAME, "menu", menu_cmd_menu)
#
# enterstate - testing the depth of the state system
#
# This is a test suite that shows off all the features of the state
# system. It sets up a test command @test_state that takes an
# argument 1-6 for moving into states with different
# characteristics. Note that the only difference as to how the various
# states are created lies in the options given to the add_state()
# function. Use @exit to leave any state.
# defining the test-state names so they are the same everywhere
TSTATE1 = 'no_globals'
TSTATE2 = 'all_globals'
TSTATE3 = 'include_some_globals'
TSTATE4 = 'exclude_some_globals'
TSTATE5 = 'global_allow_exits'
TSTATE6 = 'noglobal_allow_exits_obj_cmds'
#
#the test command 'enterstate'
#
def cmd_test_state(command):
"""
@teststate - testing the state system
Usage: @teststate [1 - 6]
Give arguments 1-6 to enter different game states. Use @exit to
get out of the state at any time.
1: A very limited state; only contains the 'test' state command.
2: All global commands are included (so this should be the same as
normal operation, except you cannot traverse exits and use
object-based cmds)
3: /Only/ the global commands 'get' and 'inventory' are included
into the state.
4: All global commands /except/ 'get' and 'inventory' are available
5: All global commands availabe + ability to traverse exits (not use
object-based cmds).
6: Only the 'test' command available, but ability to
both traverse exits and use object-based cmds.
Ideas for in-game use:
1: Try out the '@testmenu' command for an example of this state.
2: Could be used in order to stop someone from moving despite exits
being open (tied up? In combat?)
3: someone incapacitated or blinded might get only limited commands
available
4: in e.g. a combat state, things like crafting should not be
possible.
5: Pretty much default operation, just removing some global commands.
Maybe limiting the use of magical weapons in a room or similar.
6: A state of panic - You can move, but not take in your surroundings.
... the possibilities are endless.
"""
source_object = command.source_object
args = command.command_argument
# check for missing arguments
if not args:
source_object.emit_to("Usage: @teststate [1 - 6]")
return
# build up a return string
string = "\n Entering state ... \nThis state includes the"
string += " commands 'test', 'help', '@exit' and "
arg = args.strip()
# step through the various options
if arg == '1':
string += "no global commands at all. \nWith some more state commands, "
string += "this state would work well for e.g. a "
string += "combat state or a menu where the player don't need access "
string += "to the normal command definitions. Take a special "
string += "look at the help command, which is in fact a "
string += "state-only version of the normal help."
state = TSTATE1
elif arg == '2':
string += "all global commands. You should be able to do "
string += "everything as normal, but not move around."
state = TSTATE2
elif arg == '3':
string += "the global commands 'inv' and 'get' only."
state = TSTATE3
elif arg == '4':
string += "all global commands *except* 'inv' and 'get' (try "
string += "using them). \nThis allows you to disable commands that "
string += "should not be possible at a certain time (like starting "
string += "to craft while in the middle of a fight or something)."
state = TSTATE4
elif arg == '5':
string += "all global commands as well as the ability to traverse "
string += "exits. You do not have the ability to use commands "
string += "defined on objects though."
state = TSTATE5
elif arg == '6':
string += "no globals at all, but you have the ability to both "
string += "use exits and commands on items. \nThis would maybe be "
string += "interesting for a 'total darkness' state or maybe a "
string += "'panic' state where you can move around but cannot "
string += "actually take in your surroundings."
state = TSTATE6
else:
source_object.emit_to("Usage: enterstate 1 - 6")
return
#set the state
source_object.set_state(state)
info = "%s\n (Now in state %s: '%s' ... use @exit to leave the state.)"
source_object.emit_to(info % (string, arg, state))
#
# define a simple command to include in all states.
#
def cmd_instate_cmd(command):
"""
test
Usage:
test
This is the help text for the test command (created with the
auto_help sytem). This is a state-only command that does not
exist outside this state. Since this state is completely isolated
from the normal gameplay, commands can also harmlessly redefine
any normal command - so if there was a normal command named
'test', it would remain unchanged when we leave the state.
"""
command.source_object.emit_to("This state command (test) works!")
#
# Create the test states
#
#define some global commands to filter for
CMDFILTER = ['get', 'inventory']
#1: A simple, basic state with no global commands
GLOBAL_STATE_TABLE.add_state(TSTATE1, exit_command=True)
#2: Include all normal commands in the state
GLOBAL_STATE_TABLE.add_state(TSTATE2, exit_command=True, global_cmds='all')
#3: Include only the two global commands in cmdfilter
GLOBAL_STATE_TABLE.add_state(TSTATE3, exit_command=True,
global_cmds='include', global_filter=CMDFILTER)
#4: Include all global commands except the ones in cmdfilter
GLOBAL_STATE_TABLE.add_state(TSTATE4, exit_command=True,
global_cmds='exclude', global_filter=CMDFILTER)
#5: Include all global commands + ability to traverse exits
GLOBAL_STATE_TABLE.add_state(TSTATE5, exit_command=True,
global_cmds='all',
allow_exits=True)
#6: No global commands, allow exits and commands defined on objects.
GLOBAL_STATE_TABLE.add_state(TSTATE6, exit_command=True,
allow_exits=True, allow_obj_cmds=True)
#append the "test" function to all states
GLOBAL_STATE_TABLE.add_command(TSTATE1, 'test', cmd_instate_cmd)
GLOBAL_STATE_TABLE.add_command(TSTATE2, 'test', cmd_instate_cmd)
GLOBAL_STATE_TABLE.add_command(TSTATE3, 'test', cmd_instate_cmd)
GLOBAL_STATE_TABLE.add_command(TSTATE4, 'test', cmd_instate_cmd)
GLOBAL_STATE_TABLE.add_command(TSTATE5, 'test', cmd_instate_cmd)
GLOBAL_STATE_TABLE.add_command(TSTATE6, 'test', cmd_instate_cmd)
#create the entry function for testing all states
GLOBAL_CMD_TABLE.add_command('@teststate', cmd_test_state)

View file

@ -1,67 +0,0 @@
"""
Example of the event system. To try it out, make sure to import it from somewhere
covered by @reload (like the script parent). Create an object inheriting
the red_button parent to see its effects (e.g. @create button=examples/red_button)
Technically the event don't contain any game logics, all it does is locate all
objects inheriting to a particular script parent and calls one of its functions
at a regular interval.
Note that this type of event will cause *all* red buttons to blink at the same
time, regardless when they were created. This is a very efficient way
to do it (it is also very useful for global events like weather patterns
and day-night cycles), but you can also add events directly to individual objecs
(see the example event in gamesrc/parents/examples/red_button)
"""
import traceback
from src.events import IntervalEvent
from src import scheduler
from src.objects.models import Object
#the logger is useful for debugging
from src import logger
class EventBlinkButton(IntervalEvent):
"""
This event lets the button flash at regular intervals.
"""
def __init__(self):
"""
Note that we do NOT make this event persistent across
reboots since we are actually creating it (i.e. restarting it)
every time the module is reloaded.
"""
super(EventBlinkButton, self).__init__()
self.name = 'event_blink_red_button'
#how often to blink, in seconds
self.interval = 30
#the description is seen when you run @ps in-game.
self.description = "Blink red buttons regularly."
def event_function(self):
"""
This stub function is automatically fired every self.interval seconds.
In this case we do a search for all objects inheriting from the correct
parent and call a function on them.
Note that we must make sure to handle all tracebacks in this
function to avoid trouble.
"""
#find all objects inheriting from red_button (parents are per definition
#stored with the gamesrc/parent/ drawer as a base)
parent = 'examples.red_button'
buttons = Object.objects.global_object_script_parent_search(parent)
for b in buttons:
try:
b.scriptlink.blink()
except:
# Print all tracebacks to the log instead of letting them by.
# This is important, we must handle these exceptions gracefully!
logger.log_errmsg(traceback.print_exc())
#create and add the event to the global handler
blink_event = EventBlinkButton()
scheduler.add_event(blink_event)

View file

@ -0,0 +1,158 @@
"""
These are the base object typeclasses, a convenient shortcut to the
objects in src/objects/objects.py. You can start building your game
from these bases if you want.
To change these defaults to point to some other object,
change some or all of these variables in settings.py:
BASE_OBJECT_TYPECLASS
BASE_CHARACTER_TYPECLASS
BASE_ROOM_TYPECLASS
BASE_EXIT_TYPECLASS
BASE_PLAYER_TYPECLASS
Some of the main uses for these settings are not hard-coded in
Evennia, rather they are convenient defaults for in-game commands
(which you may change) Example would be build commands like '@dig'
knowing to create a particular room-type object).
New instances of Objects (inheriting from these typeclasses)
are created with src.utils.create.create_object(typeclass, ...)
where typeclass is the python path to the class you want to use.
"""
from src.objects.objects import Object as BaseObject
from src.objects.objects import Character as BaseCharacter
from src.objects.objects import Room as BaseRoom
from src.objects.objects import Exit as BaseExit
from src.players.player import Player as BasePlayer
class Object(BaseObject):
"""
This is the root typeclass object, implementing an in-game Evennia
game object, such as having a location, being able to be
manipulated or looked at, etc. If you create a new typeclass, it
must always inherit from this object (or any of the other objects
in this file, since they all actually inherit from BaseObject, as
seen in src.object.objects).
The BaseObject class implements several hooks tying into the game
engine. By re-implementing these hooks you can control the
system. You should never need to re-implement special Python
methods, such as __init__ and especially never __getattribute__ and
__setattr__ since these are used heavily by the typeclass system
of Evennia and messing with them might well break things for you.
Hooks (these are class methods, so their arguments should also start with self):
at_object_creation() - only called once, when object is first created.
Almost all object customizations go here.
at_first_login() - only called once, the very first time user logs in.
at_pre_login() - called every time the user connects, after they have
identified, just before the system actually logs them in.
at_post_login() - called at the end of login, just before setting the
player loose in the world.
at_disconnect() - called just before the use is disconnected (this is also
called if the system determines the player lost their link)
at_object_delete() - called just before the database object is permanently
deleted from the database with obj.delete(). Note that cleaning out contents
and deleting connected exits is not needed, this is handled
automatically when doing obj.delete(). If this method returns
False, deletion is aborted.
at_before_move(destination) - called by obj.move_to() just before moving object to the destination.
If this method returns False, move is cancelled.
announce_move_from(destination) - called while still standing in the old location,
if obj.move_to() has argument quiet=False.
announce_move_to(source_location) - called after move, while standing in the new location
if obj.move_to() has argument quiet=False.
at_after_move(source_location) - always called after a move has been performed.
at_object_leave(obj, target_location) - called when this object loose an object (e.g.
someone leaving the room, an object is given away etc)
at_object_receive(obj, source_location) - called when this object receives another object
(e.g. a room being entered, an object moved into inventory)
return_appearance(looker) - by default, this is used by the 'look' command to
request this object to describe itself. Looker
is the object requesting to get the information.
at_desc(looker=None) - by default called whenever the appearance is requested.
"""
pass
class Character(BaseCharacter):
"""
This is the default object created for a new user connecting - the
in-game player character representation. Note that it's important
that at_object_creation sets up an script that adds the Default
command set whenever the player logs in - otherwise they won't be
able to use any commands!
"""
def at_object_creation(self):
# This adds the default cmdset to the player every time they log
# in. Don't change this unless you really know what you are doing.
#self.scripts.add(scripts.AddDefaultCmdSet)
super(Character, self).at_object_creation()
# expand with whatever customizations you want below...
# ...
class Room(BaseRoom):
"""
Rooms are like any object, except their location is None
(which is default). Usually this object should be
assigned to room-building commands by use of the
settings.BASE_ROOM_TYPECLASS variable.
"""
pass
class Exit(BaseExit):
"""
Exits are connectors between rooms. They are identified by the
engine by having an attribute "_destination" defined on themselves,
pointing to a valid room object. That is usually defined when
the exit is created (in, say, @dig or @link-type commands), not
hard-coded in their typeclass. Exits do have to make sure they
clean up a bit after themselves though, easiest accomplished
by letting by_object_delete() call the object's parent.
"""
def at_object_delete(self):
"""
The game needs to do some cache cleanups when deleting an exit,
so we make sure to call super() here. If this method returns
False, the deletion is aborted.
"""
# handle some cleanups
return super(Exit, self).at_object_delete()
# custom modifications below.
# ...
class Player(BasePlayer):
"""
This class describes the actual OOC player (i.e. the user connecting
to the MUD). It does NOT have visual appearance in the game world (that
is handled by the character which is connected to this). Comm channels
are attended/joined using this object.
It can be useful e.g. for storing configuration options for your game, but
should generally not hold any character-related info (that's best handled
on the character level).
Can be set using BASE_PLAYER_TYPECLASS.
The following hooks are called by the engine. Note that all of the following
are called on the character object too, and mostly at the same time.
at_player_creation() - This is called once, the very first time
the player is created (i.e. first time they
register with the game). It's a good place
to store attributes all players should have,
like configuration values etc.
at_pre_login() - called every time the user connects, after they have
identified, just before the system actually logs them in.
at_post_login() - called at the end of login, just before setting the
player loose in the world.
at_disconnect() - called just before the use is disconnected (this is also
called if the system determines the player lost their link)
"""
pass

View file

@ -0,0 +1,168 @@
"""
An example script parent for a nice red button object. It has
custom commands defined on itself that are only useful in relation to this
particular object. See example.py in gamesrc/commands for more info
on the pluggable command system.
Assuming this script remains in gamesrc/parents/examples, create an object
of this type using @create button:examples.red_button
This file also shows the use of the Event system to make the button
send a message to the players at regular intervals. To show the use of
Events, we are tying two types of events to the red button, one which cause ALL
red buttons in the game to blink in sync (gamesrc/events/example.py) and one
event which cause the protective glass lid over the button to close
again some time after it was opened.
Note that if you create a test button you must drop it before you can
see its messages!
"""
import random
from game.gamesrc.objects.baseobjects import Object
from game.gamesrc.scripts.examples import red_button_scripts as scriptexamples
from game.gamesrc.commands.examples import cmdset_red_button as cmdsetexamples
#
# Definition of the object itself
#
class RedButton(Object):
"""
This class describes an evil red button.
It will use the script definition in
game/gamesrc/events/example.py to blink
at regular intervals until the lightbulb
breaks. It also use the EventCloselid script to
close the lid and do nasty stuff when pressed.
"""
def at_object_creation(self):
"""
This function is called when object is created. Use this
instead of e.g. __init__.
"""
# store desc
desc = "This is a large red button, inviting yet evil-looking. "
desc += "A closed glass lid protects it."
self.db.desc = desc
# We have to define all the variables the scripts
# are checking/using *before* adding the scripts or
# they might be deactivated before even starting!
self.db.lid_open = False
self.db.lamp_works = True
self.db.lid_locked = False
# set the default cmdset to the object, permanent=True means a
# script will automatically be created to always add this.
self.cmdset.add_default(cmdsetexamples.DefaultCmdSet, permanent=True)
# since the other cmdsets relevant to the button are added 'on the fly',
# we need to setup custom scripts to do this for us (also, these scripts
# check so they are valid (i.e. the lid is actually still closed)).
# The AddClosedCmdSet script makes sure to add the Closed-cmdset.
self.scripts.add(scriptexamples.ClosedLidState)
# the script EventBlinkButton makes the button blink regularly.
self.scripts.add(scriptexamples.BlinkButtonEvent)
# state-changing methods
def open_lid(self, feedback=True):
"""
Open the glass lid and start the timer so it will soon close
again.
"""
if self.db.lid_open:
return
desc = "This is a large red button, inviting yet evil-looking. "
desc += "Its glass cover is open and the button exposed."
self.db.desc = desc
self.db.lid_open = True
if feedback and self.location:
string = "The lid slides clear of the button with a click."
string += "\nA ticking sound is heard, suggesting the lid might have"
string += " some sort of timed locking mechanism."
self.location.msg_contents(string)
# with the lid open, we validate scripts; this will clean out
# scripts that depend on the lid to be closed.
self.scripts.validate()
# now add new scripts that define the open-lid state
self.obj.scripts.add(scriptexamples.OpenLidState)
# we also add a scripted event that will close the lid after a while.
# (this one cleans itself after being called once)
self.scripts.add(scriptexamples.CloseLidEvent)
def close_lid(self, feedback=True):
"""
Close the glass lid. This validates all scripts on the button,
which means that scripts only being valid when the lid is open
will go away automatically.
"""
if not self.db.lid_open:
return
desc = "This is a large red button, inviting yet evil-looking. "
desc += "Its glass cover is closed, protecting it."
self.db.desc = desc
self.db.lid_open = False
if feedback and self.location:
string = "With a click the lid slides back, securing the button once again."
self.location.msg_contents(string)
# clean out scripts depending on lid to be open
self.scripts.validate()
# add scripts related to the closed state
self.scripts.add(scriptexamples.ClosedLidState)
def break_lamp(self, feedback=True):
"""
Breaks the lamp in the button, stopping it from blinking.
"""
self.db.lamp_works = False
self.obj.db.desc = "The big red button has stopped blinking for the time being."
if feedback and self.location:
string = "The lamp flickers, the button going dark."
self.location.msg_contents(string)
self.scripts.validate()
def press_button(self, pobject):
"""
Someone was foolish enough to press the button!
pobject - the person pressing the button
"""
# deactivate the button so it won't flash/close lid etc.
self.scripts.add(scriptexamples.DeactivateButtonEvent)
# blind the person pressing the button. Note that this
# script is set on the *character* pressing the button!
pobject.scripts.add(scriptexamples.BlindedState)
# script-related methods
def blink(self):
"""
The script system will regularly call this
function to make the button blink. Now and then
it won't blink at all though, to add some randomness
to how often the message is echoed.
"""
loc = self.location
if loc:
rand = random.random()
if rand < 0.2:
string = "The red button flashes briefly."
elif rand < 0.4:
string = "The red button blinks invitingly."
elif rand < 0.6:
string = "The red button flashes. You know you wanna push it!"
else:
# no blink
return
loc.msg_contents(string)

View file

@ -1,6 +0,0 @@
Do not modify files in this directory! They are base classes from which other
things may be sub-classed. Modifying these classes may cause conflicts the
next time you upgrade manually or through subversion.
Instead, sub-class the classes contained here and point the default parent
imports in settings.py to your new classes.

View file

@ -1,27 +0,0 @@
"""
This is the base object type/interface that all parents are derived from by
default. Each object type sub-classes this class and over-rides methods as
needed.
NOTE: This file should NOT be directly modified. Sub-class this in
your own class in game/gamesrc/parents and change
SCRIPT_DEFAULT_OBJECT variable in settings.py to point to the new class.
"""
from src.script_parents.basicobject import EvenniaBasicObject
class BasicObject(EvenniaBasicObject):
pass
def class_factory(source_obj):
"""
This method is called any script you retrieve (via the scripthandler). It
creates an instance of the class and returns it transparently.
source_obj: (Object) A reference to the object being scripted (the child).
Since this is the only place where the object is actually instantiated,
this is also the place to put commands you want to act on this object,
do this by obj.command_table.add_command('cmd', cmd_def).
"""
obj = BasicObject(source_obj)
return obj

View file

@ -1,21 +0,0 @@
"""
This is the basic Evennia-standard player parent.
NOTE: This file should NOT be directly modified. Sub-class the BasicPlayer
class in your own class in game/gamesrc/parents and change the
SCRIPT_DEFAULT_PLAYER variable in settings.py to point to the new class.
"""
from src.script_parents.basicobject import EvenniaBasicObject
from src.script_parents.basicplayer import EvenniaBasicPlayer
class BasicPlayer(EvenniaBasicObject, EvenniaBasicPlayer):
pass
def class_factory(source_obj):
"""
This method is called any script you retrieve (via the scripthandler). It
creates an instance of the class and returns it transparently.
source_obj: (Object) A reference to the object being scripted (the child).
"""
return BasicPlayer(source_obj)

View file

@ -1,75 +0,0 @@
"""
Simple example of a custom modified object, derived from the base object.
If you want to make this your new default object type, move this into
gamesrc/parents and set SCRIPT_DEFAULT_OBJECT = 'custom_basicobject'
in game/settings.py.
Generally, if you want to conveniently set future objects to inherit from this
script parent, this file and others like it need to be
located under the game/gamesrc/parent directory.
"""
from game.gamesrc.parents.base.basicobject import BasicObject
class CustomBasicObject(BasicObject):
"""
This defines the base class of all non-player objects in game.
"""
def at_object_creation(self):
"""
This function is called whenever the object is created. Use
this instead of __init__ to set start attributes etc on a
particular object type.
"""
#Set an "sdesc" (short description) attribute on object,
#defaulting to its given name
#get the stored object related to this class
obj = self.scripted_obj
#find out the object's name
name = obj.get_name(fullname=False,
show_dbref=False,
show_flags=False)
#assign the name to the new attribute
obj.set_attribute('sdesc', name)
def at_object_destruction(self, pobject=None):
"""
This is triggered when an object is about to be destroyed via
@destroy ONLY. If an object is deleted via delete(), it is assumed
that this method is to be skipped.
values:
* pobject: (Object) The object requesting the action.
"""
pass
def at_before_move(self, target_location):
"""
This hook is called just before the object is moved.
Input:
target_location (obj): The location the player is about to move to.
Return value:
If this function returns anything but None (no return value),
the move is aborted. This allows for character-based move
restrictions (not only exit locks).
"""
pass
def at_after_move(self):
"""
This hook is called just after the object has been successfully moved.
"""
pass
def class_factory(source_obj):
"""
This method is called by any script you retrieve (via the scripthandler). It
creates an instance of the class and returns it transparently.
source_obj: (Object) A reference to the object being scripted (the child).
"""
return CustomBasicObject(source_obj)

View file

@ -1,76 +0,0 @@
"""
This is an example of customizing the basic player character object.
You will want to do this to add all sorts of custom things like
attributes, skill values, injuries and so on.
If you want to make this the default player object for all players,
move it into gamesrc/parents and set SCRIPT_DEFAULT_PLAYER =
'custom_basicplayer' in game/settings.py.
"""
from game.gamesrc.parents.base.basicplayer import BasicPlayer
class CustomBasicPlayer(BasicPlayer):
"""
This is the base class for all players in game.
"""
def at_player_creation(self):
"""
Called when player object is first created. Use this
instead of __init__ to define any custom attributes
all your player characters should have.
"""
#Example: Adding a default sdesc (short description)
#get the stored object related to this class
pobject = self.scripted_obj
#set the attribute
pobject.set_attribute('sdesc', 'A normal person')
def at_pre_login(self, session):
"""
Called when the player has entered the game but has not
logged in yet.
"""
pass
def at_post_login(self, session):
"""
This command is called after the player has logged in but
before he is allowed to give any commands.
"""
#get the object linked to this class
pobject = self.scripted_obj
#find out more about our object
name = pobject.get_name(fullname=False,
show_dbref=False,
show_flags=False)
sdesc = pobject.get_attribute_value('sdesc')
#send a greeting using our new sdesc attribute
pobject.emit_to("You are now logged in as %s - %s." % (name, sdesc))
#tell everyone else we're here
pobject.get_location().emit_to_contents("%s - %s, has connected." %
(name, sdesc), exclude=pobject)
#show us our surroundings
pobject.execute_cmd("look")
def at_move(self):
"""
This is triggered whenever the object is moved to a new location
(for whatever reason) using the src.objects.models.move_to() function.
"""
pass
def class_factory(source_obj):
"""
This method is called by any script you retrieve (via the scripthandler). It
creates an instance of the class and returns it transparently.
source_obj: (Object) A reference to the object being scripted (the child).
"""
return CustomBasicPlayer(source_obj)

View file

@ -1,220 +0,0 @@
"""
An example script parent for a nice red button object. It has
custom commands defined on itself that are only useful in relation to this
particular object. See example.py in gamesrc/commands for more info
on the pluggable command system.
Assuming this script remains in gamesrc/parents/examples, create an object
of this type using @create button:examples.red_button
This file also shows the use of the Event system to make the button
send a message to the players at regular intervals. To show the use of
Events, we are tying two types of events to the red button, one which cause ALL
red buttons in the game to blink in sync (gamesrc/events/example.py) and one
event which cause the protective glass lid over the button to close
again some time after it was opened.
Note that if you create a test button you must drop it before you can
see its messages!
"""
import traceback
from game.gamesrc.parents.base.basicobject import BasicObject
from src.objects.models import Object
from src.events import IntervalEvent
from src import scheduler
from src import logger
#
# Events
#
# Importing this will start the blink event ticking, only one
# blink event is used for all red buttons.
import game.gamesrc.events.example
# We also create an object-specific event.
class EventCloselid(IntervalEvent):
"""
This event closes the glass lid over the button
some time after it was opened.
"""
def __init__(self, obj):
"""
Note how we take an object as an argument,
this will allow instances of this event to
operate on this object only.
"""
# we must call super to make sure things work!
super(EventCloselid, self).__init__()
# store the object reference
self.obj_dbref = obj.dbref()
# This is used in e.g. @ps to show what the event does
self.description = "Close lid on %s" % obj
# We make sure that this event survives a reboot
self.persistent = True
# How many seconds from event creation to closing
# the lid
self.interval = 20
# We only run the event one time before it deletes itself.
self.repeats = 1
def event_function(self):
"""
This function is called every self.interval seconds.
Note that we must make sure to handle all errors from
this call to avoid trouble.
"""
try:
# if the lid is open, close it. We have to find the object
# again since it might have changed.
obj = Object.objects.get_object_from_dbref(self.obj_dbref)
if obj.has_flag("LID_OPEN"):
obj.scriptlink.close_lid()
retval = "The glass cover over the button silently closes by itself."
obj.get_location().emit_to_contents(retval)
except:
# send the traceback to the log instead of letting it by.
# It is important that we handle exceptions gracefully here!
logger.log_errmsg(traceback.print_exc())
#
# Object commands
#
# Commands for using the button object. These are added to
# the object in the class_factory function at the
# bottom of this module.
#
def cmd_open_lid(command):
"""
Open the glass lid cover over the button.
"""
# In the case of object commands, you can use this to
# get the object the command is defined on.
obj = command.scripted_obj
if obj.has_flag("LID_OPEN"):
retval = "The lid is already open."
else:
retval = "You lift the lid, exposing the tempting button."
obj.scriptlink.open_lid()
command.source_object.emit_to(retval)
def cmd_close_lid(command):
"""
Close the lid again.
"""
obj = command.scripted_obj
if not obj.has_flag("LID_OPEN"):
retval = "The lid is already open."
else:
retval = "You secure the glass cover over the button."
obj.scriptlink.close_lid()
command.source_object.emit_to(retval)
def cmd_push_button(command):
"""
This is a simple command that handles a user pressing the
button by returning a message. The button can only be
"""
obj = command.scripted_obj
if obj.has_flag("LID_OPEN"):
retval = "You press the button ..."
retval += "\n ..."
retval += "\n BOOOOOM!"
obj.scriptlink.close_lid()
else:
retval = "There is a glass lid covering "
retval += "the button as a safety measure. If you "
retval += "want to press the button you need to open "
retval += "the lid first."
command.source_object.emit_to(retval)
#
# Definition of the object itself
#
class RedButton(BasicObject):
"""
This class describes an evil red button.
It will use the event definition in
game/gamesrc/events/example.py to blink
at regular intervals until the lightbulb
breaks. It also use the EventCloselid event defined
above to close the lid
"""
def at_object_creation(self):
"""
This function is called when object is created. Use this
preferably over __init__.
"""
#get stored object related to this class
obj = self.scripted_obj
obj.set_attribute('desc', "This is a big red button. It has a glass cover.")
obj.set_attribute("breakpoint", 5)
obj.set_attribute("count", 0)
# add the object-based commands to the button
obj.add_command("open lid", cmd_open_lid)
obj.add_command("lift lid", cmd_open_lid)
obj.add_command("close lid", cmd_close_lid)
obj.add_command("push button", cmd_push_button)
obj.add_command("push the button", cmd_push_button)
def open_lid(self):
"""
Open the glass lid and start the timer so it will
soon close again.
"""
self.scripted_obj.set_flag("LID_OPEN")
scheduler.add_event(EventCloselid(self.scripted_obj))
def close_lid(self):
"""
Close the glass lid
"""
self.scripted_obj.unset_flag("LID_OPEN")
def blink(self):
"""
If the event system is active, it will regularly call this
function to make the button blink. Note the use of attributes
to store the variable count and breakpoint in a persistent
way.
"""
obj = self.scripted_obj
try:
count = int(obj.get_attribute_value("count"))
breakpoint = int(obj.get_attribute_value("breakpoint"))
except TypeError:
return
if count <= breakpoint:
if int(count) == int(breakpoint):
string = "The button flashes, then goes dark. "
string += "Looks like the lamp just broke."
else:
string = "The red button flashes, demanding your attention."
count += 1
obj.set_attribute("count", count)
obj.get_location().emit_to_contents(string)
def class_factory(source_obj):
"""
This method is called by any script you retrieve (via the scripthandler). It
creates an instance of the class and returns it transparently.
source_obj: (Object) A reference to the object being scripted (the child).
This is a good place for adding new commands to the button since this is
where it is actually instantiated.
"""
return RedButton(source_obj)

View file

@ -0,0 +1,63 @@
"""
The base object to inherit from when implementing new Scripts.
Scripts are objects that handle everything in the game having
a time-component (i.e. that may change with time, with or without
a player being involved in the change). Scripts can work like "events",
in that they are triggered at regular intervals to do a certain script,
but an Script set on an object can also be responsible for silently
checking if its state changes, so as to update it. Evennia use several
in-built scripts to keep track of things like time, to clean out
dropped connections etc.
New Script objects (from these classes) are created using the
src.utils.create.create_script(scriptclass, ...) where scriptclass
is the python path to the specific class of script you want to use.
"""
from src.scripts.scripts import Script as BaseScript
class Script(BaseScript):
"""
All scripts should inherit from this class and implement
some or all of its hook functions and variables.
Important variables controlling the script object:
self.key - the name of all scripts inheriting from this class
(defaults to <unnamed>), used in lists and searches.
self.desc - a description of the script, used in lists
self.interval (seconds) - How often the event is triggered and calls self.at_repeat()
(see below) Defaults to 0 - that is, never calls at_repeat().
self.start_delay (True/False). If True, will wait self.interval seconds
befor calling self.at_repeat() for the first time. Defaults to False.
self.repeats - The number of times at_repeat() should be called before automatically
stopping the script. Default is 0, which means infinitely many repeats.
self.persistent (True/False). If True, the script will survive a server restart
(defaults to False).
self.obj (game Object)- this ties this script to a particular object. It is
usually not needed to set this parameter explicitly; it's set in the
create methods.
Hook methods (should also include self as the first argument):
at_script_creation() - called only once, when an object of this class
is first created.
is_valid() - is called to check if the script is valid to be running
at the current time. If is_valid() returns False, the running
script is stopped and removed from the game. You can use this
to check state changes (i.e. an script tracking some combat
stats at regular intervals is only valid to run while there is
actual combat going on).
at_start() - Called every time the script is started, which for persistent
scripts is at least once every server start. Note that this is
unaffected by self.delay_start, which only delays the first call
to at_repeat().
at_repeat() - Called every self.interval seconds. It will be called immediately
upon launch unless self.delay_start is True, which will delay
the first call of this method by self.interval seconds. If
self.interval==0, this method will never be called.
at_stop() - Called as the script object is stopped and is about to be removed from
the game, e.g. because is_valid() returned False.
"""
pass

View file

@ -0,0 +1,275 @@
"""
Example of scripts.
These are scripts intended for a particular object - the
red_button object type in gamesrc/types/examples. A few variations
on uses of scripts are included.
"""
from game.gamesrc.scripts.basescript import Script
from game.gamesrc.commands.examples import cmdset_red_button as cmdsetexamples
#
# Scripts as state-managers
#
# Scripts have many uses, one of which is to statically
# make changes when a particular state of an object changes.
# There is no "timer" involved in this case (although there could be),
# whenever the script determines it is "invalid", it simply shuts down
# along with all the things it controls.
#
# To show as many features as possible of the script and cmdset systems,
# we will use three scripts controlling one state each of the red_button,
# each with its own set of commands, handled by cmdsets - one for when
# the button has its lid open, and one for when it is closed and a
# last one for when the player pushed the button and gets blinded by
# a bright light. The last one also has a timer component that allows it
# to remove itself after a while (and the player recovers their eyesight).
class ClosedLidState(Script):
"""
This manages the cmdset for the "closed" button state. What this
means is that while this script is valid, we add the RedButtonClosed
cmdset to it (with commands like open, nudge lid etc)
"""
def at_script_creation(self):
"Called when script first created."
self.desc = "Script that manages the closed-state cmdsets for red button."
self.persistent = True
def at_start(self):
"""
This is called once every server restart, so we want to add the
(memory-resident) cmdset to the object here. is_valid is automatically
checked so we don't need to worry about adding the script to an
open lid.
"""
#All we do is add the cmdset for the closed state.
self.obj.cmdset.add(cmdsetexamples.LidClosedCmdSet)
def is_valid(self):
"""
The script is only valid while the lid is closed.
self.obj is the red_button on which this script is defined.
"""
return not self.obj.db.lid_open
def at_stop(self):
"""
When the script stops we must make sure to clean up after us.
"""
self.obj.cmdset.delete(cmdsetexamples.LidClosedCmdSet)
class OpenLidState(Script):
"""
This manages the cmdset for the "open" button state. This will add
the RedButtonOpen
"""
def at_script_creation(self):
"Called when script first created."
self.desc = "Script that manages the opened-state cmdsets for red button."
self.persistent = True
def at_start(self):
"""
This is called once every server restart, so we want to add the
(memory-resident) cmdset to the object here. is_valid is
automatically checked, so we don't need to worry about
adding the cmdset to a closed lid-button.
"""
#print "In Open at_start (should add cmdset)"
self.obj.cmdset.add(cmdsetexamples.LidOpenCmdSet)
def is_valid(self):
"""
The script is only valid while the lid is open.
self.obj is the red_button on which this script is defined.
"""
return self.obj.db.lid_open
def at_stop(self):
"""
When the script stops (like if the lid is closed again)
we must make sure to clean up after us.
"""
self.obj.cmdset.delete(cmdsetexamples.LidOpenCmdSet)
class BlindedState(Script):
"""
This is a timed state.
This adds a (very limited) cmdset TO THE PLAYER, during a certain time,
after which the script will close and all functions are
restored. It's up to the function starting the script to actually
set it on the right player object.
"""
def at_script_creation(self):
"""
We set up the script here.
"""
self.key = "temporary_blinder"
self.desc = "Temporarily blinds the player for a little while."
self.interval = 20 # seconds
self.start_delay = True # we don't want it to stop until after 20s.
self.repeats = 1 # this will go away after interval seconds.
self.persistent = False # we will ditch this if server goes down
def at_start(self):
"""
We want to add the cmdset to the linked object.
Note that the RedButtonBlind cmdset is defined to completly
replace the other cmdsets on the stack while it is active
(this means that while blinded, only operations in this cmdset
will be possible for the player to perform). It is however
not persistent, so should there be a bug in it, we just need
to restart the server to clear out of it during development.
"""
self.obj.cmdset.add(cmdsetexamples.BlindCmdSet)
def at_stop(self):
"""
It's important that we clear out that blinded cmdset
when we are done!
"""
self.obj.msg("Your blink feverishly as your eyesight slowly returns.")
self.obj.location.msg_contents("%s seems to be recovering their eyesight."
% self.obj.name,
exclude=self.obj)
self.obj.cmdset.delete() # this will clear the latest added cmdset,
# (which is the blinded one).
#
# Timer/Event-like Scripts
#
# Scripts can also work like timers, or "events". Below we
# define three such timed events that makes the button a little
# more "alive" - one that makes the button blink menacingly, another
# that makes the lid covering the button slide back after a while.
#
class CloseLidEvent(Script):
"""
This event closes the glass lid over the button
some time after it was opened. It's a one-off
script that should be started/created when the
lid is opened.
"""
def at_script_creation(self):
"""
Called when script object is first created. Sets things up.
We want to have a lid on the button that the user can pull
aside in order to make the button 'pressable'. But after a set
time that lid should auto-close again, making the button safe
from pressing (and deleting this command).
"""
self.key = "lid_closer"
self.desc = "Closes lid on a red buttons"
self.interval = 20 # seconds
self.start_delay = True # we want to pospone the launch.
self.repeats = 1 # we only close the lid once
self.persistent = True # even if the server crashes in those 20 seconds,
# the lid will still close once the game restarts.
def is_valid(self):
"""
This script can only operate if the lid is open; if it
is already closed, the script is clearly invalid.
Note that we are here relying on an self.obj being
defined (and being a RedButton object) - this we should be able to
expect since this type of script is always tied to one individual
red button object and not having it would be an error.
"""
return self.obj.db.lid_open
def at_repeat(self):
"""
Called after self.interval seconds. It closes the lid. Before this method is
called, self.is_valid() is automatically checked, so there is no need to
check this manually.
"""
self.obj.close_lid()
class BlinkButtonEvent(Script):
"""
This timed script lets the button flash at regular intervals.
"""
def at_script_creation(self):
"""
Sets things up. We want the button's lamp to blink at
regular intervals, unless it's broken (can happen
if you try to smash the glass, say).
"""
self.key = "blink_button"
self.desc = "Blinks red buttons"
self.interval = 35 #seconds
self.start_delay = False #blink right away
self.persistent = True #keep blinking also after server reboot
def is_valid(self):
"""
Button will keep blinking unless it is broken.
"""
#print "self.obj.db.lamp_works:", self.obj.db.lamp_works
return self.obj.db.lamp_works
def at_repeat(self):
"""
Called every self.interval seconds. Makes the lamp in
the button blink.
"""
self.obj.blink()
class DeactivateButtonEvent(Script):
"""
This deactivates the button for a short while (it won't blink, won't
close its lid etc). It is meant to be called when the button is pushed
and run as long as the blinded effect lasts. We cannot put these methods
in the AddBlindedCmdSet script since that script is defined on the *player*
whereas this one must be defined on the *button*.
"""
def at_script_creation(self):
"""
Sets things up.
"""
self.key = "deactivate_button"
self.desc = "Deactivate red button temporarily"
self.interval = 21 #seconds
self.start_delay = True # wait with the first repeat for self.interval seconds.
self.persistent = True
self.repeats = 1 # only do this once
def at_start(self):
"""
Deactivate the button. Observe that this method is always
called directly, regardless of the value of self.start_delay
(that just controls when at_repeat() is called)
"""
# closing the lid will also add the ClosedState script
self.obj.close_lid(feedback=False)
# lock the lid so other players can't access it until the
# first one's effect has worn off.
self.obj.db.lid_locked = True
# breaking the lamp also sets a correct desc
self.obj.break_lamp(feedback=False)
def at_repeat(self):
"""
When this is called, reset the functionality of the button.
"""
# restore button's desc.
self.obj.db.lamp_works = True
desc = "This is a large red button, inviting yet evil-looking. "
desc += "Its glass cover is closed, protecting it."
self.db.desc = desc
# re-activate the blink button event.
self.obj.scripts.add(BlinkButtonEvent)
# unlock the lid
self.obj.db.lid_locked = False
self.obj.scripts.validate()

View file

@ -1,123 +0,0 @@
"""
This module offers a collection of useful general functions from the
game engine to make things easier to find.
Just import game.gamesrc.utils and refer to the globals defined herein.
Note that this is not intended as a comprehensive collection, merely
a convenient place to refer to for the methods we have found to be
often used. You will still have to refer to the modules
in evennia/src for more specialized operations.
You will also want to be well familiar with all the facilities each
object offers. The object model is defined in src/objects/models.py.
"""
#------------------------------------------------------------
# imports
#------------------------------------------------------------
from django.conf import settings as in_settings
from src import logger
from src import scheduler as in_scheduler
from src.objects.models import Object
from src import defines_global
from src.cmdtable import GLOBAL_CMD_TABLE as in_GLOBAL_CMD_TABLE
from src.statetable import GLOBAL_STATE_TABLE as in_GLOBAL_STATE_TABLE
from src.events import IntervalEvent as in_IntervalEvent
#------------------------------------------------------------
# Import targets
#------------------------------------------------------------
settings = in_settings
GLOBAL_CMD_TABLE = in_GLOBAL_CMD_TABLE
GLOBAL_STATE_TABLE = in_GLOBAL_STATE_TABLE
# Events
scheduler = in_scheduler
IntervalEvent = in_IntervalEvent
#------------------------------------------------------------
# Log to file/stdio
# log_xxxmsg(msg)
#------------------------------------------------------------
log_errmsg = logger.log_errmsg
log_warnmsg = logger.log_warnmsg
log_infomsg = logger.log_infomsg
#------------------------------------------------------------
# Search methods
#------------------------------------------------------------
# NOTE: All objects also has search_for_object() defined
# directly on themselves, which is a convenient entryway into a
# local and global search with automatic feedback to the
# calling player.
# def get_object_from_dbref(dbref):
# Returns an object when given a dbref.
get_object_from_dbref = Object.objects.get_object_from_dbref
# def dbref_search(dbref_string, limit_types=False):
# Searches for a given dbref.
dbref_search = Object.objects.dbref_search
# def global_object_name_search(ostring, exact_match=True, limit_types=[]):
# Searches through all objects for a name match.
global_object_name_search = Object.objects.global_object_name_search
# def global_object_script_parent_search(script_parent):
# Searches through all objects returning those which has a certain script parent.
global_object_script_parent_search = Object.objects.global_object_script_parent_search
# def player_name_search(searcher, ostring):
# Search players by name.
player_name_search = Object.objects.player_name_search
# def local_and_global_search(searcher, ostring, search_contents=True,
# search_location=True, dbref_only=False,
# limit_types=False, attribute_name=None):
# Searches an object's location then globally for a dbref or name match.
local_and_global_search = Object.objects.local_and_global_search
#------------------------------------------------------------
# Creation commands
#------------------------------------------------------------
# def create_object(name, otype, location, owner, home=None, script_parent=None):
# Create a new object
create_object = Object.objects.create_object
# def copy_object(original_object, new_name=None, new_location=None, reset=False):
# Create and return a new object as a copy of the source object. All will
# be identical to the original except for the dbref. Does not allow the
# copying of Player objects.
copy_object = Object.objects.copy_object
#------------------------------------------------------------
# Validation
#------------------------------------------------------------
# NOTE: The easiest way to check if an object
# is of a particular type is to use each object's
# is_X() function, like is_superuser(), is_thing(),
# is_room(), is_player(), is_exit() and get_type().
OTYPE_NOTHING = defines_global.OTYPE_NOTHING
OTYPE_PLAYER = defines_global.OTYPE_PLAYER
OTYPE_ROOM = defines_global.OTYPE_ROOM
OTYPE_THING = defines_global.OTYPE_THING
OTYPE_EXIT = defines_global.OTYPE_EXIT
OTYPE_GOING = defines_global.OTYPE_GOING
TYPE_GARBAGE = defines_global.OTYPE_GARBAGE
NOPERMS_MSG = defines_global.NOPERMS_MSG
NOCONTROL_MSG = defines_global.NOCONTROL_MSG
# def is_dbref(self, dbstring, require_pound=True):
# Is the input a well-formed dbref number?
is_dbref = Object.objects.is_dbref

View file

@ -3,9 +3,8 @@
#
# It allows batch processing of normal Evennia commands.
# Test it by loading it with the @batchprocess command
# (superuser only):
#
# @batchprocess[/interactive] </full/path/to/this/file>
# @batchprocess[/interactive] examples.batch_example
#
# A # as the first symbol on a line begins a comment and
# marks the end of a previous command definition (important!).
@ -18,12 +17,12 @@
# This creates a red button
@create button
@create button:examples.red_button.RedButton
# This comment ends input for @create
# Next command:
@set button=desc:
@set button/desc =
This is a large red button. Now and then
it flashes in an evil, yet strangely tantalizing way.
@ -52,6 +51,6 @@ know you want to!
@teleport #2
#... and drop it (remember, this comment ends input to @teleport, so don't
#forget it!) The very last command in the file needs not be ended with #.
#forget it!) The very last command in the file need not be ended with #.
drop button

View file

@ -0,0 +1,71 @@
#
# Batchcode script
#
#
# The Batch-code processor accepts full python modules (e.g. "batch.py") that
# looks identical to normal Python files with a few exceptions that allows them
# to the executed in blocks. This way of working assures a sequential execution
# of the file and allows for features like stepping from block to block
# (without executing those coming before), as well as automatic deletion
# of created objects etc. You can however also run a batch-code python file
# directly using Python (and can also be de).
# Code blocks are separated by python comments starting with special code words.
# #HEADER - this denotes commands global to the entire file, such as
# import statements and global variables. They will
# automatically be made available for each block. Observe
# that changes to these variables made in one block is not
# preserved between blocks!)
# #CODE [objname, objname, ...] - This designates a code block that will be executed like a
# stand-alone piece of code together with any #HEADER
# defined. <objname>s mark the (variable-)names of objects created in the code,
# and which may be auto-deleted by the processor if desired (such as when
# debugging the script). E.g., if the code contains the command
# myobj = create.create_object(...), you could put 'myobj' in the #CODE header
# regardless of what the created object is actually called in-game.
# The following variables are automatically made available for the script:
# caller - the object executing the script
#
#
#HEADER
# everything in this block will be imported to all CODE blocks when
# they are executed.
from src.utils import create, search
from game.gamesrc.typeclasses.examples import red_button
from game.gamesrc.typeclasses import basetypes
#CODE
# This is the first code block. Within each block, python
# code works as normal.
# get the limbo room.
limbo = search.objects(caller, 'Limbo', global_search=True)[0]
caller.msg(limbo)
# create a red button in limbo
red_button = create.create_object(red_button.RedButton, key="Red button",
location=limbo, aliases=["button"])
# we take a look at what we created
caller.msg("A %s was created." % red_button.key)
#CODE table, chair
# this code block has 'table' and 'chair' set as deletable
# objects. This means that when the batchcode processor runs in
# testing mode, objects created in these variables will be deleted
# again (so as to avoid duplicate objects when testing the script).
limbo = search.objects(caller, 'Limbo', global_search=True)[0]
caller.msg(limbo.key)
table = create.create_object(basetypes.Object, key="Table", location=limbo)
chair = create.create_object(basetypes.Object, key="Chair", location=limbo)
string = "A %s and %s were created. If debug was active, they were deleted again."
caller.msg(string % (table, chair))

View file

@ -1,28 +1,129 @@
#!/usr/bin/env python
"""
Set up the evennia system. A first startup consists of giving
the command './manage syncdb' to setup the system and create
the database.
"""
import sys
import os
# Tack on the root evennia directory to the python path.
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
"""
If settings.py doesn't already exist, create it and populate it with some
basic stuff.
"""
try:
VERSION = open("%s%s%s" % (os.pardir, os.sep, 'VERSION')).readline().strip()
except IOError:
VERSION = "Unknown version"
_CREATED_SETTINGS = False
if not os.path.exists('settings.py'):
print "Can't find a settings.py file, creating one for you."
f = open('settings.py', 'w')
f.write('"""\nMaster server configuration file. You may override any of the values in the\nsrc/config_defaults.py here. Copy-paste the variables here, and make changes to\nthis file rather than editing config_defaults.py directly.\n"""\n')
f.write('from src.config_defaults import *')
f.close()
# If settings.py doesn't already exist, create it and populate it with some
# basic stuff.
settings_file = open('settings.py', 'w')
_CREATED_SETTINGS = True
string = \
"""#
# Evennia MU* server configuration file
#
# You may customize your setup by copy&pasting the variables you want
# to change from the master config file src/settings_default.py to
# this file. Try to *only* copy over things you really need to customize
# and do *not* make any changes to src/settings_default.py directly.
# This way you'll always have a sane default to fall back on
# (also, the master file may change with server updates).
#
from src.settings_default import *
###################################################
# Evennia base server config
###################################################
###################################################
# Evennia Database config
###################################################
###################################################
# Evennia in-game parsers
###################################################
###################################################
# Default command sets
###################################################
###################################################
# Default Object typeclasses
###################################################
###################################################
# Batch processor
###################################################
###################################################
# Game Time setup
###################################################
###################################################
# Game Permissions
###################################################
###################################################
# In-game Channels created from server start
###################################################
###################################################
# IMC2 Configuration
###################################################
###################################################
# IRC config
###################################################
###################################################
# Config for Django web features
###################################################
###################################################
# Evennia components (django apps)
###################################################"""
settings_file.write(string)
settings_file.close()
print """
Welcome to Evennia (version %s)!
Created a fresh settings.py file for you.""" % VERSION
try:
from game import settings
except ImportError:
import sys
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
sys.exit(1)
except Exception:
import traceback
string = "\n" + traceback.format_exc()
string += """\n
Error: Couldn't import the file 'settings.py' in the directory
containing %r. There can be two reasons for this:
1) You moved your settings.py elsewhere. In that case you need to run
django-admin.py, passing it the true location of your settings module.
2) The settings module is where it's supposed to be, but an exception
was raised when trying to load it. Review the traceback above to
resolve the problem, then try again.
""" % __file__
print string
sys.exit(1)
if __name__ == "__main__":
from django.core.management import execute_manager
execute_manager(settings)
from django.core.management import execute_manager
if _CREATED_SETTINGS:
print """
Edit your new settings.py file as needed, then run
'python manage syncdb' and follow the prompts to
create the database and your superuser account.
"""
sys.exit()
# run the django setups
execute_manager(settings)

View file

@ -1,3 +0,0 @@
from django.db import models
# Create your models here.

View file

@ -1,5 +0,0 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('game.web.apps.website.views',
(r'^$', 'page_index'),
)

View file

@ -1,9 +0,0 @@
from src.config.models import ConfigValue
def general_context(request):
"""
Returns common Evennia-related context stuff.
"""
return {
'game_name': ConfigValue.objects.get_configvalue('site_name'),
}

View file

@ -0,0 +1,4 @@
The evennia logo (the python snaking a cogwheel-globe) was created in 2009
by Griatch (www.griatch-art.deviantart.com, www.griatch.com) using open-source software (of course).
The logo is released with the same licence as Evennia itself (look in evennia/LICENCE).

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

17
game/web/news/admin.py Normal file
View file

@ -0,0 +1,17 @@
#
# This makes the news model visible in the admin web interface
# so one can add/edit/delete news items etc.
#
from django.contrib import admin
from game.web.news.models import NewsTopic, NewsEntry
class NewsTopicAdmin(admin.ModelAdmin):
list_display = ('name', 'icon')
admin.site.register(NewsTopic, NewsTopicAdmin)
class NewsEntryAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'topic', 'date_posted')
list_filter = ('topic',)
search_fields = ['title']
admin.site.register(NewsEntry, NewsEntryAdmin)

View file

@ -1,5 +1,10 @@
#
# This module implements a simple news entry system
# for the evennia website. One needs to use the
# admin interface to add/edit/delete entries.
#
from django.db import models
from django.contrib import admin
from django.contrib.auth.models import User
class NewsTopic(models.Model):
@ -21,10 +26,6 @@ class NewsTopic(models.Model):
class Meta:
ordering = ['name']
class Admin:
list_display = ('name', 'icon')
admin.site.register(NewsTopic)
class NewsEntry(models.Model):
"""
An individual news entry.
@ -42,8 +43,3 @@ class NewsEntry(models.Model):
ordering = ('-date_posted',)
verbose_name_plural = "News entries"
class Admin:
list_display = ('title', 'author', 'topic', 'date_posted')
list_filter = ('topic',)
search_fields = ['title']
admin.site.register(NewsEntry)

View file

@ -1,6 +1,11 @@
"""
This structures the url tree for the news application.
It is imported from the root handler, game.web.urls.py.
"""
from django.conf.urls.defaults import *
urlpatterns = patterns('game.web.apps.news.views',
urlpatterns = patterns('game.web.news.views',
(r'^show/(?P<entry_id>\d+)/$', 'show_news'),
(r'^archive/$', 'news_archive'),
(r'^search/$', 'search_form'),

View file

@ -1,19 +1,20 @@
"""
This is a very simple news application, with most of the expected features
like:
like news-categories/topics and searchable archives.
* News categories/topics
* Searchable archives
"""
import django.views.generic.list_detail as gv_list_detail
from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext
import django.views.generic.list_detail as gv_list_detail
from django.conf import settings
from django.http import HttpResponseRedirect
from django.contrib.auth.models import User
from django import forms
from django.db.models import Q
from game.web.apps.news.models import NewsTopic, NewsEntry
from game.web.news.models import NewsTopic, NewsEntry
# The sidebar text to be included as a variable on each page. There's got to
# be a better, cleaner way to include this on every page.
@ -89,7 +90,7 @@ def search_form(request):
pagevars = {
"page_title": "Search News",
"search_form": search_form,
"debug": debug,
"debug": settings.DEBUG,
"sidebar": sidebar
}
@ -117,6 +118,7 @@ def search_results(request):
news_entries = NewsEntry.objects.filter(Q(title__contains=cleaned_get['search_terms']) | Q(body__contains=cleaned_get['search_terms']))
pagevars = {
"game_name": settings.SERVERNAME,
"page_title": "Search Results",
"searchtext": cleaned_get['search_terms'],
"browse_url": "/news/search/results",

View file

@ -0,0 +1,11 @@
{% extends "admin/base.html" %}
{% load i18n %}
{% block title %}{{ title }} | {% trans 'Evennia site admin' %}{% endblock %}
{% block branding %}
<h1 id="site-name">{% trans 'Evennia database administration' %}
<a href="/">(Back)</a> </h1>
{% endblock %}
{% block nav-global %}{% endblock %}

View file

@ -0,0 +1,242 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% load adminmedia %}{% admin_media_prefix %}css/dashboard.css" />{% endblock %}
{% block coltype %}colMS{% endblock %}
{% block bodyclass %}dashboard{% endblock %}
{% block breadcrumbs %}{% endblock %}
{% block content %}
<div id="content-main">
{% if app_list %}
{% for app in app_list %}
{% if app.name in evennia_userapps %}
<div class="module">
<table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}">
<caption><a href="{{ app.app_url }}" class="section">{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</a></caption>
{% for model in app.models %}
<tr>
{% if model.perms.change %}
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
{% else %}
<th scope="row">{{ model.name }}</th>
{% endif %}
{% if model.perms.add %}
<td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td>
{% else %}
<td>&nbsp;</td>
{% endif %}
{% if model.perms.change %}
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
{% else %}
<td>&nbsp;</td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
{% endif %}
{% endfor %}
<h1>In-game entities</h1>
{% for app in app_list %}
{% if app.name in evennia_entityapps %}
<div class="module">
<table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}">
<caption><a href="{{ app.app_url }}" class="section">{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</a></caption>
{% for model in app.models %}
<tr>
{% if model.perms.change %}
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
{% else %}
<th scope="row">{{ model.name }}</th>
{% endif %}
{% if model.perms.add %}
<td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td>
{% else %}
<td>&nbsp;</td>
{% endif %}
{% if model.perms.change %}
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
{% else %}
<td>&nbsp;</td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
{% endif %}
{% endfor %}
<h1>Game setups and configs</h1>
{% for app in app_list %}
{% if app.name in evennia_setupapps %}
<div class="module">
<table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}">
<caption><a href="{{ app.app_url }}" class="section">{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</a></caption>
{% for model in app.models %}
<tr>
{% if model.perms.change %}
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
{% else %}
<th scope="row">{{ model.name }}</th>
{% endif %}
{% if model.perms.add %}
<td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td>
{% else %}
<td>&nbsp;</td>
{% endif %}
{% if model.perms.change %}
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
{% else %}
<td>&nbsp;</td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
{% endif %}
{% endfor %}
<h1>Connection protocols</h1>
{% for app in app_list %}
{% if app.name in evennia_connectapps %}
<div class="module">
<table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}">
<caption><a href="{{ app.app_url }}" class="section">{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</a></caption>
{% for model in app.models %}
<tr>
{% if model.perms.change %}
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
{% else %}
<th scope="row">{{ model.name }}</th>
{% endif %}
{% if model.perms.add %}
<td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td>
{% else %}
<td>&nbsp;</td>
{% endif %}
{% if model.perms.change %}
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
{% else %}
<td>&nbsp;</td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
{% endif %}
{% endfor %}
<h1>Website Specific</h1>
{% for app in app_list %}
{% if app.name in evennia_websiteapps %}
<div class="module">
<table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}">
<caption><a href="{{ app.app_url }}" class="section">{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</a></caption>
{% for model in app.models %}
<tr>
{% if model.perms.change %}
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
{% else %}
<th scope="row">{{ model.name }}</th>
{% endif %}
{% if model.perms.add %}
<td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td>
{% else %}
<td>&nbsp;</td>
{% endif %}
{% if model.perms.change %}
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
{% else %}
<td>&nbsp;</td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
{% endif %}
{% endfor %}
{% else %}
<p>{% trans "You don't have permission to edit anything." %}</p>
{% endif %}
</div>
{% endblock %}
{% block sidebar %}
<div id="content-related">
<div class="module" id="recent-actions-module">
<h2>{% trans 'Recent Actions' %}</h2>
<h3>{% trans 'My Actions' %}</h3>
{% load log %}
{% get_admin_log 10 as admin_log for_user user %}
{% if not admin_log %}
<p>{% trans 'None yet.' %}</p>
{% else %}
<ul class="actionlist">
{% for entry in admin_log %}
<li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">
{% if entry.is_deletion %}
{{ entry.object_repr }}
{% else %}
<a href="{{ entry.get_admin_url }}">{{ entry.object_repr }}</a>
{% endif %}
<br/>
{% if entry.content_type %}
<span class="mini quiet">{% filter capfirst %}{% trans entry.content_type.name %}{% endfilter %}</span>
{% else %}
<span class="mini quiet">{% trans 'Unknown content' %}</span>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{% endblock %}

View file

@ -29,16 +29,15 @@
<div id="header">
<div class="superHeader">
<span>Related Sites:</span>
<a href="http://evennia.com" title="The Python-based MUD server">Evennia</a> |
<a href="http://www.oswd.org/designs/search/designer/id/3013/" title="Other designs by haran">haran&rsquo;s Designs</a>
<!--span>Sites:</span-->
<a href="http://evennia.com" title="The Python-based MUD server">Evennia.com</a>
</div>
<div class="midHeader">
<h1 class="headerTitle" lang="la">{{game_name}}</h1>
<img src="/media/images/evennia_logo_small.png" align='left'/> <h1 class="headerTitle" lang="la">{{game_name}}</h1>
<div class="headerSubTitle" title="Slogan">
<!-- Insert a slogan here if you want -->
&nbsp;
{{game_slogan}} &nbsp;
</div>
<br class="doNotDisplay doNotPrint" />
@ -79,8 +78,12 @@
<div id="footer">
<span class="doNotPrint">
Template design by
<a href="http://www.oswd.org/designs/search/designer/id/3013/"
title="Other designs by haran">haran</a>.
Powered by
<a href="http://evennia.com">Evennia</a><br />
<a href="http://evennia.com">Evennia.</a>
<br \>
</span>
</div>
</body>

View file

@ -11,12 +11,18 @@
<div class="twoThirds noBorderOnLeft">
<h1>Welcome!</h1>
<p>Welcome to your new installation of Evennia, your friendly
neighborhood next-generation MUD server. You'll want to customize
this file, webtemplates/prosimii/index.html, to have a more
valid welcome message. Should you have any questions, concerns,
ideas, or bug reports, head over to the
<a href="http://evennia.com">Evennia community</a> and
speak up!</p>
neighborhood next-generation MUD server. You are looking at Evennia's web
presence, which can be expanded to a full-fledged site as
needed. Through the <a href="/admin">admin interface</a> you can view and edit the
database without logging into the game. Also take your time to
peruse our extensive online <a href="http://code.google.com/p/evennia/wiki/Index">documentation</a>.
<p>
Should you have any questions, concerns, bug reports, or
if you want to help out, don't hesitate to come join the
<a href="http://evennia.com">Evennia community</a> and get
your voice heard!
</p>
<i>(To edit this file, go to game/web/templates/prosimii/index.html.)</i>
</div>
<div class="oneThird">
@ -47,7 +53,7 @@
<h1>Recently Connected</h1>
<ul>
{% for player in players_connected_recent %}
<li>{{player.username}} -- <em>{{player.last_login|timesince}} ago</em></li>
<li>{{player.user.username}} -- <em>{{player.user.last_login|timesince}} ago</em></li>
{% endfor %}
</ul>
<p class="filler"><!-- Filler para to extend left vertical line --></p>
@ -56,10 +62,9 @@
<div class="quarter">
<h1>Database Stats</h1>
<ul>
<li>{{num_players}} players</li>
<li>{{num_rooms}} rooms</li>
<li>{{num_things}} things</li>
<li>{{num_exits}} exits</li>
<li>{{num_players_registered}} players</li>
<li>{{num_rooms}} rooms ({{num_exits}} exits) </li>
<li>{{num_objects}} objects total</li>
</ul>
</div>
@ -69,9 +74,9 @@
<a href="http://python.org">Python</a>, on top of the
<a href="http://twistedmatrix.com">Twisted</a> and
<a href="http://djangoproject.com">Django</a> frameworks. This
combination of technology allows for the quick and easy creation
of games, as simple as complex as one desires.</p>
combination of technologies allows for the quick and easy creation
of the game of your dreams - as simple or as complex as you like.</p>
</div>
</div>
{% endblock %}
{% endblock %}

View file

@ -4,7 +4,7 @@
{% endblock %}
{% block sidebar %}
{{sidebar}}
{{sidebar|safe}}
{% endblock %}
{% block content %}
@ -48,4 +48,4 @@
{% endif %}
| <a href="{{browse_url}}/?page={{pages}}&search_terms={{searchtext|urlencode}}">Last</a>
{% endblock %}
{% endblock %}

View file

@ -4,7 +4,7 @@
{% endblock %}
{% block sidebar %}
{{sidebar}}
{{sidebar|safe}}
{% endblock %}
{% block content %}
@ -16,4 +16,4 @@
{{search_form.search_terms}}
<button type="Submit">Search</button>
</form>
{% endblock %}
{% endblock %}

View file

@ -4,11 +4,11 @@
{% endblock %}
{% block sidebar %}
{{sidebar}}
{{sidebar|safe}}
{% endblock %}
{% block content %}
<h1 id="alt-layout">{{news_entry.topic.name}}: {{news_entry.title}}</h1>
<p class="newsDate">By {{news_entry.author.username}} on {{news_entry.date_posted|time}}</p>
<p class="newsSummary">{{news_entry.body}}</p>
{% endblock %}
{% endblock %}

View file

@ -13,7 +13,7 @@ Login
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
<form method="post" action=".">
<form method="post" action="."{% csrf_token %} >
<table>
<tr>
<td><label for="id_username">Username:</label></td>

View file

@ -10,25 +10,33 @@ from django.conf.urls.defaults import *
from django.conf import settings
from django.contrib import admin
# loop over all settings.INSTALLED_APPS and execute code in
# files named admin.py ine each such app (this will add those
# models to the admin site)
admin.autodiscover()
# Setup the root url tree from /
urlpatterns = patterns('',
# User Authentication
url(r'^accounts/login', 'django.contrib.auth.views.login'),
url(r'^accounts/logout', 'django.contrib.auth.views.logout'),
# Front page
url(r'^', include('game.web.apps.website.urls')),
url(r'^', include('game.web.website.urls')),
# News stuff
url(r'^news/', include('game.web.apps.news.urls')),
url(r'^news/', include('game.web.news.urls')),
# Page place-holder for things that aren't implemented yet.
url(r'^tbi/', 'game.web.apps.website.views.to_be_implemented'),
url(r'^tbi/', 'game.web.website.views.to_be_implemented'),
# Admin interface
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/(.*)', admin.site.root, name='admin'),
url(r'^admin/', include(admin.site.urls)),
#url(r'^admin/(.*)', admin.site.root, name='admin'),
# favicon
url(r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url':'/media/images/favicon.ico'}),
)
# If you'd like to serve media files via Django (strongly not recommended!),

View file

@ -1,7 +1,7 @@
import os, sys
# Calculate the path based on the location of the WSGI script.
web_dir = os.path.dirname(__file__)
web_dir = os.path.dirname(os.path.dirname(__file__))
workspace = os.path.dirname(os.path.dirname(web_dir))
sys.path.insert(0, workspace)

View file

@ -1,3 +1,4 @@
# WARNING: mod_python is no longer the recommended way to run Evennia's
# web front end. This file is no longer actively maintained and may
# no longer work. We suggest using mod_wsgi unless absolutely necessary.

View file

@ -43,7 +43,7 @@
# WSGI Config File. You'll need to update this path as well.
WSGIScriptAlias / /home/evennia/evennia/game/web/apache_wsgi.conf
WSGIScriptAlias / /home/evennia/evennia/game/web/utils/apache_wsgi.conf
#
## END EVENNIA WEB CONFIG

View file

@ -0,0 +1,46 @@
#
# This file defines global variables that will always be
# available in a view context without having to repeatedly
# include it. For this to work, this file is included in
# the settings file, in the TEMPLATE_CONTEXT_PROCESSORS
# tuple.
#
from django.db import models
from django.conf import settings
from src.utils.utils import get_evennia_version
# Determine the site name and server version
try:
GAME_NAME = settings.SERVERNAME.strip()
except AttributeError:
GAME_NAME = "Evennia"
SERVER_VERSION = get_evennia_version()
# Setup lists of the most relevant apps so
# the adminsite becomes more readable.
USER_RELATED = ['Auth', 'Players']
GAME_ENTITIES = ['Objects', 'Scripts', 'Comms', 'Help']
GAME_SETUP = ['Permissions', 'Config']
CONNECTIONS = ['Irc', 'Imc2']
WEBSITE = ['Flatpages', 'News', 'Sites']
# The main context processor function
def general_context(request):
"""
Returns common Evennia-related context stuff, which
is automatically added to context of all views.
"""
return {
'game_name': GAME_NAME,
'game_slogan': SERVER_VERSION,
'evennia_userapps': USER_RELATED,
'evennia_entityapps': GAME_ENTITIES,
'evennia_setupapps': GAME_SETUP,
'evennia_connectapps': CONNECTIONS,
'evennia_websiteapps':WEBSITE
}

View file

@ -0,0 +1,7 @@
#
# Define database entities for the app.
#
from django.db import models

10
game/web/website/urls.py Normal file
View file

@ -0,0 +1,10 @@
"""
This structures the (simple) structure of the
webpage 'application'.
"""
from django.conf.urls.defaults import *
urlpatterns = patterns('game.web.website.views',
(r'^$', 'page_index'),
)

View file

@ -1,9 +1,13 @@
from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext
from django.contrib.auth.models import User
from django.conf import settings
from src.objects.models import Object
from game.web.apps.news.models import NewsEntry
from src.config.models import ConfigValue
from src.objects.models import ObjectDB
from src.typeclasses.models import TypedObject
from src.players.models import PlayerDB
from game.web.news.models import NewsEntry
"""
This file contains the generic, assorted views that don't fall under one of
@ -21,23 +25,23 @@ def page_index(request):
# A QuerySet of recent news entries.
news_entries = NewsEntry.objects.all().order_by('-date_posted')[:fpage_news_entries]
# Dictionary containing database statistics.
objstats = Object.objects.object_totals()
# A QuerySet of the most recently connected players.
recent_players = Object.objects.get_recently_connected_users()[:fpage_player_limit]
recent_users = PlayerDB.objects.get_recently_connected_players()[:fpage_player_limit]
exits = ObjectDB.objects.get_objs_with_attr('_destination')
rooms = [room for room in ObjectDB.objects.filter(db_home=None) if room not in exits]
pagevars = {
"page_title": "Front Page",
"news_entries": news_entries,
"players_connected_recent": recent_players,
"num_players_connected": Object.objects.get_connected_players().count(),
"num_players_registered": Object.objects.num_total_players(),
"num_players_connected_recent": Object.objects.get_recently_connected_users().count(),
"num_players_registered_recent": Object.objects.get_recently_created_users().count(),
"num_players": objstats["players"],
"num_rooms": objstats["rooms"],
"num_things": objstats["things"],
"num_exits": objstats["exits"],
"players_connected_recent": recent_users,
"num_players_connected": ConfigValue.objects.conf('nr_sessions'),#len(PlayerDB.objects.get_connected_players()),
"num_players_registered": PlayerDB.objects.num_total_players(),
"num_players_connected_recent": len(PlayerDB.objects.get_recently_connected_players()),
"num_players_registered_recent": len(PlayerDB.objects.get_recently_created_players()),
"num_rooms": len(rooms),
"num_exits": len(exits),
"num_objects" : ObjectDB.objects.all().count()
}
context_instance = RequestContext(request)
@ -55,3 +59,5 @@ def to_be_implemented(request):
context_instance = RequestContext(request)
return render_to_response('tbi.html', pagevars, context_instance)

View file

@ -1,11 +1,11 @@
"""
This file sets the default encoding for the codebase to
UTF-8 instead of ascii. This allows for just about any
language to be used in-game.
This special Python config file sets the default encoding for
the codebase to UTF-8 instead of ascii. This allows for just
about any language to be used in-game.
It is not advisable to change the value set below, as
there will be a lot of encoding errors that result in
server crashes.
"""
import sys
sys.setdefaultencoding('utf-8')
sys.setdefaultencoding('utf-8')

View file

@ -1,19 +0,0 @@
"""
Player command alias management stuff.
"""
from src.config.models import CommandAlias
CMD_ALIAS_LIST = {}
def load_cmd_aliases():
"""
Load up our command aliases.
"""
alias_list = CommandAlias.objects.all()
# Reset the list.
CMD_ALIAS_LIST.clear()
for alias in alias_list:
CMD_ALIAS_LIST[alias.user_input] = alias.equiv_command
print ' Command Aliases Loaded: %i' % (len(CMD_ALIAS_LIST),)

284
src/cache/cache.py vendored
View file

@ -1,284 +0,0 @@
"""
The cache module implements a volatile and
semi-volatile storage
object mechanism for Evennia.
Volatile Cache:
Data stored using the Cache is stored in
memory (so requires no database access). The
drawback is that it will be lost upon a
reboot. It is however @reload-safe unless
explicitly flushed with @reload/cache (the cache
is not flushed with @reload/all)
Access I/O of the cache is normally done through
the object model, using e.g.
source_object.cache.variable = data
and
data = source_object.cache.variable
Semi-persistent Cache:
This form of cache works like the volatile cache but the
data will survive a reboot since the state is backed up
to the database at regular intervals (it is thus a save-point
scheme). How often the backup is done can be set in preferences.
Access I/O:
source_object.pcache = data
and
data = source_object.pcache
Whereas you can also access the cache(s) using
set_cache/get_cache and set_pcache/get_pcache
directly, you must continue to use these methods
on a particular piece of data once you start using them
(i.e. you won't be able to use dot-notation to retrieve
a piece of data saved explicitly using set_cache())
"""
from src.cache.models import PersistentCache
from src import logger
class Cache(object):
"""
Each Cache object is intended to store the volatile properties
of one in-game database object or one user-defined application.
By default, the object allows to safely reference variables on
itself also if it does not exist (so test = cache.var will
set test to None if cache has no attribute var instead of raising
a traceback). This allows for stable and transparent operation
during most circumstances.
Due to how the objects are stored in database (using pickle), the
object has a __safedot switch to deactivate the safe mode
of variables mentioned above; this is necessary in order to have
pickle work correctly (it does not like redefining __getattr__)
and should not be used for anything else.
Observe that this object in itself is not persistent, the only
thing determining if it is persistent is which of the global
variables (CACHE or PCACHE) it is saved in (and that there
exists an event to save the cache at regular intervals, use
@ps to check that this is the case).
"""
__safedot = True
def __getattr__(self, key):
"""
This implements a safe dot notation (i.e. it will not
raise an exception if a variable does not exist)
"""
if self.__safedot:
return self.__dict__.get(key, None)
else:
super(Cache, self).__getattr__(key)
def show(self):
"""
Return nice display of data.
"""
return ", ".join(key for key in sorted(self.__dict__.keys())
if key != '_Cache__safedot')
def store(self, key, value):
"""
Store data directly, without going through the dot notation.
"""
if key != '__safedot':
self.__dict__[key] = value
def retrieve(self, key):
"""
Retrieve data directly, without going through dot notation.
Note that this intentionally raises a KeyError if key is not
found. This is mainly used by get_cache to determine if a
new cache object should be created.
"""
return self.__dict__[key]
def pickle_yes(self):
"""
Since pickle cannot handle a custom getattr, we
need to deactivate it before pickling.
"""
self.__safedot = False
for data in (data for data in self.__dict__.values()
if type(data)==type(self)):
data.pickle_yes()
def pickle_no(self):
"""
Convert back from pickle mode to normal safe dot notation.
"""
self.__safedot = True
for data in (data for data in self.__dict__.values()
if type(data)==type(self)):
data.pickle_no()
def has_key(self, key):
"""
Decide if cache has a particular piece of data.
"""
return key in self.__dict__
def to_dict(self):
"""
Return all data stored in cache in
the form of a dictionary.
"""
return self.__dict__
def del_key(self, key):
"""
Clear cache data.
"""
if key in self.__dict__:
del self.__dict__[key]
# Cache access functions - these only deal with the default global
# cache and pcache.
# Volatile cache
def set_cache(cache_key, value):
"""
Set a value in the volatile cache (oftenmost this is done
through properties instead).
"""
CACHE.store(cache_key, value)
def get_cache(cache_key):
"""
Retrieve a cache object from the storage. This is primarily
used by the objects.models.Object.cache property.
cache_key - identifies the cache storage area (e.g. an object dbref)
reference - this bool describes if the function is called as part of
a obj.cache.cache_key.data contstruct.
"""
try:
return CACHE.retrieve(cache_key)
except:
CACHE.store(cache_key, Cache())
return CACHE.retrieve(cache_key)
def flush_cache(cache_key=None):
"""
Clears a particular cache_key from memory. If
no key is given, entire cache is flushed.
"""
global CACHE
if cache_key == None:
CACHE = Cache()
else:
CACHE.del_key(cache_key)
# Persistent cache
def set_pcache(cache_key, value):
"""
Set a value in the volatile cache (oftenmost this is done
through properties instead).
"""
PCACHE.store(cache_key, value)
def get_pcache(pcache_key):
"""
Retrieve a pcache object from the storage. This is primarily
used by the objects.models.Object.cache property.
cache_key - identifies the cache storage area (e.g. an object dbref)
"""
try:
return PCACHE.retrieve(pcache_key)
except KeyError:
PCACHE.store(pcache_key, Cache())
return PCACHE.retrieve(pcache_key)
def flush_pcache(pcache_key=None):
"""
Clears a particular cache_key from memory. If
no key is given, entire cache is flushed.
"""
global PCACHE
if pcache_key == None:
PCACHE = Cache()
elif pcache_key in PCACHE.__dict__:
PCACHE.del_key(pcache_key)
def show():
"""
Show objects stored in caches
"""
return CACHE.show(), PCACHE.show()
# Admin-level commands for initializing and saving/loading pcaches.
def init_pcache(cache_name=None):
"""
Creates the global pcache object in database.
(this is normally only called by initial_setup.py)
"""
from src.cache.managers.cache import GLOBAL_PCACHE_NAME
pcache = PersistentCache()
if cache_name:
pcache.cache_name = cache_name
else:
pcache.cache_name = GLOBAL_PCACHE_NAME
#initial save of the the empty pcache object to database
pcache.save()
#create empty storage object in cache
pcache.save_cache(Cache())
def save_pcache(cache_name=""):
"""
Force-save persistent cache right away.
"""
try:
if cache_name:
pcache = PersistentCache.objects.get(cache_name=cache_name)
else:
pcache = PersistentCache.objects.get_default_pcache()
except:
logger.log_errmsg("Save error: %s Pcache not initialized." % cache_name)
return
pcache.save_cache(PCACHE)
def load_pcache(cache_name=""):
"""
Load pcache from database storage. This is also called during
startup and fills the pcache with persistent cache data.
"""
global PCACHE
try:
if cache_name:
pcache = PersistentCache.objects.get(cache_name=cache_name)
return pcache
else:
pcache = PersistentCache.objects.get_default_pcache()
except:
logger.log_errmsg("Could not load %s: Pcache not found." % cache_name)
return
if pcache :
print " Loading persistent cache from disk."
unpacked = pcache.load_cache()
if unpacked:
PCACHE = unpacked
# Volatile Cache. This is a non-persistent cache. It will be lost upon
# a reboot. This can be referenced directly, but most
# transparently it's accessed through the object model.
CACHE = Cache()
# Persistent Cache. The system will make sure to save the contents of this
# cache at regular intervals, recovering it after a server
# reboot. It is accessed directly or through the object model.
PCACHE = Cache()

View file

@ -1,20 +0,0 @@
"""
Custom manager for Cache objects
"""
from django.db import models
# This is the (arbitrary, but consistent) name used by the
# global interval-saved (persistent) cache (this is
# used by initial_setup)
GLOBAL_PCACHE_NAME = "_global_persistent_cache"
class CacheManager(models.Manager):
"""
Custom cache manager.
"""
def get_default_pcache(self):
"""
Find and return the global pcache object.
"""
return self.get(cache_name=GLOBAL_PCACHE_NAME)

56
src/cache/models.py vendored
View file

@ -1,56 +0,0 @@
"""
This implements a database storage cache for storing global
cache data persistently.
It is intended to be used with an event timer for updating
semi-regularly (otherwise, object attributes are better to use
if full persistency is needed).
"""
from django.db import models
from django.conf import settings
from src.cache.managers.cache import CacheManager
# 091120 - there is a bug in cPickle for importing the
# custom cache objects; only normal pickle works. /Griatch
import pickle
#try:
# import cPickle as pickle
#except ImportError:
# import pickle
class PersistentCache(models.Model):
"""
Implements a simple pickled database object, without
using the in-game object attribute model.
"""
cache_name = models.CharField(max_length=255)
cache_data = models.TextField(blank=True)
objects = CacheManager()
class Meta:
permissions = settings.PERM_CACHE
def load_cache(self):
"""
Recovers cache from database storage.
"""
cache_data = str(self.cache_data)
#print "loading cache: %s" % cache_data
if cache_data:
cache_data = pickle.loads(cache_data)
cache_data.pickle_no()
return cache_data
else:
return None
def save_cache(self, cache_obj):
"""
Stores a cache as a pickle.
"""
#print "saving ... '%s': %s" % (cache_obj,cache_obj.show())
cache_obj.pickle_yes()
self.cache_data = pickle.dumps(cache_obj)
cache_obj.pickle_no()
self.save()

1
src/cache/views.py vendored
View file

@ -1 +0,0 @@
# Create your views here.

View file

@ -1,14 +0,0 @@
from django.contrib import admin
from src.channels.models import CommChannel, CommChannelMessage, CommChannelMembership
class CommChannelAdmin(admin.ModelAdmin):
list_display = ('name', 'ansi_name', 'owner', 'description', 'is_joined_by_default')
admin.site.register(CommChannel, CommChannelAdmin)
class CommChannelMembershipAdmin(admin.ModelAdmin):
list_display = ('channel', 'listener', 'user_alias', 'is_listening')
admin.site.register(CommChannelMembership, CommChannelMembershipAdmin)
class CommChannelMessageAdmin(admin.ModelAdmin):
list_display = ('channel', 'date_sent', 'message')
admin.site.register(CommChannelMessage, CommChannelMessageAdmin)

View file

@ -1,126 +0,0 @@
"""
Models for the help system.
"""
from django.db import models
from django.conf import settings
from django.contrib.auth.models import User, Group
from src.objects.models import Object
from src.ansi import parse_ansi
class CommChannel(models.Model):
"""
The CommChannel class represents a comsys channel in the vein of MUX/MUSH.
"""
name = models.CharField(max_length=255)
ansi_name = models.CharField(max_length=255)
owner = models.ForeignKey(Object, related_name="channel_owner_set")
description = models.CharField(max_length=80, blank=True, null=True)
is_joined_by_default = models.BooleanField(default=False)
req_grp = models.ManyToManyField(Group, blank=True, null=True)
def __str__(self):
return "%s" % (self.name,)
class Meta:
ordering = ['-name']
permissions = settings.PERM_CHANNELS
def get_name(self):
"""
Returns a channel's name.
"""
return self.name
def get_header(self):
"""
Returns the channel's header text, or what is shown before each channel
message.
"""
return parse_ansi(self.ansi_name)
def get_owner(self):
"""
Returns a channels' owner.
"""
return self.owner
def set_name(self, new_name):
"""
Rename a channel
"""
self.name = parse_ansi(new_name, strip_ansi=True)
self.header = "[%s]" % (parse_ansi(new_name),)
self.save()
def set_header(self, new_header):
"""
Sets a channel's header text.
"""
self.header = parse_ansi(new_header)
self.save()
def set_owner(self, new_owner):
"""
Sets a channel's owner.
"""
self.owner = new_owner
self.save()
def set_description(self, new_description):
"""
Sets a channel's description.
"""
self.description = new_description
self.save()
def controlled_by(self, pobject):
"""
Use this to see if another object controls the channel. This is means
that the specified object either owns the channel or has special
permissions to control it.
pobject: (Object) Player object to check for control.
"""
if pobject.is_superuser():
return True
if self.owner and self.owner.id == pobject.id:
# If said object owns the target, then give it the green.
return True
# They've failed to meet any of the above conditions.
return False
def get_default_chan_alias(self):
"""
Returns a default channel alias for the channel if none is provided.
"""
return self.name[:3].lower()
class CommChannelMembership(models.Model):
"""
Used to track which channels an Object is listening to.
"""
channel = models.ForeignKey(CommChannel, related_name="membership_set")
listener = models.ForeignKey(Object, related_name="channel_membership_set")
user_alias = models.CharField(max_length=10)
comtitle = models.CharField(max_length=25, blank=True)
is_listening = models.BooleanField(default=True)
def __str__(self):
return "%s: %s" % (self.channel.name, self.listener.name)
class CommChannelMessage(models.Model):
"""
A single logged channel message.
"""
channel = models.ForeignKey(CommChannel, related_name="msg_channel")
message = models.TextField()
date_sent = models.DateTimeField(editable=False, auto_now_add=True)
class Meta:
ordering = ['-date_sent']
def __str__(self):
return "%s: %s" % (self.channel.name, self.message)

View file

@ -1,564 +0,0 @@
"""
This is the command processing module. It is instanced once in the main
server module and the handle() function is hit every time a player sends
something.
"""
#import time
from traceback import format_exc
from django.conf import settings
#from django.contrib.contenttypes.models import ContentType
from objects.models import Object
import defines_global
import cmdtable
import statetable
import logger
import comsys
import alias_mgr
COMMAND_MAXLEN = settings.COMMAND_MAXLEN
class UnknownCommand(Exception):
"""
Throw this when a user enters an an invalid command.
"""
pass
class CommandNotInState(Exception):
"""
Throw this when a user tries a global command that exists, but
don't happen to be defined in the current game state.
err_string: The error string returned to the user.
"""
def __init__(self,err_string):
self.err_string = err_string
class ExitCommandHandler(Exception):
"""
Thrown when something happens and it's time to exit the command handler.
"""
pass
class Command(object):
# The source object that the command originated from.
source_object = None
# The session that the command originated from (optional)
session = None
# The entire raw, un-parsed command.
raw_input = None
# Just the root command. IE: if input is "look dog", this is just "look".
command_string = None
# A list of switches in the form of strings.
command_switches = []
# The un-parsed argument provided. IE: if input is "look dog", this is "dog".
command_argument = None
# list of tuples for possible multi-space commands and their arguments
command_alternatives = None
# A reference to the command function looked up in a command table.
command_function = None
# An optional dictionary that is passed through the command table as extra_vars.
extra_vars = None
def parse_command_switches(self):
"""
Splits any switches out of a command_string into the command_switches
list, and yanks the switches out of the original command_string.
"""
splitted_command = self.command_string.split('/')
self.command_switches = splitted_command[1:]
self.command_string = splitted_command[0]
def parse_command(self):
"""
Breaks the command up into the main command string, a list of switches,
and a string containing the argument provided with the command. More
specific processing is left up to the individual command functions.
The command can come in two forms:
command/switches arg
command_with_spaces arg
The first form is the normal one, used for administration and other commands
that benefit from the use of switches and options. The drawback is that it
can only consist of one single word (no spaces).
The second form, which does not accept switches, allows for longer command
names (e.g. 'press button' instead of pressbutton) and is mainly useful for
object-based commands for roleplay, puzzles etc.
"""
if not self.raw_input:
return
# add a space after the raw input; this cause split() to always
# create a list with at least two entries.
raw = "%s " % self.raw_input
cmd_words = raw.split(' ')
try:
if '/' in cmd_words[0]:
# if we have switches we directly go for the first command form.
command_string, command_argument = \
(inp.strip() for inp in raw.split(' ', 1))
if command_argument:
self.command_argument = command_argument
if command_string:
# we have a valid command, store and parse switches.
self.command_string = command_string
self.parse_command_switches()
else:
# no switches - we need to save a list of all possible command
# names up to the max-length allowed.
command_maxlen = min(COMMAND_MAXLEN, len(cmd_words))
command_alternatives = []
for spacecount in reversed(range(command_maxlen)):
# store all space-separated possible command names
# as tuples (commandname, args). They are stored with
# the longest possible name first.
try:
command_alternatives.append( (" ".join([w.strip()
for w in cmd_words[:spacecount+1]]).strip(),
" ".join(cmd_words[spacecount+1:]).strip()) )
except IndexError:
continue
if command_alternatives:
# store alternatives. Store the one-word command
# as the default command name.
one_word_command = command_alternatives.pop()
self.command_string = one_word_command[0]
self.command_argument = one_word_command[1]
self.command_alternatives = command_alternatives
except IndexError:
# this SHOULD only happen if raw_input is malformed
# (like containing only control characters).
pass
def __init__(self, source_object, raw_input, session=None):
"""
Instantiates the Command object and does some preliminary parsing.
"""
# If we get a unicode string with un-recognizable characters, replace
# them instead of throwing errors.
self.raw_input = raw_input
if not isinstance(raw_input, unicode):
self.raw_input = unicode(raw_input, errors='replace')
self.source_object = source_object
self.session = session
# The work starts here.
self.parse_command()
def arg_has_target(self):
"""
Returns true if the argument looks to be target-style. IE:
page blah=hi
kick ball=north
"""
return "=" in self.command_argument
def get_arg_targets(self, delim=','):
"""
Returns a list of targets from the argument. These happen before
the '=' sign and may be separated by a delimiter.
"""
# Make sure we even have a target (= sign).
if not self.arg_has_target():
return None
target = self.command_argument.split('=', 1)[0]
return [targ.strip() for targ in target.split(delim)]
def get_arg_target_value(self):
"""
In a case of something like: page bob=Hello there, the target is "bob",
while the value is "Hello there". This function returns the portion
of the command that takes place after the first equal sign.
"""
# Make sure we even have a target (= sign).
if not self.arg_has_target():
return None
return self.command_argument.split('=', 1)[1]
def match_idle(command):
"""
Matches against the 'idle' command. It doesn't actually do anything, but it
lets the users get around badly configured NAT timeouts that would cause
them to drop if they don't send or receive something from the connection
for a while.
"""
if command.session and command.command_string != 'idle' \
and command.command_string != None:
# Anything other than an 'idle' command or a blank return
# updates the public-facing idle time for the session.
command.session.count_command(silently=False)
elif command.session:
# User is hitting IDLE command. Don't update their publicly
# facing idle time, drop out of command handler immediately.
command.session.count_command(silently=True)
raise ExitCommandHandler
def match_alias(command):
"""
Checks to see if the entered command matches an alias. If so, replaces
the command_string with the correct command.
We do a dictionary lookup. If the key (the player's command_string) doesn't
exist on the dict, just keep the command_string the same. If the key exists,
its value replaces the command_string. For example, sa -> say.
"""
# See if there's an entry in the global alias table.
command.command_string = alias_mgr.CMD_ALIAS_LIST.get(
command.command_string,
command.command_string)
# Run aliasing on alternative command names (for commands with
# spaces in them)
if command.command_alternatives:
command_alternatives = []
for command_alternative in command.command_alternatives:
# create correct command_alternative tuples for storage
command_alternatives.append( (alias_mgr.CMD_ALIAS_LIST.get(
command_alternative[0],
command_alternative[0]),
command_alternative[1]) )
command.command_alternatives = command_alternatives
def get_aliased_message():
"""
Convenience sub-function to combine the lopped off command string
and arguments for posing, saying, and nospace posing aliases.
"""
if not command.command_argument:
return command.command_string[1:]
else:
return "%s %s" % (command.command_string[1:],
command.command_argument)
# Match against the single-character aliases of MUX/MUSH-dom.
first_char = command.command_string[0]
# Shortened say alias.
if first_char == '"':
command.command_argument = get_aliased_message()
command.command_string = "say"
# Shortened pose alias.
elif first_char == ':':
command.command_argument = get_aliased_message()
command.command_string = "pose"
# Pose without space alias.
elif first_char == ';':
command.command_argument = get_aliased_message()
command.command_string = "pose"
command.command_switches.insert(0, "nospace")
def match_channel(command):
"""
Match against a comsys channel or comsys command. If the player is talking
over a channel, replace command_string with @cemit. If they're entering
a channel manipulation command, perform the operation and kill the things
immediately with a True value sent back to the command handler.
This only works with PLAYER objects at this point in time.
"""
if command.session and comsys.plr_has_channel(command.session,
command.command_string, alias_search=True, return_muted=True):
calias = command.command_string
cname = comsys.plr_cname_from_alias(command.session, calias)
if command.command_argument == "who":
comsys.msg_cwho(command.source_object, cname)
raise ExitCommandHandler
elif command.command_argument == "on":
comsys.plr_chan_on(command.session, calias)
raise ExitCommandHandler
elif command.command_argument == "off":
comsys.plr_chan_off(command.session, calias)
raise ExitCommandHandler
elif command.command_argument == "last":
comsys.msg_chan_hist(command.source_object, cname)
raise ExitCommandHandler
if not command.command_argument:
command.source_object.emit_to("What do you want to say?")
raise ExitCommandHandler
second_arg = "%s=%s" % (cname, command.command_argument)
command.command_string = "@cemit"
command.command_switches = ["sendername", "quiet"]
command.command_argument = second_arg
return True
def match_exits(command,test=False):
"""
See if we can find an input match to exits.
command - the command we are testing for.
if a match, move obj and exit
test - just return Truee if it is an exit command,
do not move the object there.
"""
# If we're not logged in, don't check exits.
source_object = command.source_object
location = source_object.get_location()
if location == None:
logger.log_errmsg("cmdhandler.match_exits(): Object '%s' has no location." %
source_object)
return
# get all exits at location
exits = location.get_contents(filter_type=defines_global.OTYPE_EXIT)
# /not sure why this was done this way when one can import Object.
# Object = ContentType.objects.get(app_label="objects",
# model="object").model_class()
exit_matches = None
if command.command_alternatives:
# we have command alternatives (due to spaces in command definition).
# if so we replace the command_string appropriately.
for cmd_alternative in command.command_alternatives:
# the alternatives are ordered longest -> shortest.
exit_matches = Object.objects.list_search_object_namestr(exits,
cmd_alternative[0],
match_type="exact")
if exit_matches:
command.command_string = cmd_alternative[0]
command.command_argument = cmd_alternative[1]
break
if not exit_matches:
exit_matches = Object.objects.list_search_object_namestr(exits,
command.command_string,
match_type="exact")
if exit_matches:
if test:
return True
# Only interested in the first match.
targ_exit = exit_matches[0]
# An exit's home is its destination. If the exit has a None home value,
# it's not traversible.
if targ_exit.get_home():
# SCRIPT: See if the player can traverse the exit
if not targ_exit.scriptlink.default_lock(source_object):
lock_msg = targ_exit.get_attribute_value("lock_msg")
if lock_msg:
source_object.emit_to(lock_msg)
else:
source_object.emit_to("You can't traverse that exit.")
else:
source_object.move_to(targ_exit.get_home())
else:
source_object.emit_to("That exit leads nowhere.")
# We found a match, kill the command handler.
raise ExitCommandHandler
def command_table_lookup(command, command_table, eval_perms=True,
test=False, neighbor=None):
"""
Performs a command table lookup on the specified command table. Also
evaluates the permissions tuple.
The test flag only checks without manipulating the command
neighbor (object) If this is supplied, we are looking at a object table and
must check for locks.
In the case of one-word commands with switches, this is a
quick look-up. For non-switch commands the command might
however consist of several words separated by spaces up to
a certain max number of words. We don't know beforehand if one
of these match an entry in this particular command table. We search
them in order longest to shortest before deferring to the normal,
one-word assumption.
"""
cmdtuple = None
if command.command_alternatives:
#print "alternatives:",command.command_alternatives
#print command_table.ctable
# we have command alternatives (due to spaces in command definition)
for cmd_alternative in command.command_alternatives:
# the alternatives are ordered longest -> shortest.
cmdtuple = command_table.get_command_tuple(cmd_alternative[0])
if cmdtuple:
# we have a match, so this is the 'right' command to use
# with this particular command table.
command.command_string = cmd_alternative[0]
command.command_argument = cmd_alternative[1]
break
if not cmdtuple:
# None of the alternatives match, go with the default one-word name
cmdtuple = command_table.get_command_tuple(command.command_string)
if cmdtuple:
# if we get here we have found a command match in the table
if test:
# Check if this is just a test.
return True
# Check uselocks
if neighbor and not neighbor.scriptlink.use_lock(command.source_object):
# send an locked error message only if lock_desc is defined
lock_msg = neighbor.get_attribute_value("use_lock_msg")
if lock_msg:
command.source_object.emit_to(lock_msg)
raise ExitCommandHandler
return False
# If there is a permissions element to the entry, check perms.
if eval_perms and cmdtuple[1]:
if not command.source_object.has_perm_list(cmdtuple[1]):
command.source_object.emit_to(defines_global.NOPERMS_MSG)
raise ExitCommandHandler
# If flow reaches this point, user has perms and command is ready.
command.command_function = cmdtuple[0]
command.extra_vars = cmdtuple[2]
return True
def match_neighbor_ctables(command,test=False):
"""
Looks through the command tables of neighboring objects for command
matches.
test mode just checks if the command is a match, without manipulating
any commands.
"""
source_object = command.source_object
location = source_object.get_location()
if location:
# get all objects, including the current room
neighbors = location.get_contents() + [location] + source_object.get_contents()
for neighbor in neighbors:
#print "neighbor:", neighbor
obj_cmdtable = neighbor.get_cmdtable()
if obj_cmdtable and command_table_lookup(command, obj_cmdtable,
test=test,
neighbor=neighbor):
# If there was a command match, set the scripted_obj attribute
# for the script parent to pick up.
if test:
return True
command.scripted_obj = neighbor
return True
# No matches
return False
def handle(command, ignore_state=False):
"""
Use the spliced (list) uinput variable to retrieve the correct
command, or return an invalid command error.
We're basically grabbing the player's command by tacking
their input on to 'cmd_' and looking it up in the GenCommands
class.
ignore_state : ignore eventual statetable lookups completely.
"""
try:
# TODO: Protect against non-standard characters.
if not command.command_string:
# Nothing sent in of value, ignore it.
raise ExitCommandHandler
# No state by default.
state = None
if command.session and not command.session.logged_in:
# Not logged in, look through the unlogged-in command table.
command_table_lookup(command, cmdtable.GLOBAL_UNCON_CMD_TABLE,
eval_perms=False)
else:
# User is logged in.
# Match against the 'idle' command.
match_idle(command)
# See if this is an aliased command.
match_alias(command)
state = command.source_object.get_state()
state_cmd_table = statetable.GLOBAL_STATE_TABLE.get_cmd_table(state)
if state and state_cmd_table and not ignore_state:
# Caller is in a special state.
state_allow_exits, state_allow_obj_cmds = \
statetable.GLOBAL_STATE_TABLE.get_exec_rights(state)
state_lookup = True
if match_channel(command):
command_table_lookup(command, cmdtable.GLOBAL_CMD_TABLE)
state_lookup = False
# See if the user is trying to traverse an exit.
if state_allow_exits:
match_exits(command)
# check if this is a command defined on a nearby object.
if state_allow_obj_cmds and match_neighbor_ctables(command):
state_lookup = False
#if nothing has happened to change our mind, search the state table.
if state_lookup:
command_table_lookup(command, state_cmd_table)
else:
# Not in a state. Normal operation.
state = None # make sure, in case the object had a malformed statename.
# Check if the user is using a channel command.
match_channel(command)
# See if the user is trying to traverse an exit.
match_exits(command)
# check if this is a command defined on a nearby object
if not match_neighbor_ctables(command):
command_table_lookup(command, cmdtable.GLOBAL_CMD_TABLE)
"""
By this point, we assume that the user has entered a command and not
something like a channel or exit. Make sure that the command's
function reference is value and try to run it.
"""
if callable(command.command_function):
try:
# Move to the command function, passing the command object.
command.command_function(command)
except:
"""
This is a crude way of trapping command-related exceptions
and showing them to the user and server log. Once the
codebase stabilizes, we will probably want something more
useful or give them the option to hide exception values.
"""
if command.source_object:
command.source_object.emit_to("Untrapped error, please file a bug report:\n%s" %
(format_exc(),))
logger.log_errmsg("Untrapped error, evoker %s: %s" %
(command.source_object, format_exc()))
# Prevent things from falling through to UnknownCommand.
raise ExitCommandHandler
else:
# If we reach this point, we haven't matched anything.
if state:
# if we are in a state, it could be that the command exists, but
# it is temporarily not available. If so, we want a different error message.
if match_exits(command,test=True):
raise CommandNotInState("Movement is not possible right now.")
if match_neighbor_ctables(command,test=True):
raise CommandNotInState("You can not do that at the moment.")
if command_table_lookup(command,cmdtable.GLOBAL_CMD_TABLE,test=True):
raise CommandNotInState("This command is not available right now.")
raise UnknownCommand
except ExitCommandHandler:
# When this is thrown, just get out and do nothing. It doesn't mean
# something bad has happened.
pass
except CommandNotInState, e:
# The command exists, but not in the current state
if command.source_object != None:
# The logged-in error message
command.source_object.emit_to(e.err_string)
elif command.session != None:
# States are not available before login, so this should never
# be reached. But better safe than sorry.
command.session.msg("%s %s" % (e.err_string," (Type \"help\" for help.)"))
else:
pass
except UnknownCommand:
# Default fall-through. No valid command match.
if command.source_object != None:
# A typical logged in or object-based error message.
command.source_object.emit_to("Huh? (Type \"help\" for help.)")
elif command.session != None:
# This is hit when invalid commands are sent at the login screen
# primarily. Also protect against bad things in odd cases.
command.session.msg("Huh? (Type \"help\" for help.)")
else:
# We should never get to this point, but if we do, don't freak out.
pass

View file

@ -1,89 +0,0 @@
"""
Command Table Module
Each command entry consists of a key and a tuple containing a reference to the
command's function, and a tuple of the permissions to match against. The user
only need have one of the permissions in the permissions tuple to gain
access to the command. Obviously, super users don't have to worry about this
stuff. If the command is open to all (or you want to implement your own
privilege checking in the command function), use None in place of the
permissions tuple.
Commands are located under evennia/src/commands. server.py imports these
based on the value of settings.COMMAND_MODULES and
settings.CUSTOM_COMMAND_MODULES. Each module imports cmdtable.py and runs
add_command on the command table each command belongs to.
"""
from django.conf import settings
from src.helpsys import helpsystem
class CommandTable(object):
"""
Stores commands and performs lookups.
"""
ctable = None
def __init__(self):
# This ensures there are no leftovers when the class is instantiated.
self.ctable = {}
def add_command(self, command_string, function, priv_tuple=None,
extra_vals=None, help_category="", priv_help_tuple=None,
auto_help_override=None):
"""
Adds a command to the command table.
command_string: (string) Command string (IE: WHO, QUIT, look).
function: (reference) The command's function.
priv_tuple: (tuple) String tuple of permissions required for command.
extra_vals: (dict) Dictionary to add to the Command object.
Auto-help system: (this is only used if settings.HELP_AUTO_ENABLED is active)
help_category (str): An overall help category where auto-help will place
the help entry. If not given, 'General' is assumed.
priv_help_tuple (tuple) String tuple of permissions required to view this
help entry. If nothing is given, priv_tuple is used.
auto_help_override (bool/None): Override the value in settings.AUTO_HELP_ENABLED with the
value given. Use None to not override.
This can be useful when developing a new routine and
has made manual changes to help entries of other
commands in the database (and so do not want to use global
auto-help).
Note: the auto_help system also supports limited markup. You can divide your __doc__
with markers of any combinations of the forms
[[Title]]
[[Title, category]]
[[Title, (priv_tuple)]]
[[Title, category, (priv_tuple)]],
If such markers are found, the system will automatically create
separate help topics for each topic. Your main help entry will
default to the name of your command.
"""
self.ctable[command_string] = (function, priv_tuple, extra_vals)
if auto_help_override == None:
auto_help_override = settings.HELP_AUTO_ENABLED
if auto_help_override:
#add automatic help text from the command's doc string
topicstr = command_string
entrytext = function.__doc__
if not help_category:
help_category = "General"
if not priv_help_tuple:
priv_help_tuple = priv_tuple
helpsystem.edithelp.add_help_auto(topicstr, help_category,
entrytext, priv_help_tuple)
def get_command_tuple(self, func_name):
"""
Returns a reference to the command's tuple. If there are no matches,
returns false.
"""
return self.ctable.get(func_name, False)
# Global command table, for authenticated users.
GLOBAL_CMD_TABLE = CommandTable()
# Global unconnected command table, for unauthenticated users.
GLOBAL_UNCON_CMD_TABLE = CommandTable()

View file

@ -1,543 +0,0 @@
"""
Batch processor
The batch processor accepts 'batchcommand files' e.g 'batch.ev', containing a
sequence of valid evennia commands in a simple format. The engine
runs each command in sequence, as if they had been run at the terminal prompt.
This way entire game worlds can be created and planned offline; it is
especially useful in order to create long room descriptions where a
real offline text editor is often much better than any online text editor
or prompt.
Example of batch.ev file:
----------------------------
# batch file
# all lines starting with # are comments; they also indicate
# that a command definition is over.
@create box
# this comment ends the @create command.
@set box=desc: A large box.
Inside are some scattered piles of clothing.
It seems the bottom of the box is a bit loose.
# Again, this comment indicates the @set command is over. Note how
# the description could be freely added. Excess whitespace on a line
# is ignored. An empty line in the command definition is parsed as a \n
# (so two empty lines becomes a new paragraph).
@teleport #221
# (Assuming #221 is a warehouse or something.)
# (remember, this comment ends the @teleport command! Don'f forget it)
@drop box
# Done, the box is in the warehouse! (this last comment is not necessary to
# close the @drop command since it's the end of the file)
-------------------------
An example batch file is found in game/gamesrc/commands/examples.
"""
import os
import re
from django.conf import settings
from src import logger
from src import defines_global
from src.cmdtable import GLOBAL_CMD_TABLE
from src.statetable import GLOBAL_STATE_TABLE
#global defines for storage
STATENAME="_interactive batch processor"
cwhite = r"%cn%ch%cw"
cred = r"%cn%ch%cr"
cgreen = r"%cn%ci%cg"
cyellow = r"%cn%ch%cy"
cnorm = r"%cn"
def read_batchbuild_file(filename):
"""
This reads the contents of batchfile.
Filename is considered to be the name of the batch file
relative the directory specified in settings.py
"""
filename = os.path.abspath("%s/%s" % (settings.BATCH_IMPORT_PATH, filename))
try:
f = open(filename)
except IOError:
logger.log_errmsg("file %s not found." % filename)
return None
lines = f.readlines()
f.close()
return lines
def parse_batchbuild_file(filename):
"""
This parses the lines of a batchfile according to the following
rules:
1) # at the beginning of a line marks the end of the command before it.
It is also a comment and any number of # can exist on subsequent
lines (but not inside comments).
2) Commands are placed alone at the beginning of a line and their
arguments are considered to be everything following (on any
number of lines) until the next comment line beginning with #.
3) Newlines are ignored in command definitions
4) A completely empty line in a command line definition is condered
a newline (so two empty lines is a paragraph).
5) Excess spaces and indents inside arguments are stripped.
"""
#read the indata, if possible.
lines = read_batchbuild_file(filename)
if not lines:
logger.log_errmsg("File %s not found." % filename)
return
#helper function
def identify_line(line):
"""
Identifies the line type (comment, commanddef or empty)
"""
try:
if line.strip()[0] == '#':
return "comment"
else:
return "commanddef"
except IndexError:
return "empty"
commands = []
curr_cmd = ""
#purge all superfluous whitespace and newlines from lines
reg1 = re.compile(r"\s+")
lines = [reg1.sub(" ",l) for l in lines]
#parse all command definitions into a list.
for line in lines:
typ = identify_line(line)
if typ == "commanddef":
curr_cmd += line
elif typ == "empty" and curr_cmd:
curr_cmd += "\r\n"
else: #comment
if curr_cmd:
commands.append(curr_cmd.strip())
curr_cmd = ""
if curr_cmd: commands.append(curr_cmd.strip())
#second round to clean up now merged line edges etc.
reg2 = re.compile(r"[ \t\f\v]+")
commands = [reg2.sub(" ",c) for c in commands]
#remove eventual newline at the end of commands
commands = [c.strip('\r\n') for c in commands]
return commands
def batch_process(source_object, commands):
"""
Process a file straight off.
"""
for i, command in enumerate(commands):
cmdname = command[:command.find(" ")]
source_object.emit_to("%s== %s%02i/%02i: %s %s%s" % (cgreen,cwhite,i+1,
len(commands),
cmdname,
cgreen,"="*(50-len(cmdname))))
source_object.execute_cmd(command)
#main access function @batchprocess
def cmd_batchprocess(command):
"""
@batchprocess - build from batch file
Usage:
@batchprocess[/interactive] <filename with full path>
Runs batches of commands from a batchfile. This is a
superuser command, intended for large-scale offline world
development.
Interactive mode allows the user more control over the
processing of the file.
"""
source_object = command.source_object
#check permissions; this is a superuser only command.
if not source_object.is_superuser():
source_object.emit_to(defines_global.NOPERMS_MSG)
return
args = command.command_argument
if not args:
source_object.emit_to("Usage: @batchprocess[/interactive] <path/to/file>")
return
filename = args.strip()
#parse indata file
commands = parse_batchbuild_file(filename)
if not commands:
string = "'%s' not found.\nYou have to supply the real path "
string += "of the file relative to \nyour batch-file directory (%s)."
source_object.emit_to(string % (filename, settings.BATCH_IMPORT_PATH))
return
switches = command.command_switches
if switches and switches[0] in ['inter','interactive']:
# Allow more control over how batch file is executed
if source_object.has_flag("ADMIN_NOSTATE"):
source_object.unset_flag("ADMIN_NOSTATE")
string = cred + "\nOBS: Flag ADMIN_NOSTATE unset in order to "
string += "run Interactive mode. Don't forget to re-set "
string += "it (if you need it) after you're done."
source_object.emit_to(string)
# Set interactive state directly
source_object.cache.state = STATENAME
# Store work data in cache
source_object.cache.batch_cmdstack = commands
source_object.cache.batch_stackptr = 0
source_object.cache.batch_filename = filename
source_object.emit_to("\nBatch processor - Interactive mode for %s ..." % filename)
show_curr(source_object)
else:
set_admin_nostate = False
if not source_object.has_flag("ADMIN_NOSTATE"):
source_object.set_flag("ADMIN_NOSTATE")
set_admin_nostate = True
source_object.emit_to("Running Batch processor - Automatic mode for %s ..." % filename)
source_object.clear_state()
batch_process(source_object, commands)
source_object.emit_to("%s== Batchfile '%s' applied." % (cgreen,filename))
if set_admin_nostate:
source_object.unset_flag("ADMIN_NOSTATE")
GLOBAL_CMD_TABLE.add_command("@batchprocess", cmd_batchprocess,
priv_tuple=("genperms.process_control",), help_category="Building")
# The Interactive batch processor state
def show_curr(source_object,showall=False):
"Show the current command."
ptr = source_object.cache.batch_stackptr
commands = source_object.cache.batch_cmdstack
if ptr >= len(commands):
s = "\n You have reached the end of the batch file."
s += "\n Use qq to exit or bb to go back."
source_object.emit_to(s)
source_object.cache.batch_stackptr = len(commands)-1
show_curr(source_object)
return
command = commands[ptr]
cmdname = command[:command.find(" ")]
s = "%s== %s%02i/%02i: %s %s===== %s %s%s" % (cgreen,cwhite,
ptr+1,len(commands),
cmdname,cgreen,
"(hh for help)",
"="*(35-len(cmdname)),
cnorm)
if showall:
s += "\n%s" % command
source_object.emit_to(s)
def process_commands(source_object, steps=0):
"process one or more commands "
ptr = source_object.cache.batch_stackptr
commands = source_object.cache.batch_cmdstack
if steps:
try:
cmds = commands[ptr:ptr+steps]
except IndexError:
cmds = commands[ptr:]
for cmd in cmds:
#this so it is kept in case of traceback
source_object.cache.batch_stackptr = ptr + 1
#show_curr(source_object)
source_object.execute_cmd(cmd)
else:
#show_curr(source_object)
source_object.execute_cmd(commands[ptr])
def reload_stack(source_object):
"reload the stack"
commands = parse_batchbuild_file(source_object.cache.batch_filename)
if commands:
ptr = source_object.cache.batch_stackptr
else:
source_object.emit_to("Commands in file could not be reloaded. Was it moved?")
def move_in_stack(source_object, step=1):
"store data in stack"
N = len(source_object.cache.batch_cmdstack)
currpos = source_object.cache.batch_stackptr
source_object.cache.batch_stackptr = max(0,min(N-1,currpos+step))
def exit_state(source_object):
"Quit the state"
source_object.cache.batch_cmdstack = None
source_object.cache.batch_stackptr = None
source_object.cache.batch_filename = None
# since clear_state() is protected against exiting the interactive mode
# (to avoid accidental drop-outs by rooms clearing a player's state),
# we have to clear the state directly here.
source_object.cache.state = None
def cmd_state_ll(command):
"""
ll
Look at the full source for the current
command definition.
"""
show_curr(command.source_object,showall=True)
def cmd_state_pp(command):
"""
pp
Process the currently shown command definition.
"""
process_commands(command.source_object)
def cmd_state_rr(command):
"""
rr
Reload the batch file, keeping the current
position in it.
"""
reload_stack(command.source_object)
command.source_object.emit_to("\nFile reloaded. Staying on same command.\n")
show_curr(command.source_object)
def cmd_state_rrr(command):
"""
rrr
Reload the batch file, starting over
from the beginning.
"""
reload_stack(command.source_object)
command.source_object.cache.batch_stackptr = 0
command.source_object.emit_to("\nFile reloaded. Restarting from top.\n")
show_curr(command.source_object)
def cmd_state_nn(command):
"""
nn
Go to next command. No commands are executed.
"""
source_object = command.source_object
arg = command.command_argument
if arg and arg.isdigit():
step = int(command.command_argument)
else:
step = 1
move_in_stack(source_object, step)
show_curr(source_object)
def cmd_state_nl(command):
"""
nl
Go to next command, viewing its full source.
No commands are executed.
"""
source_object = command.source_object
arg = command.command_argument
if arg and arg.isdigit():
step = int(command.command_argument)
else:
step = 1
move_in_stack(source_object, step)
show_curr(source_object, showall=True)
def cmd_state_bb(command):
"""
bb
Backwards to previous command. No commands
are executed.
"""
source_object = command.source_object
arg = command.command_argument
if arg and arg.isdigit():
step = -int(command.command_argument)
else:
step = -1
move_in_stack(source_object, step)
show_curr(source_object)
def cmd_state_bl(command):
"""
bl
Backwards to previous command, viewing its full
source. No commands are executed.
"""
source_object = command.source_object
arg = command.command_argument
if arg and arg.isdigit():
step = -int(command.command_argument)
else:
step = -1
move_in_stack(source_object, step)
show_curr(source_object, showall=True)
def cmd_state_ss(command):
"""
ss [steps]
Process current command, then step to the next
one. If steps is given,
process this many commands.
"""
source_object = command.source_object
arg = command.command_argument
if arg and arg.isdigit():
step = int(command.command_argument)
else:
step = 1
process_commands(source_object,step)
show_curr(source_object)
def cmd_state_sl(command):
"""
sl [steps]
Process current command, then step to the next
one, viewing its full source. If steps is given,
process this many commands.
"""
source_object = command.source_object
arg = command.command_argument
if arg and arg.isdigit():
step = int(command.command_argument)
else:
step = 1
process_commands(source_object,step)
show_curr(source_object, showall=True)
def cmd_state_cc(command):
"""
cc
Continue to process all remaining
commands.
"""
source_object = command.source_object
N = len(source_object.cache.batch_cmdstack)
ptr = source_object.cache.batch_stackptr
step = N - ptr
process_commands(source_object,step)
exit_state(source_object)
source_object.emit_to("Finished processing batch file.")
def cmd_state_jj(command):
"""
j <command number>
Jump to specific command number
"""
source_object = command.source_object
arg = command.command_argument
if arg and arg.isdigit():
no = int(command.command_argument)-1
else:
source_object.emit_to("You must give a number index.")
return
ptr = source_object.cache.batch_stackptr
step = no - ptr
move_in_stack(source_object, step)
show_curr(source_object)
def cmd_state_jl(command):
"""
jl <command number>
Jump to specific command number and view its full source.
"""
global STACKPTRS
source_object = command.source_object
arg = command.command_argument
if arg and arg.isdigit():
no = int(command.command_argument)-1
else:
source_object.emit_to("You must give a number index.")
return
ptr = source_object.cache.batch_stackptr
step = no - ptr
move_in_stack(source_object, step)
show_curr(source_object, showall=True)
def cmd_state_qq(command):
"""
qq
Quit the batchprocessor.
"""
exit_state(command.source_object)
command.source_object.emit_to("Aborted interactive batch mode.")
def cmd_state_hh(command):
"Help command"
s = """
Interactive batch processing commands:
nn [steps] - next command (no processing)
nl [steps] - next & look
bb [steps] - back to previous command (no processing)
bl [steps] - back & look
jj <N> - jump to command nr N (no processing)
jl <N> - jump & look
pp - process currently shown command (no step)
ss [steps] - process & step
sl [steps] - process & step & look
ll - look at full definition of current command
rr - reload batch file (stay on current)
rrr - reload batch file (start from first)
hh - this help list
cc - continue processing to end, then quit.
qq - quit (abort all remaining commands)
"""
command.source_object.emit_to(s)
#create the state; we want it as open as possible so we can do everything
# in our batch processing.
GLOBAL_STATE_TABLE.add_state(STATENAME,global_cmds='all',
allow_exits=True,allow_obj_cmds=True,exit_command=True)
#add state commands
GLOBAL_STATE_TABLE.add_command(STATENAME,"nn",cmd_state_nn)
GLOBAL_STATE_TABLE.add_command(STATENAME,"nl",cmd_state_nl)
GLOBAL_STATE_TABLE.add_command(STATENAME,"bb",cmd_state_bb)
GLOBAL_STATE_TABLE.add_command(STATENAME,"bl",cmd_state_bl)
GLOBAL_STATE_TABLE.add_command(STATENAME,"jj",cmd_state_jj)
GLOBAL_STATE_TABLE.add_command(STATENAME,"jl",cmd_state_jl)
GLOBAL_STATE_TABLE.add_command(STATENAME,"pp",cmd_state_pp)
GLOBAL_STATE_TABLE.add_command(STATENAME,"ss",cmd_state_ss)
GLOBAL_STATE_TABLE.add_command(STATENAME,"sl",cmd_state_sl)
GLOBAL_STATE_TABLE.add_command(STATENAME,"cc",cmd_state_cc)
GLOBAL_STATE_TABLE.add_command(STATENAME,"ll",cmd_state_ll)
GLOBAL_STATE_TABLE.add_command(STATENAME,"rr",cmd_state_rr)
GLOBAL_STATE_TABLE.add_command(STATENAME,"rrr",cmd_state_rrr)
GLOBAL_STATE_TABLE.add_command(STATENAME,"hh",cmd_state_hh)
GLOBAL_STATE_TABLE.add_command(STATENAME,"qq",cmd_state_qq)

364
src/commands/cmdhandler.py Normal file
View file

@ -0,0 +1,364 @@
"""
Command handler
This module contains the infrastructure for accepting commands on the
command line. The process is as follows:
1) The calling object (caller) inputs a string and triggers the command parsing system.
2) The system checks the state of the caller - loggedin or not
3) Depending on the login/not state, it collects cmdsets from different sources:
not logged in - uses the single cmdset in settings.CMDSET_UNLOGGEDIN
normal - gathers command sets from many different sources (shown in dropping priority):
channels - all available channel names are auto-created into a cmdset, to allow
for giving the channel name and have the following immediately
sent to the channel. The sending is performed by the CMD_CHANNEL
system command.
exits - exits from a room are dynamically made into a cmdset for matching,
allowing the player to give just the name and thus traverse the exit.
If a match, the traversing is handled by the CMD_EXIT system command.
object cmdsets - all objects at caller's location are scanned for non-empty
cmdsets.
caller - the caller is searched for its currently active cmdset.
4) All the gathered cmdsets (if more than one) are merged into one using the cmdset priority rules.
5) If no cmdsets where found, we raise NoCmdSet exception. This should not happen, at least the
caller should have a default cmdset available at all times. --> Finished
6) The raw input string is parsed using the parser defined by settings.CMDPARSER. It returns
a special match object since a command may consist of many space-separated words and we
thus have to match them all.
7) If no command was supplied, we search the merged cmdset for system command CMD_NOINPUT
and branches to execute that. --> Finished
8) We match the the match object against the merged cmdset and the eventual priorities given it
by the parser. The result is a list of command matches tied to their respective match object.
9) If we found no matches, branch to system command CMD_NOMATCH --> Finished
10) If we were unable to weed out multiple matches, branch CMD_MULTIMATCH --> Finished
11) If we have a single match, we now check user permissions.
not permissions: branch to system command CMD_NOPERM --> Finished
12) We analyze the matched command to determine if it is a channel-type command, that is
a command auto-created to represent a valid comm channel. If so, we see if CMD_CHANNEL is
custom-defined in the merged cmdset, or we launch the auto-created command
direclty --> Finished
13 We next check if this is an exit-type command, that is, a command auto-created to represent
an exit from this room. If so we check for custom CMD_EXIT in cmdset or launch
the auto-generated command directly --> Finished
14) At this point we have found a normal command. We assign useful variables to it, that
will be available to the command coder at run-time.
When launching the command (normal, or system command both), two hook functions are called
in sequence, cmd.parse() followed by cmd.func(). It's up to the implementation as to how to
use this to most advantage.
"""
from traceback import format_exc
from django.conf import settings
from src.comms.channelhandler import CHANNELHANDLER
from src.commands.cmdsethandler import import_cmdset
from src.objects.exithandler import EXITHANDLER
from src.utils import logger
#This switches the command parser to a user-defined one.
# You have to restart the server for this to take effect.
try:
CMDPARSER = __import__(settings.ALTERNATE_PARSER, fromlist=[True]).cmdparser
except Exception:
from src.commands.cmdparser import cmdparser as CMDPARSER
# There are a few system-hardcoded command names. These
# allow for custom behaviour when the command handler hits
# special situations -- it then calls a normal Command
# that you can customize!
CMD_NOINPUT = "__noinput_command"
CMD_NOMATCH = "__nomatch_command"
CMD_MULTIMATCH = "__multimatch_command"
CMD_NOPERM = "__noperm_command"
CMD_CHANNEL = "__send_to_channel"
CMD_EXIT = "__move_to_exit"
class NoCmdSets(Exception):
"No cmdsets found. Critical error."
pass
class ExecSystemCommand(Exception):
"Run a system command"
def __init__(self, syscmd, sysarg):
self.args = (syscmd, sysarg) # needed by exception error handling
self.syscmd = syscmd
self.sysarg = sysarg
def get_and_merge_cmdsets(caller):
"""
Gather all relevant cmdsets and merge them. Note
that this is only relevant for logged-in callers.
"""
# The calling object's cmdset
try:
caller_cmdset = caller.cmdset.current
except AttributeError:
caller_cmdset = None
# All surrounding cmdsets
channel_cmdset = None
exit_cmdset = None
local_objects_cmdsets = [None]
#print "cmdset flags:", caller_cmdset.no_channels, caller_cmdset.no_exits, caller_cmdset.no_objs
if not caller_cmdset.no_channels:
# Make cmdsets out of all valid channels
channel_cmdset = CHANNELHANDLER.get_cmdset(caller)
if not caller_cmdset.no_exits:
# Make cmdsets out of all valid exits in the room
exit_cmdset = EXITHANDLER.get_cmdset(caller)
location = caller.location
if location and not caller_cmdset.no_objs:
# Gather all cmdsets stored on objects in the room
local_objlist = location.contents
local_objects_cmdsets = [obj.cmdset.current
for obj in local_objlist
if obj.cmdset.outside_access
and obj.cmdset.allow_outside_access(caller)]
# Merge all command sets into one
# (the order matters, the higher-prio cmdsets are merged last)
cmdset = caller_cmdset
for obj_cmdset in local_objects_cmdsets:
try:
cmdset = obj_cmdset + cmdset
except TypeError:
pass
try:
cmdset = exit_cmdset + cmdset
except TypeError:
pass
try:
cmdset = channel_cmdset + cmdset
except TypeError:
pass
return cmdset
def match_command(cmd_candidates, cmdset, logged_caller=None):
"""
Try to match the command against one of the
cmd_candidates.
logged_caller - a logged-in object, if any.
"""
# Searching possible command matches in the given cmdset
matches = []
prev_found_cmds = [] # to avoid aliases clashing with themselves
for cmd_candidate in cmd_candidates:
cmdmatches = list(set([cmd for cmd in cmdset
if cmd == cmd_candidate.cmdname and
cmd not in prev_found_cmds]))
matches.extend([(cmd_candidate, cmd) for cmd in cmdmatches])
prev_found_cmds.extend(cmdmatches)
if not matches or len(matches) == 1:
return matches
# Do our damndest to resolve multiple matches
# First try candidate priority to separate them
top_ranked = []
top_priority = None
for match in matches:
if top_priority == None \
or match[0].priority >= top_priority:
top_priority = match[0].priority
top_ranked.append(match)
matches = top_ranked
if not matches or len(matches) == 1:
return matches
# still multiplies. Check if player supplied
# an obj name on the command line. We know they
# all have at least the same cmdname and obj_key
# at this point.
if logged_caller:
try:
local_objlist = logged_caller.location.contents
match = matches[0]
top_ranked = [obj for obj in local_objlist
if match[0].obj_key == obj.name
and any(cmd == match[0].cmdname
for cmd in obj.cmdset.current)]
if top_ranked:
matches = \
[(match[0],
obj.cmdset.current.get(match[0].cmdname))
for obj in top_ranked]
except Exception:
logger.log_trace()
# regardless what we have at this point, we have to be content
return matches
# Main command-handler function
def cmdhandler(caller, raw_string, unloggedin=False):
"""
This is the main function to handle any string sent to the engine.
"""
try: # catch bugs in cmdhandler itself
try: # catch special-type commands
if unloggedin:
# not logged in, so it's just one cmdset we are interested in
cmdset = import_cmdset(settings.CMDSET_UNLOGGEDIN, caller)
else:
# We are logged in, collect all relevant cmdsets and merge
cmdset = get_and_merge_cmdsets(caller)
#print cmdset
if not cmdset:
# this is bad and shouldn't happen.
raise NoCmdSets
raw_string = raw_string.strip()
if not raw_string:
# Empty input. Test for system command instead.
syscmd = cmdset.get(CMD_NOINPUT)
sysarg = ""
raise ExecSystemCommand(syscmd, sysarg)
# Parse the input string into command candidates
cmd_candidates = CMDPARSER(raw_string)
#string ="Command candidates"
#for cand in cmd_candidates:
# string += "\n %s || %s" % (cand.cmdname, cand.args)
#caller.msg(string)
# Try to produce a unique match between the merged
# cmdset and the candidates.
if unloggedin:
matches = match_command(cmd_candidates, cmdset)
else:
matches = match_command(cmd_candidates, cmdset, caller)
#print "matches: ", matches
# Deal with matches
if not matches:
# No commands match our entered command
syscmd = cmdset.get(CMD_NOMATCH)
if syscmd:
sysarg = raw_string
else:
sysarg = "Huh? (Type \"help\" for help)"
raise ExecSystemCommand(syscmd, sysarg)
if len(matches) > 1:
# We have a multiple-match
syscmd = cmdset.get(CMD_MULTIMATCH)
matchstring = ", ".join([match[0].cmdname
for match in matches])
if syscmd:
sysarg = matchstring
else:
sysarg = "There were multiple matches:\n %s"
sysarg = sysarg % matchstring
raise ExecSystemCommand(syscmd, sysarg)
# At this point, we have a unique command match.
cmd_candidate, cmd = matches[0]
# Check so we have permission to use this command.
if not cmd.has_perm(caller):
cmd = cmdset.get(CMD_NOPERM)
if cmd:
sysarg = raw_string
else:
sysarg = "Huh? (type 'help' for help)"
raise ExecSystemCommand(cmd, sysarg)
# Check if this is a Channel match.
if hasattr(cmd, 'is_channel') and cmd.is_channel:
# even if a user-defined syscmd is not defined, the
# found cmd is already a system command in its own right.
syscmd = cmdset.get(CMD_CHANNEL)
if syscmd:
# replace system command with custom version
cmd = syscmd
sysarg = "%s:%s" % (cmd_candidate.cmdname,
cmd_candidate.args)
raise ExecSystemCommand(cmd, sysarg)
# Check if this is an Exit match.
if hasattr(cmd, 'is_exit') and cmd.is_exit:
# even if a user-defined syscmd is not defined, the
# found cmd is already a system command in its own right.
syscmd = cmdset.get(CMD_EXIT)
if syscmd:
# replace system command with custom version
cmd = syscmd
sysarg = raw_string
raise ExecSystemCommand(cmd, sysarg)
# A normal command.
# Assign useful variables to the instance
cmd.caller = caller
cmd.cmdstring = cmd_candidate.cmdname
cmd.args = cmd_candidate.args
cmd.cmdset = cmdset
if hasattr(cmd, 'obj') and hasattr(cmd.obj, 'scripts'):
# cmd.obj are automatically made available.
# we make sure to validate its scripts.
cmd.obj.scripts.validate()
# Parse and execute
cmd.parse()
cmd.func()
# Done!
except ExecSystemCommand, exc:
# Not a normal command: run a system command, if available,
# or fall back to a return string.
syscmd = exc.syscmd
sysarg = exc.sysarg
if syscmd:
syscmd.caller = caller
syscmd.cmdstring = syscmd.key
syscmd.args = sysarg
syscmd.cmdset = cmdset
if hasattr(cmd, 'obj') and hasattr(cmd.obj, 'scripts'):
# cmd.obj is automatically made available.
# we make sure to validate its scripts.
cmd.obj.scripts.validate()
# parse and run the command
syscmd.parse()
syscmd.func()
elif sysarg:
caller.msg(exc.sysarg)
except NoCmdSets:
# Critical error.
string = "No command sets found! This is a sign of a critical bug.\n"
string += "The error was logged.\n"
string += "If logging out/in doesn't solve the problem, try to "
string += "contact the server admin through some other means "
string += "for assistance."
caller.msg(string)
logger.log_errmsg("No cmdsets found: %s" % caller)
except Exception:
# We should not end up here. If we do, it's a programming bug.
string = "%s\nAbove traceback is from an untrapped error."
string += " Please file a bug report."
logger.log_trace(string)
caller.msg(string % format_exc())
except Exception:
# This catches exceptions in cmdhandler exceptions themselves
string = "%s\nAbove traceback is from a Command handler bug."
string += " Please contact an admin."
logger.log_trace(string)
caller.msg(string % format_exc())
#----------------------------------------------------- end cmdhandler

174
src/commands/cmdparser.py Normal file
View file

@ -0,0 +1,174 @@
"""
The default command parser. Use your own by assigning
settings.ALTERNATE_PARSER to a Python path to a module containing the
replacing cmdparser function. The replacement parser must
return a CommandCandidates object.
"""
import re
from django.conf import settings
# This defines how many space-separated words may at most be in a command.
COMMAND_MAXLEN = settings.COMMAND_MAXLEN
# These chars (and space) end a command name and may
# thus never be part of a command name. Exception is
# if the char is the very first character - the char
# is then treated as the name of the command.
SPECIAL_CHARS = ["/", "\\", "'", '"', ":", ";", "\-", '#', '=', '!']
# Pre-compiling the regular expression is more effective
REGEX = re.compile(r"""["%s"]""" % ("".join(SPECIAL_CHARS)))
class CommandCandidate(object):
"""
This is a convenient container for one possible
combination of command names that may appear if we allow
many-word commands.
"""
def __init__(self, cmdname, args=0, priority=0, obj_key=None):
"initiate"
self.cmdname = cmdname
self.args = args
self.priority = priority
self.obj_key = obj_key
def __str__(self):
return "<cmdname:'%s',args:'%s'>" % (self.cmdname, self.args)
#
# The command parser
#
def cmdparser(raw_string):
"""
This function parses the raw string into three parts: command
name(s), keywords(if any) and arguments(if any). It returns a
CommandCandidates object. It should be general enough for most
game implementations, but you can also overwrite it should you
wish to implement some completely different way of handling and
ranking commands. Arguments and keywords are parsed/dealt with by
each individual command's parse() command.
The cmdparser understand the following command combinations (where
[] marks optional parts and <char> is one of the SPECIAL_CHARs
defined globally.):
[<char>]cmdname[ cmdname2 cmdname3 ...][<char>] [the rest]
A command may contain spaces, but never any of of the <char>s. A
command can maximum have CMD_MAXLEN words, or the number of words
up to the first <char>, whichever is smallest. An exception is if
<char> is the very first character in the string - the <char> is
then assumed to be the actual command name (a common use for this
is for e.g ':' to be a shortcut for 'emote').
All words not part of the command name is considered a part of the
command's argument. Note that <char>s ending a command are never
removed but are included as the first character in the
argument. This makes it easier for individual commands to identify
things like switches. Example: '@create/drop ball' finds the
command name to trivially be '@create' since '/' ends it. As the
command's arguments are sent '/drop ball'. In this MUX-inspired
example, '/' denotes a keyword (or switch) and it is now easy for
the receiving command to parse /drop as a keyword just by looking
at the first character.
Allowing multiple command names means we have to take care of all
possible meanings and the result will be a CommandCandidates
object with up to COMMAND_MAXLEN names stored in it. So if
COMMAND_MAXLEN was, say, 4, we would have to search all commands
matching one of 'hit', 'hit orc', 'hit orc with' and 'hit orc with
sword' - each which are potentially valid commands. Assuming a
longer written name means being more specific, a longer command
name takes precedence over a short one.
There is one optional form:
<objname>'s [<char>]cmdname[ cmdname2 cmdname3 ...][<char>] [the rest]
This is to be used for object command sets with the 'duplicate' flag
set. It allows the player to define a particular object by name.
This object name(without the 's) will be stored as obj_key in the
CommandCandidates object and one version of the command name will be added
that lack this first part. If a command exists that has the same
name (including the 's), that command will be used
instead. Observe that the player setting <objname> will not override
normal commandset priorities - it's only used if there is no other
way to differentiate between commands (e.g. two objects in the
room both having the exact same command names and priorities).
"""
def produce_candidates(nr_candidates, wordlist):
"Helper function"
candidates = []
cmdwords_list = []
#print "wordlist:",wordlist
for n_words in range(nr_candidates):
cmdwords_list.append(wordlist.pop(0))
cmdwords = " ".join([word.strip().lower()
for word in cmdwords_list])
args = ""
for word in wordlist:
if not args or (word and (REGEX.search(word[0]))):
#print "nospace: %s '%s'" % (args, word)
args += word
else:
#print "space: %s '%s'" % (args, word)
args += " %s" % word
#print "'%s' | '%s'" % (cmdwords, args)
candidates.append(CommandCandidate(cmdwords, args, priority=n_words))
return candidates
raw_string = raw_string.strip()
#TODO: check for non-standard characters.
candidates = []
regex_result = REGEX.search(raw_string)
if not regex_result == None:
# there are characters from SPECIAL_CHARS in the string.
# since they cannot be part of a longer command, these
# will cut short the command, no matter how long we
# allow commands to be.
end_index = regex_result.start()
end_char = raw_string[end_index]
if end_index == 0:
# There is one exception: if the input begins with
# a special char, we let that be the command name.
cmdwords = end_char
if len(raw_string) > 1:
args = raw_string[1:]
else:
args = ""
candidates.append(CommandCandidate(cmdwords, args))
return candidates
else:
# the special char occurred somewhere inside the string
if end_char == "'" and \
len(raw_string) > end_index+1 and \
raw_string[end_index+1:end_index+3] == "s ":
# The command is of the form "<word>'s ". The
# player might have made an attempt at identifying the
# object of which's cmdtable we should prefer (e.g.
# > red door's button).
obj_key = raw_string[:end_index]
alt_string = raw_string[end_index+2:]
alt_candidates = cmdparser(alt_string)
for candidate in alt_candidates:
candidate.obj_key = obj_key
candidates.extend(alt_candidates)
# now we let the parser continue as normal, in case
# the 's -business was not meant to be an obj ref at all.
# We only run the command finder up until the end char
nr_candidates = len(raw_string[:end_index].split(None))
if nr_candidates <= COMMAND_MAXLEN:
wordlist = raw_string[:end_index].split(" ")
wordlist.extend(raw_string[end_index:].split(" "))
#print "%i, wordlist: %s" % (nr_candidates, wordlist)
candidates.extend(produce_candidates(nr_candidates, wordlist))
return candidates
# if there were no special characters, or that character
# was not found within the allowed number of words, we run normally
nr_candidates = min(COMMAND_MAXLEN,
len(raw_string.split(None)))
wordlist = raw_string.split(" ")
candidates.extend(produce_candidates(nr_candidates, wordlist))
return candidates

Some files were not shown because too many files have changed in this diff Show more