mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
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:
parent
df29defbcd
commit
f83c2bddf8
222 changed files with 22304 additions and 14371 deletions
71
ABOUT
71
ABOUT
|
|
@ -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
|
||||
50
CODING_STYLE
50
CODING_STYLE
|
|
@ -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
129
INSTALL
|
|
@ -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).
|
||||
|
||||
|
|
|
|||
2
LICENSE
2
LICENSE
|
|
@ -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
161
README
|
|
@ -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
9
TODO
|
|
@ -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
1
VERSION
Normal file
|
|
@ -0,0 +1 @@
|
|||
SVN-griatch-branch
|
||||
1630
docs/Doxyfile
Normal file
1630
docs/Doxyfile
Normal file
File diff suppressed because it is too large
Load diff
57
docs/README
57
docs/README
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
112
game/evennia.py
112
game/evennia.py
|
|
@ -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')
|
||||
|
|
|
|||
78
game/gamesrc/commands/basecommand.py
Normal file
78
game/gamesrc/commands/basecommand.py
Normal 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)
|
||||
689
game/gamesrc/commands/default/batchprocess.py
Normal file
689
game/gamesrc/commands/default/batchprocess.py
Normal 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())
|
||||
98
game/gamesrc/commands/default/cmdset_default.py
Normal file
98
game/gamesrc/commands/default/cmdset_default.py
Normal 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())
|
||||
|
||||
21
game/gamesrc/commands/default/cmdset_unloggedin.py
Normal file
21
game/gamesrc/commands/default/cmdset_unloggedin.py
Normal 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())
|
||||
792
game/gamesrc/commands/default/comms.py
Normal file
792
game/gamesrc/commands/default/comms.py
Normal 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))
|
||||
|
||||
703
game/gamesrc/commands/default/general.py
Normal file
703
game/gamesrc/commands/default/general.py
Normal 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)
|
||||
281
game/gamesrc/commands/default/help.py
Normal file
281
game/gamesrc/commands/default/help.py
Normal 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)
|
||||
204
game/gamesrc/commands/default/info.py
Normal file
204
game/gamesrc/commands/default/info.py
Normal 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)
|
||||
147
game/gamesrc/commands/default/muxcommand.py
Normal file
147
game/gamesrc/commands/default/muxcommand.py
Normal 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)
|
||||
1685
game/gamesrc/commands/default/objmanip.py
Normal file
1685
game/gamesrc/commands/default/objmanip.py
Normal file
File diff suppressed because it is too large
Load diff
670
game/gamesrc/commands/default/privileged.py
Normal file
670
game/gamesrc/commands/default/privileged.py
Normal 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))
|
||||
|
||||
173
game/gamesrc/commands/default/syscommands.py
Normal file
173
game/gamesrc/commands/default/syscommands.py
Normal 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")
|
||||
190
game/gamesrc/commands/default/tests.py
Normal file
190
game/gamesrc/commands/default/tests.py
Normal 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")
|
||||
|
|
@ -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:
|
||||
295
game/gamesrc/commands/default/unloggedin.py
Normal file
295
game/gamesrc/commands/default/unloggedin.py
Normal 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)
|
||||
292
game/gamesrc/commands/examples/cmdset_red_button.py
Normal file
292
game/gamesrc/commands/examples/cmdset_red_button.py
Normal 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())
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
@ -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)
|
||||
158
game/gamesrc/objects/baseobjects.py
Normal file
158
game/gamesrc/objects/baseobjects.py
Normal 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
|
||||
168
game/gamesrc/objects/examples/red_button.py
Normal file
168
game/gamesrc/objects/examples/red_button.py
Normal 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)
|
||||
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
63
game/gamesrc/scripts/basescript.py
Normal file
63
game/gamesrc/scripts/basescript.py
Normal 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
|
||||
0
game/web/apps/news/__init__.py → game/gamesrc/scripts/examples/__init__.py
Executable file → Normal file
0
game/web/apps/news/__init__.py → game/gamesrc/scripts/examples/__init__.py
Executable file → Normal file
275
game/gamesrc/scripts/examples/red_button_scripts.py
Normal file
275
game/gamesrc/scripts/examples/red_button_scripts.py
Normal 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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
71
game/gamesrc/world/examples/batch_code.py
Normal file
71
game/gamesrc/world/examples/batch_code.py
Normal 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))
|
||||
133
game/manage.py
133
game/manage.py
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('game.web.apps.website.views',
|
||||
(r'^$', 'page_index'),
|
||||
)
|
||||
|
|
@ -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'),
|
||||
}
|
||||
4
game/web/media/images/LICENCE
Normal file
4
game/web/media/images/LICENCE
Normal 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).
|
||||
BIN
game/web/media/images/evennia_logo.png
Executable file
BIN
game/web/media/images/evennia_logo.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 672 KiB |
BIN
game/web/media/images/evennia_logo_small.png
Executable file
BIN
game/web/media/images/evennia_logo_small.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
game/web/media/images/favicon.ico
Normal file
BIN
game/web/media/images/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
0
game/web/apps/website/__init__.py → game/web/news/__init__.py
Normal file → Executable file
0
game/web/apps/website/__init__.py → game/web/news/__init__.py
Normal file → Executable file
17
game/web/news/admin.py
Normal file
17
game/web/news/admin.py
Normal 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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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'),
|
||||
|
|
@ -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",
|
||||
11
game/web/templates/admin/base_site.html
Normal file
11
game/web/templates/admin/base_site.html
Normal 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 %}
|
||||
242
game/web/templates/admin/index.html
Normal file
242
game/web/templates/admin/index.html
Normal 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> </td>
|
||||
{% endif %}
|
||||
|
||||
{% if model.perms.change %}
|
||||
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
|
||||
{% else %}
|
||||
<td> </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> </td>
|
||||
{% endif %}
|
||||
|
||||
{% if model.perms.change %}
|
||||
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
|
||||
{% else %}
|
||||
<td> </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> </td>
|
||||
{% endif %}
|
||||
|
||||
{% if model.perms.change %}
|
||||
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
|
||||
{% else %}
|
||||
<td> </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> </td>
|
||||
{% endif %}
|
||||
|
||||
{% if model.perms.change %}
|
||||
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
|
||||
{% else %}
|
||||
<td> </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> </td>
|
||||
{% endif %}
|
||||
|
||||
{% if model.perms.change %}
|
||||
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
|
||||
{% else %}
|
||||
<td> </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 %}
|
||||
|
|
@ -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’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 -->
|
||||
|
||||
{{game_slogan}}
|
||||
</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>
|
||||
|
|
@ -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 %}
|
||||
|
|
@ -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 %}
|
||||
|
|
@ -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 %}
|
||||
|
|
@ -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 %}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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!),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
46
game/web/utils/general_context.py
Normal file
46
game/web/utils/general_context.py
Normal 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
|
||||
}
|
||||
7
game/web/website/models.py
Normal file
7
game/web/website/models.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# Define database entities for the app.
|
||||
#
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
10
game/web/website/urls.py
Normal file
10
game/web/website/urls.py
Normal 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'),
|
||||
)
|
||||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
284
src/cache/cache.py
vendored
|
|
@ -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()
|
||||
20
src/cache/managers/cache.py
vendored
20
src/cache/managers/cache.py
vendored
|
|
@ -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
56
src/cache/models.py
vendored
|
|
@ -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
1
src/cache/views.py
vendored
|
|
@ -1 +0,0 @@
|
|||
# Create your views here.
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
364
src/commands/cmdhandler.py
Normal 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
174
src/commands/cmdparser.py
Normal 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
Loading…
Add table
Add a link
Reference in a new issue