mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Updated and cleaned the wiki2rest converter. The ReST documentation should look a lot better now, with less weirdness. Using a python google-code snippet to convert now, so no more need for third-party ruby downloads! This should transfer to readthedocs shortly.
This commit is contained in:
parent
43f16094c1
commit
ae0f7a04c5
55 changed files with 3990 additions and 1778 deletions
|
|
@ -46,7 +46,6 @@ intervals.
|
|||
wiki/StartStopReload
|
||||
wiki/UpdatingYourGame
|
||||
wiki/Internationalization
|
||||
wiki/StaffVersionControl
|
||||
wiki/ApacheConfig
|
||||
wiki/TextEncodings
|
||||
wiki/IRC
|
||||
|
|
@ -65,7 +64,6 @@ intervals.
|
|||
wiki/Licensing
|
||||
wiki/Contributing
|
||||
wiki/UsingMUXAsAStandard
|
||||
wiki/BazaarDevel
|
||||
wiki/DirectoryOverview
|
||||
wiki/PortalAndServer
|
||||
wiki/Commands
|
||||
|
|
@ -88,3 +86,19 @@ intervals.
|
|||
wiki/WorkshopDefaultGame
|
||||
wiki/Workshop
|
||||
wiki/EvenniaDevel
|
||||
wiki/Banning
|
||||
wiki/CodingIntroduction
|
||||
wiki/CodingUtils
|
||||
wiki/CommandCooldown
|
||||
wiki/GamePlanning
|
||||
wiki/GettingHelp
|
||||
wiki/OnlineSetup
|
||||
wiki/Quirks
|
||||
wiki/RSS
|
||||
wiki/ServerConf
|
||||
wiki/TickerScripts
|
||||
wiki/Tutorials
|
||||
wiki/VersionControl
|
||||
wiki/Zones
|
||||
wiki/evAPI
|
||||
wiki/AddingCommandTutorial
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ Customizing the server
|
|||
Administrating the running game
|
||||
-------------------------------
|
||||
|
||||
- `Banning <Banning.html>`_ Banning and deleting users
|
||||
- `Banning <Banning.html>`_ and deleting users
|
||||
|
||||
Working with Evennia
|
||||
--------------------
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ gunicorn, Tornado, uwsgi, etc.) Below are instructions on how to set
|
|||
things up with various apache2 Python modules. If you get things working
|
||||
using a different setup, please feel free to provide details below.
|
||||
|
||||
--------------
|
||||
----
|
||||
|
||||
SQLite Note
|
||||
-----------
|
||||
|
|
@ -29,7 +29,7 @@ the game and the web front-end. The best bet to any game wishing to
|
|||
power their web presence with Evennia is to use Postgres, MySQL, Oracle,
|
||||
or any other supported full-blown relational database.
|
||||
|
||||
--------------
|
||||
----
|
||||
|
||||
mod\_wsgi Setup
|
||||
---------------
|
||||
|
|
@ -37,9 +37,9 @@ mod\_wsgi Setup
|
|||
Install mod\_wsgi
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
mod\ *wsgi is an excellent, secure, and high-performance way to serve
|
||||
mod\_wsgi is an excellent, secure, and high-performance way to serve
|
||||
Python projects. Code reloading is a breeze, Python modules are executed
|
||||
as a user of your choice (which is a great security win), and mod*\ wsgi
|
||||
as a user of your choice (which is a great security win), and mod\_wsgi
|
||||
is easy to set up on most distributions.
|
||||
|
||||
For the sake of brevity, this guide will refer you to mod\_wsgi's
|
||||
|
|
@ -50,10 +50,10 @@ Ubuntu, you may install the entire stack with the following command:
|
|||
|
||||
``sudo aptitude install libapache2-mod-wsgi``
|
||||
|
||||
This should install apache2 (if it isn't already), mod\ *wsgi, and load
|
||||
This should install apache2 (if it isn't already), mod\_wsgi, and load
|
||||
the module. On Fedora or CentOS, you'll do this with ``yum`` and a
|
||||
similar package name that you'll need to search for. On Windows, you'll
|
||||
need to download and install apache2 and mod*\ wsgi binaries.
|
||||
need to download and install apache2 and mod\_wsgi binaries.
|
||||
|
||||
Copy and modify the VHOST
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -88,8 +88,8 @@ site <http://evennia.com>`_.
|
|||
A note on code reloading
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If your mod\ *wsgi is set up to run on daemon mode (as will be the case
|
||||
by default on Debian and Ubuntu), you may tell mod*\ wsgi to reload by
|
||||
If your mod\_wsgi is set up to run on daemon mode (as will be the case
|
||||
by default on Debian and Ubuntu), you may tell mod\_wsgi to reload by
|
||||
using the ``touch`` command on
|
||||
``evennia/game/web/utils/apache_wsgi.conf``. When mod\_wsgi sees that
|
||||
the file modification time has changed, it will force a code reload. Any
|
||||
|
|
@ -114,5 +114,8 @@ are trouble.
|
|||
|
||||
::
|
||||
|
||||
<Directory "/home/<yourname>/evennia/game/web"> Options +ExecCGI Allow from all </Directory>
|
||||
<Directory "/home/<yourname>/evennia/game/web">
|
||||
Options +ExecCGI
|
||||
Allow from all
|
||||
</Directory>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Running code asynchronously
|
||||
|
||||
Asynchronous code
|
||||
=================
|
||||
|
||||
|
|
@ -17,7 +19,9 @@ Consider this piece of code:
|
|||
|
||||
::
|
||||
|
||||
print "before call ..." long_running_function() print "after call ..."
|
||||
print "before call ..."
|
||||
long_running_function()
|
||||
print "after call ..."
|
||||
|
||||
When run, this will print ``"before call ..."``, after which the
|
||||
``long_running_function`` gets to work for however long time. Only once
|
||||
|
|
@ -43,7 +47,10 @@ use of the ``run_async()`` function in ``src/utils/utils.py``.
|
|||
|
||||
::
|
||||
|
||||
from ev import utils print "before call ..." utils.run_async(long_running_function) print "after call ..."
|
||||
from ev import utils
|
||||
print "before call ..."
|
||||
utils.run_async(long_running_function)
|
||||
print "after call ..."
|
||||
|
||||
Now, when running this you will find that the program will not wait
|
||||
around for ``long_running_function`` to finish. Infact you will see
|
||||
|
|
@ -72,9 +79,10 @@ called automatically.
|
|||
argument ``r`` will then be the return value of that function (or
|
||||
``None``). Example:
|
||||
|
||||
::
|
||||
::
|
||||
|
||||
def at_return(r): print r
|
||||
def at_return(r):
|
||||
print r
|
||||
|
||||
- ``at_err(e)`` (the *errback*) is called if the asynchronous function
|
||||
fails and raises an exception. This exception is passed to the
|
||||
|
|
@ -83,17 +91,37 @@ called automatically.
|
|||
writes errors to the evennia log. An example of an errback is found
|
||||
below:
|
||||
|
||||
::
|
||||
::
|
||||
|
||||
def at_err(e):
|
||||
print "There was an error:", str(e)
|
||||
def at_err(e):
|
||||
print "There was an error:", str(e)
|
||||
|
||||
An example of making an asynchronous call from inside a
|
||||
`Command <Commands.html>`_ definition:
|
||||
|
||||
::
|
||||
|
||||
from ev import utils from game.gamesrc.commands.basecommand import Command class CmdAsync(Command): key = "asynccommand" def func(self): def long_running_function(): #[... lots of time-consuming code return final_value def at_return(r): self.caller.msg("The final value is %s" % r) def at_err(e): self.caller.msg("There was an error: %s" % e) # do the async call, setting all callbacks utils.run_async(long_running_function, at_return, at_err)
|
||||
from ev import utils
|
||||
from game.gamesrc.commands.basecommand import Command
|
||||
|
||||
class CmdAsync(Command):
|
||||
|
||||
key = "asynccommand"
|
||||
|
||||
def func(self):
|
||||
|
||||
def long_running_function():
|
||||
#[... lots of time-consuming code
|
||||
return final_value
|
||||
|
||||
def at_return(r):
|
||||
self.caller.msg("The final value is %s" % r)
|
||||
|
||||
def at_err(e):
|
||||
self.caller.msg("There was an error: %s" % e)
|
||||
|
||||
# do the async call, setting all callbacks
|
||||
utils.run_async(long_running_function, at_return, at_err)
|
||||
|
||||
That's it - from here on we can forget about ``long_running_function``
|
||||
and go on with what else need to be done. *Whenever* it finishes, the
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Using attributes to store data
|
||||
|
||||
Attributes
|
||||
==========
|
||||
|
||||
|
|
@ -24,12 +26,15 @@ Saving and Retrieving data
|
|||
--------------------------
|
||||
|
||||
To save persistent data on a Typeclassed object you normally use the
|
||||
``db`` (!DataBase) operator. Let's try to save some data to a *Rose* (an
|
||||
``db`` (DataBase) operator. Let's try to save some data to a *Rose* (an
|
||||
`Object <Objects.html>`_):
|
||||
|
||||
::
|
||||
|
||||
# saving rose.db.has_thorns = True # getting it back is_ouch = rose.db.has_thorns
|
||||
# saving
|
||||
rose.db.has_thorns = True
|
||||
# getting it back
|
||||
is_ouch = rose.db.has_thorns
|
||||
|
||||
This looks like any normal Python assignment, but that ``db`` makes sure
|
||||
that an *Attribute* is created behind the scenes and is stored in the
|
||||
|
|
@ -37,12 +42,15 @@ database. Your rose will continue to have thorns throughout the life of
|
|||
the server now, until you deliberately remove them.
|
||||
|
||||
To be sure to save **non-persistently**, i.e. to make sure NOT to create
|
||||
a database entry, you use ``ndb`` (!NonDataBase). It works in the same
|
||||
a database entry, you use ``ndb`` (NonDataBase). It works in the same
|
||||
way:
|
||||
|
||||
::
|
||||
|
||||
# saving rose.ndb.has_thorns = True # getting it back is_ouch = rose.ndb.has_thorns
|
||||
# saving
|
||||
rose.ndb.has_thorns = True
|
||||
# getting it back
|
||||
is_ouch = rose.ndb.has_thorns
|
||||
|
||||
Strictly speaking, ``ndb`` has nothing to do with ``Attributes``,
|
||||
despite how similar they look. No ``Attribute`` object is created behind
|
||||
|
|
@ -62,7 +70,8 @@ properties.
|
|||
|
||||
::
|
||||
|
||||
list_of_all_rose_attributes = rose.db.all list_of_all_rose_ndb_attrs = rose.ndb.all
|
||||
list_of_all_rose_attributes = rose.db.all
|
||||
list_of_all_rose_ndb_attrs = rose.ndb.all
|
||||
|
||||
If you use ``all`` as the name of an attribute, this will be used
|
||||
instead. Later deleting your custom ``all`` will return the default
|
||||
|
|
@ -76,7 +85,10 @@ assign Attributes like you would any normal Python property:
|
|||
|
||||
::
|
||||
|
||||
# saving rose.has_thorns = True # getting it back is_ouch = rose.has_thorns
|
||||
# saving
|
||||
rose.has_thorns = True
|
||||
# getting it back
|
||||
is_ouch = rose.has_thorns
|
||||
|
||||
This looks like any normal Python assignment, but calls ``db`` behind
|
||||
the scenes for you.
|
||||
|
|
@ -87,7 +99,9 @@ you know what you are doing, this can cause lots of trouble.
|
|||
|
||||
::
|
||||
|
||||
rose.msg("hello") # this uses the in-built msg() method rose.msg = "Ouch!" # this OVERLOADS the msg() method with a string rose.msg("hello") # this now a gives traceback!
|
||||
rose.msg("hello") # this uses the in-built msg() method
|
||||
rose.msg = "Ouch!" # this OVERLOADS the msg() method with a string
|
||||
rose.msg("hello") # this now a gives traceback!
|
||||
|
||||
Overloading ``msg()`` with a string is a very bad idea since Evennia
|
||||
uses this method all the time to send text to you. There are of course
|
||||
|
|
@ -97,7 +111,8 @@ something that works.
|
|||
|
||||
::
|
||||
|
||||
rose.db.msg = "Ouch" # this stands no risk of overloading msg() rose.msg("hello") # this works as it should
|
||||
rose.db.msg = "Ouch" # this stands no risk of overloading msg()
|
||||
rose.msg("hello") # this works as it should
|
||||
|
||||
So using ``db``/``ndb`` will always do what you expect and is usually
|
||||
the safer bet. It also makes it visually clear at all times when you are
|
||||
|
|
@ -178,20 +193,46 @@ Examples of valid attribute data:
|
|||
|
||||
::
|
||||
|
||||
# a single value obj.db.test1 = 23 obj.db.test1 = False # a database object (will be stored as dbref) obj.db.test2 = myobj # a list of objects obj.db.test3 = [obj1, 45, obj2, 67] # a dictionary obj.db.test4 = 'str':34, 'dex':56, 'agi':22, 'int':77 # a mixed dictionary/list obj.db.test5 = 'members': [obj1,obj2,obj3], 'enemies':[obj4,obj5] # a tuple with a list in it obj.db.test6 = (1,3,4,8, ["test", "test2"], 9) # a set will still be stored and returned as a list [1,2,3,4,5]! obj.db.test7 = set([1,2,3,4,5]) # in-situ manipulation obj.db.test8 = [1,2,"test":1] obj.db.test8[0] = 4 obj.db.test8[2]["test"] = 5 # test8 is now [4,2,"test":5]
|
||||
# a single value
|
||||
obj.db.test1 = 23
|
||||
obj.db.test1 = False
|
||||
# a database object (will be stored as dbref)
|
||||
obj.db.test2 = myobj
|
||||
# a list of objects
|
||||
obj.db.test3 = [obj1, 45, obj2, 67]
|
||||
# a dictionary
|
||||
obj.db.test4 = {'str':34, 'dex':56, 'agi':22, 'int':77}
|
||||
# a mixed dictionary/list
|
||||
obj.db.test5 = {'members': [obj1,obj2,obj3], 'enemies':[obj4,obj5]}
|
||||
# a tuple with a list in it
|
||||
obj.db.test6 = (1,3,4,8, ["test", "test2"], 9)
|
||||
# a set will still be stored and returned as a list [1,2,3,4,5]!
|
||||
obj.db.test7 = set([1,2,3,4,5])
|
||||
# in-situ manipulation
|
||||
obj.db.test8 = [1,2,{"test":1}]
|
||||
obj.db.test8[0] = 4
|
||||
obj.db.test8[2]["test"] = 5
|
||||
# test8 is now [4,2,{"test":5}]
|
||||
|
||||
Example of non-supported save:
|
||||
|
||||
::
|
||||
|
||||
# this will fool the dbobj-check since myobj (a database object) is "hidden" # inside a custom object. This is unsupported and will lead to unexpected # results! class BadStorage(object): pass bad = BadStorage() bad.dbobj = myobj obj.db.test8 = bad # this will likely lead to a traceback
|
||||
# this will fool the dbobj-check since myobj (a database object) is "hidden"
|
||||
# inside a custom object. This is unsupported and will lead to unexpected
|
||||
# results!
|
||||
class BadStorage(object):
|
||||
pass
|
||||
bad = BadStorage()
|
||||
bad.dbobj = myobj
|
||||
obj.db.test8 = bad # this will likely lead to a traceback
|
||||
|
||||
Retrieving Mutable objects
|
||||
--------------------------
|
||||
|
||||
A side effect of the way Evennia stores Attributes is that Python Lists
|
||||
and Dictionaries (only) are handled by custom objects called PackedLists
|
||||
and !PackedDicts. These behave just like normal lists and dicts except
|
||||
and PackedDicts. These behave just like normal lists and dicts except
|
||||
they have the special property that they save to the database whenever
|
||||
new data gets assigned to them. This allows you to do things like
|
||||
``self.db.mylist[4]`` = val without having to extract the mylist
|
||||
|
|
@ -199,13 +240,17 @@ Attribute into a temporary variable first.
|
|||
|
||||
There is however an important thing to remember. If you retrieve this
|
||||
data into another variable, e.g. ``mylist2 = obj.db.mylist``, your new
|
||||
variable (``mylist2``) will *still* be a !PackedList! This means it will
|
||||
variable (``mylist2``) will *still* be a PackedList! This means it will
|
||||
continue to save itself to the database whenever it is updated! This is
|
||||
important to keep in mind so you are not confused by the results.
|
||||
|
||||
::
|
||||
|
||||
obj.db.mylist = [1,2,3,4] mylist = obj.db.mylist mylist[3] = 5 # this will also update database print mylist # this is now [1,2,3,5] print mylist.db.mylist # this is also [1,2,3,5]
|
||||
obj.db.mylist = [1,2,3,4]
|
||||
mylist = obj.db.mylist
|
||||
mylist[3] = 5 # this will also update database
|
||||
print mylist # this is now [1,2,3,5]
|
||||
print mylist.db.mylist # this is also [1,2,3,5]
|
||||
|
||||
To "disconnect" your extracted mutable variable from the database you
|
||||
simply need to convert the PackedList or PackedDict to a normal Python
|
||||
|
|
@ -216,7 +261,11 @@ structure's connection to the database.
|
|||
|
||||
::
|
||||
|
||||
obj.db.mylist = [1,2,3,4] mylist = list(obj.db.mylist) # convert to normal list mylist[3] = 5 print mylist # this is now [1,2,3,5] print obj.db.mylist # this remains [1,2,3,4]
|
||||
obj.db.mylist = [1,2,3,4]
|
||||
mylist = list(obj.db.mylist) # convert to normal list
|
||||
mylist[3] = 5
|
||||
print mylist # this is now [1,2,3,5]
|
||||
print obj.db.mylist # this remains [1,2,3,4]
|
||||
|
||||
Remember, this is only valid for mutable iterables - lists and dicts and
|
||||
combinations of the two.
|
||||
|
|
@ -227,7 +276,14 @@ stop any changes to the structure from updating the database.
|
|||
|
||||
::
|
||||
|
||||
obj.db.mytup = (1,2,[3,4]) obj.db.mytup[0] = 5 # this fails since tuples are immutable obj.db.mytup[2][1] = 5 # this works but will NOT update database since outermost iterable is a tuple print obj.db.mytup[2][1] # this still returns 4, not 5 mytup1 = obj.db.mytup # mytup1 is already disconnected from database since outermost # iterable is a tuple, so we can edit the internal list as we want # without affecting the database.
|
||||
obj.db.mytup = (1,2,[3,4])
|
||||
obj.db.mytup[0] = 5 # this fails since tuples are immutable
|
||||
obj.db.mytup[2][1] = 5 # this works but will NOT update database since outermost iterable is a tuple
|
||||
print obj.db.mytup[2][1] # this still returns 4, not 5
|
||||
mytup1 = obj.db.mytup
|
||||
# mytup1 is already disconnected from database since outermost
|
||||
# iterable is a tuple, so we can edit the internal list as we want
|
||||
# without affecting the database.
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Using the Evennia batch code processor
|
||||
|
||||
The Batch-Code processor
|
||||
========================
|
||||
|
||||
|
|
@ -13,7 +15,7 @@ The batch-command processor is a superuser-only function, invoked by
|
|||
|
||||
::
|
||||
|
||||
> @batchcode path.to.batchcodefile
|
||||
> @batchcode path.to.batchcodefile
|
||||
|
||||
Where ``path.to.batchcodefile`` is the path to a *batch-code file* with
|
||||
the "``.py``\ " file ending. This path is given like a python path
|
||||
|
|
@ -24,7 +26,7 @@ relative to a folder you define to hold your batch files, set by
|
|||
|
||||
::
|
||||
|
||||
> @batchcommand examples.batch_code
|
||||
> @batchcommand examples.batch_code
|
||||
|
||||
This will try to run through the entire batch file in one go. For more
|
||||
gradual, *interactive* control you can use the ``/interactive`` switch.
|
||||
|
|
@ -82,7 +84,37 @@ Below is a version of the example file found in
|
|||
|
||||
#
|
||||
# This is an example batch-code build file for Evennia.
|
||||
##HEADER# This will be included in all other #CODE blocksfrom src.utils import create, search from game.gamesrc.objects.examples import red_button from game.gamesrc.objects import baseobjectslimbo = search.objects(caller, 'Limbo', global_search=True)[0]#CODE (create red button)red_button = create.create_object(red_button.RedButton, key="Red button", location=limbo, aliases=["button"])# caller points to the one running the script caller.msg("A red button was created.")# importing more code from another batch-code file #INSERT examples.batch_code_insert#CODE (create table and chair) table, chairtable = create.create_object(baseobjects.Object, key="Blue Table", location=limbo) chair = create.create_object(baseobjects.Object, key="Blue Chair", location=limbo)string = "A %s and %s were created. If debug was active, they were deleted again." caller.msg(string % (table, chair))
|
||||
#
|
||||
|
||||
#HEADER
|
||||
|
||||
# This will be included in all other #CODE blocks
|
||||
|
||||
from src.utils import create, search
|
||||
from game.gamesrc.objects.examples import red_button
|
||||
from game.gamesrc.objects import baseobjects
|
||||
|
||||
limbo = search.objects(caller, 'Limbo', global_search=True)[0]
|
||||
|
||||
|
||||
#CODE (create red button)
|
||||
|
||||
red_button = create.create_object(red_button.RedButton, key="Red button",
|
||||
location=limbo, aliases=["button"])
|
||||
|
||||
# caller points to the one running the script
|
||||
caller.msg("A red button was created.")
|
||||
|
||||
# importing more code from another batch-code file
|
||||
#INSERT examples.batch_code_insert
|
||||
|
||||
#CODE (create table and chair) table, chair
|
||||
|
||||
table = create.create_object(baseobjects.Object, key="Blue Table", location=limbo)
|
||||
chair = create.create_object(baseobjects.Object, key="Blue Chair", location=limbo)
|
||||
|
||||
string = "A %s and %s were created. If debug was active, they were deleted again."
|
||||
caller.msg(string % (table, chair))
|
||||
|
||||
This uses Evennia's Python API to create three objects in sequence.
|
||||
|
||||
|
|
@ -93,7 +125,7 @@ Try to run the example script with
|
|||
|
||||
::
|
||||
|
||||
> @batchcode/debug examples.batch_code
|
||||
> @batchcode/debug examples.batch_code
|
||||
|
||||
The batch script will run to the end and tell you it completed. You will
|
||||
also get messages that the button and the two pieces of furniture where
|
||||
|
|
@ -127,13 +159,13 @@ mode.
|
|||
|
||||
::
|
||||
|
||||
> @batchcode/interactive examples.batch_code
|
||||
> @batchcode/interactive examples.batch_code
|
||||
|
||||
You should see the following:
|
||||
|
||||
::
|
||||
|
||||
01/02: #CODE (create red button) [...] (hh for help)
|
||||
01/02: #CODE (create red button) [...] (hh for help)
|
||||
|
||||
This shows that you are on the first ``#CODE`` block, the first of only
|
||||
two commands in this batch file. Observe that the block has *not*
|
||||
|
|
@ -144,7 +176,17 @@ To take a look at the full code snippet you are about to run, use ``ll``
|
|||
|
||||
::
|
||||
|
||||
from src.utils import create, search from game.gamesrc.objects.examples import red_button from game.gamesrc.objects import baseobjectslimbo = search.objects(caller, 'Limbo', global_search=True)[0]red_button = create.create_object(red_button.RedButton, key="Red button", location=limbo, aliases=["button"])# caller points to the one running the script caller.msg("A red button was created.")
|
||||
from src.utils import create, search
|
||||
from game.gamesrc.objects.examples import red_button
|
||||
from game.gamesrc.objects import baseobjects
|
||||
|
||||
limbo = search.objects(caller, 'Limbo', global_search=True)[0]
|
||||
|
||||
red_button = create.create_object(red_button.RedButton, key="Red button",
|
||||
location=limbo, aliases=["button"])
|
||||
|
||||
# caller points to the one running the script
|
||||
caller.msg("A red button was created.")
|
||||
|
||||
Compare with the example code given earlier. Notice how the content of
|
||||
``#HEADER`` has been pasted at the top of the ``#CODE`` block. Use
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Using the Evennia command batch processors
|
||||
|
||||
The Batch-Command processor
|
||||
===========================
|
||||
|
||||
|
|
@ -13,7 +15,7 @@ The batch-command processor is a superuser-only function, invoked by
|
|||
|
||||
::
|
||||
|
||||
> @batchcommand path.to.batchcmdfile
|
||||
> @batchcommand path.to.batchcmdfile
|
||||
|
||||
Where ``path.to.batchcmdfile`` is the path to a *batch-command file*
|
||||
with the "``.ev``\ " file ending. This path is given like a python path
|
||||
|
|
@ -24,7 +26,7 @@ relative to a folder you define to hold your batch files, set with
|
|||
|
||||
::
|
||||
|
||||
> @batchcommand examples.batch_cmds
|
||||
> @batchcommand examples.batch_cmds
|
||||
|
||||
A batch-command file contains a list of Evennia in-game commands
|
||||
separated by comments. The processor will run the batch file from
|
||||
|
|
@ -75,7 +77,46 @@ Below is a version of the example file found in
|
|||
|
||||
::
|
||||
|
||||
# # This is an example batch build file for Evennia. ## This creates a red button @create button:examples.red_button.RedButton # (This comment ends input for @create) # Next command. Let's create something. @set button/desc = This is a large red button. Now and then it flashes in an evil, yet strangely tantalizing way. A big sign sits next to it. It says:----------- Press me! ----------- ... It really begs to be pressed! You know you want to! # This inserts the commands from another batch-cmd file named # batch_insert_file.ev. #INSERT examples.batch_insert_file # (This ends the @set command). Note that single line breaks # and extra whitespace in the argument are ignored. Empty lines # translate into line breaks in the output. # Now let's place the button where it belongs (let's say limbo #2 is # the evil lair in our example) @teleport #2 # (This comments ends the @teleport command.) # Now we drop it so others can see it. # The very last command in the file needs not be ended with #. drop button
|
||||
#
|
||||
# This is an example batch build file for Evennia.
|
||||
#
|
||||
|
||||
# This creates a red button
|
||||
@create button:examples.red_button.RedButton
|
||||
# (This comment ends input for @create)
|
||||
# Next command. Let's create something.
|
||||
@set button/desc =
|
||||
This is a large red button. Now and then
|
||||
it flashes in an evil, yet strangely tantalizing way.
|
||||
|
||||
A big sign sits next to it. It says:
|
||||
|
||||
|
||||
-----------
|
||||
|
||||
Press me!
|
||||
|
||||
-----------
|
||||
|
||||
|
||||
... It really begs to be pressed! You
|
||||
know you want to!
|
||||
|
||||
# This inserts the commands from another batch-cmd file named
|
||||
# batch_insert_file.ev.
|
||||
#INSERT examples.batch_insert_file
|
||||
|
||||
|
||||
# (This ends the @set command). Note that single line breaks
|
||||
# and extra whitespace in the argument are ignored. Empty lines
|
||||
# translate into line breaks in the output.
|
||||
# Now let's place the button where it belongs (let's say limbo #2 is
|
||||
# the evil lair in our example)
|
||||
@teleport #2
|
||||
# (This comments ends the @teleport command.)
|
||||
# Now we drop it so others can see it.
|
||||
# The very last command in the file needs not be ended with #.
|
||||
drop button
|
||||
|
||||
To test this, run ``@batchcommand`` on the file. A button will be
|
||||
created, described and dropped in Limbo. All commands will be executed
|
||||
|
|
@ -97,13 +138,13 @@ same-named objects, for example). Use ``@batchcommand`` with the
|
|||
|
||||
::
|
||||
|
||||
> @batchcommand/interactive examples.batch_cmds
|
||||
> @batchcommand/interactive examples.batch_cmds
|
||||
|
||||
You will see this:
|
||||
|
||||
::
|
||||
|
||||
01/04: @create button:examples.red_button.RedButton (hh for help)
|
||||
01/04: @create button:examples.red_button.RedButton (hh for help)
|
||||
|
||||
This shows that you are on the ``@create`` command, the first out of
|
||||
only four commands in this batch file. Observe that the command
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Introduction to batch processors
|
||||
|
||||
Batch Processors - overview
|
||||
===========================
|
||||
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ Building basics
|
|||
Advanced building and World building
|
||||
------------------------------------
|
||||
|
||||
`Overview of batch processors <BatchProcessors.html>`_
|
||||
- `Overview of batch processors <BatchProcessors.html>`_
|
||||
|
||||
- `Batch-command processor <BatchCommandProcessor.html>`_
|
||||
- `Batch-code processor <BatchCodeProcessor.html>`_
|
||||
- `Batch-command processor <BatchCommandProcessor.html>`_
|
||||
- `Batch-code processor <BatchCodeProcessor.html>`_
|
||||
|
||||
`Adding zones <Zones.html>`_
|
||||
- `Adding zones <Zones.html>`_
|
||||
|
||||
The Tutorial world
|
||||
------------------
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Introduction to setting permissions
|
||||
|
||||
Giving permissions to your staff
|
||||
================================
|
||||
|
||||
|
|
@ -32,7 +34,7 @@ as well. By default Evennia creates the following hierarchy:
|
|||
console or import files from the hard drive.
|
||||
#. *Builders* has all the build commands, but cannot affect other
|
||||
players or mess with the server.
|
||||
#. *!PlayerHelpers* are almost like a normal *Player*, but they can also
|
||||
#. *PlayerHelpers* are almost like a normal *Player*, but they can also
|
||||
add help files to the database.
|
||||
#. *Players* is the default group that new players end up in. A new
|
||||
player have permission to use tells, to use and create new channels.
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ optional parts):
|
|||
|
||||
::
|
||||
|
||||
command[/switch/switch...] [arguments ...]
|
||||
command[/switch/switch...] [arguments ...]
|
||||
|
||||
A *switch* is a special, optional flag to the command to make it behave
|
||||
differently. It is always put directly after the command name, and
|
||||
|
|
@ -46,7 +46,7 @@ player build rights:
|
|||
|
||||
::
|
||||
|
||||
@perm Anna = Builders
|
||||
@perm Anna = Builders
|
||||
|
||||
You could give the permission "Immortals" instead, if you want to assign
|
||||
full admin privileges. Log out of your superuser account (``@quit``) and
|
||||
|
|
@ -127,7 +127,8 @@ and try to get the box now:
|
|||
|
||||
::
|
||||
|
||||
> get box You can't get that.
|
||||
> get box
|
||||
You can't get that.
|
||||
|
||||
Think the default error message looks dull? The ``get`` command looks
|
||||
for an `Attribute <Attributes.html>`_ named ``get_err_msg`` for
|
||||
|
|
@ -137,7 +138,7 @@ attributes using the ``@set`` command:
|
|||
|
||||
::
|
||||
|
||||
> @set box/get_err_msg = The box is way too heavy for you to lift.
|
||||
> @set box/get_err_msg = The box is way too heavy for you to lift.
|
||||
|
||||
Try to get it now and you should see a nicer error message echoed back
|
||||
to you.
|
||||
|
|
@ -162,7 +163,7 @@ while and you will notice yourself starting making random observations.
|
|||
|
||||
::
|
||||
|
||||
> @script self
|
||||
> @script self
|
||||
|
||||
This will show details about scripts on yourself (also ``examine``
|
||||
works). You will see how long it is until it "fires" next. Don't be
|
||||
|
|
@ -320,7 +321,7 @@ command.
|
|||
|
||||
::
|
||||
|
||||
> @sethelp/add MyTopic = This help topic is about ...
|
||||
> @sethelp/add MyTopic = This help topic is about ...
|
||||
|
||||
Adding a World
|
||||
--------------
|
||||
|
|
@ -330,7 +331,7 @@ need to log back in as *superuser*. Place yourself in Limbo and do:
|
|||
|
||||
::
|
||||
|
||||
@batchcommand contrib.tutorial_world.build
|
||||
@batchcommand contrib.tutorial_world.build
|
||||
|
||||
This will take a while, but you will see a lot of messages as the world
|
||||
is built for you. You will end up with a new exit from Limbo named
|
||||
|
|
|
|||
|
|
@ -34,9 +34,9 @@ your game has an very large database and/or extensive web presence
|
|||
through a separate server process.
|
||||
|
||||
**Warning:** Postgres has issues with Evennia on some installs at the
|
||||
moment. "http://code.google.com/p/evennia/issues/detail?id
|
||||
|
||||
151">Issue 151 outlines this. If unsure, avoid Postgres for now.
|
||||
moment. `Issue
|
||||
151 <http://code.google.com/p/evennia/issues/detail?id=151>`_ outlines
|
||||
this. If unsure, avoid Postgres for now.
|
||||
|
||||
MySQL
|
||||
-----
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Adding colour
|
||||
|
||||
Adding Colour to your game
|
||||
==========================
|
||||
|
||||
|
|
@ -26,7 +28,8 @@ mark colour:
|
|||
|
||||
::
|
||||
|
||||
This is a %crRed text%cn This is normal text again. %cRThis text has red background%cn this is normal text.
|
||||
This is a %crRed text%cn This is normal text again.
|
||||
%cRThis text has red background%cn this is normal text.
|
||||
|
||||
``%c#`` - markup works like a switch that is on until you actively turn
|
||||
it off with ``%cn`` (this returns the text to your default setting).
|
||||
|
|
@ -40,16 +43,16 @@ grey becomes white, dark yellow becomes bright yellow etc.
|
|||
The drawback of the ``%cs`` style has to do with how Python formats
|
||||
strings - the ``%`` is used in Python to create special text formatting,
|
||||
and combining that with colour codes easily leads to messy and
|
||||
unreadable code. It is thus often easier to use ``#`` style codes:
|
||||
unreadable code. It is thus often easier to use ``{#`` style codes:
|
||||
|
||||
::
|
||||
|
||||
This is a rBright red textn This is normal text again
|
||||
This is a {rBright red text{n This is normal text again
|
||||
|
||||
The ``x`` format don't include background colour, it only colours the
|
||||
The ``{x`` format don't include background colour, it only colours the
|
||||
foreground text. The basic rule is that lower-case letter means bright
|
||||
(hilighted) colour, whereas the upper-case one is for darker colour. So
|
||||
``g`` means bright green and ``G`` means dark green. ``n`` returns to
|
||||
``{g`` means bright green and ``{G`` means dark green. ``{n`` returns to
|
||||
normal text colour. The equivalent in ``%c``-style markup is ``%cg%ch``
|
||||
for bright green and ``%cg`` for dark green.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Adding a command prompt to your game
|
||||
|
||||
Adding a command prompt
|
||||
=======================
|
||||
|
||||
|
|
@ -11,11 +13,13 @@ Prompt after the command
|
|||
|
||||
The easiest form of prompt is one that is sent after every command you
|
||||
send. So, say you enter the look command; you would then get the result
|
||||
of the look command, followed by the prompt. As an example:
|
||||
of the look command, followed by the prompt. As an example:Â
|
||||
|
||||
::
|
||||
|
||||
> look You see nothing special. HP:10, SP:20, MP: 5
|
||||
> look
|
||||
You see nothing special.
|
||||
HP:10, SP:20, MP: 5
|
||||
|
||||
MUD clients can be set to detect prompts like this and display them in
|
||||
various client-specific ways.
|
||||
|
|
@ -31,7 +35,20 @@ administration for example).
|
|||
|
||||
::
|
||||
|
||||
class MyCommand(Command): [...] def at_post_cmd(self): # we assume health/stamina/magic are just stored # as simple attributes on the character. hp = self.caller.db.hp sp = self.caller.db.sp mp = self.caller.db.mp self.caller.msg("HP: %i, SP: %i, MP: %i" % (hp, sp, mp))
|
||||
class MyCommand(Command):
|
||||
|
||||
[...]
|
||||
|
||||
def at_post_cmd(self):
|
||||
|
||||
# we assume health/stamina/magic are just stored
|
||||
# as simple attributes on the character.
|
||||
|
||||
hp = self.caller.db.hp
|
||||
sp = self.caller.db.sp
|
||||
mp = self.caller.db.mp
|
||||
|
||||
self.caller.msg("HP: %i, SP: %i, MP: %i" % (hp, sp, mp))
|
||||
|
||||
Prompt on the same line
|
||||
-----------------------
|
||||
|
|
@ -41,7 +58,8 @@ return of every command, on the same line:
|
|||
|
||||
::
|
||||
|
||||
> look HP: 10, SP:20, MP:5 -- You see nothing special.
|
||||
> look
|
||||
HP: 10, SP:20, MP:5 -- You see nothing special.
|
||||
|
||||
Now, there is an ``at_pre_cmd()`` hook analogous to the hook from last
|
||||
section except called just *before* parsing of the command. But putting
|
||||
|
|
@ -50,20 +68,23 @@ before* the function return:
|
|||
|
||||
::
|
||||
|
||||
> look HP:10, SP:20, MP: 5 You see nothing special.
|
||||
> look
|
||||
HP:10, SP:20, MP: 5
|
||||
You see nothing special.
|
||||
|
||||
... which might be cool too, but not what we wanted. To have the prompt
|
||||
appear on the same line as the return this, we need to change how
|
||||
messages are returned to the player. This means a slight modification to
|
||||
our *Character class* (see `here <Objects#Characters.html>`_ on how to
|
||||
change the default Character class to your custom one). Now, all
|
||||
commands use the ``object.msg()`` method for communicating with the
|
||||
player. This is defined in ``src/objects/models.py``, on the
|
||||
``ObjectDB`` base class. This is how the ``msg()`` method is defined:
|
||||
our *Character class* (see [Objects#Characters here] on how to change
|
||||
the default Character class to your custom one). Now, all commands use
|
||||
the ``object.msg()`` method for communicating with the player. This is
|
||||
defined in ``src/objects/models.py``, on the ``ObjectDB`` base class.
|
||||
This is how the ``msg()`` method is defined:
|
||||
|
||||
::
|
||||
|
||||
def msg(self, outgoing_message, from_obj=None, data=None): ...
|
||||
def msg(self, outgoing_message, from_obj=None, data=None):
|
||||
...
|
||||
|
||||
The only argument we are interested in here is the ``outgoing_message``,
|
||||
which contains the text that is about to be passed on to the player. We
|
||||
|
|
@ -74,7 +95,19 @@ custom Character typeclass add this:
|
|||
|
||||
::
|
||||
|
||||
def msg(self, outgoing_message, from_obj=None, data=None): # prepend the prompt in front of the message hp = self.db.hp sp = self.db.sp mp = self.db.mp prompt = "%i, %i, %i -- " % (hp, sp, mp) outgoing_message = prompt + outgoing_message # pass this on to the original msg() method on the database object self.dbobj.msg(outgoing_message, from_obj=from_obj, data=data)
|
||||
def msg(self, outgoing_message, from_obj=None, data=None):
|
||||
|
||||
# prepend the prompt in front of the message
|
||||
|
||||
hp = self.db.hp
|
||||
sp = self.db.sp
|
||||
mp = self.db.mp
|
||||
prompt = "%i, %i, %i -- " % (hp, sp, mp)
|
||||
outgoing_message = prompt + outgoing_message
|
||||
|
||||
# pass this on to the original msg() method on the database object
|
||||
|
||||
self.dbobj.msg(outgoing_message, from_obj=from_obj, data=data)
|
||||
|
||||
Note that this solution will *always* give you the prompt, also if you
|
||||
use admin commands, which could get annoying. You might want to have
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Details on how to use and extend the command system.
|
||||
|
||||
Command system
|
||||
==============
|
||||
|
||||
|
|
@ -17,21 +19,24 @@ should look to them for inspiration and inherit your own designs from
|
|||
them.
|
||||
|
||||
There are two components to having a command running - the *Command*
|
||||
class and the *Command Set* you want to store that command in.
|
||||
class and the *Command Set*.
|
||||
|
||||
A *Command* is a python class containing all the functioning code for
|
||||
what a command does - for example, a *look* command would contain code
|
||||
for describing other objects.
|
||||
what a command does - for example, a *get* command would contain code
|
||||
for picking up objects.
|
||||
|
||||
A *Command Set* (often referred to as a !CmdSet) is a python class
|
||||
holding any number of Command class instances, and one Command can go
|
||||
into many different command sets. By storing the command set on a
|
||||
character object you will make all the commands therein available to use
|
||||
by that character. You can also store command sets on normal objects if
|
||||
you want users to be able to use the object in various ways. Consider a
|
||||
"Tree" object with a cmdset defining the commands *climb* and *chop
|
||||
down*. Or a "Clock" with a cmdset containing the single command *check
|
||||
time*.
|
||||
A *Command Set* (often referred to as a CmdSet) is like a container for
|
||||
one or more Commands. A given Command can go into any number of
|
||||
different command sets. By putting the command set on a character object
|
||||
you will make all the commands therein available to use by that
|
||||
character. You can also store command sets on normal objects if you want
|
||||
users to be able to use the object in various ways. Consider a "Tree"
|
||||
object with a cmdset defining the commands *climb* and *chop down*. Or a
|
||||
"Clock" with a cmdset containing the single command *check time*.
|
||||
|
||||
This page goes into full detail about how to use Commands. There is also
|
||||
a step-by-step `beginner's tutorial <AddingCommandTutorial.html>`_ that
|
||||
will get you started quickly without the explanations.
|
||||
|
||||
Defining a Command
|
||||
------------------
|
||||
|
|
@ -53,56 +58,59 @@ class directly.
|
|||
This is the help-text for the command
|
||||
"""
|
||||
key = "mycommand"
|
||||
locks = "cmd:all()" # this lock means cmd is available to all
|
||||
def parse(self):
|
||||
# parsing the command line here
|
||||
def func(self):
|
||||
# executing the command here
|
||||
# executing the command here
|
||||
|
||||
You define a new command by assigning a few class-global properties on
|
||||
your inherited class and overloading one or two hook functions. The full
|
||||
gritty mechanic behind how commands work are found towards the end of
|
||||
this page; for now you only need to know that the command handler
|
||||
creates an instance of this class when you call that command, then
|
||||
assigns the new object a few useful properties that you can assume to
|
||||
always be available.
|
||||
creates an instance of this class and uses that instance whenever you
|
||||
use this command - it also dynamically assigns the new command instance
|
||||
a few useful properties that you can assume to always be available.
|
||||
|
||||
Properties assigned to the command instance at run-time
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Let's say player *Bob* with a character *!BigGuy* enters the command
|
||||
``look at sword``. After the system having successfully identified this
|
||||
a the "look" command and determined that *!BigGuy* really has access to
|
||||
a command named ``look``, it chugs the ``look`` command class out of
|
||||
storage and creates a new instance of it. After some more checks it then
|
||||
assigns it the following properties:
|
||||
Let's say player *Bob* with a character *BigGuy* enters the command
|
||||
*look at sword*. After the system having successfully identified this a
|
||||
the "look" command and determined that *BigGuy* really has access to a
|
||||
command named ``look``, it chugs the ``look`` command class out of
|
||||
storage and either loads an existing Command instance from cache or
|
||||
creates one. After some more checks it then assigns it the following
|
||||
properties:
|
||||
|
||||
- ``caller`` - a reference to the object executing the command - this
|
||||
is normally *!BigGuy*, not *Bob*! The command system usually deals
|
||||
with things on the character level. If you want to do something to
|
||||
the player in your command, do so through ``caller.player``. The only
|
||||
exception to this is if you stored your cmdset directly on the
|
||||
*Player* object (usually used only when a Player has no character
|
||||
assigned to them, like when creating a new one).
|
||||
- ``cmdstring`` - the matched key for the command. This would be
|
||||
"``look``\ " in our example.
|
||||
is normally the Character\_BigGuy\_, not his Player *Bob* ! If you
|
||||
want to do something to the player (*Bob*) in your command, do so
|
||||
through ``caller.player``. (Since cmdsets can be put directly on
|
||||
Players, caller *can* be a Player object as well, such commands are
|
||||
usually quite specific though).
|
||||
- ``cmdstring`` - the matched key for the command. This would be *look*
|
||||
in our example.
|
||||
- ``args`` - this is the rest of the string, except the command name.
|
||||
So if the string entered was ``look at sword``, ``args`` would simply
|
||||
be "``at sword``\ ".
|
||||
So if the string entered was *look at sword*, ``args`` would be "*at
|
||||
sword*\ ".
|
||||
- ``obj`` - the game `Object <Objects.html>`_ on which this command is
|
||||
defined. This need not be the caller, but since ``look`` is a common
|
||||
(default) command, this is probably defined directly on *!BigGuy* -
|
||||
so ``obj`` will point to !BigGuy. Otherwise ``obj`` could be any
|
||||
interactive object with commands defined on it, like in the example
|
||||
of the "check time" command defined on a "Clock" object or a `red
|
||||
button <https://code.google.com/p/evennia/source/browse/trunk/game/gamesrc/objects/examples/red<i>button.py.html>`_
|
||||
(default) command, this is probably defined directly on *BigGuy* - so
|
||||
``obj`` will point to BigGuy. Otherwise ``obj`` could be a Player or
|
||||
any interactive object with commands defined on it, like in the
|
||||
example of the "check time" command defined on a "Clock" object or a
|
||||
`red
|
||||
button <https://code.google.com/p/evennia/source/browse/trunk/game/gamesrc/objects/examples/red_button.py>`_
|
||||
that you can "``push``\ ".
|
||||
- ``cmdset`` - this is a reference to the merged CmdSet (see below)
|
||||
from which this command was matched. This variable is rarely used,
|
||||
it's main use is for the `auto-help system <HelpSystem.html>`_
|
||||
(Advanced note: the merged cmdset need NOT be the same as
|
||||
!BigGuy.cmdset. The merged set can be a combination of the cmdsets
|
||||
from other objects in the room, for example\_).
|
||||
(*Advanced note: the merged cmdset need NOT be the same as
|
||||
BigGuy.cmdset. The merged set can be a combination of the cmdsets
|
||||
from other objects in the room, for example*).
|
||||
- ``raw_string`` - this is the raw input coming from the user, without
|
||||
stripping any surrounding whitespace. The only thing that is stripped
|
||||
is the ending newline marker.
|
||||
|
||||
Defining your own command classes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -111,43 +119,44 @@ Beyond the properties Evennia always assigns to the command at runtime
|
|||
(listed above), your job is to define the following class properties:
|
||||
|
||||
- ``key`` (string) - the identifier for the command, like ``look``.
|
||||
This should (ideally) be unique. it can be more than one word long in
|
||||
a string, like "press button" or "pull lever left".
|
||||
This should (ideally) be unique. A key can consist of more than one
|
||||
word, like "press button" or "pull left lever".
|
||||
- ``aliases`` (optional list) - a list of alternate names for the
|
||||
command (``["l", "glance", "see"]``). Same name rules as for ``key``
|
||||
applies.
|
||||
- ``locks`` (string) - a `lock definition <Locks.html>`_, usually on
|
||||
the form ``cmd:<lockfuncs>``. Locks is a rather big topic, so until
|
||||
you learn more about locks, stick to giving the lockstring
|
||||
``"cmd:all()"`` to make the command available to everyone.
|
||||
``"cmd:all()"`` to make the command available to everyone (if you
|
||||
don't put a lock string, this will be assigned for you).
|
||||
- ``help_category`` (optional string) - setting this helps to structure
|
||||
the auto-help into categories. If none is set, this will be set to
|
||||
*General*.
|
||||
- ``save_for_next`` (optional boolean). This defaults to ``False``. If
|
||||
``True``, this command object (along with any changes you have done
|
||||
to it) will be stored by the system and can be accessed by the next
|
||||
command called by retrieving ``self.caller.ndb.last_cmd``. The next
|
||||
``True``, a copy of this command object (along with any changes you
|
||||
have done to it) will be stored by the system and can be accessed by
|
||||
the next command by retrieving ``self.caller.ndb.last_cmd``. The next
|
||||
run command will either clear or replace the storage.
|
||||
- 'arg\ *regex' (optional raw string): This should be given as a `raw
|
||||
- 'arg\_regex' (optional raw string): This should be given as a `raw
|
||||
regular expression string <http://docs.python.org/library/re.html>`_.
|
||||
This will be compiled by the system at runtime. This allows you to
|
||||
customize how the part*\ immediately following the command name (or
|
||||
alias) must look in order for the parser to match for this command.
|
||||
Normally the parser is highly efficient in picking out the command
|
||||
name, also as the beginning of a longer word (as long as the longer
|
||||
word is not a command name in it self). So ``"lookme"`` will be
|
||||
parsed as the command ``"look"`` followed by the argument ``"me"``.
|
||||
By using ``arg_regex`` you could for example force the parser to
|
||||
require an optional space following the command name (regex string
|
||||
for this would be ``r"\s.*?|$"``). In that case, ``"lookme"`` will
|
||||
lead to an "command not found" error while ``"look me"`` will work as
|
||||
expected.
|
||||
- autohelp (optional boolean). Defaults to ``True``. This allows for
|
||||
turning off the `auto-help
|
||||
system <HelpSystem#Command<i>Auto-help</i>system.html>`_ on a
|
||||
per-command basis. This could be useful if you either want to write
|
||||
your help entries manually or hide the existence of a command from
|
||||
``help``'s generated list.
|
||||
The regex will be compiled by the system at runtime. This allows you
|
||||
to customize how the part *immediately following* the command name
|
||||
(or alias) must look in order for the parser to match for this
|
||||
command. Normally the parser is highly efficient in picking out the
|
||||
command name, also as the beginning of a longer word (as long as the
|
||||
longer word is not a command name in it self). So ``"lookme"`` will
|
||||
be parsed as the command ``"look"`` followed by the argument
|
||||
``"me"``. By using ``arg_regex`` you could for example force the
|
||||
parser to require a space to follow the command name (regex string
|
||||
for this would be ``r"\s.*?|$"``). In that case, only ``"look me"``
|
||||
will work whereas ``"lookme"`` will lead to an "command not found"
|
||||
error.
|
||||
- auto\_help (optional boolean). Defaults to ``True``. This allows for
|
||||
turning off the
|
||||
[`HelpSystem <HelpSystem.html>`_\ #Command\_Auto-help\_system
|
||||
auto-help system] on a per-command basis. This could be useful if you
|
||||
either want to write your help entries manually or hide the existence
|
||||
of a command from ``help``'s generated list.
|
||||
|
||||
You should also implement at least two methods, ``parse()`` and
|
||||
``func()`` (You could also implement ``perm()``, but that's not needed
|
||||
|
|
@ -155,9 +164,14 @@ unless you want to fundamentally change how access checks work).
|
|||
|
||||
``parse()`` is intended to parse the arguments (``self.args``) of the
|
||||
function. You can do this in any way you like, then store the result(s)
|
||||
in variable(s) on the command object itself (i.e. on ``self``). The
|
||||
default mux-like system uses this method to detect "command switches",
|
||||
to take one example.
|
||||
in variable(s) on the command object itself (i.e. on ``self``). To take
|
||||
an example, the default mux-like system uses this method to detect
|
||||
"command switches" and store them as a list in ``self.switches``. Since
|
||||
the parsing is usually quite similar inside a command scheme you should
|
||||
make ``parse()`` as generic as possible and then inherit from it rather
|
||||
than re-implementing it over and over. In this way, the default
|
||||
``MuxCommand`` class implements a ``parse()`` for all child commands to
|
||||
use.
|
||||
|
||||
``func()`` is called right after ``parse()`` and should make use of the
|
||||
pre-parsed input to actually do whatever the command is supposed to do.
|
||||
|
|
@ -170,11 +184,55 @@ by the `Help system <HelpSystem.html>`_ to create the help entry for
|
|||
this command. You should decide on a way to format your help and stick
|
||||
to that.
|
||||
|
||||
Below is how you define a simple alternative "``look at``\ " command:
|
||||
Below is how you define a simple alternative "``smile``\ " command:
|
||||
|
||||
::
|
||||
|
||||
from ev import Commandclass CmdLookAt(Command): """ An alternative (and silly) look command Usage: look at <what> Where <what> may only be 'here' in this example. This initial string (the __doc__ string) is also used to auto-generate the help for this command ... """ key = "look at" # this is the command name to use aliases = ["la", "look a"] # aliases to the command name locks = "cmd:all()" help_category = "General" def parse(self): "Very trivial parser" self.what = self.args.strip() def func(self): "This actually does things" caller = self.caller if not self.what: caller.msg("Look at what?") elif self.what == 'here': # look at the current location description = caller.location.db.desc caller.msg(description) else: # we don't add any more functionality in this example caller.msg("Sorry, you can only look 'here'...")
|
||||
from ev import Command
|
||||
|
||||
class CmdSmile(Command):
|
||||
"""
|
||||
A smile command
|
||||
|
||||
Usage:
|
||||
smile [at] [<someone>]
|
||||
grin [at] [<someone>]
|
||||
|
||||
Smiles to someone in your vicinity or to the room
|
||||
in general.
|
||||
|
||||
(This initial string (the __doc__ string)
|
||||
is also used to auto-generate the help
|
||||
for this command)
|
||||
"""
|
||||
|
||||
key = "smile"
|
||||
aliases = ["smile at", "grin", "grin at"]
|
||||
locks = "cmd:all()"
|
||||
help_category = "General"
|
||||
|
||||
def parse(self):
|
||||
"Very trivial parser"
|
||||
self.target = self.args.strip()
|
||||
|
||||
def func(self):
|
||||
"This actually does things"
|
||||
caller = self.caller
|
||||
if not self.target or self.target == "here":
|
||||
string = "%s smiles." % caller.name
|
||||
caller.location.msg_contents(string, exclude=caller)
|
||||
caller.msg("You smile.")
|
||||
else:
|
||||
target = self.search(self.target)
|
||||
if not target:
|
||||
# self.search handles error messages
|
||||
return
|
||||
string = "%s smiles to you." % caller.name
|
||||
target.msg(string)
|
||||
string = "You smile to %s." % target.name
|
||||
caller.msg(string)
|
||||
string = "%s smiles to %s." % (caller.name, target.name)
|
||||
caller.location.msg_contents(string, exclude=[caller,target])
|
||||
|
||||
The power of having commands as classes and to separate ``parse()`` and
|
||||
``func()`` lies in the ability to inherit functionality without having
|
||||
|
|
@ -185,27 +243,25 @@ specifics of MUX-like commands. Almost none of the default commands thus
|
|||
need to implement ``parse()`` at all, but can assume the incoming string
|
||||
is already split up and parsed in suitable ways by its parent.
|
||||
|
||||
So we have created our own first command! But Evennia doesn't know about
|
||||
it yet. To tell it we have to add it to a *Command Set*.
|
||||
|
||||
Command Sets
|
||||
------------
|
||||
|
||||
All commands in Evennia are always grouped together into *Command Sets*
|
||||
(!CmdSets). A particular ``Command`` class definition can be part of any
|
||||
number of different !CmdSets. CmdSets can be stored on game
|
||||
`objects <Objects.html>`_ and on `Players <Players.html>`_ respectively.
|
||||
(CmdSets). A particular ``Command`` class definition can be part of any
|
||||
number of different CmdSets. CmdSets can be stored either on game
|
||||
`Objects <Objects.html>`_ or on `Players <Players.html>`_.
|
||||
|
||||
When a user issues a command, it is matched against the contents of all
|
||||
cmdsets available at the time, `merged
|
||||
together <Commands#Adding<i>and</i>merging<i>command</i>sets.html>`_.
|
||||
The currently valid command sets are collected from the following
|
||||
sources, in this order:
|
||||
cmdsets available to the user at the moment,
|
||||
[Commands#Adding\_and\_merging\_command\_sets merged together]. The
|
||||
currently valid command sets are collected from the following sources,
|
||||
in this order:
|
||||
|
||||
- The active cmdset on the character object
|
||||
- The cmdsets of objects carried by the character
|
||||
- The cmdset of the current location
|
||||
- The cmdset of objects in the currrent location (this includes exits)
|
||||
- The cmdset(s) of objects in the current location (this includes
|
||||
exits)
|
||||
- The channel commandset
|
||||
- The cmdset defined on the Player object controlling the character
|
||||
(OOC cmdset)
|
||||
|
|
@ -228,19 +284,32 @@ inheriting from the correct parent (``ev.CmdSet`` or
|
|||
``src.commands.cmdset.CmdSet``). The CmdSet class only needs to define
|
||||
one method, called ``at_cmdset_creation()``. All other class parameters
|
||||
are optional, but are used for more advanced set manipulation and coding
|
||||
(see the `merge rules <Commands#Merge_rules.html>`_ section).
|
||||
(see the [Commands#Merge\_rules merge rules] section).
|
||||
|
||||
::
|
||||
|
||||
from ev import CmdSet from game.gamesrc.commands import mycommands class MyCmdSet(CmdSet): def at_cmdset_creation(self): """ The only thing this method should need to do is to add commands to the set. """ self.add(mycommands.MyCommand1()) self.add(mycommands.MyCommand2()) self.add(mycommands.MyCommand3())
|
||||
from ev import CmdSet
|
||||
from game.gamesrc.commands import mycommands
|
||||
class MyCmdSet(CmdSet):
|
||||
def at_cmdset_creation(self):
|
||||
"""
|
||||
The only thing this method should need
|
||||
to do is to add commands to the set.
|
||||
"""
|
||||
self.add(mycommands.MyCommand1())
|
||||
self.add(mycommands.MyCommand2())
|
||||
self.add(mycommands.MyCommand3())
|
||||
|
||||
The !CmdSet's ``add()`` method can also take another CmdSet as input. In
|
||||
The CmdSet's ``add()`` method can also take another CmdSet as input. In
|
||||
this case all the commands from that CmdSet will be appended to this one
|
||||
as if you added them line by line:
|
||||
|
||||
::
|
||||
|
||||
at_cmdset_creation(): ... self.add(AdditionalCmdSet) # adds all command from this set ...
|
||||
at_cmdset_creation():
|
||||
...
|
||||
self.add(AdditionalCmdSet) # adds all command from this set
|
||||
...
|
||||
|
||||
If you added your command to an existing cmdset (like to the default
|
||||
cmdset), that set is already loaded into memory. You need to make the
|
||||
|
|
@ -248,7 +317,7 @@ server aware of the code changes:
|
|||
|
||||
::
|
||||
|
||||
@reload
|
||||
@reload
|
||||
|
||||
You should now be able to use the command.
|
||||
|
||||
|
|
@ -268,149 +337,18 @@ server, or you run
|
|||
|
||||
@py self.cmdset.delete('game.gamesrc.commands.mycmdset.MyCmdSet')
|
||||
|
||||
For more permanent addition, read the `step-by-step
|
||||
guide <Commands#Adding<i>a</i>new<i>command</i>-<i>a</i>step<i>by</i>step_guide.html>`_
|
||||
below. Generally you can customize which command sets are added to your
|
||||
objects by using ``self.cmdset.add()`` or ``self.cmdset.add_default()``.
|
||||
|
||||
Adding a new command - a step by step guide
|
||||
-------------------------------------------
|
||||
|
||||
This is a summary of the information from the previous sections. Let's
|
||||
assume you have just downloaded Evennia and wants to try to add a new
|
||||
command to use. This is the way to do it.
|
||||
|
||||
#. In ``game/gamesrc/commands``, create a new module. Name it, say,
|
||||
``mycommand.py``. Copy from the templates in ``examples/`` if you
|
||||
like.
|
||||
#. Import ``ev.default_cmds`` and access ``MuxCommand`` from that (this
|
||||
is a convenient shortcut so you don't have to import from src/
|
||||
directly). The ``MuxCommand`` class handles command line parsing for
|
||||
you, giving you stuff like /switches, the syntax using '=' etc). In
|
||||
other words, you don't have to implement ``parse()`` on your own.
|
||||
#. Create a new class in ``mycommand`` that inherits from
|
||||
``MuxCommand``.
|
||||
#. Set the class variable ``key`` to the name to call your command with,
|
||||
say ``mycommand``.
|
||||
#. Set the ``locks`` property on the command to a suitable
|
||||
`lockstring <Locks#Defining<i>locks.html>`_. If you are unsure, use
|
||||
"cmd:all()".
|
||||
#. Define a class method ``func()`` that does stuff. See below.
|
||||
#. Give your class a useful *doc*\ \_ string, this acts as the help
|
||||
entry for the command.
|
||||
|
||||
Your command definition is now ready. Here's an example of how it could
|
||||
look:
|
||||
|
||||
::
|
||||
|
||||
from ev import default_cmdsclass MyCommand(default_cmds.MuxCommand): """ Simple command example Usage: mycommand <text> This command simply echoes text back to the caller. (this string is also the help text for the command) """ key = "mycommand" locks = "cmd:all()" def func(self): "This actually does things" if not self.args: self.caller.msg("You didn't enter anything!") else: self.caller.msg("You gave the string: '%s'" % self.args)
|
||||
|
||||
Next we want to make this command available to us. There are many ways
|
||||
to do this, but all of them involves putting this command in a *Command
|
||||
Set*. First, let's try the more involved way.
|
||||
|
||||
#. Create a class that inherits from ``ev.CmdSet``.
|
||||
#. (Optionally) set a key to name your command set.
|
||||
#. Add a method ``at_cmdset_creation()`` and use the ``self.add()``
|
||||
method to add your command to the command set.
|
||||
|
||||
This is what we have now:
|
||||
|
||||
::
|
||||
|
||||
from ev import CmdSet
|
||||
from game.gamesrc.commands import mycommandclass MyCmdSet(CmdSet): key = "MyCmdSet" def at_cmdset_creation(self): self.add(mycommand.MyCommand())
|
||||
|
||||
This new command set could of course contain any number of commands. We
|
||||
will now temporarily *merge* this command set to your current set. This
|
||||
is a convenient way to test cmdsets without messing with your default
|
||||
setup - if you reset those extra comands will be gone again.
|
||||
|
||||
Log into your game (as user #1) and add the cmdset to yourself like
|
||||
this:
|
||||
|
||||
::
|
||||
|
||||
@py self.cmdset.add('game.gamesrc.commands.mycmdset.MyCmdSet')
|
||||
|
||||
There, you should now be able to run the ``mycommand`` command.
|
||||
|
||||
To remove a cmdset, use ``self.cmdset.delete()``. This will remove the
|
||||
latest set (you can also name the set to remove, this where the cmdset's
|
||||
key comes in handy).
|
||||
|
||||
You can replace an object's default command set with this command:
|
||||
|
||||
::
|
||||
|
||||
@py self.search("myobject").cmdset.add_default('game.gamesrc.commands.mycmdset.MyCmdSet')
|
||||
|
||||
If you want to this to survive a server shutdown, use the ``permanent``
|
||||
keyword:
|
||||
|
||||
::
|
||||
|
||||
@py self.search("myobject").cmdset.add_default('game.gamesrc.commands.mycmdset.MyCmdSet', permanent=True)
|
||||
|
||||
The default cmdset is never affected by ``cmdset.add()`` or
|
||||
``cmdset.delete()``, you have to use ``cmdset.add_default()`` to set it
|
||||
explicitly (use ``remove_default()`` to remove it). Be careful to
|
||||
replace or remove the default cmdset on yourself unless you really know
|
||||
what you are doing - you will loose all the default Evennia commands and
|
||||
might not be able to get them back ...
|
||||
|
||||
And finally,
|
||||
|
||||
::
|
||||
|
||||
@reload
|
||||
|
||||
to update the server to your changes.
|
||||
|
||||
Appending a new command to the default command set
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Instead of manually appending your custom cmdset you can choose to
|
||||
extend Evennia's default command set directly. That way your new command
|
||||
will be loaded every server start along with all the other default
|
||||
commands.
|
||||
|
||||
The default command set is found in
|
||||
``src/commands/default/cmdset_default.py`` but the template in
|
||||
``gamesrc/commands/examples/`` already shows how to extend it. Copy that
|
||||
file to ``game/gamesrc/`` and edit ``settings.CMDSET_DEFAULT`` to point
|
||||
to this class instead. Next you add your new commands to the end of the
|
||||
Default Cmdset in that file and you will in fact append it to the
|
||||
existing command set.
|
||||
|
||||
::
|
||||
|
||||
# file gamesrc/commands/examples/cmdset.py ... from game.gamesrc.commands import mycommandclass DefaultSet(BaseDefaultSet): key = DefaultMUX def at_cmdset_creation(self): # this first adds all default commands super(DefaultSet, self).at_cmdset_creation() # all commands added after this point will extend or # overwrite the default commands. self.add(mycommand.MyCommand())
|
||||
|
||||
Again, you need to run the ``@reload`` command to make these changes
|
||||
available.
|
||||
|
||||
Editing/replacing an existing default command
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This works the same way as in the previous section. Just set your new
|
||||
command class' ``key`` variable to be the same as that of the command to
|
||||
replace. So if you want to replace the default ``look`` command, just
|
||||
make sure to set your new command's ``key`` variable to ``look`` as
|
||||
well.
|
||||
|
||||
If you want to expand/build on the original command, just copy&paste its
|
||||
command class from ``src/commands/default``\ (the ``look`` class is for
|
||||
example found in ``src/commands/default/general.py``) into
|
||||
``game/gamesrc/commands`` and edit it there.
|
||||
For more permanent addition, read the
|
||||
[Commands#Adding\_a\_new\_command\_-*a\_step\_by\_step\_guide
|
||||
step-by-step guide] below. Generally you can customize which command
|
||||
sets are added to your objects by using ``self.cmdset.add()`` or
|
||||
``self.cmdset.add_default()``.*
|
||||
|
||||
Adding and merging command sets
|
||||
-------------------------------
|
||||
|
||||
*Note: This is an advanced topic. It's useful to know about, but you
|
||||
\_Note: This is an advanced topic. It's useful to know about, but you
|
||||
might want to skip it if this is your first time learning about
|
||||
commands.*
|
||||
commands.
|
||||
|
||||
CmdSets have the special ability that they can be *merged* together into
|
||||
new sets. This would happen if you, for example, did
|
||||
|
|
@ -445,7 +383,7 @@ having taken the super power-up. All this can be done on the fly by
|
|||
merging command sets.
|
||||
|
||||
Merge rules
|
||||
^^^^^^^^^^^
|
||||
~~~~~~~~~~~
|
||||
|
||||
To understand how sets merge, we need to define a little lingo. Let's
|
||||
call the first command set **A** and the second **B**. We will merge
|
||||
|
|
@ -475,7 +413,8 @@ Same-key commands are merged by priority.
|
|||
|
||||
::
|
||||
|
||||
# Union A1,A2 + B1,B2,B3,B4 = A1,A2,B3,B4
|
||||
# Union
|
||||
A1,A2 + B1,B2,B3,B4 = A1,A2,B3,B4
|
||||
|
||||
**Intersect** - Only commands found in *both* cmdsets (i.e. which have
|
||||
the same keys) end up in the merged cmdset, with the higher-priority
|
||||
|
|
@ -483,7 +422,8 @@ cmdset replacing the lower one's commands.
|
|||
|
||||
::
|
||||
|
||||
# Intersect A1,A3,A5 + B1,B2,B4,B5 = A1,A5
|
||||
# Intersect
|
||||
A1,A3,A5 + B1,B2,B4,B5 = A1,A5
|
||||
|
||||
**Replace** - The commands of the higher-prio cmdset completely replaces
|
||||
the lower-priority cmdset's commands, regardless of if same-key commands
|
||||
|
|
@ -491,7 +431,8 @@ exist or not.
|
|||
|
||||
::
|
||||
|
||||
# Replace A1,A3 + B1,B2,B4,B5 = A1,A3
|
||||
# Replace
|
||||
A1,A3 + B1,B2,B4,B5 = A1,A3
|
||||
|
||||
**Remove** - The high-priority command sets removes same-key commands
|
||||
from the lower-priority cmdset. They are not replaced with anything, so
|
||||
|
|
@ -500,35 +441,50 @@ high-prio one as a template.
|
|||
|
||||
::
|
||||
|
||||
# Remove A1,A3 + B1,B2,B3,B4,B5 = B2,B4,B5
|
||||
# Remove
|
||||
A1,A3 + B1,B2,B3,B4,B5 = B2,B4,B5
|
||||
|
||||
Besides ``priority`` and ``mergetype``, a command set also takes a few
|
||||
other variables to control how they merge:
|
||||
|
||||
- *allow*\ duplicates\ *(bool) - determines what happens when two sets
|
||||
of equal priority merge. Default is that the new set in the merger
|
||||
(i.e. **A** above) automatically takes precedence. But
|
||||
if*\ allow\ *duplicates* is true, the result will be a merger with
|
||||
more than one of each name match. This will usually lead to the
|
||||
player receiving a multiple-match error higher up the road, but can
|
||||
be good for things like cmdsets on non-player objects in a room, to
|
||||
allow the system to warn that more than one 'ball' in the room has
|
||||
the same 'kick' command defined on it, so it may offer a chance to
|
||||
select which ball to kick ... Allowing duplicates only makes sense
|
||||
for *Union* and *Intersect*, the setting is ignored for the other
|
||||
- *allow\_duplicates* (bool) - determines what happens when two sets of
|
||||
equal priority merge. Default is that the new set in the merger (i.e.
|
||||
**A** above) automatically takes precedence. But if
|
||||
*allow\_duplicates* is true, the result will be a merger with more
|
||||
than one of each name match. This will usually lead to the player
|
||||
receiving a multiple-match error higher up the road, but can be good
|
||||
for things like cmdsets on non-player objects in a room, to allow the
|
||||
system to warn that more than one 'ball' in the room has the same
|
||||
'kick' command defined on it, so it may offer a chance to select
|
||||
which ball to kick ... Allowing duplicates only makes sense for
|
||||
*Union* and *Intersect*, the setting is ignored for the other
|
||||
mergetypes.
|
||||
- *key*\ mergetype\ *(dict) - allows the cmdset to define a unique
|
||||
- *key\_mergetype* (dict) - allows the cmdset to define a unique
|
||||
mergetype for particular cmdsets, identified by their cmdset-key.
|
||||
Format is ``CmdSetkey:mergetype``. Priorities still apply. Example:
|
||||
``'Myevilcmdset','Replace'`` which would make sure for this set to
|
||||
always use 'Replace' on ``Myevilcmdset`` only, no matter
|
||||
what*\ mergetype\_ is set to.
|
||||
Format is ``{CmdSetkey:mergetype}``. Priorities still apply. Example:
|
||||
``{'Myevilcmdset','Replace'}`` which would make sure for this set to
|
||||
always use 'Replace' on ``Myevilcmdset`` only, no matter what
|
||||
*mergetype* is set to.
|
||||
|
||||
More advanced cmdset example:
|
||||
|
||||
::
|
||||
|
||||
class MyCmdSet(CmdSet): key = "MyCmdSet" priority = 4 mergetype = "Replace" key_mergetype = 'MyOtherCmdSet':'Union' def at_cmdset_creation(self): """ The only thing this method should need to do is to add commands to the set. """ self.add(mycommands.MyCommand1()) self.add(mycommands.MyCommand2()) self.add(mycommands.MyCommand3())
|
||||
class MyCmdSet(CmdSet):
|
||||
|
||||
key = "MyCmdSet"
|
||||
priority = 4
|
||||
mergetype = "Replace"
|
||||
key_mergetype = {'MyOtherCmdSet':'Union'}
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
"""
|
||||
The only thing this method should need
|
||||
to do is to add commands to the set.
|
||||
"""
|
||||
self.add(mycommands.MyCommand1())
|
||||
self.add(mycommands.MyCommand2())
|
||||
self.add(mycommands.MyCommand3())
|
||||
|
||||
System commands
|
||||
---------------
|
||||
|
|
@ -539,7 +495,7 @@ learning about commands.*
|
|||
There are several command-situations that are exceptional in the eyes of
|
||||
the server. What happens if the player enters an empty string? What if
|
||||
the 'command' given is infact the name of a channel the user wants to
|
||||
send a message to? Or maybe the name of an exit they want to traverse?
|
||||
send a message to? Or if there are multiple command possibilities?
|
||||
|
||||
Such 'special cases' are handled by what's called *system commands*. A
|
||||
system command is defined in the same way as other commands, except that
|
||||
|
|
@ -583,7 +539,13 @@ command must be added to a cmdset as well before it will work.
|
|||
|
||||
::
|
||||
|
||||
from ev import syscmdkeys, Commandclass MyNoInputCommand(Command): "Usage: Just press return, I dare you" key = syscmdkeys.CMD_NOINPUT def func(self): self.caller.msg("Don't just press return like that, talk to me!")
|
||||
from ev import syscmdkeys, Command
|
||||
|
||||
class MyNoInputCommand(Command):
|
||||
"Usage: Just press return, I dare you"
|
||||
key = syscmdkeys.CMD_NOINPUT
|
||||
def func(self):
|
||||
self.caller.msg("Don't just press return like that, talk to me!")
|
||||
|
||||
Exits
|
||||
-----
|
||||
|
|
@ -618,75 +580,76 @@ Any time the user sends text to Evennia, the server tries to figure out
|
|||
if the text entered corresponds to a known command. This is how the
|
||||
command handler sequence looks for a logged-in user:
|
||||
|
||||
A user (the *caller*) enters a string of text and presses enter.
|
||||
#. A user (the *caller*) enters a string of text and presses enter.
|
||||
|
||||
- If input is an empty string, resend command as ``CMD_NOINPUT``. If no
|
||||
such command is found in cmdset, ignore.
|
||||
- If command.key matches ``settings.IDLE_COMMAND``, update timers but
|
||||
don't do anything more.
|
||||
- If input is an empty string, resend command as ``CMD_NOINPUT``. If
|
||||
no such command is found in cmdset, ignore.
|
||||
- If command.key matches ``settings.IDLE_COMMAND``, update timers
|
||||
but don't do anything more.
|
||||
|
||||
Evennia's *commandhandler* gathers the CmdSets available to *caller* at
|
||||
the time:
|
||||
#. Evennia's *commandhandler* gathers the CmdSets available to *caller*
|
||||
at the time:
|
||||
|
||||
- The caller's own currently active !CmdSet.
|
||||
- The active CmdSets of eventual objects in the same location (if any).
|
||||
This includes commands on `Exits <Objects#Exits.html>`_.
|
||||
- Sets of dynamically created *System commands* representing available
|
||||
`Channels <Communications.html>`_.
|
||||
- !CmdSet defined on the *caller.player* (OOC cmdset).
|
||||
- The caller's own currently active CmdSet.
|
||||
- The active CmdSets of eventual objects in the same location (if
|
||||
any). This includes commands on [Objects#Exits Exits].
|
||||
- Sets of dynamically created *System commands* representing
|
||||
available `Channels <Communications.html>`_.
|
||||
- CmdSet defined on the *caller.player* (OOC cmdset).
|
||||
|
||||
All the CmdSets are *merged* into one combined CmdSet according to each
|
||||
set's merge rules.
|
||||
#. All the CmdSets are *merged* into one combined CmdSet according to
|
||||
each set's merge rules.
|
||||
#. Evennia's *command parser* takes the merged cmdset and matches each
|
||||
of its commands (using its key and aliases) against the beginning of
|
||||
the string entered by *caller*. This produces a set of candidates.
|
||||
#. The *cmd parser* next rates the matches by how many characters they
|
||||
have and how many percent matches the respective known command. Only
|
||||
if candidates cannot be separated will it return multiple matches.
|
||||
|
||||
Evennia's *command parser* takes the merged cmdset and matches each of
|
||||
its commands (using its key and aliases) against the beginning of the
|
||||
string entered by *caller*. This produces a set of candidates.
|
||||
- If multiple matches were returned, resend as ``CMD_MULTIMATCH``.
|
||||
If no such command is found in cmdset, return hard-coded list of
|
||||
matches.
|
||||
- If no match was found, resend as ``CMD_NOMATCH``. If no such
|
||||
command is found in cmdset, give hard-coded error message.
|
||||
|
||||
The *cmd parser* next rates the matches by how many characters they have
|
||||
and how many percent matches the respective known command. Only if
|
||||
candidates cannot be separated will it return multiple matches.
|
||||
|
||||
- If multiple matches were returned, resend as ``CMD_MULTIMATCH``. If
|
||||
no such command is found in cmdset, return hard-coded list of
|
||||
matches.
|
||||
- If no match was found, resend as ``CMD_NOMATCH``. If no such command
|
||||
is found in cmdset, give hard-coded error message.
|
||||
|
||||
If a single command was found by the parser, the correct command class
|
||||
is plucked out of storage and instantiated.
|
||||
|
||||
It is checked that the caller actually has access to the command by
|
||||
validating the *lockstring* of the command. If not, it is not considered
|
||||
as a suitable match it is resent as ``CMD_NOPERM`` is created. If no
|
||||
such command is found in cmdset, use hard-coded error message.
|
||||
|
||||
If the new command is tagged as a channel-command, resend as
|
||||
``CMD_CHANNEL``. If no such command is found in cmdset, use hard-coded
|
||||
implementation.
|
||||
|
||||
Assign several useful variables to the command instance.
|
||||
|
||||
Call ``at_pre_command()`` on the command instance.
|
||||
|
||||
Call ``parse()`` on the command instance. This is is fed the remainder
|
||||
of the string, after the name of the command. It's intended to pre-parse
|
||||
the string int a form useful for the ``func()`` method.
|
||||
|
||||
Call ``func()`` on the command instance. This is the functional body of
|
||||
the command, actually doing useful things.
|
||||
|
||||
Call ``at_post_command()`` on the command instance.
|
||||
#. If a single command was found by the parser, the correct command
|
||||
class is plucked out of storage and instantiated.
|
||||
#. It is checked that the caller actually has access to the command by
|
||||
validating the *lockstring* of the command. If not, it is not
|
||||
considered as a suitable match it is resent as ``CMD_NOPERM`` is
|
||||
created. If no such command is found in cmdset, use hard-coded error
|
||||
message.
|
||||
#. If the new command is tagged as a channel-command, resend as
|
||||
``CMD_CHANNEL``. If no such command is found in cmdset, use
|
||||
hard-coded implementation.
|
||||
#. Assign several useful variables to the command instance.
|
||||
#. Call ``at_pre_command()`` on the command instance.
|
||||
#. Call ``parse()`` on the command instance. This is is fed the
|
||||
remainder of the string, after the name of the command. It's intended
|
||||
to pre-parse the string int a form useful for the ``func()`` method.
|
||||
#. Call ``func()`` on the command instance. This is the functional body
|
||||
of the command, actually doing useful things.
|
||||
#. Call ``at_post_command()`` on the command instance.
|
||||
|
||||
Assorted notes
|
||||
--------------
|
||||
|
||||
The return value of ``Command.func()`` *is* safely passed on should one
|
||||
have some very specific use case in mind. So one could in principle do
|
||||
``value = obj.execute_cmd(cmdname)``. Evennia does not use this
|
||||
functionality at all by default (all default commands simply returns
|
||||
``None``) and it's probably not relevant to any but the most
|
||||
advanced/exotic designs (one might use it to create a "nested" command
|
||||
structure for example).
|
||||
The return value of ``Command.func()`` is a Twisted
|
||||
`deferred <http://twistedmatrix.com/documents/current/core/howto/defer.html>`_.
|
||||
Evennia does not use this return value at all by default. If you do, you
|
||||
must thus do so asychronously, using callbacks.
|
||||
|
||||
::
|
||||
|
||||
# in command class func()
|
||||
def callback(ret, caller):
|
||||
caller.msg("Returned is %s" % ret)
|
||||
deferred = self.execute_command("longrunning")
|
||||
deferred.addCallback(callback, self.caller)
|
||||
|
||||
This is probably not relevant to any but the most advanced/exotic
|
||||
designs (one might use it to create a "nested" command structure for
|
||||
example).
|
||||
|
||||
The ``save_for_next`` class variable can be used to implement
|
||||
state-persistent commands. For example it can make a command operate on
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Handling of ooc communications in game
|
||||
|
||||
Communications
|
||||
==============
|
||||
|
||||
|
|
@ -24,7 +26,7 @@ communications, both in channels, but also for allowing
|
|||
senders/receivers to have 'mailboxes' with the messages they want to
|
||||
keep.
|
||||
|
||||
Properties defined on ``Msg``
|
||||
Properties defined on \`Msg\`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- ``sender`` - this is a reference to a unique `Player <Players.html>`_
|
||||
|
|
@ -36,8 +38,8 @@ Properties defined on ``Msg``
|
|||
- ``date_sent`` - when message was sent (auto-created).
|
||||
- ``locks`` - a `lock definition <Locks.html>`_.
|
||||
|
||||
You create new messages in code using
|
||||
``src.utils.create.create_message.``
|
||||
You create new messages in code using ``ev.create_message`` (or
|
||||
``src.utils.create.create_message.``)
|
||||
|
||||
!TempMsg
|
||||
~~~~~~~~
|
||||
|
|
@ -62,7 +64,8 @@ for asking questions). The default channels created are defined by
|
|||
``settings.CHANNEL_PUBLIC``, ``settings.CHANNEL_MUDINFO`` and
|
||||
``settings.CHANNEL_CONNECTINFO``.
|
||||
|
||||
You create new channels with ``src.utils.create.create_channel()``.
|
||||
You create new channels with ``ev.create_message`` (or
|
||||
``src.utils.create.create_channel``).
|
||||
|
||||
In code, messages are sent to a channel using the
|
||||
``msg(message, from_obj=None)`` method. The argument ``message`` can
|
||||
|
|
@ -76,14 +79,25 @@ send a non-persistent message, also if you send it a ``Msg`` object.
|
|||
|
||||
::
|
||||
|
||||
# assume we have a 'sender' object and a channel named 'mychan'# send and store in database from src.utils import create mymsg = create.create_message(sender, "Hello!", channels=[mychan]) mychan.msg(mymsg)# send a one-time message mychan.msg("Hello!")# send a one-time message created from a Msg object mychan.tempmsg(mymsg)
|
||||
# assume we have a 'sender' object and a channel named 'mychan'
|
||||
|
||||
# send and store in database
|
||||
from src.utils import create
|
||||
mymsg = create.create_message(sender, "Hello!", channels=[mychan])
|
||||
mychan.msg(mymsg)
|
||||
|
||||
# send a one-time message
|
||||
mychan.msg("Hello!")
|
||||
|
||||
# send a one-time message created from a Msg object
|
||||
mychan.tempmsg(mymsg)
|
||||
|
||||
As a more advanced note, sending text to channels is a "special
|
||||
exception" as far as commands are concerned, and you may completely
|
||||
customize how this works by defining a *system*\ command\_ with your own
|
||||
customize how this works by defining a *system\_command* with your own
|
||||
code. See `Commands <Commands.html>`_ for more details.
|
||||
|
||||
Properties defined on ``Channel``
|
||||
Properties defined on \`Channel\`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- ``key`` - main name for channel
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Customizing the connection screen
|
||||
|
||||
The Connection Screen
|
||||
=====================
|
||||
|
||||
|
|
@ -8,7 +10,15 @@ tells you how to connect.
|
|||
::
|
||||
|
||||
==============================================================
|
||||
Welcome to Evennia, version HG-Alpha! If you have an existing account, connect to it by typing: connect <email> <password> If you need to create an account, type (without the <>'s): create "<username>" <email> <password> Enter help for more info. look will re-show this screen. ==============================================================
|
||||
Welcome to Evennia, version HG-Beta!
|
||||
|
||||
If you have an existing account, connect to it by typing:
|
||||
connect <email> <password>
|
||||
If you need to create an account, type (without the <>'s):
|
||||
create "<username>" <email> <password>
|
||||
|
||||
Enter help for more info. look will re-show this screen.
|
||||
==============================================================
|
||||
|
||||
Effective, but not very exciting. You will most likely want to change
|
||||
this to be more unique for your game.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Contributing
|
||||
|
||||
Contributing to Evennia
|
||||
=======================
|
||||
|
||||
|
|
@ -12,9 +14,8 @@ typos is a great help. To edit the wiki yourself you need contributor
|
|||
access. Otherwise, it goes a long way just pointing out wiki errors so
|
||||
devs can fix them (in an Issue or just over chat/forum). You can also
|
||||
commit wiki changes over Mercurial - just go to the wiki repository
|
||||
"http://code.google.com/p/evennia/source/checkout?repo
|
||||
|
||||
wiki">here and then continue from point ``2`` below.
|
||||
`here <http://code.google.com/p/evennia/source/checkout?repo=wiki>`_ and
|
||||
then continue from point ``2`` below.
|
||||
|
||||
Contributing with Code through a clone repository
|
||||
-------------------------------------------------
|
||||
|
|
@ -37,8 +38,8 @@ do this once):
|
|||
something useful, like "Johns-evennia-fixes". Give a brief summary,
|
||||
like "my repo for contributing to Evennia". Accept.
|
||||
#. Your new repo is created. You should see it appear in the `clone-repo
|
||||
list <https://code.google.com/p/evennia/source/clones.html>`_. This
|
||||
is actually your own mini-version of the Evennia page!
|
||||
list <https://code.google.com/p/evennia/source/clones>`_. This is
|
||||
actually your own mini-version of the Evennia page!
|
||||
#. Choose your repo and you will find it has its own Checkout page. Use
|
||||
the command shown there to get a local copy of your clone to your
|
||||
computer.
|
||||
|
|
@ -61,7 +62,9 @@ Once you have an online clone and a local copy of it:
|
|||
commits, so it's possible to pick individual features.
|
||||
|
||||
From your online repo, Evennia devs can then, assuming the change is
|
||||
deemed good, pick and merge your work into Evennia proper.
|
||||
deemed good, pick and merge your work into Evennia proper. Mercurial
|
||||
will automatically make sure you get proper credit for your contribution
|
||||
in the source code history.
|
||||
|
||||
Contributing with Patches
|
||||
-------------------------
|
||||
|
|
@ -69,21 +72,20 @@ Contributing with Patches
|
|||
To help with Evennia development it's recommended to do so using a clone
|
||||
repository as described above. But for small, well isolated fixes you
|
||||
are also welcome to submit your suggested Evennia fixes/addendums as
|
||||
*patches*. You can use normal
|
||||
`patches <https://secure.wikimedia.org/wikipedia/en/wiki/Patch_%28computing%29.html>`_,
|
||||
*patches*. You can use `normal
|
||||
patches <https://secure.wikimedia.org/wikipedia/en/wiki/Patch_%28computing%29>`_,
|
||||
but it might be easier to use mercurial's own patch mechanism. Make sure
|
||||
you have committed your latest fixes first, then
|
||||
|
||||
::
|
||||
|
||||
hg export tip > mypatch.patch
|
||||
hg export tip > mypatch.patch
|
||||
|
||||
This will create a patch file ``mypatch.patch`` that can be imported by
|
||||
others with ``hg import mypatch.patch``. Depending on what fits best,
|
||||
post your patch to the `issue
|
||||
tracker <https://code.google.com/p/evennia/issues/list.html>`_ or to the
|
||||
`discussion
|
||||
forum <https://groups.google.com/forum/#!forum/evennia.html>`_. Please
|
||||
avoid pasting the full patch text directly in your post though, best is
|
||||
to use a site like `Pastebin <http://pastebin.com/>`_ and just supply
|
||||
the link.
|
||||
tracker <https://code.google.com/p/evennia/issues/list>`_ or to the
|
||||
`discussion forum <https://groups.google.com/forum/#!forum/evennia>`_.
|
||||
Please avoid pasting the full patch text directly in your post though,
|
||||
best is to use a site like `Pastebin <http://pastebin.com/>`_ and just
|
||||
supply the link.
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -7,15 +7,9 @@ codebase itself. Everyone is welcome to `help
|
|||
out <http://code.google.com/p/evennia/wiki/Contributing>`_! If you have
|
||||
any questions, please feel free to ask them in the `Forum/Discussion
|
||||
Group <http://www.evennia.com/discussions>`_. If you want more docs on a
|
||||
particular issue, consider filling out our
|
||||
"https://docs.google.com/spreadsheet/viewform?hl
|
||||
|
||||
en\_US&formkey
|
||||
|
||||
dGN0VlJXMWpCT3VHaHpscDEzY1RoZGc6MQ#gid
|
||||
======================================
|
||||
|
||||
0.html">online form and tell us! Bugs should be reported to the `Issue
|
||||
particular issue, consider filling out our `online
|
||||
form <https://docs.google.com/spreadsheet/viewform?hl=en_US&formkey=dGN0VlJXMWpCT3VHaHpscDEzY1RoZGc6MQ#gid=0>`_
|
||||
and tell us! Bugs should be reported to the `Issue
|
||||
tracker <http://code.google.com/p/evennia/issues/list>`_. You can find
|
||||
more links to Evennia resources from the `Links <Links.html>`_ page.
|
||||
|
||||
|
|
@ -31,36 +25,30 @@ General Evennia development information
|
|||
- `Policy for 'MUX-like' default commands <UsingMUXAsAStandard.html>`_
|
||||
- `Setting up a Mercurial environment for
|
||||
coding <VersionControl.html>`_
|
||||
- `Planning your own Evennia game <GamePlanning.html>`_
|
||||
|
||||
Evennia Component Documentation
|
||||
-------------------------------
|
||||
|
||||
- `ev - the flat API <evAPI.html>`_
|
||||
|
||||
`Directory Overview <DirectoryOverview.html>`_
|
||||
- `Directory Overview <DirectoryOverview.html>`_
|
||||
- `Portal and Server <PortalAndServer.html>`_
|
||||
- `Commands <Commands.html>`_
|
||||
- `Typeclass system <Typeclasses.html>`_
|
||||
|
||||
`Portal and Server <PortalAndServer.html>`_
|
||||
- `Objects <Objects.html>`_
|
||||
- `Scripts <Scripts.html>`_
|
||||
- `Players <Players.html>`_
|
||||
- `Attributes <Attributes.html>`_
|
||||
|
||||
`Commands <Commands.html>`_
|
||||
|
||||
`Typeclass system <Typeclasses.html>`_
|
||||
|
||||
- `Objects <Objects.html>`_
|
||||
- `Scripts <Scripts.html>`_
|
||||
- `Players <Players.html>`_
|
||||
- `Attributes <Attributes.html>`_
|
||||
|
||||
`Locks and Permissions <Locks.html>`_
|
||||
|
||||
`Communications <Communications.html>`_
|
||||
|
||||
`Help System <HelpSystem.html>`_
|
||||
|
||||
`Nicks <Nicks.html>`_
|
||||
|
||||
`Sessions and Protocols <SessionProtocols.html>`_
|
||||
|
||||
`Web features <WebFeatures.html>`_
|
||||
- `Locks and Permissions <Locks.html>`_
|
||||
- `Communications <Communications.html>`_
|
||||
- `Help System <HelpSystem.html>`_
|
||||
- `Nicks <Nicks.html>`_
|
||||
- `Sessions and Protocols <SessionProtocols.html>`_
|
||||
- `Web features <WebFeatures.html>`_
|
||||
- `Configuration and module plugins <ServerConf.html>`_
|
||||
|
||||
Programming Evennia
|
||||
-------------------
|
||||
|
|
@ -69,18 +57,8 @@ Programming Evennia
|
|||
game <ExecutePythonCode.html>`_
|
||||
- `Useful coding utilities <CodingUtils.html>`_
|
||||
- `Running and writing unit tests for Evennia <UnitTesting.html>`_
|
||||
- `Removing Colour from your game - tutorial on redefining typeclass
|
||||
methods <RemovingColour.html>`_
|
||||
- `Adding a Command prompt <CommandPrompt.html>`_
|
||||
- `Running processes asynchronously <AsyncProcess.html>`_
|
||||
|
||||
Game implementation hints
|
||||
-------------------------
|
||||
|
||||
- `Planning your own Evennia game <GamePlanning.html>`_
|
||||
- `Creating a Zoning system <Zones.html>`_
|
||||
- `Implementing cooldowns for commands <CommandCooldown.html>`_
|
||||
|
||||
Work in Progress - Developer brainstorms and whitepages
|
||||
-------------------------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
The layout of the evennia package
|
||||
|
||||
Evennia directory overview
|
||||
==========================
|
||||
|
||||
|
|
@ -9,7 +11,7 @@ Evennia directory overview
|
|||
docs/
|
||||
game/
|
||||
locale/
|
||||
src/
|
||||
src/
|
||||
|
||||
Evennia's main directory (``evennia``) is divided into five sub
|
||||
directories - ``src/``, ``game/``, ``contrib/`` , ``locale`` and
|
||||
|
|
@ -32,7 +34,7 @@ places in ``src/``. By importing ``ev`` from your code in ``game/`` you
|
|||
have access to most important Evennia systems without *having* to know
|
||||
where everything is located, as described in the following sections.
|
||||
|
||||
The ``docs/`` directory
|
||||
The \`docs/\` directory
|
||||
-----------------------
|
||||
|
||||
This contains Evennia's offline documentation. The main source of
|
||||
|
|
@ -51,7 +53,7 @@ create a nice browsable web-index of all the sources and comments. In
|
|||
the same way you could in theory also create nice ``LaTeX``-formatted
|
||||
PDFs of the Evennia source (all 400+ pages of it ...).
|
||||
|
||||
The ``locale/`` directory
|
||||
The \`locale/\` directory
|
||||
-------------------------
|
||||
|
||||
This contains internationalization strings for translating the Evennia
|
||||
|
|
@ -59,7 +61,7 @@ core server to different languages. See
|
|||
`Internationalization <Internationalization.html>`_ for more
|
||||
information.
|
||||
|
||||
The ``contrib/`` ("contributions") directory
|
||||
The \`contrib/\` ("contributions") directory
|
||||
--------------------------------------------
|
||||
|
||||
This directory contains various stand-alone code snippets that are
|
||||
|
|
@ -69,7 +71,7 @@ you explicitly import and use them. The contrib folder also contains the
|
|||
`Tutorial World <TutorialWorldIntroduction.html>`_ game example. See
|
||||
``contrib/README`` for more information.
|
||||
|
||||
The ``game/`` directory
|
||||
The \`game/\` directory
|
||||
-----------------------
|
||||
|
||||
``game/`` contains everything related to a particular game world. If you
|
||||
|
|
@ -83,9 +85,20 @@ the server.
|
|||
|
||||
game/
|
||||
evennia.py
|
||||
manage.py gamesrc/ commands/ examples/ scripts/ examples/ objects/ examples/ world/ examples/ conf/
|
||||
manage.py
|
||||
|
||||
``game/gamesrc/``
|
||||
gamesrc/
|
||||
commands/
|
||||
examples/
|
||||
scripts/
|
||||
examples/
|
||||
objects/
|
||||
examples/
|
||||
world/
|
||||
examples/
|
||||
conf/
|
||||
|
||||
\`game/gamesrc/\`
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
``game/gamesrc`` is where you will be spending most of your time. All
|
||||
|
|
@ -97,7 +110,7 @@ blinks and does interesting stuff when pressed. It's designed to combine
|
|||
many different systems and to show off several advanced features of
|
||||
Evennia.
|
||||
|
||||
``gamesrc/commands/``
|
||||
\`gamesrc/commands/\`
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``gamesrc/commands/`` contains modules for defining
|
||||
|
|
@ -105,14 +118,14 @@ Evennia.
|
|||
templates for starting to define your own commands and cmdsets. Copy
|
||||
these out into the parent ``command`` folder and work from there.
|
||||
|
||||
``gamesrc/scripts/``
|
||||
\`gamesrc/scripts/\`
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``gamesrc/scripts/`` holds everything related to
|
||||
`Scripts <Scripts.html>`_. ``scripts/examples`` holds templates you can
|
||||
make copies of and build from to define your own scripts.
|
||||
|
||||
``gamesrc/objects/``
|
||||
\`gamesrc/objects/\`
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``gamesrc/objects/`` should contain the definitions for all your
|
||||
|
|
@ -121,7 +134,7 @@ make copies of and build from to define your own scripts.
|
|||
*Exit*. Make copies of these templates to have somthing to start from
|
||||
when defining your own in-game entities.
|
||||
|
||||
``gamesrc/world/``
|
||||
\`gamesrc/world/\`
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``gamesrc/world/``, contains all the rest that make up your world. This
|
||||
|
|
@ -142,7 +155,7 @@ formatting.
|
|||
creates a *Red Button* object in *Limbo* using their respective special
|
||||
syntax.
|
||||
|
||||
``gamesrc/conf/``
|
||||
\`gamesrc/conf/\`
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
``gamesrc/conf/`` holds optional extension modules for the Evennia
|
||||
|
|
@ -151,7 +164,7 @@ for the various config files that the server undertands. Each template
|
|||
file contains instructions for how you should use them; copy out the
|
||||
ones you want into the ``conf/`` directory and edit them there.
|
||||
|
||||
The ``src/`` directory
|
||||
The \`src/\` directory
|
||||
----------------------
|
||||
|
||||
``src/`` contains the main running code of the Evennia server. You can
|
||||
|
|
@ -167,7 +180,19 @@ or features missing, file a bug report or send us a message.
|
|||
::
|
||||
|
||||
src/
|
||||
settings_defaults.py commands/ comms/ help/ objects/ locks/ players/ scripts/ server/ typeclasses/ utils/ web/
|
||||
settings_defaults.py
|
||||
|
||||
commands/
|
||||
comms/
|
||||
help/
|
||||
objects/
|
||||
locks/
|
||||
players/
|
||||
scripts/
|
||||
server/
|
||||
typeclasses/
|
||||
utils/
|
||||
web/
|
||||
|
||||
Most of the folders in ``src/`` are technically "Django apps",
|
||||
identified by containing a file ``models.py`` and usually
|
||||
|
|
@ -193,7 +218,7 @@ file. This is the main configuration file of Evennia. You should
|
|||
copy&paste entries from this file to your ``game/settings.py`` file if
|
||||
you want to customize any setting.
|
||||
|
||||
``src/commands/``
|
||||
\`src/commands/\`
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
This directory contains the `command system <Commands.html>`_ of
|
||||
|
|
@ -208,41 +233,41 @@ here. If you want to edit a default command, copy&paste the respective
|
|||
module to ``game/gamesrc/commands/`` and edit the default cmdset to
|
||||
point to your copy.
|
||||
|
||||
``src/comms/``
|
||||
\`src/comms/\`
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
``src/comms/`` defines all aspects of OOC
|
||||
`communication <Communications.html>`_, notably *channels*, *messages*
|
||||
and the basic operators for connecting external listeners to channels.
|
||||
|
||||
``src/help/``
|
||||
\`src/help/\`
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This defines the `help system <HelpSystem.html>`_ of Evennia, the
|
||||
command auto-help as well as the database-centric storage of in-game
|
||||
help files.
|
||||
|
||||
``src/objects/``
|
||||
\`src/objects/\`
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
``src/objects/`` defines how the in-game `objects <Objects.html>`_ are
|
||||
stored, found and handled in the database.
|
||||
|
||||
``src/locks/``
|
||||
\`src/locks/\`
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This directory defines the powerful `lock system <Locks.html>`_ of
|
||||
Evennia, a system that serves to restrict access to objects. The default
|
||||
lock functions are found here.
|
||||
|
||||
``src/players/``
|
||||
\`src/players/\`
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The `Player <Players.html>`_ is the OOC-represention of the person
|
||||
connected to the game. This directory defines the database handling and
|
||||
methods acting on the Player object.
|
||||
|
||||
``src/scripts/``
|
||||
\`src/scripts/\`
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
``src/scripts/`` defines all aspects of `Scripts <Scripts.html>`_ - how
|
||||
|
|
@ -250,7 +275,7 @@ they are activated, repeated and stored in-memory or in-database. The
|
|||
main engine scripts (e.g. for keeping track of game-time, uptime and
|
||||
connection timeouts) are also defined here.
|
||||
|
||||
``src/server/``
|
||||
\`src/server/\`
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
This directory is the heart of Evennia. It holds the server process
|
||||
|
|
@ -259,7 +284,7 @@ and protocols <SessionProtocols.html>`_ that allow users to connect to
|
|||
the game. It also knows how to store dynamic server info in the
|
||||
database.
|
||||
|
||||
``src/typeclasses/``
|
||||
\`src/typeclasses/\`
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``src/typeclasses/`` defines the `Typeclass system <Typeclasses.html>`_
|
||||
|
|
@ -270,7 +295,7 @@ and Players all inherit from its core classes. Also
|
|||
`attributes <Attributes.html>`_ are defined here, being an vital part of
|
||||
the typeclass system.
|
||||
|
||||
``src/utils/``
|
||||
\`src/utils/\`
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
``src/utils/`` is a useful directory that contains helper functions for
|
||||
|
|
@ -281,7 +306,7 @@ managers directly. ``utils/search.py`` search a similar function for
|
|||
searching the database. This directory also contains many helper modules
|
||||
for parsing and converting data in various ways.
|
||||
|
||||
``src/web/``
|
||||
\`src/web/\`
|
||||
~~~~~~~~~~~~
|
||||
|
||||
This directory contains features related to running Evennia's `web site
|
||||
|
|
|
|||
|
|
@ -51,8 +51,8 @@ function on the database model or if it in fact sat on the script parent
|
|||
(the call was made through something called the "scriptlink").
|
||||
|
||||
By contrast, a typeclass is a normal python class that inherits from the
|
||||
*!TypeClass* parent. There are no other required functions to define.
|
||||
This class uses **\ getattribute\ ** and **\ setattr\ ** transparently
|
||||
*TypeClass* parent. There are no other required functions to define.
|
||||
This class uses *\_getattribute\_* and *\_setattr\_* transparently
|
||||
behind the scenes to store data onto the persistent django object. Also
|
||||
the django model is aware of the typeclass in the reverse direction. The
|
||||
admin don't really have to worry about this connection, they can usually
|
||||
|
|
@ -70,12 +70,12 @@ create the objects rather than to create objects with plain Django by
|
|||
instantiating the model class; this so that the rather complex
|
||||
relationships can be instantiated safely behind the scenes.
|
||||
|
||||
Command functions + !StateCommands-> Command classes + CmdSets
|
||||
--------------------------------------------------------------
|
||||
Command functions + !StateCommands-> Command classes + !CmdSets
|
||||
---------------------------------------------------------------
|
||||
|
||||
In trunk, there was one default group of commands in a list
|
||||
GLOBAL\ *CMD*\ TABLE. Every player in game used this. There was a second
|
||||
dictionary GLOBAL\ *STATE*\ TABLE that held commands valid only for
|
||||
GLOBAL\_CMD\_TABLE. Every player in game used this. There was a second
|
||||
dictionary GLOBAL\_STATE\_TABLE that held commands valid only for
|
||||
certain *states* the player might end up in - like entering a dark room,
|
||||
a text editor, or whatever. The problem with this state system, was that
|
||||
it was limited in its use - every player could ever only be in one state
|
||||
|
|
@ -85,7 +85,7 @@ object could not offer different commands dependent on its state, for
|
|||
example.
|
||||
|
||||
In devel, *every* command definition is grouped in what's called a
|
||||
*!CmdSet* (this is, like most things in Devel, defined as a class). A
|
||||
*CmdSet* (this is, like most things in Devel, defined as a class). A
|
||||
command can exist in any number of cmdsets at the same time. Also the
|
||||
'default' group of commands belong to a cmdset. These command sets are
|
||||
no longer stored globally, but instead locally on each object capable of
|
||||
|
|
@ -117,7 +117,9 @@ Example of new command definition:
|
|||
|
||||
::
|
||||
|
||||
class CmdTest(Command): def func(self): self.caller.msg("This is the test!")
|
||||
class CmdTest(Command):
|
||||
def func(self):
|
||||
self.caller.msg("This is the test!")
|
||||
|
||||
Events + States -> Scripts
|
||||
--------------------------
|
||||
|
|
@ -263,14 +265,16 @@ just do:
|
|||
|
||||
::
|
||||
|
||||
obj.db.attr = value value = obj.db.attr
|
||||
obj.db.attr = value
|
||||
value = obj.db.attr
|
||||
|
||||
And for storing something non-persistently (stored only until the server
|
||||
reboots) you can just do
|
||||
|
||||
::
|
||||
|
||||
obj.attr = value value = obj.attr
|
||||
obj.attr = value
|
||||
value = obj.attr
|
||||
|
||||
The last example may sound trivial, but it's actually impossible to do
|
||||
in trunk since django objects are not guaranteed to remain the same
|
||||
|
|
@ -320,7 +324,7 @@ where it might be unclear if you receive a session or a player object
|
|||
(especially during login/logout), you can now use simply use ``msg()``
|
||||
without having to check (however, you *can* still use ``emit_to`` for
|
||||
legacy code, it's an alias to msg() now). Same is true with
|
||||
emit\ *to*\ contents() -> msg\ *to*\ contents().
|
||||
emit\_to\_contents() -> msg\_to\_contents().
|
||||
|
||||
``source_object`` in default commands are now consistently named
|
||||
*caller* instead.
|
||||
|
|
@ -343,7 +347,7 @@ function into a class and add the parse(self) and func(self) methods
|
|||
much double code), as well as learn what variable names is made
|
||||
available (see the commands in ``gamesrc/commands/default`` for
|
||||
guidance). You can make States into CmdSets very easy - just listing the
|
||||
commands needed for the state in a new !CmdSet.
|
||||
commands needed for the state in a new CmdSet.
|
||||
|
||||
Script parents are made into Typeclasses by deleting the factory
|
||||
function and making them inherit from a TypeClassed object (such as
|
||||
|
|
|
|||
|
|
@ -1,23 +1,26 @@
|
|||
"*A MUD (originally Multi-User Dungeon, with later variants Multi-User
|
||||
Dimension and Multi-User Domain), pronounced /ˈmʌd/, is a multiplayer
|
||||
real-time virtual world described primarily in text. MUDs combine
|
||||
elements of role-playing games, hack and slash, player versus player,
|
||||
interactive fiction, and online chat. Players can read or view
|
||||
descriptions of rooms, objects, other players, non-player characters,
|
||||
and actions performed in the virtual world. Players typically interact
|
||||
with each other and the world by typing commands that resemble a natural
|
||||
language.*\ " - `Wikipedia <http://en.wikipedia.org/wiki/MUD>`_
|
||||
Introduction
|
||||
|
||||
Evennia introduction=
|
||||
"*A MUD (originally Multi-User Dungeon, with later variants
|
||||
Multi-User Dimension and Multi-User Domain), pronounced 'mud', is a
|
||||
multiplayer real-time virtual world described primarily in text.
|
||||
MUDs combine elements of role-playing games, hack and slash, player
|
||||
versus player, interactive fiction, and online chat. Players can
|
||||
read or view descriptions of rooms, objects, other players,
|
||||
non-player characters, and actions performed in the virtual world.
|
||||
Players typically interact with each other and the world by typing
|
||||
commands that resemble a natural language.*\ " -
|
||||
`Wikipedia <http://en.wikipedia.org/wiki/MUD>`_
|
||||
|
||||
Evennia introduction
|
||||
====================
|
||||
|
||||
If you are reading this, it's quite likely you are dreaming of creating
|
||||
and running a text-based massively-multiplayer game
|
||||
(`MUD/MUX/MU <http://en.wikipedia.org/wiki/Mu<strong>>`_ etc) of your
|
||||
very own. You might just be starting to think about it, or you might
|
||||
have lugged around that *perfect* game in your mind for years ... you
|
||||
know *just* how good it would be, if you could only make it come to
|
||||
reality. We know how you feel. That is, after all, why Evennia came to
|
||||
be.
|
||||
(`MUD/MUX/MUSH <http://tinyurl.com/c5sc4bm>`_ etc) of your very own. You
|
||||
might just be starting to think about it, or you might have lugged
|
||||
around that *perfect* game in your mind for years ... you know *just*
|
||||
how good it would be, if you could only make it come to reality. We know
|
||||
how you feel. That is, after all, why Evennia came to be.
|
||||
|
||||
Evennia is in principle a MUD-building system: a bare-bones Python
|
||||
codebase and server intended to be highly extendable for any style of
|
||||
|
|
@ -54,7 +57,7 @@ LP, MOO and so on. Or why not create a new and better command system of
|
|||
your own design.
|
||||
|
||||
Can I test it somewhere?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
------------------------
|
||||
|
||||
There are Evennia-based muds under development but they are still not
|
||||
publicly available. If you do try to install Evennia (it's not hard), it
|
||||
|
|
@ -70,7 +73,7 @@ Brief summary of features
|
|||
=========================
|
||||
|
||||
Technical
|
||||
~~~~~~~~~
|
||||
---------
|
||||
|
||||
- Game development is done by the server importing your normal Python
|
||||
modules. Specific server features are implemented by overloading
|
||||
|
|
@ -95,7 +98,7 @@ Technical
|
|||
- Unit-testing suite, including tests of default commands and plugins
|
||||
|
||||
Default content
|
||||
~~~~~~~~~~~~~~~
|
||||
---------------
|
||||
|
||||
- Basic classes for Objects, Characers, Rooms and Exits
|
||||
- Basic login system, using the Player's login name as their in-game
|
||||
|
|
@ -107,7 +110,7 @@ Default content
|
|||
alternative login, menus, character generation and more
|
||||
|
||||
Standards/Protocols supported
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
-----------------------------
|
||||
|
||||
- Telnet with mud-specific extensions (MCCP, MSSP, TTYPE)
|
||||
- SSH
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
The ``@py`` command
|
||||
Running python code parsers for testing and debugging
|
||||
|
||||
The \`@py\` command
|
||||
===================
|
||||
|
||||
The ``@py`` command supplied with the default command set of Evennia
|
||||
|
|
@ -10,7 +12,8 @@ to just anybody.
|
|||
|
||||
::
|
||||
|
||||
@py 1+2 <<< 3
|
||||
@py 1+2
|
||||
<<< 3
|
||||
|
||||
Available variables
|
||||
-------------------
|
||||
|
|
@ -33,7 +36,8 @@ found in ``src/utils/utils.py``, but also accessible through
|
|||
|
||||
::
|
||||
|
||||
@py from ev import utils; utils.time_format(33333) <<< Done.
|
||||
@py from ev import utils; utils.time_format(33333)
|
||||
<<< Done.
|
||||
|
||||
Note that we didn't get any return value, all we where told is that the
|
||||
code finished executing without error. This is often the case in more
|
||||
|
|
@ -43,7 +47,9 @@ system to echo it to us explicitly with ``self.msg()``.
|
|||
|
||||
::
|
||||
|
||||
@py from ev import utils; self.msg(utils.time_format(33333)) 09:15 <<< Done.
|
||||
@py from ev import utils; self.msg(utils.time_format(33333))
|
||||
09:15
|
||||
<<< Done.
|
||||
|
||||
If you were to use Python's standard ``print``, you will see the result
|
||||
in your current ``stdout`` (your terminal by default), *if* you are
|
||||
|
|
@ -60,7 +66,14 @@ Locating an object is best done using ``self.search()``:
|
|||
|
||||
::
|
||||
|
||||
@py self.search("red_ball") <<< Ball @py self.search("red_ball").db.color = "red" <<< Done. @py self.search("red_ball").db.color <<< red
|
||||
@py self.search("red_ball")
|
||||
<<< Ball
|
||||
|
||||
@py self.search("red_ball").db.color = "red"
|
||||
<<< Done.
|
||||
|
||||
@py self.search("red_ball").db.color
|
||||
<<< red
|
||||
|
||||
``self.search()`` is by far the most used case, but you can also search
|
||||
other database tables for other Evennia entities like scripts or
|
||||
|
|
@ -69,7 +82,8 @@ entries found in ``ev.search_*``.
|
|||
|
||||
::
|
||||
|
||||
@py ev.search_script("sys_game_time") <<< [<src.utils.gametime.GameTime object at 0x852be2c>]
|
||||
@py ev.search_script("sys_game_time")
|
||||
<<< [<src.utils.gametime.GameTime object at 0x852be2c>]
|
||||
|
||||
(Note that since this becomes a simple statement, we don't have to wrap
|
||||
it in ``self.msg()`` to get the output). You can also use the database
|
||||
|
|
@ -80,17 +94,19 @@ in each manager.
|
|||
|
||||
::
|
||||
|
||||
@py ev.db_scripts.script_search("sys_game_time") <<< [<src.utils.gametime.GameTime object at 0x852be2c>]
|
||||
@py ev.db_scripts.script_search("sys_game_time")
|
||||
<<< [<src.utils.gametime.GameTime object at 0x852be2c>]
|
||||
|
||||
The managers are useful for all sorts of database studies.
|
||||
|
||||
::
|
||||
|
||||
@py ev.db_configvalues.all() <<< [<ConfigValue: default_home]>, <ConfigValue:site_name>, ...]
|
||||
@py ev.db_configvalues.all()
|
||||
<<< [<ConfigValue: default_home]>, <ConfigValue:site_name>, ...]
|
||||
|
||||
In doing so however, keep in mind the difference between `Typeclasses
|
||||
and Database Objects <Typeclasses.html>`_: Using the search commands in
|
||||
the managers will return *!TypeClasses*. Using Django's default search
|
||||
the managers will return *TypeClasses*. Using Django's default search
|
||||
methods (``get``, ``filter`` etc) will return *Database objects*. This
|
||||
distinction can often be disregarded, but as a convention you should try
|
||||
to stick with the manager search functions and work with TypeClasses in
|
||||
|
|
@ -98,7 +114,15 @@ most situations.
|
|||
|
||||
::
|
||||
|
||||
# this uses Evennia's manager method get_id(). # It returns a Character typeclass instance @py ev.db_objects.get_id(1).__class__ <<< Character# this uses the standard Django get() query. # It returns a django database model instance. @py ev.db_objects.get(id=1).__class__ <<< <class 'src.objects.models.ObjectDB'>
|
||||
# this uses Evennia's manager method get_id().
|
||||
# It returns a Character typeclass instance
|
||||
@py ev.db_objects.get_id(1).__class__
|
||||
<<< Character
|
||||
|
||||
# this uses the standard Django get() query.
|
||||
# It returns a django database model instance.
|
||||
@py ev.db_objects.get(id=1).__class__
|
||||
<<< <class 'src.objects.models.ObjectDB'>
|
||||
|
||||
Running a Python Parser outside the game
|
||||
========================================
|
||||
|
|
@ -124,5 +148,12 @@ tab-completion and ``__doc__``-string reading.
|
|||
|
||||
::
|
||||
|
||||
$ python manage.py shellIPython 0.10 -- An enhanced Interactive Python ...In [1]: import ev In [2]: ev.db_objects.all() Out[3]: [<ObjectDB: Harry>, <ObjectDB: Limbo>, ...]
|
||||
$ python manage.py shell
|
||||
|
||||
IPython 0.10 -- An enhanced Interactive Python
|
||||
...
|
||||
|
||||
In [1]: import ev
|
||||
In [2]: ev.db_objects.all()
|
||||
Out[3]: [<ObjectDB: Harry>, <ObjectDB: Limbo>, ...]
|
||||
|
||||
|
|
|
|||
|
|
@ -49,46 +49,51 @@ platform, please let us know.
|
|||
You'll need the following packages and minimum versions in order to run
|
||||
Evennia:
|
||||
|
||||
**Python** (http://www.python.org)
|
||||
- **Python** (`http://www.python.org <http://www.python.org>`_)
|
||||
|
||||
- Version 2.5+. Obs- Python3.x is not supported.
|
||||
- Windows users are recommended to use ActivePython
|
||||
(http://www.activestate.com/activepython/downloads)
|
||||
- Version 2.6+. Obs- Python3.x is not supported.
|
||||
- Windows users are recommended to use ActivePython
|
||||
(`http://www.activestate.com/activepython/downloads <http://www.activestate.com/activepython/downloads>`_)
|
||||
|
||||
**Twisted** (http://twistedmatrix.com)
|
||||
- **Twisted** (`http://twistedmatrix.com <http://twistedmatrix.com>`_)
|
||||
|
||||
Version 10.0+
|
||||
- Version 10.0+
|
||||
- Twisted also requires:
|
||||
|
||||
Twisted also requires:
|
||||
- ZopeInterface 3.0+
|
||||
(`http://www.zope.org/Products/ZopeInterface <http://www.zope.org/Products/ZopeInterface>`_)
|
||||
- For Windows only: pywin32
|
||||
(`http://sourceforge.net/projects/pywin32 <http://sourceforge.net/projects/pywin32>`_)
|
||||
|
||||
- !ZopeInterface 3.0+ (http://www.zope.org/Products/ZopeInterface)
|
||||
- For Windows only: pywin32 (http://sourceforge.net/projects/pywin32)
|
||||
- **Django**
|
||||
(`http://www.djangoproject.com <http://www.djangoproject.com>`_)
|
||||
|
||||
**Django** (http://www.djangoproject.com)
|
||||
|
||||
- Version 1.2.5+ or latest development versions highly recommended.
|
||||
- PIL (Python Imaging Library) (http://www.pythonware.com/products/pil)
|
||||
- not strictly required unless you use images in Django.
|
||||
- Version 1.3+ or latest development versions highly recommended.
|
||||
- PIL (Python Imaging Library)
|
||||
(`http://www.pythonware.com/products/pil <http://www.pythonware.com/products/pil>`_)
|
||||
- not strictly required unless you use images in Django.
|
||||
|
||||
To download/update Evennia:
|
||||
|
||||
**Mercurial** (http://mercurial.selenic.com/)
|
||||
- **Mercurial**
|
||||
(`http://mercurial.selenic.com/ <http://mercurial.selenic.com/>`_)
|
||||
|
||||
- This is needed to download and update Evennia itself.
|
||||
- This is needed to download and update Evennia itself.
|
||||
|
||||
Optional packages:
|
||||
|
||||
**South** (http://south.aeracode.org/)
|
||||
- **South**
|
||||
(`http://south.aeracode.org/ <http://south.aeracode.org/>`_)
|
||||
|
||||
- Version 0.7+
|
||||
- Optional, but highly recommended. Used for database migrations.
|
||||
- Version 0.7+
|
||||
- Optional, but highly recommended. Used for database migrations.
|
||||
|
||||
**Apache2** (http://httpd.apache.org)
|
||||
- **Apache2** (`http://httpd.apache.org <http://httpd.apache.org>`_)
|
||||
|
||||
- Optional. Most likely you'll not need to bother with this since
|
||||
Evennia runs its own threaded web server based on Twisted. Other
|
||||
equivalent web servers with a Python interpreter module can also be
|
||||
used.
|
||||
- Optional. Most likely you'll not need to bother with this since
|
||||
Evennia runs its own threaded web server based on Twisted. Other
|
||||
equivalent web servers with a Python interpreter module can also
|
||||
be used.
|
||||
|
||||
Installing pre-requisites
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -96,10 +101,10 @@ Installing pre-requisites
|
|||
**All platforms** can set up an *virtual Python environment* and install
|
||||
Evennia to that. All you need pre-installed is Python. Setup is
|
||||
described in detail
|
||||
`here <GettingStarted#Optional:<i>A</i>separate<i>installation</i>environment<i>with</i>virtualenv.html>`_.
|
||||
Windows users will probably want to go the ActivePython way instead
|
||||
though (see below), there are issues with installing certain extensions
|
||||
in Windows.
|
||||
[`GettingStarted <GettingStarted.html>`_\ #Optional:\ *A\_separate\_installation\_environment\_with\_virtualenv
|
||||
here]. Windows users will probably want to go the ActivePython way
|
||||
instead though (see below), there are issues with installing certain
|
||||
extensions in Windows.*
|
||||
|
||||
**Linux** package managers should usually handle all this for you.
|
||||
Python itself is definitely available through all distributions. On
|
||||
|
|
@ -108,23 +113,23 @@ Debian-derived systems (such as Ubuntu) you can do something like this
|
|||
|
||||
::
|
||||
|
||||
apt-get install python python-django python-twisted mercurial python-django-south
|
||||
apt-get install python python-django python-twisted mercurial python-django-south
|
||||
|
||||
Few distros actually keep the latest updated security updates (notably
|
||||
django and twisted) in their repos though. So it might be worth to use
|
||||
Python's
|
||||
`easyinstall <http://packages.python.org/distribute/easy<i>install.html>`_
|
||||
`easy\_install <http://packages.python.org/distribute/easy_install.html>`_
|
||||
or the alternative
|
||||
`pip <http://www.pip-installer.org/en/latest/index.html>`_ to get some
|
||||
or all of these instead:
|
||||
|
||||
::
|
||||
|
||||
easy_install django twisted pil mercurial south
|
||||
easy_install django twisted pil mercurial south
|
||||
|
||||
::
|
||||
|
||||
pip install django twisted pil mercurial south
|
||||
pip install django twisted pil mercurial south
|
||||
|
||||
If you already have Python and mercurial, and have downloaded Evennia,
|
||||
the package comes with a ``requirements.txt`` file. This can be used
|
||||
|
|
@ -133,19 +138,35 @@ automated build systems):
|
|||
|
||||
::
|
||||
|
||||
pip install -r requirements.txt
|
||||
pip install -r requirements.txt
|
||||
|
||||
**Mac** users should be able to get most dependencies through
|
||||
``easy_install`` or ``pip`` like Linux users do. There are however
|
||||
reports that you might need to get the
|
||||
`Xcode <https://developer.apple.com/xcode/.html>`_ development system to
|
||||
install the packages that requires extension compiling. You can also
|
||||
``easy_install`` or ``pip`` like Linux users do. All interaction is done
|
||||
from a terminal window. There are some reports that you might need to
|
||||
get the `Xcode <https://developer.apple.com/xcode/>`_ development system
|
||||
to install the packages that requires extension compiling. You can also
|
||||
retrieve the dependencies directly and install them through their native
|
||||
installers or python setups. Some users have reported problems compiling
|
||||
the ``PIL`` library on Mac, it's however not strictly required to use
|
||||
Django.
|
||||
the ``PIL`` library on Mac, it's however not strictly required in order
|
||||
to use Django (it's used for images).
|
||||
|
||||
**Windows** users may want to install
|
||||
\_Note (June 2012): Some versions of MacOSX does not seem to have a
|
||||
locale setting out of the box, and this causes a traceback during
|
||||
database creation. This is a known upstream bug in Django 1.4, described
|
||||
`here <http://code.google.com/p/evennia/wiki/Quirks#Known_upstream_bugs>`_.
|
||||
In the bug comments is also described how to add the locale and
|
||||
circumvent this bug for now. This affects also Unix/Linux systems, but
|
||||
those usually have the locale set out of the box.
|
||||
|
||||
**Windows** users should first and foremost recognize that the Evennia
|
||||
server is run from the command line, something which they might not be
|
||||
familiar with. In the Windows launch menu, just start *All Programs ->
|
||||
Accessories -> command prompt* and you will get the Windows command line
|
||||
interface. There are plenty of online tutorials on using the Windows
|
||||
command line, one example is found
|
||||
`here <http://www.bleepingcomputer.com/tutorials/windows-command-prompt-introduction/>`_.
|
||||
|
||||
Windows users may want to install
|
||||
`ActivePython <http://www.activestate.com/activepython/downloads>`_
|
||||
instead of the usual Python. Get the 32-bit version (it seems the 64-bit
|
||||
one won't let you download any packages without paying for a "Business"
|
||||
|
|
@ -154,12 +175,13 @@ license). If ActivePython is installed, you can use
|
|||
same manner as ``easy_install``/``pip`` above. This *greatly* simplifies
|
||||
getting started on Windows since that platform is by default missing
|
||||
many of the sane developer systems that Linux users take for granted.
|
||||
After installing ActivePython you may need to open a new DOS window to
|
||||
make this new command available on the command line:
|
||||
|
||||
After installing ActivePython you may need to restart the terminal/DOS
|
||||
window to make the pypm command available on the command line:
|
||||
|
||||
::
|
||||
|
||||
pypm install Django Twisted PIL Mercurial South
|
||||
pypm install Django Twisted PIL Mercurial South
|
||||
|
||||
This installs everything you need in one go.
|
||||
|
||||
|
|
@ -183,7 +205,7 @@ trick (first place yourself in a directory where you want a new folder
|
|||
|
||||
::
|
||||
|
||||
hg clone https://code.google.com/p/evennia/ evennia
|
||||
hg clone https://code.google.com/p/evennia/ evennia
|
||||
|
||||
(Mercurial is abbreviated ``hg`` since this is the chemical symbol for
|
||||
mercury).
|
||||
|
|
@ -192,7 +214,8 @@ In the future, you just do
|
|||
|
||||
::
|
||||
|
||||
hg pull hg update
|
||||
hg pull
|
||||
hg update
|
||||
|
||||
from your ``evennia/`` directory to obtain the latest updates.
|
||||
|
||||
|
|
@ -210,7 +233,7 @@ the automatic creation of an empty ``settings.py`` file.
|
|||
|
||||
::
|
||||
|
||||
python manage.py
|
||||
python manage.py
|
||||
|
||||
Your new ``settings.py`` file will just be an empty template initially.
|
||||
In ``evennia/src/settings_default.py`` you will find the settings that
|
||||
|
|
@ -239,7 +262,7 @@ with the standard tables and values:
|
|||
|
||||
::
|
||||
|
||||
python manage.py syncdb
|
||||
python manage.py syncdb
|
||||
|
||||
You should be asked for a superuser username, email, and password. Make
|
||||
**sure** you create a superuser here when asked, this becomes your login
|
||||
|
|
@ -253,7 +276,7 @@ need to do this:
|
|||
|
||||
::
|
||||
|
||||
python manage.py migrate
|
||||
python manage.py migrate
|
||||
|
||||
This will migrate the server to the latest version. If you don't use
|
||||
``South``, migrations will not be used and your server will already be
|
||||
|
|
@ -268,7 +291,7 @@ and execute ``evennia.py`` like this:
|
|||
|
||||
::
|
||||
|
||||
python evennia.py -i start
|
||||
python evennia.py -i start
|
||||
|
||||
This starts the server and portal. The ``-i`` flag means that the server
|
||||
starts in *interactive mode*, as a foreground process. You will see
|
||||
|
|
@ -279,11 +302,11 @@ To stop Evennia, do:
|
|||
|
||||
::
|
||||
|
||||
python evennia.py stop
|
||||
python evennia.py stop
|
||||
|
||||
See `Running
|
||||
Evennia <https://code.google.com/p/evennia/wiki/StartStopReload.html>`_
|
||||
for more advanced options on controlling Evennia's processes.
|
||||
Evennia <https://code.google.com/p/evennia/wiki/StartStopReload>`_ for
|
||||
more advanced options on controlling Evennia's processes.
|
||||
|
||||
Step 4: Connecting to the server
|
||||
--------------------------------
|
||||
|
|
@ -310,7 +333,7 @@ Apart from installing the packages and versions as above, you can also
|
|||
set up a very easy self-contained Evennia install using the
|
||||
`virtualenv <http://pypi.python.org/pypi/virtualenv>`_ program. If you
|
||||
are unsure how to get it, just grab the
|
||||
`virtualenv.py <https://raw.github.com/pypa/virtualenv/master/virtualenv.py.html>`_
|
||||
`virtualenv.py <https://raw.github.com/pypa/virtualenv/master/virtualenv.py>`_
|
||||
file from that page and run it directly in the terminal with
|
||||
``python virtualenv.py``.
|
||||
|
||||
|
|
@ -341,7 +364,10 @@ virtual environment in here.
|
|||
|
||||
::
|
||||
|
||||
# for Linux/Unix: source bin/activate # for Windows: <path_to_this_place>\Scripts\activate.bat
|
||||
# for Linux/Unix:
|
||||
source bin/activate
|
||||
# for Windows:
|
||||
<path_to_this_place>\Scripts\activate.bat
|
||||
|
||||
The virtual environment within our *mudenv* folder is now active. Next
|
||||
we get all the requirements with *pip*, which is included with
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
*How to use Evennia's help system*
|
||||
Evennia's help system
|
||||
|
||||
Help system
|
||||
===========
|
||||
|
|
@ -17,7 +17,7 @@ The main command is ``help``.
|
|||
|
||||
::
|
||||
|
||||
help [searchstring]
|
||||
help [searchstring]
|
||||
|
||||
This will show a list of help entries, ordered after categories. You
|
||||
will find two sections, *Command help entries* and *Other help entries*
|
||||
|
|
@ -49,7 +49,24 @@ Example (from a module with command definitions):
|
|||
|
||||
::
|
||||
|
||||
class CmdMyCmd(Command): """ mycmd - my very own command Usage: mycmd[/switches] <args> Switches: test - test the command run - do something else This is my own command that does things to you when you supply it with arguments. """ ... help_category = "Building" ...
|
||||
class CmdMyCmd(Command):
|
||||
"""
|
||||
mycmd - my very own command
|
||||
|
||||
Usage:
|
||||
mycmd[/switches] <args>
|
||||
|
||||
Switches:
|
||||
test - test the command
|
||||
run - do something else
|
||||
|
||||
This is my own command that does things to you when you
|
||||
supply it with arguments.
|
||||
|
||||
"""
|
||||
...
|
||||
help_category = "Building"
|
||||
...
|
||||
|
||||
The text at the very top of the command class definition is the class'
|
||||
``__doc__``-string and will be shown to users looking for help. Try to
|
||||
|
|
@ -89,7 +106,10 @@ You can create new help entries in code by using
|
|||
|
||||
::
|
||||
|
||||
from src.utils import create entry = create.create_help_entry("emote", "Emoting is important because ...", category="Roleplaying", locks="view:all()"):
|
||||
from src.utils import create
|
||||
entry = create.create_help_entry("emote",
|
||||
"Emoting is important because ...",
|
||||
category="Roleplaying", locks="view:all()"):
|
||||
|
||||
From inside the game those with the right permissions can use the
|
||||
``@sethelp`` command to add and modify help entries.
|
||||
|
|
|
|||
|
|
@ -1,24 +1,18 @@
|
|||
Getting and giving Evennia-help
|
||||
|
||||
How to *get* Help
|
||||
=================
|
||||
How to \_get\_ Help
|
||||
===================
|
||||
|
||||
If you cannot find what you are looking for in the `online
|
||||
documentation <Index.html>`_, here's what to do:
|
||||
|
||||
- If you think the documentation is not clear enough, fill in our quick
|
||||
little "https://docs.google.com/spreadsheet/viewform?hl
|
||||
|
||||
en\ *US&formkey*
|
||||
|
||||
dGN0VlJXMWpCT3VHaHpscDEzY1RoZGc6MQ#gid
|
||||
======================================
|
||||
|
||||
0.html">online form and say so (no login required). Maybe the docs
|
||||
need to be improved or a new tutorial added! Note that this form will
|
||||
help you by helping us improve documentation, but you cannot get
|
||||
direct, specific answers back from it.
|
||||
|
||||
little `online
|
||||
form <https://docs.google.com/spreadsheet/viewform?hl=en_US&formkey=dGN0VlJXMWpCT3VHaHpscDEzY1RoZGc6MQ#gid=0>`_
|
||||
and say so (no login required). Maybe the docs need to be improved or
|
||||
a new tutorial added! Note that this form will help you by helping us
|
||||
improve documentation, but you cannot get direct, specific answers
|
||||
back from it.
|
||||
- If you have trouble with a missing feature or a problem you think is
|
||||
a bug, go to the `issue
|
||||
tracker <http://code.google.com/p/evennia/issues/list>`_. If you
|
||||
|
|
@ -31,15 +25,14 @@ documentation <Index.html>`_, here's what to do:
|
|||
the online interface.
|
||||
- If you want more direct discussions with developers and other users,
|
||||
consider dropping into our IRC chat channel
|
||||
"http://webchat.freenode.net/?channels
|
||||
evennia">#evennia on the Freenode\_ network. Please note however that
|
||||
you have to be patient if you don't get any response immediately; we
|
||||
are all in very different time zones and many have busy personal
|
||||
lives. So you might have to lurk about for a while - you'll get
|
||||
noticed eventually!
|
||||
`#evennia <http://webchat.freenode.net/?channels=evennia>`_ on the
|
||||
*Freenode* network. Please note however that you have to be patient
|
||||
if you don't get any response immediately; we are all in very
|
||||
different time zones and many have busy personal lives. So you might
|
||||
have to lurk about for a while - you'll get noticed eventually!
|
||||
|
||||
How to *give* Help
|
||||
==================
|
||||
How to \_give\_ Help
|
||||
====================
|
||||
|
||||
Evennia is a completely un-funded project. It relies on the time donated
|
||||
by its users and developers in order to progress.
|
||||
|
|
@ -60,10 +53,9 @@ to get going:
|
|||
you can help improve or expand the documentation (even small things
|
||||
like fixing typos!).
|
||||
- Send a message to our
|
||||
`forum <http://groups.google.com/group/evennia/>`_ and/or our
|
||||
"http://webchat.freenode.net/?channels
|
||||
evennia">IRC chat asking about what needs doing, along with what your
|
||||
interests and skills are.
|
||||
`forum <http://groups.google.com/group/evennia/>`_ and/or our `IRC
|
||||
chat <http://webchat.freenode.net/?channels=evennia>`_ asking about
|
||||
what needs doing, along with what your interests and skills are.
|
||||
- Take a look at our `issue
|
||||
tracker <http://code.google.com/p/evennia/issues/list>`_ and see if
|
||||
there's something you feel like taking on.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
Introduction to and configuration for IMC2.
|
||||
|
||||
IMC2
|
||||
====
|
||||
|
||||
`IMC2 <http://en.wikipedia.org/wiki/InterMUD>`_, *!InterMud
|
||||
`IMC2 <http://en.wikipedia.org/wiki/InterMUD>`_, *InterMud
|
||||
Communications, protocol version 2*, is a protocol that allows
|
||||
individual mud games (Evennia-powered or not) to connect to a remote
|
||||
server for the purpose of IRC-like communication with other games. By
|
||||
|
|
@ -65,11 +67,11 @@ could also use an existing channel like ``ooc`` if you wanted):
|
|||
|
||||
::
|
||||
|
||||
@ccreate imc2 = This is connected to an IMC2 channel!
|
||||
@ccreate imc2 = This is connected to an IMC2 channel!
|
||||
|
||||
You should join the channel automatically.
|
||||
|
||||
Setting up a Channel ``<->`` IMC2 binding
|
||||
Setting up a Channel \`<->\` IMC2 binding
|
||||
-----------------------------------------
|
||||
|
||||
Evennia developers have an open-access IMC channel called ``ievennia``
|
||||
|
|
@ -81,12 +83,12 @@ Activating IMC2 have made new commands available, the one you need is
|
|||
channel and an existing Evennia channel of your choice. You can use the
|
||||
``imcchanlist`` to see which IMC channels are available on the network.
|
||||
|
||||
Let's connect our new ``imc2`` channel to the ``ievennia`` channel on
|
||||
Server01.
|
||||
Let's connect our new ``imc2`` channel to the ``ievennia`` channel
|
||||
on Server01.
|
||||
|
||||
::
|
||||
|
||||
@imc2chan imc2 = ievennia
|
||||
@imc2chan imc2 = ievennia
|
||||
|
||||
To test, use the IMC mud *Talon*, make sure you "listen" to
|
||||
``ievennia``, then write something to the channel. You should see the
|
||||
|
|
@ -101,7 +103,7 @@ To permanently remove a connection, use ``@imc2chan`` with the
|
|||
|
||||
::
|
||||
|
||||
@imc2chan/delete imc2 = ievennia
|
||||
@imc2chan/delete imc2 = ievennia
|
||||
|
||||
A single Evennia channel may *listen* to any number of remote IMC
|
||||
channels. Just use ``@imc2chan`` to add more connections. Your channel
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
Configuring IRC connectivity
|
||||
|
||||
IRC
|
||||
===
|
||||
|
||||
`IRC (Internet Relay
|
||||
Chat) <http://en.wikipedia.org/wiki/Internet<i>Relay</i>Chat>`_ is a
|
||||
long standing chat protocol used by many open-source projects for
|
||||
Chat) <http://en.wikipedia.org/wiki/Internet_Relay_Chat>`_ is a long
|
||||
standing chat protocol used by many open-source projects for
|
||||
communicating in real time. By connecting one of Evennia's
|
||||
`Channels <Communications.html>`_ to an IRC channel you can communicate
|
||||
also with people not on an mud themselves. Note that you can use IRC
|
||||
|
|
@ -27,7 +29,7 @@ command availabele: ``@irc2chan``. This command is called like this:
|
|||
|
||||
::
|
||||
|
||||
@irc2chan[/switches] <evennia_channel> = <ircnetwork> <port> <#irchannel> <botname>
|
||||
@irc2chan[/switches] <evennia_channel> = <ircnetwork> <port> <#irchannel> <botname>
|
||||
|
||||
If you already know how IRC works, this should be pretty self-evident to
|
||||
use. Read the help entry for more features.
|
||||
|
|
@ -40,7 +42,7 @@ up a new channel ``irc``.
|
|||
|
||||
::
|
||||
|
||||
@ccreate irc = This is connected to an irc channel!
|
||||
@ccreate irc = This is connected to an irc channel!
|
||||
|
||||
You will automatically join the new channel.
|
||||
|
||||
|
|
@ -75,7 +77,7 @@ Next we connect Evennia with the IRC channel.
|
|||
|
||||
::
|
||||
|
||||
@irc2chan irc = irc.freenode.net 6667 #myevennia-test mud-bot
|
||||
@irc2chan irc = irc.freenode.net 6667 #myevennia-test mud-bot
|
||||
|
||||
Evennia will now create a new IRC bot ``mud-bot`` and connect it to the
|
||||
IRC network and the channel #myevennia. If you are connected to the IRC
|
||||
|
|
@ -85,7 +87,8 @@ Write something in the Evennia channel *irc*.
|
|||
|
||||
::
|
||||
|
||||
irc Hello, World! [irc] Anna: Hello, World!
|
||||
irc Hello, World!
|
||||
[irc] Anna: Hello, World!
|
||||
|
||||
If you are viewing your IRC channel with a separate IRC client you
|
||||
should see your text appearing there, spoken by the bot:
|
||||
|
|
@ -100,6 +103,7 @@ normal channel, marked with the name of the IRC channel you used
|
|||
|
||||
::
|
||||
|
||||
|
||||
[irc] Anna@#myevennia-test: Hello!
|
||||
|
||||
Your Evennia gamers can now chat with users on external IRC channels!
|
||||
|
|
|
|||
|
|
@ -4,15 +4,8 @@ Evennia Documentation
|
|||
This is Evennia's manual. You should hopefully find all you need to know
|
||||
about coding with, extending and using the codebase among these pages.
|
||||
If you have trouble with unclear documentation, fill in our quick
|
||||
"https://docs.google.com/spreadsheet/viewform?hl
|
||||
|
||||
en\_US&formkey
|
||||
|
||||
dGN0VlJXMWpCT3VHaHpscDEzY1RoZGc6MQ#gid
|
||||
======================================
|
||||
|
||||
0.html">online form and tell us so - maybe more details or a new
|
||||
tutorial is needed!
|
||||
`online form <http://tinyurl.com/c4tue23>`_ and tell us so - maybe more
|
||||
details or a new tutorial is needed!
|
||||
|
||||
The documentation is divided into several main categories. If you are
|
||||
new, it might be an idea to browse the sections in the order they are
|
||||
|
|
@ -35,4 +28,7 @@ Sections
|
|||
- The `Developer Central <DeveloperCentral.html>`_ covers
|
||||
implementation details and guides of interest for game-world coders
|
||||
as well as for current and prospective Evennia developers.
|
||||
- The `Tutorials & Examples <Tutorials.html>`_ section summarizes help
|
||||
pages on a step-by-step or tutorial format. Some of these are
|
||||
reachable from their respective sections as well.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
changing and creating new translations
|
||||
|
||||
Internationalization
|
||||
====================
|
||||
|
||||
|
|
@ -9,23 +11,25 @@ without anyone having to go in and add it manually. Take a look at the
|
|||
which languages are currently supported.
|
||||
|
||||
Note, what is translated in this way are hard-coded strings from the
|
||||
server, things like "Connection closed" or "Server restarted".
|
||||
Basically, the things users are not supposed to change on their own.
|
||||
This means that the default command set is *not* translated. The reason
|
||||
for this is that commands are *intended* to be modified by users. Adding
|
||||
*i18n* code to commands tend to add complexity to code that will be
|
||||
changed anyway. One of the goals of Evennia is to keep the
|
||||
user-changeable code as clean and easy-to-read as possible.
|
||||
server, things like "Connection closed" or "Server restarted" - things
|
||||
that Players will see and which game devs are not supposed to change on
|
||||
their own. So stuff seen in the log file or on stdout will not be
|
||||
translated. It also means that the default command set is *not*
|
||||
translated. The reason for this is that commands are *intended* to be
|
||||
modified by users. Adding *i18n* code to commands tend to add complexity
|
||||
to code that will be changed anyway. One of the goals of Evennia is to
|
||||
keep the user-changeable code as clean and easy-to-read as possible.
|
||||
|
||||
Changing server language
|
||||
------------------------
|
||||
|
||||
Change language by copy&pasting the following from the default file to
|
||||
your ``game/settings.py`` file:
|
||||
Change language by adding the following to your ``game/settings.py``
|
||||
file:
|
||||
|
||||
::
|
||||
|
||||
USE_I18N = True LANGUAGE_CODE = 'en'
|
||||
USE_I18N = True
|
||||
LANGUAGE_CODE = 'en'
|
||||
|
||||
Here ``'en'`` should be changed to the abbreviation for one of the
|
||||
supported languages found in ``locale/``. Restart the server to activate
|
||||
|
|
@ -43,7 +47,7 @@ and run
|
|||
|
||||
::
|
||||
|
||||
django-admin makemessages -l <language-code>
|
||||
django-admin makemessages -l <language-code>
|
||||
|
||||
where ``<language-code>`` is the two-letter locale code for the language
|
||||
you want, like 'sv' for Swedish or 'es' for Spanish.
|
||||
|
|
@ -55,7 +59,11 @@ normal text editor -- best is to use a po-file editor from the web
|
|||
|
||||
The concept of translating is simple, it's just a matter of taking the
|
||||
english strings you find in the ``*.po`` file and add your language's
|
||||
translation best you can.
|
||||
translation best you can. The ``*.po`` format (and many supporting
|
||||
editors) allow you to mark translations as "fuzzy". This tells the
|
||||
system (and future translators) that you are unsure about the
|
||||
translation, or that you couldn't find a translation that exactly
|
||||
matched the intention of the original text.
|
||||
|
||||
Finally, you need to compile your translation into a more efficient
|
||||
form.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
The Licensing of Evennia
|
||||
|
||||
Evennia Licence FAQ
|
||||
===================
|
||||
|
||||
|
|
@ -14,7 +16,7 @@ Q: When creating a game using Evennia, what does the licence permit me to do wit
|
|||
|
||||
**A:** It's your own game world to do with as you please! To summarize,
|
||||
a MUD world you create using Evennia (i.e. the files you create in
|
||||
``/game/``) falls under **§6** of the license (it's a sort of
|
||||
``/game/``) falls under **§6** of the license (it's a sort of
|
||||
"library"). So your game world and all its contents belongs to you (as
|
||||
it should be). Keep it to yourself or re-distribute it under any license
|
||||
of your choice - or sell it and become filthy rich for all we care.
|
||||
|
|
@ -25,12 +27,12 @@ Q: I have modified Evennia itself, what does the license say about that?
|
|||
**A:** The Evennia package itself (i.e. the stuff you download from us)
|
||||
is referred to as "The Package, Standard version" in the license.
|
||||
|
||||
- If you just fixed a typo or bug, that falls under **§2** - that is,
|
||||
- If you just fixed a typo or bug, that falls under **§2** - that is,
|
||||
you don't *have* to do anything to appease the license. Regardless,
|
||||
we'd of course appreciate it if you submitted bugs/fixes to us so
|
||||
Evennia becomes more complete!.
|
||||
- If you made bigger modifications or added new features to the server,
|
||||
that's also ok, but falls under **§3** - you must make a clear note
|
||||
that's also ok, but falls under **§3** - you must make a clear note
|
||||
of the changes you did and put those changes into public domain
|
||||
(since it's then no longer a "Standard version"). You could also
|
||||
contact the Evennia developers to make separate arrangements ... but
|
||||
|
|
@ -40,7 +42,7 @@ is referred to as "The Package, Standard version" in the license.
|
|||
Q: Can I re-distribute the Evennia server package along with my custom game implementation?
|
||||
-------------------------------------------------------------------------------------------
|
||||
|
||||
**A:** Sure. This is covered in **§4** - just package the "Standard
|
||||
**A:** Sure. This is covered in **§4** - just package the "Standard
|
||||
version" (that is, the one you download from us) with your game files.
|
||||
Also make sure to include the original license and disclaimers and note
|
||||
where users may get "plain" Evennia should they want to download it of
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
External links
|
||||
|
||||
External links and resources
|
||||
============================
|
||||
|
||||
|
|
@ -61,19 +63,23 @@ General mud/game development ideas and discussions
|
|||
blog with long and interesting articles (not MUD-specific)
|
||||
- `What Games Are <http://whatgamesare.com/>`_ is a blog about general
|
||||
game design (not MUD-specific)
|
||||
- `The Alexandrian <http://thealexandrian.net/>`_ is a blog about
|
||||
tabletop roleplaying and board games, but with lots of general
|
||||
discussion about rule systems and game balance that could be
|
||||
applicable also for MUDs.
|
||||
|
||||
Frameworks
|
||||
----------
|
||||
|
||||
`Django's homepage <http://www.djangoproject.com/>`_
|
||||
- `Django's homepage <http://www.djangoproject.com/>`_
|
||||
|
||||
- `Documentation <http://docs.djangoproject.com/en>`_
|
||||
- `Code <http://code.djangoproject.com/>`_
|
||||
- `Documentation <http://docs.djangoproject.com/en>`_
|
||||
- `Code <http://code.djangoproject.com/>`_
|
||||
|
||||
`Twisted homepage <http://twistedmatrix.com/>`_
|
||||
- `Twisted homepage <http://twistedmatrix.com/>`_
|
||||
|
||||
- `Documentation <http://twistedmatrix.com/documents/current/core/howto/index.html>`_
|
||||
- `Code <http://twistedmatrix.com/trac/browser>`_
|
||||
- `Documentation <http://twistedmatrix.com/documents/current/core/howto/index.html>`_
|
||||
- `Code <http://twistedmatrix.com/trac/browser>`_
|
||||
|
||||
Tools
|
||||
-----
|
||||
|
|
@ -87,10 +93,11 @@ Tools
|
|||
Python Info
|
||||
-----------
|
||||
|
||||
`Python Website <http://www.python.org/>`_
|
||||
- `Python Website <http://www.python.org/>`_
|
||||
|
||||
- `Documentation <http://www.python.org/doc/>`_
|
||||
- `Tutorial <http://docs.python.org/tut/tut.html>`_
|
||||
- `Library Reference <http://docs.python.org/lib/lib.html>`_
|
||||
- `Language Reference <http://docs.python.org/ref/ref.html>`_
|
||||
|
||||
- `Documentation <http://www.python.org/doc/>`_
|
||||
- `Tutorial <http://docs.python.org/tut/tut.html>`_
|
||||
- `Library Reference <http://docs.python.org/lib/lib.html>`_
|
||||
- `Language Reference <http://docs.python.org/ref/ref.html>`_
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Locks and Permissions
|
||||
|
||||
Locks
|
||||
=====
|
||||
|
||||
|
|
@ -6,8 +8,8 @@ Evennia such restrictions are applied and checked by something called
|
|||
*locks*. All Evennia entities (`commands <Commands.html>`_,
|
||||
`objects <Objects.html>`_, `scripts <Scripts.html>`_,
|
||||
`players <Players.html>`_, `help system <HelpSystem.html>`_,
|
||||
`messages <Communications#Msg.html>`_ and
|
||||
`channels <Communications#Channels.html>`_) are accessed through locks.
|
||||
[Communications#Msg messages] and [Communications#Channels channels])
|
||||
are accessed through locks.
|
||||
|
||||
A lock can be thought of as an "access rule" restricting a particular
|
||||
use of an Evennia entity. Whenever another entity wants that kind of
|
||||
|
|
@ -32,7 +34,7 @@ The in-game command for setting locks on objects is ``@lock``:
|
|||
|
||||
::
|
||||
|
||||
> @lock obj = <lockstring>
|
||||
> @lock obj = <lockstring>
|
||||
|
||||
The ``<lockstring>`` is a string on a certain form that defines the
|
||||
behaviour of the lock. We will go into more detail on how
|
||||
|
|
@ -44,7 +46,7 @@ add, delete and check locks.
|
|||
|
||||
::
|
||||
|
||||
myobj.locks.add(<lockstring>)
|
||||
myobj.locks.add(<lockstring>)
|
||||
|
||||
One can call ``locks.check()`` to perform a lock check, but to hide the
|
||||
underlying implementation all objects also have a convenience function
|
||||
|
|
@ -55,7 +57,9 @@ do) look from inside the ``@delete`` command:
|
|||
|
||||
::
|
||||
|
||||
if not obj.access(accessing_obj, 'delete'): accessing_obj.msg("Sorry, you may not delete that.") return
|
||||
if not obj.access(accessing_obj, 'delete'):
|
||||
accessing_obj.msg("Sorry, you may not delete that.")
|
||||
return
|
||||
|
||||
Defining locks
|
||||
--------------
|
||||
|
|
@ -68,13 +72,15 @@ Here are some examples of lock strings (not including the quotes):
|
|||
|
||||
::
|
||||
|
||||
delete:id(34) # only allow obj #34 to delete edit:all() # let everyone edit get: not attr(very_weak) or perm(Wizard) # only those who are not "very_weak" or are Wizards may pick this up
|
||||
delete:id(34) # only allow obj #34 to delete
|
||||
edit:all() # let everyone edit
|
||||
get: not attr(very_weak) or perm(Wizard) # only those who are not "very_weak" or are Wizards may pick this up
|
||||
|
||||
Formally, a lockstring has the following syntax:
|
||||
|
||||
::
|
||||
|
||||
access_type:[not] func1([arg1,..])[[and|or][ not] func2([arg1,...])[...]]
|
||||
access_type:[not] func1([arg1,..])[[and|or][ not] func2([arg1,...])[...]]
|
||||
|
||||
where ``[..]`` marks optional parts. AND, OR and NOT are not case
|
||||
sensitive and excess spaces are ignored. ``func1, func2`` etc are
|
||||
|
|
@ -110,47 +116,50 @@ tries, as in the example of ``@delete`` above that uses the 'delete'
|
|||
|
||||
Below are the access\_types checked by the default commandset.
|
||||
|
||||
`Commands <Commands.html>`_: ``cmd`` - this defines who may call this
|
||||
command at all.
|
||||
- `Commands <Commands.html>`_: ``cmd`` - this defines who may call this
|
||||
command at all.
|
||||
- `Objects <Objects.html>`_:
|
||||
|
||||
`Objects <Objects.html>`_:
|
||||
- ``control`` - who is the "owner" of the object. Can set locks,
|
||||
delete it etc. Defaults to the creator of the object.
|
||||
- ``call`` - who may call object-commands on this object.
|
||||
- ``examine`` - who may examine this object's properties.
|
||||
- ``delete`` - who may delete the object.
|
||||
- ``edit`` - who may edit properties and attributes of the object.
|
||||
- ``get``- who may pick up the object and carry it around.
|
||||
- ``puppet`` - who may "become" this object and control it as their
|
||||
"character".
|
||||
- ``attrcreate`` - allows to create new objects on object (default
|
||||
True)
|
||||
|
||||
- ``control`` - who is the "owner" of the object. Can set locks, delete
|
||||
it etc. Defaults to the creator of the object.
|
||||
- ``call`` - who may call object-commands on this object.
|
||||
- ``examine`` - who may examine this object's properties.
|
||||
- ``delete`` - who may delete the object.
|
||||
- ``edit`` - who may edit properties and attributes of the object.
|
||||
- ``get``- who may pick up the object and carry it around.
|
||||
- ``puppet`` - who may "become" this object and control it as their
|
||||
"character".
|
||||
- [Objects#Characters Characters]: ``<Same as Objects>``
|
||||
- [Objects#Exits Exits]: ``<Same as Objects>`` + ``traverse`` - who may
|
||||
pass the exit.
|
||||
- `Players <Players.html>`_:
|
||||
|
||||
`Characters <Objects#Characters.html>`_: ``<Same as Objects>``
|
||||
- ``examine`` - who may examine the player's properties.
|
||||
- ``delete`` - who may delete the player.
|
||||
- ``edit`` - who may edit the player's attributes and properties.
|
||||
- ``msg`` - who may send messages to the player.
|
||||
- ``boot`` - who may boot the player.
|
||||
|
||||
`Exits <Objects#Exits.html>`_: ``<Same as Objects>`` + ``traverse`` -
|
||||
who may pass the exit.
|
||||
- `Attributes <Attributes.html>`_: (*only checked by
|
||||
``obj.secure_attr``*)
|
||||
|
||||
`Players <Players.html>`_:
|
||||
- ``attrread`` - see/access attribute
|
||||
- ``attredit`` - change/delete attribute
|
||||
|
||||
- ``examine`` - who may examine the player's properties.
|
||||
- ``delete`` - who may delete the player.
|
||||
- ``edit`` - who may edit the player's attributes and properties.
|
||||
- ``msg`` - who may send messages to the player.
|
||||
- ``boot`` - who may boot the player.
|
||||
- [Communications#Channels Channels]:
|
||||
|
||||
`Attributes <Attributes.html>`_: ``<None>``
|
||||
- ``control`` - who is administrating the channel. This means the
|
||||
ability to delete the channel, boot listeners etc.
|
||||
- ``send`` - who may send to the channel.
|
||||
- ``listen`` - who may subscribe and listen to the channel.
|
||||
|
||||
`Channels <Communications#Channels.html>`_:
|
||||
- `HelpEntry <HelpSystem.html>`_:
|
||||
|
||||
- ``control`` - who is administrating the channel. This means the
|
||||
ability to delete the channel, boot listeners etc.
|
||||
- ``send`` - who may send to the channel.
|
||||
- ``listen`` - who may subscribe and listen to the channel.
|
||||
|
||||
`HelpEntry <HelpSystem.html>`_:
|
||||
|
||||
- ``examine`` - who may view this help entry (usually everyone)
|
||||
- ``edit`` - who may edit this help entry.
|
||||
- ``examine`` - who may view this help entry (usually everyone)
|
||||
- ``edit`` - who may edit this help entry.
|
||||
|
||||
So to take an example, whenever an exit is to be traversed, a lock of
|
||||
the type *traverse* will be checked. Defining a suitable lock type for
|
||||
|
|
@ -176,7 +185,13 @@ appear as extra arguments.
|
|||
|
||||
::
|
||||
|
||||
# A simple example lock function. Called with e.g. id(34)def id(accessing_obj, accessed_obj, *args, **kwargs): if args: wanted_id = args[0] return accessing_obj.id == wanted_id return False
|
||||
# A simple example lock function. Called with e.g. id(34)
|
||||
|
||||
def id(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
if args:
|
||||
wanted_id = args[0]
|
||||
return accessing_obj.id == wanted_id
|
||||
return False
|
||||
|
||||
(Using the ``*`` and ``**`` syntax causes Python to magically put all
|
||||
extra arguments into a list ``args`` and all keyword arguments into a
|
||||
|
|
@ -189,17 +204,17 @@ Some useful default lockfuncs (see ``src/locks/lockfuncs.py`` for more):
|
|||
- ``false()/none()/superuser()`` - give access to noone. Superusers
|
||||
bypass the check entirely.
|
||||
- ``perm(perm)`` - this tries to match a given ``permission`` property.
|
||||
See `below <Locks#Permissions.html>`_.
|
||||
See [Locks#Permissions below].
|
||||
- ``perm_above(perm)`` - requres a "higher" permission level than the
|
||||
one given.
|
||||
- ``id(num)/dbref(num)`` - checks so the accessobject has a certain
|
||||
- ``id(num)/dbref(num)`` - checks so the access\_object has a certain
|
||||
dbref/id.
|
||||
- ``attr(attrname)`` - checks if a certain
|
||||
`Attribute <Attributes.html>`_ exists on accessingobject.
|
||||
`Attribute <Attributes.html>`_ exists on accessing\_object.
|
||||
- ``attr(attrname, value)`` - checks so an attribute exists on
|
||||
accessing\ *object*\ and has the given value.
|
||||
- ``attr_gt(attrname, value)`` - checks so accessingobject has a value
|
||||
larger (``>``) than the given value.
|
||||
accessing\_object *and* has the given value.
|
||||
- ``attr_gt(attrname, value)`` - checks so accessing\_object has a
|
||||
value larger (``>``) than the given value.
|
||||
- ``attr_ge, attr_lt, attr_le, attr_ne`` - corresponding for ``>=``,
|
||||
``<``, ``<=`` and ``!=``.
|
||||
- ``holds(objid)`` - checks so the accessing objects contains an object
|
||||
|
|
@ -233,7 +248,7 @@ set by the ``@perm`` command.
|
|||
|
||||
::
|
||||
|
||||
@perm Tommy = Builders
|
||||
@perm Tommy = Builders
|
||||
|
||||
All new players/character are given a default set of permissions defined
|
||||
by ``settings.PERMISSION_PLAYER_DEFAULT``.
|
||||
|
|
@ -244,7 +259,11 @@ default permission hierarchy is as follows:
|
|||
|
||||
::
|
||||
|
||||
Immortals Wizards Builders PlayerHelpers Players # this is what all new Players start with by default
|
||||
Immortals
|
||||
Wizards
|
||||
Builders
|
||||
PlayerHelpers
|
||||
Players # this is what all new Players start with by default
|
||||
|
||||
The main use of this is that if you use the lock function ``perm()``
|
||||
mentioned above, a lock check for a particular permission in the
|
||||
|
|
@ -257,7 +276,10 @@ looked for is not in the hierarchy, an exact match is required.
|
|||
|
||||
::
|
||||
|
||||
obj1.permissions = ["Builders", "cool_guy"] obj2.locks.add("enter:perm_above(Players) and perm(cool_guy)")obj2.access(obj1, "enter") # this returns True!
|
||||
obj1.permissions = ["Builders", "cool_guy"]
|
||||
obj2.locks.add("enter:perm_above(Players) and perm(cool_guy)")
|
||||
|
||||
obj2.access(obj1, "enter") # this returns True!
|
||||
|
||||
Superusers
|
||||
----------
|
||||
|
|
@ -293,7 +315,7 @@ their use of this particular command henceforth.
|
|||
|
||||
::
|
||||
|
||||
open: holds('the green key') or perm(Builder)
|
||||
open: holds('the green key') or perm(Builder)
|
||||
|
||||
This could be called by the ``open`` command on a "door" object. The
|
||||
check is passed if you are a Builder or has the right key in your
|
||||
|
|
@ -328,7 +350,8 @@ other is an `Object <Objects.html>`_ called ``box``.
|
|||
|
||||
::
|
||||
|
||||
> @create/drop box > @desc box = "This is a very big and heavy box."
|
||||
> @create/drop box
|
||||
> @desc box = "This is a very big and heavy box."
|
||||
|
||||
We want to limit which objects can pick up this heavy box. Let's say
|
||||
that to do that we require the would-be lifter to to have an attribute
|
||||
|
|
@ -337,7 +360,7 @@ ourselves to begin with.
|
|||
|
||||
::
|
||||
|
||||
> @set self/strength = 45
|
||||
> @set self/strength = 45
|
||||
|
||||
Ok, so for testing we made ourselves strong, but not strong enough. Now
|
||||
we need to look at what happens when someone tries to pick up the the
|
||||
|
|
@ -347,16 +370,21 @@ this snippet:
|
|||
|
||||
::
|
||||
|
||||
if not obj.access(caller, 'get'): if obj.db.get_err_msg: caller.msg(obj.db.get_err_msg) else: caller.msg("You can't get that.") return
|
||||
if not obj.access(caller, 'get'):
|
||||
if obj.db.get_err_msg:
|
||||
caller.msg(obj.db.get_err_msg)
|
||||
else:
|
||||
caller.msg("You can't get that.")
|
||||
return
|
||||
|
||||
So the ``get`` command looks for a lock with the type *get* (not so
|
||||
surprising). It also looks for an `Attribute <Attributes.html>`_ on the
|
||||
checked object called *get*\ err\ *msg* in order to return a customized
|
||||
checked object called *get\_err\_msg* in order to return a customized
|
||||
error message. Sounds good! Let's start by setting that on the box:
|
||||
|
||||
::
|
||||
|
||||
> @set box/get_err_msg = You are not strong enough to lift this box.
|
||||
> @set box/get_err_msg = You are not strong enough to lift this box.
|
||||
|
||||
Next we need to craft a Lock of type *get* on our box. We want it to
|
||||
only be passed if the accessing object has the attribute *strength* of
|
||||
|
|
@ -370,7 +398,7 @@ We put this on the box now:
|
|||
|
||||
::
|
||||
|
||||
@lock box = get:attr_gt(strength, 50)
|
||||
@lock box = get:attr_gt(strength, 50)
|
||||
|
||||
Try to ``get`` the object and you should get the message that we are not
|
||||
strong enough. Increase your strength above 50 however and you'll pick
|
||||
|
|
@ -381,7 +409,19 @@ like this:
|
|||
|
||||
::
|
||||
|
||||
from ev import create_objectbox = create_object(None, key="box") box.locks.add("get:attr_gt(strength, 50)")# or we can assign locks right away box = create_object(None, key="box", locks="get:attr_gt(strength, 50)")# set the attributes box.db.desc = "This is a very big and heavy box." box.db.get_err_msg = "You are not strong enough to lift this box."# one heavy box, ready to withstand all but the strongest...
|
||||
from ev import create_object
|
||||
|
||||
box = create_object(None, key="box")
|
||||
box.locks.add("get:attr_gt(strength, 50)")
|
||||
|
||||
# or we can assign locks right away
|
||||
box = create_object(None, key="box", locks="get:attr_gt(strength, 50)")
|
||||
|
||||
# set the attributes
|
||||
box.db.desc = "This is a very big and heavy box."
|
||||
box.db.get_err_msg = "You are not strong enough to lift this box."
|
||||
|
||||
# one heavy box, ready to withstand all but the strongest...
|
||||
|
||||
On Django's permission system
|
||||
=============================
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Using nicknames
|
||||
|
||||
Nicks
|
||||
=====
|
||||
|
||||
|
|
@ -29,7 +31,7 @@ command):
|
|||
|
||||
::
|
||||
|
||||
nick ls = look
|
||||
nick ls = look
|
||||
|
||||
This is a good one for unix/linux users who are accustomed to using the
|
||||
``ls`` command in their daily life. It is equivalent to
|
||||
|
|
@ -37,26 +39,26 @@ This is a good one for unix/linux users who are accustomed to using the
|
|||
|
||||
::
|
||||
|
||||
nick/object mycar2 = The red sports car
|
||||
nick/object mycar2 = The red sports car
|
||||
|
||||
With this example, substitutions will only be done specifically for
|
||||
commands expecting an object reference, such as
|
||||
|
||||
::
|
||||
|
||||
look mycar2
|
||||
look mycar2
|
||||
|
||||
becomes equivalent to "``look The red sports car``\ ".
|
||||
|
||||
::
|
||||
|
||||
nick/players tom = Thomas Johnsson
|
||||
nick/players tom = Thomas Johnsson
|
||||
|
||||
This is useful for commands searching for players explicitly:
|
||||
|
||||
::
|
||||
|
||||
@find *tom
|
||||
@find *tom
|
||||
|
||||
One can use nicks to speed up input. Below we add ourselves a quicker
|
||||
way to build red buttons. In the future just writing *rb* will be enough
|
||||
|
|
@ -64,26 +66,42 @@ to execute that whole long string.
|
|||
|
||||
::
|
||||
|
||||
nick rb = @create button:examples.red_button.RedButton
|
||||
nick rb = @create button:examples.red_button.RedButton
|
||||
|
||||
Nicks could also be used as the start for building a "recog" system
|
||||
suitable for an RP mud.
|
||||
|
||||
::
|
||||
|
||||
nick/player Arnold = The mysterious hooded man
|
||||
nick/player Arnold = The mysterious hooded man
|
||||
|
||||
Coding with nicks
|
||||
-----------------
|
||||
|
||||
Nicks are are stored as the ``Nick`` database model and are referred
|
||||
from the normal Evennia `object <Objects.html>`_ through the ``nicks``
|
||||
property. ``nicks`` is a special handler that offers effective error
|
||||
property. `` nicks`` is a special handler that offers effective error
|
||||
checking, searches and conversion.
|
||||
|
||||
::
|
||||
|
||||
# A command/channel nick: object.nicks.add("greetjack", "tell Jack = Hello pal!")# An object nick: object.nicks.add("rose", "The red flower", nick_type="object")# An player nick: object.nicks("tom", "Tommy Hill", nick_type="player")# My own custom nick type (handled by my own game code somehow): object.nicks.add("hood", "The hooded man", nick_type="my_identsystem")# get back the translated nick: full_name = object.nicks.get("rose", nick_type="object")# delete a previous set nick object.nicks.del("rose", nick_type="object")
|
||||
# A command/channel nick:
|
||||
object.nicks.add("greetjack", "tell Jack = Hello pal!")
|
||||
|
||||
# An object nick:
|
||||
object.nicks.add("rose", "The red flower", nick_type="object")
|
||||
|
||||
# An player nick:
|
||||
object.nicks("tom", "Tommy Hill", nick_type="player")
|
||||
|
||||
# My own custom nick type (handled by my own game code somehow):
|
||||
object.nicks.add("hood", "The hooded man", nick_type="my_identsystem")
|
||||
|
||||
# get back the translated nick:
|
||||
full_name = object.nicks.get("rose", nick_type="object")
|
||||
|
||||
# delete a previous set nick
|
||||
object.nicks.del("rose", nick_type="object")
|
||||
|
||||
In a command definition you can reach the nick handler through
|
||||
``self.caller.nicks``. See the ``nick`` command in
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,3 +1,5 @@
|
|||
Player typeclass
|
||||
|
||||
Players
|
||||
=======
|
||||
|
||||
|
|
@ -18,10 +20,12 @@ OOC mode. You are quite limited in this mode, basically it works like a
|
|||
simple chat program. It acts as a staging area for switching between
|
||||
Characters (if your game supports that) or as a safety mode if your
|
||||
Character gets deleted. . Use ``@ic`` to switch back "into" your
|
||||
character. Also note that the Player object can have a different set of
|
||||
`Permissions <Locks#Permissions.html>`_ from the Character they control
|
||||
(in the first character you create permissions for Player and Character
|
||||
are the same, however).
|
||||
character.
|
||||
|
||||
Also note that the Player object can have a different set of
|
||||
[Locks#Permissions Permissions] from the Character they control (in the
|
||||
first character you create permissions for Player and Character are the
|
||||
same, however).
|
||||
|
||||
How to create your own Player types
|
||||
-----------------------------------
|
||||
|
|
@ -40,7 +44,19 @@ Here's how to define a new Player typeclass in code:
|
|||
|
||||
::
|
||||
|
||||
from ev import Player class ConfigPlayer(Player): """ This creates a Player with some configuration options """ at_player_creation(self): "this is called only once, when player is first created" self.db.real_name = None # this is set later self.db.real_address = None # '' self.db.config_1 = True # default config self.db.config_2 = False # " self.db.config_3 = 1 # " # ... whatever else our game needs to know
|
||||
from ev import Player
|
||||
class ConfigPlayer(Player):
|
||||
"""
|
||||
This creates a Player with some configuration options
|
||||
"""
|
||||
at_player_creation(self):
|
||||
"this is called only once, when player is first created"
|
||||
self.db.real_name = None # this is set later
|
||||
self.db.real_address = None # ''
|
||||
self.db.config_1 = True # default config
|
||||
self.db.config_2 = False # "
|
||||
self.db.config_3 = 1 # "
|
||||
# ... whatever else our game needs to know
|
||||
|
||||
There is no pre-made folder in ``game/gamesrc`` to store custom player
|
||||
typeclasses. Make your own folder or store it in ``gamesrc/objects``
|
||||
|
|
@ -73,8 +89,8 @@ Special handlers:
|
|||
defined by ``settings.CMDSET_OOC``.
|
||||
- ``nicks`` - This stores and handles `Nicks <Nicks.html>`_, in the
|
||||
same way as nicks it works on Objects. For Players, nicks are
|
||||
primarily used to store custom aliases for
|
||||
`Channels <Communications#Channels.html>`_.
|
||||
primarily used to store custom aliases for [Communications#Channels
|
||||
Channels].
|
||||
|
||||
How it all hangs together
|
||||
-------------------------
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Portal and Server
|
||||
|
||||
Portal and Server layout
|
||||
========================
|
||||
|
||||
|
|
@ -10,7 +12,7 @@ to have players connect to the Portal but keep the MUD running on the
|
|||
Server. This way one can restart/reload the game (the Server part)
|
||||
without Players getting disconnected.
|
||||
|
||||
https://2498159658166209538-a-1802744773732722657-s-sites.googlegroups.com/site/evenniaserver/file-cabinet/evennia\ *server*\ portal.png
|
||||
|image0|
|
||||
|
||||
The Server and Portal are glued together via an AMP (Asynchronous
|
||||
Messaging Protocol) connection. This allows the two programs to
|
||||
|
|
@ -65,3 +67,5 @@ the AMP bridge. All types of Sessions hold a reference to their
|
|||
respective Sessionhandler (the property is called ``sessionhandler``) so
|
||||
they can relay data. See `protocols <SessionProtocols.html>`_ for more
|
||||
info on building new protocols.
|
||||
|
||||
.. |image0| image:: https://2498159658166209538-a-1802744773732722657-s-sites.googlegroups.com/site/evenniaserver/file-cabinet/evennia_server_portal.png
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Tutorial for adding configuration
|
||||
|
||||
Tutorial: Removing colour from your game
|
||||
========================================
|
||||
|
||||
|
|
@ -34,7 +36,12 @@ inheriting from ``game.gamesrc.objects.baseobjecs.Character``.
|
|||
|
||||
::
|
||||
|
||||
from ev import Characterclass ColourableCharacter(Character): at_object_creation(self): # set a colour config value self.db.config_colour = True
|
||||
from ev import Character
|
||||
|
||||
class ColourableCharacter(Character):
|
||||
at_object_creation(self):
|
||||
# set a colour config value
|
||||
self.db.config_colour = True
|
||||
|
||||
Above we set a simple config value as an `attribute <Attributes.html>`_.
|
||||
|
||||
|
|
@ -48,7 +55,7 @@ everything works - you don't want to render your root user unusable!).
|
|||
|
||||
::
|
||||
|
||||
@typeclass/reset/force Bob = mycharacter.ColourableCharacter
|
||||
@typeclass/reset/force Bob = mycharacter.ColourableCharacter
|
||||
|
||||
``@typeclass`` changes Bob's typeclass and runs all its creation hooks
|
||||
all over again. The ``/reset`` switch clears all attributes and
|
||||
|
|
@ -58,7 +65,7 @@ properties from the old typeclass and the new one. ``/force`` might be
|
|||
needed if you edit the typeclass and want to update the object despite
|
||||
the actual typeclass name not having changed.
|
||||
|
||||
Overload the ``msg()`` method
|
||||
Overload the \`msg()\` method
|
||||
-----------------------------
|
||||
|
||||
Next we need to overload the ``msg()`` method. What we want is to check
|
||||
|
|
@ -76,7 +83,13 @@ original. Here's how it could look:
|
|||
|
||||
::
|
||||
|
||||
from ev import ansimsg(self, message, from_obj=None, data=None): "our custom msg()" if not self.db.config_colour: message = ansi.parse_ansi(message, strip_ansi=True) self.dbobj.msg(message, from_obj, data)
|
||||
from ev import ansi
|
||||
|
||||
msg(self, message, from_obj=None, data=None):
|
||||
"our custom msg()"
|
||||
if not self.db.config_colour:
|
||||
message = ansi.parse_ansi(message, strip_ansi=True)
|
||||
self.dbobj.msg(message, from_obj, data)
|
||||
|
||||
Above we create a custom version of the ``msg()`` method that cleans all
|
||||
ansi characters if the config value is not set to True. Once that's
|
||||
|
|
@ -94,7 +107,7 @@ command:
|
|||
|
||||
::
|
||||
|
||||
@py self.db.config_colour = False
|
||||
@py self.db.config_colour = False
|
||||
|
||||
Custom colour config command
|
||||
----------------------------
|
||||
|
|
@ -111,7 +124,28 @@ for configuration down the line).
|
|||
from ev import default_cmds
|
||||
class ConfigColourCmd(default_cmds.MuxCommand):
|
||||
"""
|
||||
Configures your colour Usage: @setcolour on|off This turns ansii-colours on/off. Default is on. """ key = "@setcolour" aliases = ["@setcolor"] def func(self): "Implements the command" if not self.args or not self.args in ("on", "off"): self.caller.msg("Usage: @setcolour on|off") return if self.args == "on": self.caller.db.config_colour = True else: self.caller.db.config_colour = False self.caller.msg("Colour was turned %s." % self.args)
|
||||
Configures your colour
|
||||
|
||||
Usage:
|
||||
@setcolour on|off
|
||||
|
||||
This turns ansii-colours on/off.
|
||||
Default is on.
|
||||
"""
|
||||
|
||||
key = "@setcolour"
|
||||
aliases = ["@setcolor"]
|
||||
|
||||
def func(self):
|
||||
"Implements the command"
|
||||
if not self.args or not self.args in ("on", "off"):
|
||||
self.caller.msg("Usage: @setcolour on|off")
|
||||
return
|
||||
if self.args == "on":
|
||||
self.caller.db.config_colour = True
|
||||
else:
|
||||
self.caller.db.config_colour = False
|
||||
self.caller.msg("Colour was turned %s." % self.args)
|
||||
|
||||
Lastly, we make this command available to the user by adding it to the
|
||||
default command set. Easiest is to add it to copy the template file from
|
||||
|
|
@ -128,7 +162,7 @@ module.
|
|||
|
||||
def at_cmdset_creation(self):
|
||||
super(DefaultCmdSet, self).at_cmdset_creation()
|
||||
self.add(configcmds.ConfigColourCmd())
|
||||
self.add(configcmds.ConfigColourCmd())
|
||||
|
||||
When adding a new command to a cmdset like this you need to run the
|
||||
``@reload`` command (or reboot the server). From here on out, your users
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Script typeclass
|
||||
|
||||
Scripts
|
||||
=======
|
||||
|
||||
|
|
@ -59,7 +61,7 @@ can try it out with an example script:
|
|||
|
||||
::
|
||||
|
||||
> @script self = examples.bodyfunctions.BodyFunctions
|
||||
> @script self = examples.bodyfunctions.BodyFunctions
|
||||
|
||||
This should cause some random messages. The ``/stop`` switch will kill
|
||||
the script again.
|
||||
|
|
@ -71,7 +73,11 @@ care of all initialization and startup of the script for you.
|
|||
|
||||
::
|
||||
|
||||
# adding a script to an existing object 'myobj' myobj.scripts.add("game.gamesrc.scripts.myscripts.CoolScript") # alternative way from src.utils.create import create_script create_script("game.gamesrc.scripts.myscripts.CoolScript", obj=myobj)
|
||||
# adding a script to an existing object 'myobj'
|
||||
myobj.scripts.add("game.gamesrc.scripts.myscripts.CoolScript")
|
||||
# alternative way
|
||||
from src.utils.create import create_script
|
||||
create_script("game.gamesrc.scripts.myscripts.CoolScript", obj=myobj)
|
||||
|
||||
The creation method(s) takes an optional argument *key* that allows you
|
||||
to name your script uniquely before adding it. This can be useful if you
|
||||
|
|
@ -79,143 +85,14 @@ add many scripts of the same type and later plan to use
|
|||
``myobj.scripts.delete`` to remove individual scripts.
|
||||
|
||||
You can create global scripts with
|
||||
``ev.create_script (a shortcut to``\ src.utils.create.create\_script()
|
||||
``ev.create_script (a shortcut to ``\ src.utils.create.create\_script()\ ``). Just don't supply an object to store it on. {{{ # adding a global script from ev import create_script create_script("game.gamesrc.scripts.globals.MyGlobalEconomy", key="economy", obj=None) }}} Assuming the Script ``\ game.gamesrc.scripts.globals.MyGlobalEconomy\ `` exists, this will create and start it as a global script. == Properties and functions defined on Scripts == It's important to know the variables controlling the script before one can create one. Beyond those properties assigned to all typeclassed objects (see [Typeclasses]), such as ``\ key\ ``, ``\ db\ ``, ``\ ndb\ `` etc, all Scripts also have the following properties: * ``\ desc\ `` - an optional description of the script's function. Seen in script listings. * ``\ interval\ `` - how often the script should run. If ``\ interval
|
||||
==
|
||||
0\ `` (default), it runs forever, without any repeating (it will not accept a negative value). * ``\ start\_delay\ `` - (bool), if we should wait ``\ interval\ `` seconds before firing for the first time or not. * ``\ repeats\ `` - How many times we should repeat, assuming ``\ interval
|
||||
> 0\ ``. If repeats is set to ``\ <=
|
||||
0\ ``, the script will repeat indefinitely. * ``\ persistent\ ``- if this script should survive a server reboot. There is one special property: * ``\ obj\ `` - the [Objects Object] this script is attached to (if any). You should not need to set this manually. If you add the script to the Object with ``\ myobj.scripts.add(myscriptpath)\ `` or give ``\ myobj\ `` as an argument to the ``\ utils.create.create\_script\ `` function, the ``\ obj\ `` property will be set to ``\ myobj\ `` for you. It's also imperative to know the hook functions. Normally, overriding these are all the customization you'll need to do in Scripts. You can find longer descriptions of these in ``\ src/scripts/scripts.py\ ``. * ``\ at\_script\_creation()\ `` - this is usually where the script class sets things like ``\ interval\ `` and ``\ repeats\ ``; things that control how the script runs. It is only called once - when the script is first created. * ``\ is\_valid()\ `` - determines if the script should still be running or not. This is called when running ``\ obj.scripts.validate()\ ``, which you can run manually, but which also Evennia calls during certain situations such as reloads. This is also useful for using scripts as state managers. If the method returns ``\ False\ ``, the script is stopped and cleanly removed. * ``\ at\_start()\ `` - this is called when the script first starts. For persistent scripts this is at least once ever server startup. Note that this will _always_ be called right away, also if ``\ start\_delay\ `` is ``\ True\ ``. * ``\ at\_repeat()\ `` - this is called every ``\ interval\ `` seconds, or not at all. It is called right away at startup, unless ``\ start\_delay\ `` is ``\ True\ ``, in which case the system will wait ``\ interval\ `` seconds before calling. * ``\ at\_stop()\ `` - this is called when the script stops for whatever reason. It's a good place to do custom cleanup. * ``\ at\_server\_reload()\ `` - this is called whenever the server is warm-rebooted (e.g. with the ``\ @reload\ `` command). It's a good place to save non-persistent data you might want to survive a reload. * ``\ at\_server\_shutdown()\ `` - this is called on a full systems shutdown. Running methods (usually called automatically by the engine, but possible to also invoke manually) * ``\ start()\ `` - this will start the script. This is called automatically whenever you add a new script to a handler. ``\ at\_start()\ `` will be called. * ``\ stop()\ `` - this will stop the script and delete it. Removing a script from a handler will stop it automatically. ``\ at\_stop()\ `` will be called. * ``\ pause()\ `` - this pauses a running script, rendering it inactive, but not deleting it. All properties are saved and timers can be resumed. This is called automatically when the server reloads. No hooks are called - as far as the script knows, it never stopped - this is a suspension of the script, not a change of state. * ``\ unpause()\ `` - resumes a previously paused script. Timers etc are restored to what they were before pause. The server unpauses all paused scripts after a server reload. No hooks are called - as far as the script is concerned, it never stopped running. * ``\ time\_until\_next\_repeat()\ `` - for timed scripts, this returns the time in seconds until it next fires. Returns ``\ None\ `` if ``\ interval==0\ ``. == Example script == {{{ import random from ev import Script class Weather(Script): "Displays weather info. Meant to be attached to a room." def at_script_creation(self): "Called once, during initial creation" self.key = "weather_script" self.desc = "Gives random weather messages." self.interval = 60 * 5 # every 5 minutes self.persistent = True self.at_repeat(self): "called every self.interval seconds." rand = random.random() if rand < 0.5: weather = "A faint breeze is felt." elif rand < 0.7: weather = "Clouds sweep across the sky." else: weather = "There is a light drizzle of rain." # send this message to everyone inside the object this # script is attached to (likely a room) self.obj.msg_contents(weather) }}} This is a simple weather script that we can put on an object. Every 5 minutes it will tell everyone inside that object how the weather is. To activate it, just add it to the script handler (``\ scripts\ ``) on an [Objects Room]. That object becomes ``\ self.obj\ `` in the example above. Here we put it on a room called ``\ myroom\ ``: {{{ myroom.scripts.add(weather.Weather) }}} In code you can also use the create function directly if you know how to locate the room you want: {{{ from ev import create_script create_script('game.gamesrc.scripts.weather.Weather', obj=myroom) }}} Or, from in-game, use the ``\ @script\ ````
|
||||
command:
|
||||
|
||||
::
|
||||
|
||||
). Just don't supply an object to store it on. # adding a global script from ev import create_script create_script("game.gamesrc.scripts.globals.MyGlobalEconomy", key="economy", obj=None)
|
||||
|
||||
Assuming the Script ``game.gamesrc.scripts.globals.MyGlobalEconomy``
|
||||
exists, this will create and start it as a global script.
|
||||
|
||||
Properties and functions defined on Scripts
|
||||
-------------------------------------------
|
||||
|
||||
It's important to know the variables controlling the script before one
|
||||
can create one. Beyond those properties assigned to all typeclassed
|
||||
objects (see `Typeclasses <Typeclasses.html>`_), such as ``key``,
|
||||
``db``, ``ndb`` etc, all Scripts also have the following properties:
|
||||
|
||||
- ``desc`` - an optional description of the script's function. Seen in
|
||||
script listings.
|
||||
- ``interval`` - how often the script should run. If ``interval == 0``
|
||||
(default), it runs forever, without any repeating (it will not accept
|
||||
a negative value).
|
||||
- ``start_delay`` - (bool), if we should wait ``interval`` seconds
|
||||
before firing for the first time or not.
|
||||
- ``repeats`` - How many times we should repeat, assuming
|
||||
``interval > 0``. If repeats is set to ``<= 0``, the script will
|
||||
repeat indefinitely.
|
||||
- ``persistent``- if this script should survive a server reboot.
|
||||
|
||||
There is one special property:
|
||||
|
||||
- ``obj`` - the `Object <Objects.html>`_ this script is attached to (if
|
||||
any). You should not need to set this manually. If you add the script
|
||||
to the Object with ``myobj.scripts.add(myscriptpath)`` or give
|
||||
``myobj`` as an argument to the ``utils.create.create_script``
|
||||
function, the ``obj`` property will be set to ``myobj`` for you.
|
||||
|
||||
It's also imperative to know the hook functions. Normally, overriding
|
||||
these are all the customization you'll need to do in Scripts. You can
|
||||
find longer descriptions of these in ``src/scripts/scripts.py``.
|
||||
|
||||
- ``at_script_creation()`` - this is usually where the script class
|
||||
sets things like ``interval`` and ``repeats``; things that control
|
||||
how the script runs. It is only called once - when the script is
|
||||
first created.
|
||||
- ``is_valid()`` - determines if the script should still be running or
|
||||
not. This is called when running ``obj.scripts.validate()``, which
|
||||
you can run manually, but which also Evennia calls during certain
|
||||
situations such as reloads. This is also useful for using scripts as
|
||||
state managers. If the method returns ``False``, the script is
|
||||
stopped and cleanly removed.
|
||||
- ``at_start()`` - this is called when the script first starts. For
|
||||
persistent scripts this is at least once ever server startup. Note
|
||||
that this will *always* be called right away, also if ``start_delay``
|
||||
is ``True``.
|
||||
- ``at_repeat()`` - this is called every ``interval`` seconds, or not
|
||||
at all. It is called right away at startup, unless ``start_delay`` is
|
||||
``True``, in which case the system will wait ``interval`` seconds
|
||||
before calling.
|
||||
- ``at_stop()`` - this is called when the script stops for whatever
|
||||
reason. It's a good place to do custom cleanup.
|
||||
- ``at_server_reload()`` - this is called whenever the server is
|
||||
warm-rebooted (e.g. with the ``@reload`` command). It's a good place
|
||||
to save non-persistent data you might want to survive a reload.
|
||||
- ``at_server_shutdown()`` - this is called on a full systems shutdown.
|
||||
|
||||
Running methods (usually called automatically by the engine, but
|
||||
possible to also invoke manually)
|
||||
|
||||
- ``start()`` - this will start the script. This is called
|
||||
automatically whenever you add a new script to a handler.
|
||||
``at_start()`` will be called.
|
||||
- ``stop()`` - this will stop the script and delete it. Removing a
|
||||
script from a handler will stop it automatically. ``at_stop()`` will
|
||||
be called.
|
||||
- ``pause()`` - this pauses a running script, rendering it inactive,
|
||||
but not deleting it. All properties are saved and timers can be
|
||||
resumed. This is called automatically when the server reloads. No
|
||||
hooks are called - as far as the script knows, it never stopped -
|
||||
this is a suspension of the script, not a change of state.
|
||||
- ``unpause()`` - resumes a previously paused script. Timers etc are
|
||||
restored to what they were before pause. The server unpauses all
|
||||
paused scripts after a server reload. No hooks are called - as far as
|
||||
the script is concerned, it never stopped running.
|
||||
- ``time_until_next_repeat()`` - for timed scripts, this returns the
|
||||
time in seconds until it next fires. Returns ``None`` if
|
||||
``interval==0``.
|
||||
|
||||
Example script
|
||||
--------------
|
||||
|
||||
::
|
||||
|
||||
import random
|
||||
from ev import Script
|
||||
class Weather(Script):
|
||||
"Displays weather info. Meant to be attached to a room."
|
||||
def at_script_creation(self):
|
||||
"Called once, during initial creation"
|
||||
self.key = "weather_script"
|
||||
self.desc = "Gives random weather messages."
|
||||
self.interval = 60 * 5 # every 5 minutes
|
||||
self.persistent = True
|
||||
self.at_repeat(self):
|
||||
"called every self.interval seconds."
|
||||
rand = random.random()
|
||||
if rand < 0.5:
|
||||
weather = "A faint breeze is felt."
|
||||
elif rand < 0.7:
|
||||
weather = "Clouds sweep across the sky."
|
||||
else:
|
||||
weather = "There is a light drizzle of rain."
|
||||
# send this message to everyone inside the object this
|
||||
# script is attached to (likely a room)
|
||||
self.obj.msg_contents(weather)
|
||||
|
||||
This is a simple weather script that we can put on an object. Every 5
|
||||
minutes it will tell everyone inside that object how the weather is.
|
||||
|
||||
To activate it, just add it to the script handler (``scripts``) on an
|
||||
`Room <Objects.html>`_. That object becomes ``self.obj`` in the example
|
||||
above. Here we put it on a room called ``myroom``:
|
||||
|
||||
::
|
||||
|
||||
myroom.scripts.add(weather.Weather)
|
||||
|
||||
In code you can also use the create function directly if you know how to
|
||||
locate the room you want:
|
||||
|
||||
::
|
||||
|
||||
from ev import create_script create_script('game.gamesrc.scripts.weather.Weather', obj=myroom)
|
||||
|
||||
Or, from in-game, use the ``@script`` command:
|
||||
|
||||
::
|
||||
|
||||
@script here = weather.Weather
|
||||
@script here = weather.Weather
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Describing the Portal Session and Protocol system
|
||||
|
||||
Portal Sessions and Protocols
|
||||
=============================
|
||||
|
||||
|
|
@ -160,7 +162,20 @@ create a module there with the following functions:
|
|||
|
||||
::
|
||||
|
||||
# the caller is automatically added as first argument def get_health(character): "Get health, stored as simple attribute" return character.db.health def get_stamina(character): "Get stamina level, stored as simple attribute" return character.db.stamina def get_skill(character, skillname, master=False): """we assume skills are stored as a dictionary stored in an attribute. Master skills are stored separately (for whatever reason)""" if master: return character.db.skills_master.get(skillname, "NoSkill") return character.db.skills.get(skillname, "NoSkill")
|
||||
# the caller is automatically added as first argument
|
||||
def get_health(character):
|
||||
"Get health, stored as simple attribute"
|
||||
return character.db.health
|
||||
def get_stamina(character):
|
||||
"Get stamina level, stored as simple attribute"
|
||||
return character.db.stamina
|
||||
def get_skill(character, skillname, master=False):
|
||||
"""we assume skills are stored as a dictionary
|
||||
stored in an attribute. Master skills are
|
||||
stored separately (for whatever reason)"""
|
||||
if master:
|
||||
return character.db.skills_master.get(skillname, "NoSkill")
|
||||
return character.db.skills.get(skillname, "NoSkill")
|
||||
|
||||
Done, the functions will return what we want assuming Characters do
|
||||
store this information in our game. Let's finish up the first part of
|
||||
|
|
@ -168,7 +183,25 @@ the portal protocol:
|
|||
|
||||
::
|
||||
|
||||
# this method could be named differently depending on the # protocol you are using (this is telnet) def lineReceived(self, string): # (does stuff to analyze the incoming string) # ... outdict = if GET_HEALTH: # call get_health(char) outdict["get_health"] = ([], ) elif GET_STAMINA: # call get_mana(char) outdict["get_stamina"] = ([], ) elif GET_MASTER_SKILL_SMITH: # call get_skill(char, "smithing", master=True) outdict["get_skill"] = (["smithing"], 'master':True) [...] self.sessionhandler.oob_data_out(outdict)
|
||||
# this method could be named differently depending on the
|
||||
# protocol you are using (this is telnet)
|
||||
def lineReceived(self, string):
|
||||
# (does stuff to analyze the incoming string)
|
||||
# ...
|
||||
outdict = {}
|
||||
if GET_HEALTH:
|
||||
# call get_health(char)
|
||||
outdict["get_health"] = ([], {})
|
||||
elif GET_STAMINA:
|
||||
# call get_mana(char)
|
||||
outdict["get_stamina"] = ([], {})
|
||||
elif GET_MASTER_SKILL_SMITH:
|
||||
# call get_skill(char, "smithing", master=True)
|
||||
outdict["get_skill"] = (["smithing"], {'master':True})
|
||||
|
||||
[...]
|
||||
|
||||
self.sessionhandler.oob_data_out(outdict)
|
||||
|
||||
The Server will properly accept this and call the relevant functions to
|
||||
get their return values for the health, stamina and skill. The return
|
||||
|
|
@ -178,7 +211,17 @@ being passed back to the Portal. We need to define
|
|||
|
||||
::
|
||||
|
||||
def oob_data_out(self, data): # the indata is a dictionary funcname:retval outstring = "" for funcname, retval in data.items(): if funcname == 'get_health': # convert to the right format for sending back to client, store # in outstring ... [...] # send off using the protocols send method (this is telnet) sendLine(outstring)
|
||||
def oob_data_out(self, data):
|
||||
# the indata is a dictionary {funcname:retval}
|
||||
|
||||
outstring = ""
|
||||
for funcname, retval in data.items():
|
||||
if funcname == 'get_health':
|
||||
# convert to the right format for sending back to client, store
|
||||
# in outstring ...
|
||||
[...]
|
||||
# send off using the protocols send method (this is telnet)
|
||||
sendLine(outstring)
|
||||
|
||||
As seen, ``oob_data`` takes the values and formats into a form the
|
||||
protocol understands before sending it off.
|
||||
|
|
@ -196,7 +239,7 @@ Portal call and data will be sent back to be handled by the portal as
|
|||
normal.
|
||||
|
||||
Assorted notes
|
||||
--------------
|
||||
==============
|
||||
|
||||
To take two examples, Evennia supports the *telnet* protocol as well as
|
||||
*webclient*, a custom ajax protocol. You'll find that whereas telnet is
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
A brief explanation of what MUSH softcode is and why we use Python
|
||||
instead.
|
||||
|
||||
On MUX and Softcode: A brief overview
|
||||
=====================================
|
||||
|
||||
|
|
@ -51,7 +54,8 @@ retrieved when emitting:
|
|||
|
||||
::
|
||||
|
||||
&HELLO_VALUE.D me=Hello World &HELLO_WORLD.C me=$hello:@pemit %#=[v(HELLO_VALUE.D)]
|
||||
&HELLO_VALUE.D me=Hello World
|
||||
&HELLO_WORLD.C me=$hello:@pemit %#=[v(HELLO_VALUE.D)]
|
||||
|
||||
The v() function returns the HELLO\_VALUE.D attribute on the object that
|
||||
the command resides (``me``, which is yourself in this case). This
|
||||
|
|
@ -60,8 +64,8 @@ should yield the same output as the first example.
|
|||
If you are still curious about how Softcode works, take a look at some
|
||||
external resources:
|
||||
|
||||
- http://www.tinymux.com/wiki/index.php/Softcode
|
||||
- http://www.duh.com/discordia/mushman/man2x1
|
||||
- `http://www.tinymux.com/wiki/index.php/Softcode <http://www.tinymux.com/wiki/index.php/Softcode>`_
|
||||
- `http://www.duh.com/discordia/mushman/man2x1 <http://www.duh.com/discordia/mushman/man2x1>`_
|
||||
|
||||
Problems with Softcode
|
||||
----------------------
|
||||
|
|
@ -107,14 +111,25 @@ Python modules out there in the wild. Our complex systems may be
|
|||
organized neatly into modules, sub-modules, or even broken out into
|
||||
entire Python packages.
|
||||
|
||||
So what is *not* included in Evennia is a MUX/MOO-like online player
|
||||
building system. Advanced coding and building in Evennia is primarily
|
||||
intended to be done outside the game, in full-fledged Python modules. We
|
||||
feel that with a small development team you are better off using a
|
||||
professional source-control system (svn, git, bazaar, mercurial etc)
|
||||
anyway.
|
||||
So what is *not* included in Evennia proper is a MUX/MOO-like online
|
||||
player building system. Advanced coding and building in Evennia is
|
||||
primarily intended to be done outside the game, in full-fledged Python
|
||||
modules. We feel that with a small development team you are better off
|
||||
using a professional source-control system (svn, git, bazaar, mercurial
|
||||
etc) anyway.
|
||||
|
||||
There is of course nothing stopping you from adding very advanced online
|
||||
building commands to Evennia (or even re-implement MUX' softcode in
|
||||
Python should you be very ambitious), but at this time this is not
|
||||
something planned to come with he core distribution.
|
||||
Your Solution
|
||||
=============
|
||||
|
||||
Adding very advanced and flexible building commands to your game will
|
||||
probably often be enough to satisfy most creative builders. However, if
|
||||
you really, *really* want to offer online coding there is of course
|
||||
nothing stopping you from adding that to Evennia, no matter our
|
||||
recommendations. You could even re-implement MUX' softcode in Python
|
||||
should you be very ambitious.
|
||||
|
||||
There is an experimental restricted python environment named *Evlang* to
|
||||
be found in our *contrib* folder. Being in this folder means it's not a
|
||||
part of the core server and is completely optional to use. Evlang could
|
||||
serve as a starting point if you want to go down the route of simple
|
||||
online player coding.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Controlling Evennia processes
|
||||
|
||||
Running Evennia
|
||||
===============
|
||||
|
||||
|
|
@ -8,13 +10,13 @@ multiple-choice menu instead.
|
|||
|
||||
::
|
||||
|
||||
python evennia.py menu
|
||||
python evennia.py menu
|
||||
|
||||
Starting Evennia
|
||||
----------------
|
||||
|
||||
Evennia consists of two components, the Evennia `Server and
|
||||
Portal <ServerAndPortal.html>`_. Briefly, the *Server* is what is
|
||||
Portal <PortalAndServer.html>`_. Briefly, the *Server* is what is
|
||||
running the mud. It handles all game-specific things but don't care
|
||||
exactly how players connect, only that they have. The *Portal* is a
|
||||
gateway to which players connect. It knows everything about telnet, ssh,
|
||||
|
|
@ -23,7 +25,7 @@ required for a functioning mud.
|
|||
|
||||
::
|
||||
|
||||
python evennia.py start
|
||||
python evennia.py start
|
||||
|
||||
The above command automatically starts both Portal and Server at the
|
||||
same time, logging to the log files in ``game/log``.
|
||||
|
|
@ -33,7 +35,7 @@ for quickly debugging your code), you use the -i (interactive) flag:
|
|||
|
||||
::
|
||||
|
||||
python evennia.py -i start
|
||||
python evennia.py -i start
|
||||
|
||||
This will start the *Server* in interactive mode. The Portal will
|
||||
continue to log to its log file. This is normally what you want unless
|
||||
|
|
@ -43,7 +45,8 @@ You can also start the two components one at a time.
|
|||
|
||||
::
|
||||
|
||||
python evennia.py start server python evennia.py start portal
|
||||
python evennia.py start server
|
||||
python evennia.py start portal
|
||||
|
||||
Adding -i to either of these explicit commands will start that component
|
||||
in interactive mode so it logs to the terminal rather than to log file.
|
||||
|
|
@ -67,7 +70,7 @@ Windows):
|
|||
|
||||
::
|
||||
|
||||
python evennia.py reload
|
||||
python evennia.py reload
|
||||
|
||||
Resetting
|
||||
---------
|
||||
|
|
@ -84,7 +87,8 @@ A reset is equivalent to
|
|||
|
||||
::
|
||||
|
||||
python evennia.py stop server python evennia.py start server
|
||||
python evennia.py stop server
|
||||
python evennia.py start server
|
||||
|
||||
Shutting down
|
||||
-------------
|
||||
|
|
@ -97,6 +101,6 @@ From command line you do
|
|||
|
||||
::
|
||||
|
||||
python.py evennia.py stop
|
||||
python.py evennia.py stop
|
||||
|
||||
You will see messages of both Server and Portal closing down.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Text encodings
|
||||
|
||||
Notes on text encodings
|
||||
=======================
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Introducing the Tutorial world area
|
||||
|
||||
Tutorial World Introduction
|
||||
===========================
|
||||
|
||||
|
|
@ -47,12 +49,14 @@ install, log into the server as the superuser (user #1) and run:
|
|||
The world will be built (there will be a lot of text output) and you
|
||||
will end up back in Limbo with a new exit called ``tutorial``.
|
||||
|
||||
An alternative is
|
||||
|
||||
::
|
||||
|
||||
@batchcommand/interactive contrib.tutorial_world.build
|
||||
|
||||
will allow you to step through the building process at your own pace to
|
||||
see what happens in detail.
|
||||
with the /interactive switch you are able to step through the building
|
||||
process at your own pace to see what happens in detail.
|
||||
|
||||
To play the tutorial "correctly", you should *not* do so as superuser.
|
||||
The reason for this is that many game systems ignore the presence of a
|
||||
|
|
@ -75,25 +79,22 @@ down!*
|
|||
and rain screaming in your face you stand where the moor meets the sea
|
||||
along a high, rocky coast ...*
|
||||
|
||||
Look at everything.
|
||||
- Look at everything.
|
||||
- Some objects are interactive in more than one way. Use the normal
|
||||
``help`` command to get a feel for which commands are available at
|
||||
any given time. (use the command ``tutorial`` to get insight behind
|
||||
the scenes of the tutorial).
|
||||
- In order to fight, you need to first find some type of weapon.
|
||||
|
||||
Some objects are interactive in more than one way. Use the normal
|
||||
``help`` command to get a feel for which commands are available at any
|
||||
given time. (use the command ``tutorial`` to get insight behind the
|
||||
scenes of the tutorial).
|
||||
- *slash* is a normal attack
|
||||
- *stab* launches an attack that makes more damage but has a lower
|
||||
chance to hit.
|
||||
- *defend* will lower the chance to taking damage on your enemy's
|
||||
next attack.
|
||||
|
||||
In order to fight, you need to first find some type of weapon.
|
||||
|
||||
- *slash* is a normal attack
|
||||
- *stab* launches an attack that makes more damage but has a lower
|
||||
chance to hit.
|
||||
- *defend* will lower the chance to taking damage on your enemy's next
|
||||
attack.
|
||||
|
||||
You *can* run from a fight that feels too deadly. Expect to be chased
|
||||
though.
|
||||
|
||||
Being defeated is a part of the experience ...
|
||||
- You *can* run from a fight that feels too deadly. Expect to be chased
|
||||
though.
|
||||
- Being defeated is a part of the experience ...
|
||||
|
||||
Uninstall
|
||||
---------
|
||||
|
|
@ -103,7 +104,8 @@ and objects it consists of. First, move out of the tutorial area.
|
|||
|
||||
::
|
||||
|
||||
@find tut#01 @find tut#17
|
||||
@find tut#01
|
||||
@find tut#17
|
||||
|
||||
This should locate the first and last rooms created by ``build.ev`` -
|
||||
*Intro* and *Outro*. If you installed normally, everything created
|
||||
|
|
@ -113,7 +115,7 @@ that range:
|
|||
|
||||
::
|
||||
|
||||
@del 5-80
|
||||
@del 5-80
|
||||
|
||||
You will see some errors since some objects are auto-deleted and so
|
||||
cannot be found when the delete mechanism gets to them. That's fine. You
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
Typeclassed objects
|
||||
===================
|
||||
Descibing how Objects and Typeclasses work
|
||||
|
||||
Typeclassed entities
|
||||
====================
|
||||
|
||||
How do you represent different objects in a game? What makes a bear
|
||||
different from a stone, or a character different from a house? How do
|
||||
you store such differences in the database? One way would be to create
|
||||
new database tables for each type. So a bear would have a database field
|
||||
different from a stone, a character different from a house or a AI
|
||||
script different from a script handling light and darkness? How do you
|
||||
store such differences in the database? One way would be to create new
|
||||
database tables for each type. So a bear would have a database field
|
||||
"claws" and the stone would have fields specifying its weight and colour
|
||||
... and you'd soon go crazy with making custom database manipulations
|
||||
for all infinite combinations.
|
||||
|
|
@ -18,17 +21,15 @@ There are three main game 'entities' in Evennia that are what we call
|
|||
*typeclassed*. They are `Players <Players.html>`_,
|
||||
`Objects <Objects.html>`_ and `Scripts <Scripts.html>`_. This means that
|
||||
they are *almost* normal Python classes - they behave and can be
|
||||
inherited from etc just like normal Python classes except that for
|
||||
storing data they hide underlying database models ...
|
||||
inherited from etc just like normal Python classes. But whenever they
|
||||
store data they are infact transparently storing this data into the
|
||||
database.
|
||||
|
||||
... and that's basically all you *really* need to know about how
|
||||
typeclasses work behind the scenes.
|
||||
All typeclassed entities share a few very useful properties and methods.
|
||||
These are described in the next section.
|
||||
|
||||
To *work* with them you should however know that Objects, Scripts and
|
||||
Players all inherit a lot of helper methods from the typeclass system,
|
||||
properties you can *always* expect a typeclassed entity to have. In
|
||||
addition to these, each of the three sub types also offer a host of
|
||||
additional, subtype-specific helper methods and properties for your use.
|
||||
See the pages for `Players <Players.html>`_, `Objects <Objects.html>`_
|
||||
and `Scripts <Scripts.html>`_ for more specific usage examples.
|
||||
|
||||
Properties available to all typeclassed entities (Players, Objects, Scripts)
|
||||
----------------------------------------------------------------------------
|
||||
|
|
@ -43,24 +44,24 @@ Properties available to all typeclassed entities (Players, Objects, Scripts)
|
|||
|
||||
There are three further properties that warrant special mention:
|
||||
|
||||
- ``db`` (!DataBase) - this is the interface to the `Attribute
|
||||
- ``db`` (DataBase) - this is the interface to the `Attribute
|
||||
system <Attributes.html>`_, allowing for *persistently* storing your
|
||||
own custom data on the entity (i.e. data that will survive a server
|
||||
restart).
|
||||
- ``ndb`` (!NotDataBase) - this is equivalent to the functionality of
|
||||
- ``ndb`` (NotDataBase) - this is equivalent to the functionality of
|
||||
``db`` but is used to store *non-peristent* data (i.e. data that will
|
||||
be lost on server restart).
|
||||
- ``dbobj`` - this is a reference to the *Database object* connected to
|
||||
this typeclass (reversely, ``dbobj.typeclass`` is a reference back to
|
||||
this very typeclass).
|
||||
|
||||
Each of the typeclassed entities then extend this list with their own
|
||||
properties. Go to the pages for `Objects <Objects.html>`_,
|
||||
As said, each of the typeclassed entities then extend this list with
|
||||
their own properties. Go to the pages for `Objects <Objects.html>`_,
|
||||
`Scripts <Scripts.html>`_ and `Players <Players.html>`_ respectively for
|
||||
more info.
|
||||
|
||||
Things to remember when using TypeClasses
|
||||
-----------------------------------------
|
||||
Things to remember when using !TypeClasses
|
||||
------------------------------------------
|
||||
|
||||
Typeclasses *mostly* behave like normal Python classes - you can
|
||||
add/overload custom methods and inherit your own classes from them -
|
||||
|
|
@ -100,9 +101,9 @@ a few things that you need to remember however:
|
|||
How typeclasses actually work
|
||||
=============================
|
||||
|
||||
\_You don't need to read this section to use typeclassed entities, but
|
||||
it might be useful if you want to do advanced things or are interested
|
||||
in knowing how Evennia actually does things behind the scenes.\_
|
||||
*This is considered an advanced section. Skip it on your first
|
||||
read-through unless you are really interested in what's going on under
|
||||
the hood.*
|
||||
|
||||
All typeclassed entities actually consist of two (three) parts:
|
||||
|
||||
|
|
@ -176,9 +177,7 @@ where one.
|
|||
|
||||
Below is a schematic of the database/typeclass structure.
|
||||
|
||||
.. figure:: http://d.imagehost.org/0784/typeclasses1.png
|
||||
:align: center
|
||||
:alt:
|
||||
|image0|
|
||||
|
||||
Let's see how object creation looks like in an example.
|
||||
|
||||
|
|
@ -187,9 +186,9 @@ Let's see how object creation looks like in an example.
|
|||
``game.gamesrc.objects.baseobjects.Object``, which is a grandchild of
|
||||
``src.typeclasses.typeclass.TypeClass``. So the rose a typeclassed
|
||||
object, just as it should be.
|
||||
#. Using a command we create a new *Rose* instance *!RedRose* (e.g. with
|
||||
#. Using a command we create a new *Rose* instance *RedRose* (e.g. with
|
||||
``@create redrose:flowers.Rose``).
|
||||
#. A new database model is created and given the key *!RedRose*. Since
|
||||
#. A new database model is created and given the key *RedRose*. Since
|
||||
this is an `Object <Objects.html>`_ typeclass (rather than a Script
|
||||
or Player), the database model used is
|
||||
``src.objects.models.ObjectDB``, which inherits directly from
|
||||
|
|
@ -200,7 +199,7 @@ Let's see how object creation looks like in an example.
|
|||
database model will restart from this point.
|
||||
#. The database model next *imports* the Typeclass from its stored path
|
||||
and creates a new instance of it in memory. It stores a reference to
|
||||
this instance of *Rose* (*!RedRose*)in a property called
|
||||
this instance of *Rose* (*RedRose*)in a property called
|
||||
``typeclass``.
|
||||
#. As *Rose* is instantiated, its ``__init__()`` method is called. What
|
||||
this does it to make sure to store the back-reference to the Django
|
||||
|
|
@ -225,13 +224,12 @@ are intended to *overload* the default methods on the database object.
|
|||
These are thus searched and run first, and you can then safely use
|
||||
``self.dbobj`` from the typeclass to call the original function if you
|
||||
want. An example of Typeclass overloading is found
|
||||
`here <CommandPrompt#Prompt<i>on</i>the<i>same</i>line.html>`_.
|
||||
[`CommandPrompt <CommandPrompt.html>`_\ #Prompt\_on\_the\_same\_line
|
||||
here].
|
||||
|
||||
Another example:
|
||||
|
||||
.. figure:: http://b.imagehost.org/0023/typeclasses2.png
|
||||
:align: center
|
||||
:alt:
|
||||
|image1|
|
||||
|
||||
Caveats of the typeclass system
|
||||
-------------------------------
|
||||
|
|
@ -253,7 +251,8 @@ query). You can easily convert between them with ``dbobj.typeclass`` and
|
|||
|
||||
::
|
||||
|
||||
obj = ObjectDB.objects.get_id(1) # custom evennia manager method. This returns the typeclass. obj = ObjectDB.objects.get(1) # standard Django. Returns a Django model object.
|
||||
obj = ObjectDB.objects.get_id(1) # custom evennia manager method. This returns the typeclass.
|
||||
obj = ObjectDB.objects.get(1) # standard Django. Returns a Django model object.
|
||||
|
||||
Even more important to know for Django affectionados: Evennia's custom
|
||||
methods return *lists* where you with normal Django methods would expect
|
||||
|
|
@ -264,3 +263,6 @@ should be fine.
|
|||
|
||||
Read the ``manager.py`` files in each relevant folder under ``src/`` to
|
||||
see which database access methods are available.
|
||||
|
||||
.. |image0| image:: http://d.imagehost.org/0784/typeclasses1.png
|
||||
.. |image1| image:: http://b.imagehost.org/0023/typeclasses2.png
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Using and writing unit tests for Evennia.
|
||||
|
||||
Unit Testing
|
||||
============
|
||||
|
||||
|
|
@ -24,7 +26,7 @@ Running the test suite
|
|||
To run the Evennia test suite, go to the ``game/`` folder and issue the
|
||||
command
|
||||
|
||||
``python manage.py test``
|
||||
``python manage.py test``
|
||||
|
||||
A temporary database will be instantiated to manage the tests. If
|
||||
everything works out you will see how many tests were run and how long
|
||||
|
|
@ -46,8 +48,8 @@ used to test a single aspect or component in various ways. Each test
|
|||
case contains one ore more *test methods* - these define the actual
|
||||
tests to run. You can name the test methods anything you want as long as
|
||||
the name starts with "``test_``\ ". Your ``TestCase`` class can also
|
||||
have a method !SetUp(). This is run before each test, setting up
|
||||
whatever preparations the test methods need.
|
||||
have a method SetUp(). This is run before each test, setting up whatever
|
||||
preparations the test methods need.
|
||||
|
||||
To test the results, you use special methods of the ``TestCase`` class.
|
||||
Many of those start with "``assert``\ ", such as ``assertEqual`` or
|
||||
|
|
@ -57,7 +59,34 @@ Example of a ``TestCase`` class (inside a file ``tests.py``):
|
|||
|
||||
::
|
||||
|
||||
# testing a simple funciontry: # this is an optimized version only available in later Django versions from django.utils.unittest import TestCase except ImportError: # if the first fail, we use the old version from django.test import TestCase# the function we want to test from mypath import myfuncTestObj(unittest.TestCase): "This tests a function myfunc." def test_return_value(self): "test method. Makes sure return value is as expected." expected_return = "This is me being nice." actual_return = myfunc() # test self.assertEqual(expected_return, actual_return) def test_alternative_call(self): "test method. Calls with a keyword argument." expected_return = "This is me being baaaad." actual_return = myfunc(bad=True) # test self.assertEqual(expected_return, actual_return)
|
||||
|
||||
# testing a simple funcion
|
||||
|
||||
try:
|
||||
# this is an optimized version only available in later Django versions
|
||||
from django.utils.unittest import TestCase
|
||||
except ImportError:
|
||||
# if the first fail, we use the old version
|
||||
from django.test import TestCase
|
||||
|
||||
# the function we want to test
|
||||
from mypath import myfunc
|
||||
|
||||
TestObj(unittest.TestCase):
|
||||
"This tests a function myfunc."
|
||||
|
||||
def test_return_value(self):
|
||||
"test method. Makes sure return value is as expected."
|
||||
expected_return = "This is me being nice."
|
||||
actual_return = myfunc()
|
||||
# test
|
||||
self.assertEqual(expected_return, actual_return)
|
||||
def test_alternative_call(self):
|
||||
"test method. Calls with a keyword argument."
|
||||
expected_return = "This is me being baaaad."
|
||||
actual_return = myfunc(bad=True)
|
||||
# test
|
||||
self.assertEqual(expected_return, actual_return)
|
||||
|
||||
The above example is very simplistic, but you should get the idea. Look
|
||||
at ``src/objects/tests.py`` for more realistic examples of tests. You
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ via Mercurial. If you haven't already, see the `Getting Started
|
|||
guide <GettingStarted.html>`_ and get everything running. There are many
|
||||
ways to get told when to update: You can subscribe to the RSS feed or
|
||||
manually check up on the feeds from
|
||||
`http://www.evennia.com. <http://www.evennia.com.>`_ You can also join
|
||||
`http://www.evennia.com <http://www.evennia.com>`_. You can also join
|
||||
the `Evennia Commit
|
||||
Log <http://groups.google.com/group/evennia-commits/>`_ group, which
|
||||
will send you an email when the server repository changes.
|
||||
|
|
@ -16,7 +16,8 @@ root directory and type:
|
|||
|
||||
::
|
||||
|
||||
hg pull hg update
|
||||
hg pull
|
||||
hg update
|
||||
|
||||
Assuming you've got the command line client. If you're using a graphical
|
||||
client, you will probably want to navigate to the ``evennia`` directory
|
||||
|
|
@ -27,7 +28,7 @@ You can review the latest changes with
|
|||
|
||||
::
|
||||
|
||||
hg log
|
||||
hg log
|
||||
|
||||
or the equivalent in the graphical client. The log tends to scroll past
|
||||
quite quickly, so if you are in linux it might be an idea to *pipe* the
|
||||
|
|
@ -37,7 +38,7 @@ permanent solution):
|
|||
|
||||
::
|
||||
|
||||
hg log | less
|
||||
hg log | less
|
||||
|
||||
You can also see the latest changes online
|
||||
`here <http://code.google.com/p/evennia/source/list>`_.
|
||||
|
|
@ -62,21 +63,22 @@ components you need to clear the data from all of them:
|
|||
|
||||
::
|
||||
|
||||
python manage.py reset server objects players scripts comms help web auth
|
||||
python manage.py reset server objects players scripts comms help web auth
|
||||
|
||||
Django also offers an easy way to start the database's own management
|
||||
should we want more direct control:
|
||||
|
||||
::
|
||||
|
||||
python manage.py dbshell
|
||||
python manage.py dbshell
|
||||
|
||||
In e.g. MySQL you can then do something like this (assuming your MySQL
|
||||
database is named "Evennia":
|
||||
|
||||
::
|
||||
|
||||
mysql> DROP DATABASE Evennia; mysql> exit
|
||||
mysql> DROP DATABASE Evennia;
|
||||
mysql> exit
|
||||
|
||||
A Note on Schema Migration
|
||||
--------------------------
|
||||
|
|
@ -93,7 +95,7 @@ using the database's command line. This often means adding/removing new
|
|||
tables or fields as well as possibly convert existing data to match what
|
||||
the new Evennia version expects. It should be quite obvious that this
|
||||
quickly becomes cumbersome and error-prone. If your database doesn't
|
||||
contain anything critical yet iẗ́'s probably easiest to simply reset it
|
||||
contain anything critical yet it's probably easiest to simply reset it
|
||||
and start over rather than to bother converting.
|
||||
|
||||
Enter `South <http://south.aeracode.org/>`_. South keeps track of
|
||||
|
|
@ -116,7 +118,7 @@ online list), you go to ``game/`` and run this command:
|
|||
|
||||
::
|
||||
|
||||
python manage.py migrate
|
||||
python manage.py migrate
|
||||
|
||||
This will convert your database to the new schema and you should be set
|
||||
to go.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Our policy on default commands
|
||||
|
||||
The 'MUX-like' default of Evennia
|
||||
=================================
|
||||
|
||||
|
|
@ -21,7 +23,7 @@ Evennia.
|
|||
|
||||
However, Evennia has taken a completely different stance on how admins
|
||||
extend and improve their games. Instead of implementing a special
|
||||
in-game language (!SoftCode), all game extension is done through Python
|
||||
in-game language (SoftCode), all game extension is done through Python
|
||||
modules, like the rest of Evennia. This gives the admin practically
|
||||
unlimited power to extend the game leveraging the full power of a mature
|
||||
high level programming language. You can find a more elaborate
|
||||
|
|
@ -36,9 +38,10 @@ comes with a caveat though - there are many cases where this is
|
|||
impossible without sacrificing the usability and utility of the
|
||||
codebase. In those cases, differences in implementation as well as
|
||||
command syntax is to be expected. Evennia is *not* MUX - we handle all
|
||||
underlying systems very differently and don't use SoftCode. The WWMD
|
||||
policy is only applied to the default commands, not to any other
|
||||
programming paradigms in the codebase.
|
||||
underlying systems very differently and don't use
|
||||
`SoftCode <SoftCode.html>`_. The WWMD policy is only applied to the
|
||||
default commands, not to any other programming paradigms in the
|
||||
codebase.
|
||||
|
||||
If you are an Evennia codebase developer, consider activating
|
||||
``IMPORT_MUX_HELP`` in your ``settings.py`` file. This will import a
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Summarizes the web feautures of Evennia
|
||||
|
||||
Web Features
|
||||
============
|
||||
|
||||
|
|
@ -28,7 +30,7 @@ so the server can access the directory.
|
|||
|
||||
You also need to modify the settings file. Set ``ROOT_URLCONF`` to your
|
||||
new ``game.gamesrc.web.urls`` and add an entry
|
||||
``os.path.join(GAME_DIR, "web", "templates", ACTIVE_TEMPLATE)`` to the
|
||||
`` os.path.join(GAME_DIR, "web", "templates", ACTIVE_TEMPLATE)`` to the
|
||||
``TEMPLATE_DIRS`` tuple. You should now have a separate website setup
|
||||
you can edit as you like. Be aware that updates we do to ``src/web``
|
||||
will not transfer automatically to your copy, so you'll need to apply
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Devel workshop
|
||||
|
||||
rtclient protocol
|
||||
=================
|
||||
|
||||
|
|
@ -19,11 +21,11 @@ twisted/python.
|
|||
There are two principle aspects to the rtclient protocol, mode control
|
||||
and buffering.
|
||||
|
||||
modes
|
||||
-----
|
||||
Modes
|
||||
=====
|
||||
|
||||
Unencoded Mode
|
||||
~~~~~~~~~~~~~~
|
||||
--------------
|
||||
|
||||
All output is buffered until ascii char 10, 13, or 255 is encountered or
|
||||
the mode changes or no output has been added to the buffer in the last
|
||||
|
|
@ -31,19 +33,19 @@ the mode changes or no output has been added to the buffer in the last
|
|||
interprets the entire buffer as plain text and flushes the buffer.
|
||||
|
||||
HTML Mode
|
||||
~~~~~~~~~
|
||||
---------
|
||||
|
||||
All output is buffered. When the mode changes, the client then parses
|
||||
the entire buffer as HTML.
|
||||
|
||||
Javascript Mode
|
||||
~~~~~~~~~~~~~~~
|
||||
---------------
|
||||
|
||||
All output is buffered. When the mode changes, the client then parses
|
||||
the entire buffer as Javascript.
|
||||
|
||||
Sample Sessions
|
||||
---------------
|
||||
===============
|
||||
|
||||
# start html mode, send html, force buffer flush
|
||||
|
||||
|
|
@ -75,21 +77,21 @@ note we are using the tokens imported instead of the constants
|
|||
session.msg(chr(244))
|
||||
|
||||
Values of Tokens
|
||||
----------------
|
||||
================
|
||||
|
||||
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| chr() value \| name \| function |
|
||||
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| 240 \| HTMLTOKEN \| lets client know it is about to receive HTML |
|
||||
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| 241 \| JAVASCRIPTTOKEN \| lets client know it is about to receive javascript |
|
||||
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| 242 \| UNENCODEDTOKEN \| lets client know it is about to receive plain telnet text |
|
||||
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| 243 \| NOAUTOCHUNKTOKEN \| applies to unencoded mode only, prevents the chunking of text at end-of-line characters so that only mode changes force the buffer to be sent to the client |
|
||||
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| 244 \| AUTOCHUNKTOKEN \| applies to unencoded mode only, enables automatic chunking of text by end-of-line characters and by non-blank buffers not having been written to in the last 1/10th second |
|
||||
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| chr() value \| name \| function |
|
||||
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| 240 \| HTML\_TOKEN \| lets client know it is about to receive HTML |
|
||||
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| 241 \| JAVASCRIPT\_TOKEN \| lets client know it is about to receive javascript |
|
||||
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| 242 \| UNENCODED\_TOKEN \| lets client know it is about to receive plain telnet text |
|
||||
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| 243 \| NO\_AUTOCHUNK\_TOKEN \| applies to unencoded mode only, prevents the chunking of text at end-of-line characters so that only mode changes force the buffer to be sent to the client |
|
||||
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| 244 \| AUTOCHUNK\_TOKEN \| applies to unencoded mode only, enables automatic chunking of text by end-of-line characters and by non-blank buffers not having been written to in the last 1/10th second |
|
||||
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
identifying as an rtclient
|
||||
--------------------------
|
||||
|
|
@ -111,16 +113,18 @@ NO\_AUTOCHUNK
|
|||
Contents are never sent to the client until the encoding mode changes
|
||||
(for example, switching from HTML to UNENCODED will send the HTML
|
||||
buffer) or the buffering mode changes (for example, one could set
|
||||
NO\ *AUTOCHUNK, send some text, and set NO*\ AUTOCHUNK again to force a
|
||||
NO\_AUTOCHUNK, send some text, and set NO\_AUTOCHUNK again to force a
|
||||
flush.
|
||||
|
||||
AUTOCHUNK
|
||||
~~~~~~~~~
|
||||
|
||||
It sends the buffer to the client as unencoded text whenever one of two
|
||||
things happen: \* the buffer is non-blank but hasn't had anything added
|
||||
to it very recently (about 1/10th of a second) \* the buffer ends with
|
||||
an end-of-line character (10, 13, 255)
|
||||
It sends the buffer to the client as unencoded text whenever one of
|
||||
two things happen:
|
||||
|
||||
**the buffer is non-blank but hasn't had anything added to it very
|
||||
recently (about 1/10th of a second)** the buffer ends with an
|
||||
end-of-line character (10, 13, 255)
|
||||
|
||||
Autochunking strips end-of-line characters and the client adds in its
|
||||
own EOL! If you would like to preserve them, send them from within
|
||||
|
|
|
|||
|
|
@ -25,24 +25,25 @@ The first stage serves to establish a prototype implementation -
|
|||
something that shows the parts hanging together, but with only a subset
|
||||
of the functionality.
|
||||
|
||||
Create custom `TypeClasses <Objects.html>`_ supporting the SMAUG system:
|
||||
#. Create custom `TypeClasses <Objects.html>`_ supporting the SMAUG
|
||||
system:
|
||||
|
||||
- Object->!SmaugObject->!SmaugBeing->!SmaugCharacter,Character
|
||||
- Object->!SmaugObject->!SmaugBeing->!SmaugMob-> ...
|
||||
- Object->!SmaugObject->!SmaugThing-> ...
|
||||
- Object->SmaugObject->SmaugBeing->SmaugCharacter,Character
|
||||
- Object->SmaugObject->SmaugBeing->SmaugMob-> ...
|
||||
- Object->SmaugObject->SmaugThing-> ...
|
||||
|
||||
Create limited subclasses or attributes on objects
|
||||
#. Create limited subclasses or attributes on objects
|
||||
|
||||
- Limited classes/races (1-2?)
|
||||
- Skills (<lvl 5?) - not too many!
|
||||
- Limited classes/races (1-2?)
|
||||
- Skills (<lvl 5?) - not too many!
|
||||
|
||||
Behind-the-scenes SMAUG engine
|
||||
#. Behind-the-scenes SMAUG engine
|
||||
|
||||
- Contest resolution
|
||||
- Mobs moving around, "AI"
|
||||
- Base combat system
|
||||
- Contest resolution
|
||||
- Mobs moving around, "AI"
|
||||
- Base combat system
|
||||
|
||||
Import of small data set, testing.
|
||||
#. Import of small data set, testing.
|
||||
|
||||
SMAUG specifics
|
||||
===============
|
||||
|
|
@ -50,249 +51,249 @@ SMAUG specifics
|
|||
Code Availability By Lvl
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
+-------+-------------------------------+
|
||||
| Lvl | Code Bit |
|
||||
+-------+-------------------------------+
|
||||
| 0 | spell\ *disenchant*\ weapon |
|
||||
+-------+-------------------------------+
|
||||
| 1 | spell\ *cause*\ light |
|
||||
+-------+-------------------------------+
|
||||
| 1 | dohide |
|
||||
+-------+-------------------------------+
|
||||
| 1 | spellventriloquate |
|
||||
+-------+-------------------------------+
|
||||
| 1 | docook |
|
||||
+-------+-------------------------------+
|
||||
| 1 | doclimb |
|
||||
+-------+-------------------------------+
|
||||
| 1 | spellnull |
|
||||
+-------+-------------------------------+
|
||||
| 1 | dopick |
|
||||
+-------+-------------------------------+
|
||||
| 1 | dosteal |
|
||||
+-------+-------------------------------+
|
||||
| 1 | dobackstab |
|
||||
+-------+-------------------------------+
|
||||
| 1 | spellsmaug |
|
||||
+-------+-------------------------------+
|
||||
| 1 | dokick |
|
||||
+-------+-------------------------------+
|
||||
| 2 | dodig |
|
||||
+-------+-------------------------------+
|
||||
| 2 | domount |
|
||||
+-------+-------------------------------+
|
||||
| 2 | spell\ *faerie*\ fire |
|
||||
+-------+-------------------------------+
|
||||
| 2 | spell\ *create*\ food |
|
||||
+-------+-------------------------------+
|
||||
| 2 | spell\ *create*\ water |
|
||||
+-------+-------------------------------+
|
||||
| 2 | spellweaken |
|
||||
+-------+-------------------------------+
|
||||
| 2 | spellblackhand |
|
||||
+-------+-------------------------------+
|
||||
| 3 | doscan |
|
||||
+-------+-------------------------------+
|
||||
| 3 | dosearch |
|
||||
+-------+-------------------------------+
|
||||
| 3 | dofeed |
|
||||
+-------+-------------------------------+
|
||||
| 3 | spell\ *chill*\ touch |
|
||||
+-------+-------------------------------+
|
||||
| 4 | dorescue |
|
||||
+-------+-------------------------------+
|
||||
| 4 | spellcureblindness |
|
||||
+-------+-------------------------------+
|
||||
| 4 | spellinvis |
|
||||
+-------+-------------------------------+
|
||||
| 4 | doaid |
|
||||
+-------+-------------------------------+
|
||||
| 4 | spellgalvanicwhip |
|
||||
+-------+-------------------------------+
|
||||
| 5 | spellblindness |
|
||||
+-------+-------------------------------+
|
||||
| 5 | spell\ *cause*\ serious |
|
||||
+-------+-------------------------------+
|
||||
| 5 | spell\ *detect*\ poison |
|
||||
+-------+-------------------------------+
|
||||
| 5 | spell\ *burning*\ hands |
|
||||
+-------+-------------------------------+
|
||||
| 5 | spell\ *know*\ alignment |
|
||||
+-------+-------------------------------+
|
||||
| 6 | spell\ *locate*\ object |
|
||||
+-------+-------------------------------+
|
||||
| 6 | dotrack |
|
||||
+-------+-------------------------------+
|
||||
| 6 | spellremoveinvis |
|
||||
+-------+-------------------------------+
|
||||
| 6 | spellpoison |
|
||||
+-------+-------------------------------+
|
||||
| 7 | spellearthquake |
|
||||
+-------+-------------------------------+
|
||||
| 7 | spellshockinggrasp |
|
||||
+-------+-------------------------------+
|
||||
| 8 | spellteleport |
|
||||
+-------+-------------------------------+
|
||||
| 8 | dobashdoor |
|
||||
+-------+-------------------------------+
|
||||
| 8 | spellsummon |
|
||||
+-------+-------------------------------+
|
||||
| 8 | spell\ *cure*\ poison |
|
||||
+-------+-------------------------------+
|
||||
| 8 | spelldisruption |
|
||||
+-------+-------------------------------+
|
||||
| 9 | spellbethsaideantouch |
|
||||
+-------+-------------------------------+
|
||||
| 9 | spellcausecritical |
|
||||
+-------+-------------------------------+
|
||||
| 9 | spelllightningbolt |
|
||||
+-------+-------------------------------+
|
||||
| 10 | spellidentify |
|
||||
+-------+-------------------------------+
|
||||
| 10 | spell\ *faerie*\ fog |
|
||||
+-------+-------------------------------+
|
||||
| 10 | spell\ *control*\ weather |
|
||||
+-------+-------------------------------+
|
||||
| 10 | spell\ *dispel*\ evil |
|
||||
+-------+-------------------------------+
|
||||
| 10 | dodisarm |
|
||||
+-------+-------------------------------+
|
||||
| 11 | spellcolourspray |
|
||||
+-------+-------------------------------+
|
||||
| 11 | dobite |
|
||||
+-------+-------------------------------+
|
||||
| 11 | spell\ *dispel*\ magic |
|
||||
+-------+-------------------------------+
|
||||
| 11 | dobloodlet |
|
||||
+-------+-------------------------------+
|
||||
| 12 | spellsleep |
|
||||
+-------+-------------------------------+
|
||||
| 12 | spellcurse |
|
||||
+-------+-------------------------------+
|
||||
| 12 | spellcalllightning |
|
||||
+-------+-------------------------------+
|
||||
| 12 | spellremovecurse |
|
||||
+-------+-------------------------------+
|
||||
| 12 | spellenchantweapon |
|
||||
+-------+-------------------------------+
|
||||
| 12 | spellword\ *of*\ recall |
|
||||
+-------+-------------------------------+
|
||||
| 13 | spellharm |
|
||||
+-------+-------------------------------+
|
||||
| 13 | spellfireball |
|
||||
+-------+-------------------------------+
|
||||
| 13 | spellexpurgation |
|
||||
+-------+-------------------------------+
|
||||
| 13 | spellflamestrike |
|
||||
+-------+-------------------------------+
|
||||
| 13 | spell\ *midas*\ touch |
|
||||
+-------+-------------------------------+
|
||||
| 13 | spell\ *energy*\ drain |
|
||||
+-------+-------------------------------+
|
||||
| 14 | spell\ *spectral*\ furor |
|
||||
+-------+-------------------------------+
|
||||
| 14 | spell\ *charm*\ person |
|
||||
+-------+-------------------------------+
|
||||
| 15 | spell\ *remove*\ trap |
|
||||
+-------+-------------------------------+
|
||||
| 16 | spellfarsight |
|
||||
+-------+-------------------------------+
|
||||
| 16 | dodetrap |
|
||||
+-------+-------------------------------+
|
||||
| 17 | spelltransport |
|
||||
+-------+-------------------------------+
|
||||
| 17 | spelldream |
|
||||
+-------+-------------------------------+
|
||||
| 18 | spell\ *sulfurous*\ spray |
|
||||
+-------+-------------------------------+
|
||||
| 18 | spell\ *pass*\ door |
|
||||
+-------+-------------------------------+
|
||||
| 19 | spell\ *sonic*\ resonance |
|
||||
+-------+-------------------------------+
|
||||
| 20 | dogouge |
|
||||
+-------+-------------------------------+
|
||||
| 20 | spellacidblast |
|
||||
+-------+-------------------------------+
|
||||
| 21 | spellportal |
|
||||
+-------+-------------------------------+
|
||||
| 23 | spell\ *black*\ fist |
|
||||
+-------+-------------------------------+
|
||||
| 25 | dopunch |
|
||||
+-------+-------------------------------+
|
||||
| 25 | docircle |
|
||||
+-------+-------------------------------+
|
||||
| 25 | dobrew |
|
||||
+-------+-------------------------------+
|
||||
| 27 | spellmagneticthrust |
|
||||
+-------+-------------------------------+
|
||||
| 27 | dopoisonweapon |
|
||||
+-------+-------------------------------+
|
||||
| 28 | spellscorchingsurge |
|
||||
+-------+-------------------------------+
|
||||
| 30 | doscribe |
|
||||
+-------+-------------------------------+
|
||||
| 30 | dobash |
|
||||
+-------+-------------------------------+
|
||||
| 30 | spellastralwalk |
|
||||
+-------+-------------------------------+
|
||||
| 31 | domistwalk |
|
||||
+-------+-------------------------------+
|
||||
| 32 | spell\ *ethereal*\ fist |
|
||||
+-------+-------------------------------+
|
||||
| 32 | spellknock |
|
||||
+-------+-------------------------------+
|
||||
| 33 | spellrecharge |
|
||||
+-------+-------------------------------+
|
||||
| 34 | spell\ *caustic*\ fount |
|
||||
+-------+-------------------------------+
|
||||
| 35 | spell\ *sacral*\ divinity |
|
||||
+-------+-------------------------------+
|
||||
| 35 | spell\ *plant*\ pass |
|
||||
+-------+-------------------------------+
|
||||
| 37 | spell\ *hand*\ ofchaos |
|
||||
+-------+-------------------------------+
|
||||
| 37 | spellacetumprimus |
|
||||
+-------+-------------------------------+
|
||||
| 39 | spellsolarflight |
|
||||
+-------+-------------------------------+
|
||||
| 41 | dobroach |
|
||||
+-------+-------------------------------+
|
||||
| 41 | spell\ *frost*\ breath |
|
||||
+-------+-------------------------------+
|
||||
| 42 | spell\ *helical*\ flow |
|
||||
+-------+-------------------------------+
|
||||
| 42 | spell\ *animate*\ dead |
|
||||
+-------+-------------------------------+
|
||||
| 42 | spell\ *lightning*\ breath |
|
||||
+-------+-------------------------------+
|
||||
| 43 | spell\ *acid*\ breath |
|
||||
+-------+-------------------------------+
|
||||
| 44 | spell\ *fire*\ breath |
|
||||
+-------+-------------------------------+
|
||||
| 45 | spell\ *gas*\ breath |
|
||||
+-------+-------------------------------+
|
||||
| 46 | spell\ *spiral*\ blast |
|
||||
+-------+-------------------------------+
|
||||
| 46 | spell\ *black*\ lightning |
|
||||
+-------+-------------------------------+
|
||||
| 48 | dostun |
|
||||
+-------+-------------------------------+
|
||||
| 48 | spellquantumspike |
|
||||
+-------+-------------------------------+
|
||||
| 50 | dohitall |
|
||||
+-------+-------------------------------+
|
||||
| 51 | spellpossess |
|
||||
+-------+-------------------------------+
|
||||
| 51 | spellchangesex |
|
||||
+-------+-------------------------------+
|
||||
| 51 | spellgate |
|
||||
+-------+-------------------------------+
|
||||
| 51 | doslice |
|
||||
+-------+-------------------------------+
|
||||
| 51 | spellpolymorph |
|
||||
+-------+-------------------------------+
|
||||
| 51 | do\_berserk |
|
||||
+-------+-------------------------------+
|
||||
+-------+-----------------------------+
|
||||
| Lvl | Code Bit |
|
||||
+-------+-----------------------------+
|
||||
| 0 | spell\_disenchant\_weapon |
|
||||
+-------+-----------------------------+
|
||||
| 1 | spell\_cause\_light |
|
||||
+-------+-----------------------------+
|
||||
| 1 | do\_hide |
|
||||
+-------+-----------------------------+
|
||||
| 1 | spell\_ventriloquate |
|
||||
+-------+-----------------------------+
|
||||
| 1 | do\_cook |
|
||||
+-------+-----------------------------+
|
||||
| 1 | do\_climb |
|
||||
+-------+-----------------------------+
|
||||
| 1 | spell\_null |
|
||||
+-------+-----------------------------+
|
||||
| 1 | do\_pick |
|
||||
+-------+-----------------------------+
|
||||
| 1 | do\_steal |
|
||||
+-------+-----------------------------+
|
||||
| 1 | do\_backstab |
|
||||
+-------+-----------------------------+
|
||||
| 1 | spell\_smaug |
|
||||
+-------+-----------------------------+
|
||||
| 1 | do\_kick |
|
||||
+-------+-----------------------------+
|
||||
| 2 | do\_dig |
|
||||
+-------+-----------------------------+
|
||||
| 2 | do\_mount |
|
||||
+-------+-----------------------------+
|
||||
| 2 | spell\_faerie\_fire |
|
||||
+-------+-----------------------------+
|
||||
| 2 | spell\_create\_food |
|
||||
+-------+-----------------------------+
|
||||
| 2 | spell\_create\_water |
|
||||
+-------+-----------------------------+
|
||||
| 2 | spell\_weaken |
|
||||
+-------+-----------------------------+
|
||||
| 2 | spell\_black\_hand |
|
||||
+-------+-----------------------------+
|
||||
| 3 | do\_scan |
|
||||
+-------+-----------------------------+
|
||||
| 3 | do\_search |
|
||||
+-------+-----------------------------+
|
||||
| 3 | do\_feed |
|
||||
+-------+-----------------------------+
|
||||
| 3 | spell\_chill\_touch |
|
||||
+-------+-----------------------------+
|
||||
| 4 | do\_rescue |
|
||||
+-------+-----------------------------+
|
||||
| 4 | spell\_cure\_blindness |
|
||||
+-------+-----------------------------+
|
||||
| 4 | spell\_invis |
|
||||
+-------+-----------------------------+
|
||||
| 4 | do\_aid |
|
||||
+-------+-----------------------------+
|
||||
| 4 | spell\_galvanic\_whip |
|
||||
+-------+-----------------------------+
|
||||
| 5 | spell\_blindness |
|
||||
+-------+-----------------------------+
|
||||
| 5 | spell\_cause\_serious |
|
||||
+-------+-----------------------------+
|
||||
| 5 | spell\_detect\_poison |
|
||||
+-------+-----------------------------+
|
||||
| 5 | spell\_burning\_hands |
|
||||
+-------+-----------------------------+
|
||||
| 5 | spell\_know\_alignment |
|
||||
+-------+-----------------------------+
|
||||
| 6 | spell\_locate\_object |
|
||||
+-------+-----------------------------+
|
||||
| 6 | do\_track |
|
||||
+-------+-----------------------------+
|
||||
| 6 | spell\_remove\_invis |
|
||||
+-------+-----------------------------+
|
||||
| 6 | spell\_poison |
|
||||
+-------+-----------------------------+
|
||||
| 7 | spell\_earthquake |
|
||||
+-------+-----------------------------+
|
||||
| 7 | spell\_shocking\_grasp |
|
||||
+-------+-----------------------------+
|
||||
| 8 | spell\_teleport |
|
||||
+-------+-----------------------------+
|
||||
| 8 | do\_bashdoor |
|
||||
+-------+-----------------------------+
|
||||
| 8 | spell\_summon |
|
||||
+-------+-----------------------------+
|
||||
| 8 | spell\_cure\_poison |
|
||||
+-------+-----------------------------+
|
||||
| 8 | spell\_disruption |
|
||||
+-------+-----------------------------+
|
||||
| 9 | spell\_bethsaidean\_touch |
|
||||
+-------+-----------------------------+
|
||||
| 9 | spell\_cause\_critical |
|
||||
+-------+-----------------------------+
|
||||
| 9 | spell\_lightning\_bolt |
|
||||
+-------+-----------------------------+
|
||||
| 10 | spell\_identify |
|
||||
+-------+-----------------------------+
|
||||
| 10 | spell\_faerie\_fog |
|
||||
+-------+-----------------------------+
|
||||
| 10 | spell\_control\_weather |
|
||||
+-------+-----------------------------+
|
||||
| 10 | spell\_dispel\_evil |
|
||||
+-------+-----------------------------+
|
||||
| 10 | do\_disarm |
|
||||
+-------+-----------------------------+
|
||||
| 11 | spell\_colour\_spray |
|
||||
+-------+-----------------------------+
|
||||
| 11 | do\_bite |
|
||||
+-------+-----------------------------+
|
||||
| 11 | spell\_dispel\_magic |
|
||||
+-------+-----------------------------+
|
||||
| 11 | do\_bloodlet |
|
||||
+-------+-----------------------------+
|
||||
| 12 | spell\_sleep |
|
||||
+-------+-----------------------------+
|
||||
| 12 | spell\_curse |
|
||||
+-------+-----------------------------+
|
||||
| 12 | spell\_call\_lightning |
|
||||
+-------+-----------------------------+
|
||||
| 12 | spell\_remove\_curse |
|
||||
+-------+-----------------------------+
|
||||
| 12 | spell\_enchant\_weapon |
|
||||
+-------+-----------------------------+
|
||||
| 12 | spell\_word\_of\_recall |
|
||||
+-------+-----------------------------+
|
||||
| 13 | spell\_harm |
|
||||
+-------+-----------------------------+
|
||||
| 13 | spell\_fireball |
|
||||
+-------+-----------------------------+
|
||||
| 13 | spell\_expurgation |
|
||||
+-------+-----------------------------+
|
||||
| 13 | spell\_flamestrike |
|
||||
+-------+-----------------------------+
|
||||
| 13 | spell\_midas\_touch |
|
||||
+-------+-----------------------------+
|
||||
| 13 | spell\_energy\_drain |
|
||||
+-------+-----------------------------+
|
||||
| 14 | spell\_spectral\_furor |
|
||||
+-------+-----------------------------+
|
||||
| 14 | spell\_charm\_person |
|
||||
+-------+-----------------------------+
|
||||
| 15 | spell\_remove\_trap |
|
||||
+-------+-----------------------------+
|
||||
| 16 | spell\_farsight |
|
||||
+-------+-----------------------------+
|
||||
| 16 | do\_detrap |
|
||||
+-------+-----------------------------+
|
||||
| 17 | spell\_transport |
|
||||
+-------+-----------------------------+
|
||||
| 17 | spell\_dream |
|
||||
+-------+-----------------------------+
|
||||
| 18 | spell\_sulfurous\_spray |
|
||||
+-------+-----------------------------+
|
||||
| 18 | spell\_pass\_door |
|
||||
+-------+-----------------------------+
|
||||
| 19 | spell\_sonic\_resonance |
|
||||
+-------+-----------------------------+
|
||||
| 20 | do\_gouge |
|
||||
+-------+-----------------------------+
|
||||
| 20 | spell\_acid\_blast |
|
||||
+-------+-----------------------------+
|
||||
| 21 | spell\_portal |
|
||||
+-------+-----------------------------+
|
||||
| 23 | spell\_black\_fist |
|
||||
+-------+-----------------------------+
|
||||
| 25 | do\_punch |
|
||||
+-------+-----------------------------+
|
||||
| 25 | do\_circle |
|
||||
+-------+-----------------------------+
|
||||
| 25 | do\_brew |
|
||||
+-------+-----------------------------+
|
||||
| 27 | spell\_magnetic\_thrust |
|
||||
+-------+-----------------------------+
|
||||
| 27 | do\_poison\_weapon |
|
||||
+-------+-----------------------------+
|
||||
| 28 | spell\_scorching\_surge |
|
||||
+-------+-----------------------------+
|
||||
| 30 | do\_scribe |
|
||||
+-------+-----------------------------+
|
||||
| 30 | do\_bash |
|
||||
+-------+-----------------------------+
|
||||
| 30 | spell\_astral\_walk |
|
||||
+-------+-----------------------------+
|
||||
| 31 | do\_mistwalk |
|
||||
+-------+-----------------------------+
|
||||
| 32 | spell\_ethereal\_fist |
|
||||
+-------+-----------------------------+
|
||||
| 32 | spell\_knock |
|
||||
+-------+-----------------------------+
|
||||
| 33 | spell\_recharge |
|
||||
+-------+-----------------------------+
|
||||
| 34 | spell\_caustic\_fount |
|
||||
+-------+-----------------------------+
|
||||
| 35 | spell\_sacral\_divinity |
|
||||
+-------+-----------------------------+
|
||||
| 35 | spell\_plant\_pass |
|
||||
+-------+-----------------------------+
|
||||
| 37 | spell\_hand\_of\_chaos |
|
||||
+-------+-----------------------------+
|
||||
| 37 | spell\_acetum\_primus |
|
||||
+-------+-----------------------------+
|
||||
| 39 | spell\_solar\_flight |
|
||||
+-------+-----------------------------+
|
||||
| 41 | do\_broach |
|
||||
+-------+-----------------------------+
|
||||
| 41 | spell\_frost\_breath |
|
||||
+-------+-----------------------------+
|
||||
| 42 | spell\_helical\_flow |
|
||||
+-------+-----------------------------+
|
||||
| 42 | spell\_animate\_dead |
|
||||
+-------+-----------------------------+
|
||||
| 42 | spell\_lightning\_breath |
|
||||
+-------+-----------------------------+
|
||||
| 43 | spell\_acid\_breath |
|
||||
+-------+-----------------------------+
|
||||
| 44 | spell\_fire\_breath |
|
||||
+-------+-----------------------------+
|
||||
| 45 | spell\_gas\_breath |
|
||||
+-------+-----------------------------+
|
||||
| 46 | spell\_spiral\_blast |
|
||||
+-------+-----------------------------+
|
||||
| 46 | spell\_black\_lightning |
|
||||
+-------+-----------------------------+
|
||||
| 48 | do\_stun |
|
||||
+-------+-----------------------------+
|
||||
| 48 | spell\_quantum\_spike |
|
||||
+-------+-----------------------------+
|
||||
| 50 | do\_hitall |
|
||||
+-------+-----------------------------+
|
||||
| 51 | spell\_possess |
|
||||
+-------+-----------------------------+
|
||||
| 51 | spell\_change\_sex |
|
||||
+-------+-----------------------------+
|
||||
| 51 | spell\_gate |
|
||||
+-------+-----------------------------+
|
||||
| 51 | do\_slice |
|
||||
+-------+-----------------------------+
|
||||
| 51 | spell\_polymorph |
|
||||
+-------+-----------------------------+
|
||||
| 51 | do\_berserk |
|
||||
+-------+-----------------------------+
|
||||
|
||||
+ the affects they apply float, sneak, hide, detect invisibility, detect
|
||||
magic, detect evil, invisibility
|
||||
( + the affects they apply float, sneak, hide, detect invisibility,
|
||||
detect magic, detect evil, invisibility)
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
Index: wiki2html/lib/wiki_syntax.rb
|
||||
===================================================================
|
||||
--- wiki2html/lib/wiki_syntax.rb (revision 1961)
|
||||
+++ wiki2html/lib/wiki_syntax.rb (working copy)
|
||||
@@ -227,7 +227,8 @@
|
||||
block
|
||||
else
|
||||
# remove newlines within normal (non-code) blocks of text
|
||||
- "<p>" + block.gsub(/\n/, ' ') + "</p>"
|
||||
+ #"<p>" + block.gsub(/\n/, ' ') + "</p>"
|
||||
+ "<p>" + block + "</p>"
|
||||
end
|
||||
end.join
|
||||
end
|
||||
|
|
@ -1,49 +1,30 @@
|
|||
#! /usr/bin/python
|
||||
#! /usr/bin/python
|
||||
#
|
||||
# Converts Evennia's google-style wiki pages to reST documents
|
||||
#
|
||||
# Setting up to run:
|
||||
# Setting up to run:
|
||||
#
|
||||
# 1) From this directory, use SVN to download wiki2html converter by Chris Roos. Make sure
|
||||
# to download into a directory "wiki2html" like this:
|
||||
#
|
||||
# svn co http://chrisroos.googlecode.com/svn/trunk/google-wiki-syntax wiki2html
|
||||
#
|
||||
# This is a Ruby program! Sorry, couldn't find a Python lib to do this. So if you
|
||||
# don't have Ruby, you need to install that too.
|
||||
#
|
||||
# You also need to patch a bug in above program to make multiline code snippets work.
|
||||
# From the same folder as the patch file, apply the patch like this:
|
||||
#
|
||||
# patch -p0 -i wiki2html.patch
|
||||
#
|
||||
# 2) Install pandoc (converts from html to reST):
|
||||
# 1) Install pandoc (converts from html to reST):
|
||||
#
|
||||
# apt-get install pandoc (debian)
|
||||
# or download from
|
||||
# or download from
|
||||
# http://johnmacfarlane.net/pandoc/
|
||||
#
|
||||
# 3) Retrieve wiki files (*.wiki) from Google code by mercurial. Make sure
|
||||
# to retrieve them into a directory wikiconvert/wiki:
|
||||
# 2) Retrieve wiki files (*.wiki) from Google code by mercurial. Make sure
|
||||
# to retrieve them into a subdirectory wiki here:
|
||||
#
|
||||
# hg clone https://code.google.com/p/evennia.wiki wiki
|
||||
#
|
||||
# 4) Check so that you have the following file structure:
|
||||
# Regular Usage:
|
||||
#
|
||||
# wiki/ (containing google code wiki files)
|
||||
# wiki2html/ (containing the wiki_converter.rb ruby program (patch applied).)
|
||||
# html/ (empty)
|
||||
# rest/ (empty)
|
||||
# (this file)
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# 1) Pull the wiki files into wiki/ so you have the latest.
|
||||
# 2) Run wiki2rest.py. Folders html and rest will end up containing the conversions and the contents
|
||||
# of rest/ will automatically be copied over to docs/sphinx/source/wiki.
|
||||
# 1) Make sure to pull/update the wiki files into wiki/ so you have the latest.
|
||||
# 2) Run wiki2rest.py. Temporary work folders html and rest will be created, so make sure you
|
||||
# have the rights to create directories here. The contents
|
||||
# of rest/ will automatically be copied over to docs/sphinx/source/wiki.
|
||||
# 3) From docs/sphinx, run e.g. "make html" to build the documentation from the reST sources.
|
||||
#
|
||||
|
||||
import sys, os, subprocess, re, urllib
|
||||
import sys, os, subprocess, re, urllib, shutil
|
||||
|
||||
# Setup
|
||||
|
||||
|
|
@ -69,7 +50,7 @@ WIKI_CRUMB_URL = "/p/evennia/wiki/"
|
|||
NO_CONVERT = ["SideBar", "Screenshot"]
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
#------------------------------------------------------------
|
||||
# This is a version of the importer that imports Google html pages
|
||||
# directly instead of going through the ruby converter. Alas, while
|
||||
# being a lot cleaner in implementation, this seems to produce worse
|
||||
|
|
@ -81,11 +62,11 @@ NO_CONVERT = ["SideBar", "Screenshot"]
|
|||
|
||||
def fetch_google_wiki_html_files():
|
||||
"""
|
||||
Acquire wiki html pages from google code
|
||||
Acquire wiki html pages from google code
|
||||
"""
|
||||
# use wiki repo to find html filenames
|
||||
html_urls = dict([(re.sub(r"\.wiki", "", fn), WIKI_ROOT_URL + re.sub(r"\.wiki", "?show=content", fn))
|
||||
for fn in os.listdir(WIKI_DIR) if fn.endswith(".wiki")])
|
||||
for fn in os.listdir(WIKI_DIR) if fn.endswith(".wiki")])
|
||||
|
||||
#html_urls = {"Index":html_urls["Index"]} #SR!
|
||||
|
||||
|
|
@ -102,30 +83,30 @@ def fetch_google_wiki_html_files():
|
|||
f = open(os.path.join(HTML_DIR, "%s.html" % name), 'w')
|
||||
f.write(s)
|
||||
f.close()
|
||||
|
||||
return html_pages
|
||||
|
||||
return html_pages
|
||||
|
||||
def clean_html(htmlstring):
|
||||
"""
|
||||
Clean up html properties special to google code and not known by pandoc
|
||||
Clean up html properties special to google code and not known by pandoc
|
||||
"""
|
||||
# remove wikiheader tag (searches over many lines). Unfortunately python <2.7 don't support
|
||||
# DOTALL flag in re.sub ...
|
||||
# remove wikiheader tag (searches over many lines). Unfortunately python <2.7 don't support
|
||||
# DOTALL flag in re.sub ...
|
||||
matches = re.findall(r'<div id="wikiheader">.*?</div>.*?</div>.*?</div>', htmlstring, re.DOTALL)
|
||||
for match in matches:
|
||||
for match in matches:
|
||||
htmlstring = htmlstring.replace(match, "")
|
||||
#htmlstring = re.sub(r'<div id="wikiheader">.*?</div>.*?</div>.*?</div>', "", htmlstring, re.DOTALL)
|
||||
# remove prefix from urls
|
||||
# remove prefix from urls
|
||||
htmlstring = re.sub('href="' + WIKI_CRUMB_URL, 'href="', htmlstring)
|
||||
# remove #links from headers
|
||||
htmlstring = re.sub(r'(<h[0-9]>.*?)(<a href="#.*?</a>)(.*?</h[0-9]>)', r"\1\3", htmlstring)
|
||||
# remove #links from headers
|
||||
htmlstring = re.sub(r'(<h[0-9]>.*?)(<a href="#.*?</a>)(.*?</h[0-9]>)', r"\1\3", htmlstring)
|
||||
return htmlstring
|
||||
|
||||
def html2rest(name, htmlstring):
|
||||
"""
|
||||
Convert html data to reST with pandoc
|
||||
Convert html data to reST with pandoc
|
||||
"""
|
||||
print "pandoc: Converting %s ..." % name
|
||||
print " pandoc: Converting %s ..." % name
|
||||
p = subprocess.Popen([PANDOC_EXE, '--from=html', '--to=rst', '--reference-links'],
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
return p.communicate(htmlstring)[0]
|
||||
|
|
@ -133,18 +114,18 @@ def html2rest(name, htmlstring):
|
|||
|
||||
def wiki2rest_ver2():
|
||||
"""
|
||||
Convert Google wiki pages to reST.
|
||||
Convert Google wiki pages to reST.
|
||||
"""
|
||||
# obtain all html data from google code
|
||||
# obtain all html data from google code
|
||||
html_pages = fetch_google_wiki_html_files()
|
||||
|
||||
|
||||
# convert to output files
|
||||
for name, htmldata in html_pages.items():
|
||||
restfilename = os.path.join(REST_DIR, "%s.rst" % name)
|
||||
f = open(restfilename, 'w')
|
||||
f.write(html2rest(name, htmldata))
|
||||
f.close()
|
||||
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# This converter uses the 3rd party ruby script to convert wiki pages
|
||||
|
|
@ -154,55 +135,74 @@ def wiki2rest_ver2():
|
|||
|
||||
def wiki2rest():
|
||||
"""
|
||||
Convert from wikifile to rst file, going through html
|
||||
"""
|
||||
Convert from wikifile to rst file, going through html
|
||||
"""
|
||||
|
||||
# convert from wikifile to html with wiki2html
|
||||
subprocess.call([RUBY_EXE, "wiki_convertor.rb", WIKI_DIR, HTML_DIR], cwd=WIKI2HTML_DIR)
|
||||
#subprocess.call([RUBY_EXE, "wiki_convertor.rb", WIKI_DIR, HTML_DIR], cwd=WIKI2HTML_DIR)
|
||||
# use google html output directly (really bad)
|
||||
#subprocess.call(["python", "get_wiki_as_html.py"])
|
||||
# use wikify importer
|
||||
print " wikify: converting wiki -> html ..."
|
||||
subprocess.call(["python", "wikify.py", "-e", "-m", "-c", "-a", "-s", "wiki", "-d", "html"])
|
||||
|
||||
# convert from html to rest with pandoc
|
||||
htmlfilenames = [fn for fn in os.listdir(HTML_DIR)
|
||||
htmlfilenames = [fn for fn in os.listdir(HTML_DIR)
|
||||
if fn.endswith(".html") and not re.sub(r".html", "", fn) in NO_CONVERT]
|
||||
|
||||
for filename in htmlfilenames:
|
||||
print " pandoc: converting html -> ReST ..."
|
||||
for filename in htmlfilenames:
|
||||
|
||||
htmlfilename = os.path.join(HTML_DIR, filename)
|
||||
|
||||
# cleanup of code
|
||||
string = "".join(open(htmlfilename, 'r').readlines())
|
||||
# cleanup of code
|
||||
string = "".join(open(htmlfilename, 'r').readlines())
|
||||
string = re.sub(r'<p class="summary">[A-Za-z0-9 .-\:]*</p>', "", string)
|
||||
string = re.sub(r"<wiki:toc max_depth="[0-9]*" />", "", string)
|
||||
string = re.sub(r"<wiki:toc max_depth<h1>"[0-9]*" /></h1>", "", string)
|
||||
string = re.sub(r"<wiki:toc max_depth="[0-9]*" />", "", string)
|
||||
string = re.sub(r"<wiki:toc max_depth<h1>"[0-9]*" /></h1>", "", string)
|
||||
string = re.sub(r"<p>#settings Featured</p>", "", string)
|
||||
string = re.sub(r'<p class="labels">Featured</p>', "", string)
|
||||
string = re.sub(r'<wiki:comment>', "", string)
|
||||
string = re.sub(r'</wiki:comment>', "", string)
|
||||
#string = re.sub(r'<wiki:comment>[<>;a-zA\/\n-&Z0-9 ]*</wiki:comment>', "", string)
|
||||
string = re.sub(r'<wiki:comment>[<>;a-zA\/\n-&Z0-9 ]*</wiki:comment>', "", string)
|
||||
f = open(htmlfilename, 'w')
|
||||
f.write(string)
|
||||
f.close()
|
||||
|
||||
rstfilename = os.path.join(REST_DIR, re.sub(r".html$", ".rst", filename))
|
||||
print "pandoc: converting %s -> %s" % (htmlfilename, rstfilename)
|
||||
#print "pandoc: converting %s -> %s" % (htmlfilename, rstfilename)
|
||||
subprocess.call([PANDOC_EXE, "--from=html", "--to=rst", "-o", rstfilename, htmlfilename])
|
||||
|
||||
|
||||
# main program
|
||||
# main program
|
||||
if __name__ == "__main__":
|
||||
|
||||
print "creating/cleaning output dirs ...",
|
||||
try:
|
||||
wiki2rest()
|
||||
shutil.rmtree(REST_DIR)
|
||||
os.mkdir(REST_DIR)
|
||||
except OSError:
|
||||
os.mkdir(REST_DIR)
|
||||
try:
|
||||
shutil.rmtree(HTML_DIR)
|
||||
os.mkdir(HTML_DIR)
|
||||
except Exception:
|
||||
os.mkdir(HTML_DIR)
|
||||
try:
|
||||
shutil.rmtree(SPHINX_WIKI_DIR)
|
||||
except Exception:
|
||||
# this is created by copy mechanism.
|
||||
pass
|
||||
print "done."
|
||||
print "running conversions ..."
|
||||
|
||||
try:
|
||||
wiki2rest()
|
||||
except Exception, e:
|
||||
print e
|
||||
print "Make sure to read this file's header to make sure everything is correctly set up. "
|
||||
sys.exit()
|
||||
|
||||
import shutil
|
||||
try:
|
||||
shutil.rmtree(SPHINX_WIKI_DIR)
|
||||
print "Deleted old %s." % SPHINX_WIKI_DIR
|
||||
except OSError:
|
||||
pass
|
||||
print "Copying %s -> %s" % (REST_DIR, SPHINX_WIKI_DIR)
|
||||
print "... conversions finished (make sure there are no error messages above)."
|
||||
print "copying rest data to %s ..." % SPHINX_WIKI_DIR
|
||||
shutil.copytree(REST_DIR, SPHINX_WIKI_DIR)
|
||||
|
||||
print "... done. You can now build the docs from the sphinx directory with e.g. 'make html'."
|
||||
|
|
|
|||
962
docs/sphinx/wiki2rest/wikify.py
Normal file
962
docs/sphinx/wiki2rest/wikify.py
Normal file
|
|
@ -0,0 +1,962 @@
|
|||
#!/usr/bin/python
|
||||
#
|
||||
# wikify.py - Convert from wikitext to HTML
|
||||
# Based on large portions of JeremyRuston's TiddlyWiki JS Wikifier
|
||||
# Changed to GoogleCode wiki syntax, python by Michael Crawford <mike@dataunity.com>
|
||||
""" Convert wikitext to HTML """
|
||||
|
||||
# Jeremy's license:
|
||||
# Copyright (c) UnaMesa Association 2004-2007
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice, this
|
||||
# list of conditions and the following disclaimer in the documentation and/or other
|
||||
# materials provided with the distribution.
|
||||
#
|
||||
# Neither the name of the UnaMesa Association nor the names of its contributors may be
|
||||
# used to endorse or promote products derived from this software without specific
|
||||
# prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
# My license:
|
||||
# Copyright (c) Data Unity 2007
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice, this
|
||||
# list of conditions and the following disclaimer in the documentation and/or other
|
||||
# materials provided with the distribution.
|
||||
#
|
||||
# Neither the name of the Data Unity nor the names of its contributors may be
|
||||
# used to endorse or promote products derived from this software without
|
||||
# specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import re, os, os.path, htmlentitydefs, urllib
|
||||
|
||||
|
||||
class _HTML:
|
||||
""" An HTML node factory factory. """
|
||||
|
||||
class Node:
|
||||
""" An HTML element. """
|
||||
def __init__(self, parent, tagname, text="", attribs={}, empty=False, **kwargs):
|
||||
self.tagname = tagname
|
||||
self.attribs = dict(attribs)
|
||||
self.children = list()
|
||||
self.empty = empty
|
||||
if text != "":
|
||||
self.appendText(text)
|
||||
if parent is not None:
|
||||
parent.children.append(self)
|
||||
self.parent = parent
|
||||
|
||||
def appendText(self, text):
|
||||
if text == "": return
|
||||
_HTML.Text(self, text)
|
||||
|
||||
def __str__(self):
|
||||
attrs = " ".join([ '%s="%s"' % i for i in self.attribs.iteritems() ])
|
||||
if attrs: attrs = " " + attrs
|
||||
if self.empty:
|
||||
return "<%s%s/>" % (self.tagname, attrs)
|
||||
|
||||
children = "".join([str(c) for c in self.children])
|
||||
return "<%s%s>%s</%s>" % (self.tagname, attrs, children, self.tagname)
|
||||
|
||||
def isInside(self, tagname):
|
||||
k = self
|
||||
while k is not None:
|
||||
if k.tagname == tagname:
|
||||
return True
|
||||
k = k.parent
|
||||
return False
|
||||
|
||||
class Text:
|
||||
""" Simple text node. """
|
||||
entities = [ (k,v)
|
||||
for k,v in htmlentitydefs.entitydefs.iteritems()
|
||||
if k != "amp" and k[0] != "#" ]
|
||||
|
||||
def __init__(self, parent, text=""):
|
||||
self.text = self._clean(text)
|
||||
if parent is not None:
|
||||
parent.children.append(self)
|
||||
|
||||
def _clean(self, text):
|
||||
text = text.replace("&", "&")
|
||||
for k,v in self.entities:
|
||||
text = text.replace(v, "&%s;" % k)
|
||||
return text
|
||||
|
||||
def __str__(self):
|
||||
return self.text
|
||||
|
||||
|
||||
def __getattr__(self, attr):
|
||||
""" Return an element constructor using the attribute as the tagname """
|
||||
def factory(parent=None, **kwargs):
|
||||
return self.Node(parent, attr, **kwargs)
|
||||
return factory
|
||||
|
||||
HTML = _HTML()
|
||||
|
||||
URLSTR = r"(?:file|http|https|mailto|ftp|irc|news|data):[^\s'\"]+(?:/|\b)"
|
||||
URL = re.compile(URLSTR, re.M)
|
||||
IMGURLSTR = r".+((\.[Pp][Nn][Gg])|(\.[Gg][Ii][Ff])|(\.[Jj][Pp][Ee]?[Gg]))"
|
||||
IMGURL = re.compile(IMGURLSTR, re.M)
|
||||
YOUTUBESTR = r"http://www.youtube.com/watch\?v=([A-Za-z0-9_-]+)"
|
||||
YOUTUBEURL = re.compile(YOUTUBESTR, re.M)
|
||||
YOUTUBEREPL = r'<object width="425" height="355"><param name="movie" value="http://www.youtube.com/v/%s&rel=1"></param><param name="wmode" value="transparent"></param><embed src="http://www.youtube.com/v/hQPHf_8J8Eg&rel=1" type="application/x-shockwave-flash" wmode="transparent" width="425" height="355"></embed></object>'
|
||||
VIDEOURLSTR = r".+((\.[Aa][Vv][Ii])|(\.[Mm][Oo][Vv])|(\.[Mm][Pp][Ee]?[Gg]))"
|
||||
VIDEOURL = re.compile(VIDEOURLSTR, re.M)
|
||||
VIDEOREPL = r'<embed src = "%s" width="400" height="350" hidden=false autostart=true loop=1>'
|
||||
CODEURLSTR = r"http://([^\.]+).googlecode.com/svn/trunk/([^#]+)#((?:(?:(?:[\d]+)?\-)?[\d]+)|(?:[\d]+\-?))((?:\:(?:[\:]|[^\W])+))?"
|
||||
CODEURL = re.compile(CODEURLSTR, re.M)
|
||||
CODEREPL = r'<a href="%(url)s">svn://%(site)s/trunk/%(file)s</a><pre name="code" class="%(class)s">%(lines)s</pre>'
|
||||
|
||||
def GoogleCode_ReadSVNFile(wikifier, domain, path, start, end):
|
||||
""" Try to read a file from subversion for inclusion in the wiki. """
|
||||
|
||||
gcurl = "http://%s.googlecode.com/svn/trunk/%s" % (domain,path)
|
||||
fdata = urllib.urlopen(gcurl).readlines()
|
||||
return gcurl, fdata[start-1:end]
|
||||
|
||||
|
||||
def GoogleCode_IsExternalLink(wikifier, link):
|
||||
""" See if the link points outside of the wiki. """
|
||||
|
||||
if GoogleCode_Exists(wikifier, link):
|
||||
return False;
|
||||
|
||||
if URL.match(link):
|
||||
return True
|
||||
|
||||
if '.' in link or '\\' in link or '/' in link or '#' in link:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def GoogleCode_Exists(wikifier, wikipage):
|
||||
""" See if a wiki page exists inside this wiki. """
|
||||
path = os.path.join(wikifier.srcdir, "%s.wiki" % wikipage)
|
||||
if os.path.exists(path):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def GoogleCode_Heading(wikifier, termRegExp=None, **kwargs):
|
||||
termMatch = termRegExp.search(wikifier.source, wikifier.nextMatch)
|
||||
if termMatch is None: return
|
||||
if (len(wikifier.output.children) and
|
||||
"br" == getattr(wikifier.output.children[-1], 'tagname', '')):
|
||||
wikifier.output.children.pop(-1)
|
||||
if (len(wikifier.output.children) and
|
||||
"br" == getattr(wikifier.output.children[-1], 'tagname', '')):
|
||||
wikifier.output.children.pop(-1)
|
||||
output = HTML.Node(wikifier.output, "h%i" % wikifier.matchLength)
|
||||
wikifier.outputText(output, wikifier.nextMatch, termMatch.start())
|
||||
wikifier.nextMatch = termMatch.end()
|
||||
|
||||
def GoogleCode_SimpleElement(wikifier, termRegExp=None, tagName=None, **kwargs):
|
||||
if wikifier.output.isInside(tagName):
|
||||
wikifier.outputText(wikifier.output, wikifier.matchStart, wikifier.nextMatch)
|
||||
return
|
||||
elif wikifier.source[wikifier.nextMatch-1] == "_":
|
||||
wikifier.outputText(wikifier.output, wikifier.matchStart, wikifier.nextMatch-1)
|
||||
|
||||
if termRegExp.search(wikifier.source, wikifier.nextMatch) is None: return
|
||||
output = HTML.Node(wikifier.output, tagName, **kwargs)
|
||||
wikifier.subWikifyTerm(output, termRegExp)
|
||||
#if wikifier.source[wikifer.nextMatch-2] == "_":
|
||||
# wikifier.nextMatch -= 1
|
||||
|
||||
def GoogleCode_Blockquote(wikifier, termRegExp=None, **kwargs):
|
||||
sibs = wikifier.output.children
|
||||
if len(sibs) and getattr(sibs[-1], 'tagname', None) == "blockquote":
|
||||
wikifier.subWikifyTerm(sibs[-1], termRegExp)
|
||||
else:
|
||||
output = HTML.blockquote(wikifier.output, **kwargs)
|
||||
wikifier.subWikifyTerm(output, termRegExp)
|
||||
|
||||
def GoogleCode_Codeblock(wikifier, tagName=None, termRegExp=None, initRegExp=None, **kwargs):
|
||||
if 'attribs' not in kwargs:
|
||||
kwargs['attribs'] = {}
|
||||
|
||||
kwargs['attribs']['name'] = 'code'
|
||||
if 'class' not in kwargs['attribs']:
|
||||
kwargs['attribs']['class'] = wikifier.defaultHiLang.lower()
|
||||
else:
|
||||
kwargs['attribs']['class'] += " " + wikifier.defaultHiLang.lower()
|
||||
|
||||
output = HTML.Node(wikifier.output, tagName, **kwargs)
|
||||
tcount = 1
|
||||
matchStart = wikifier.nextMatch
|
||||
# Find the matching terminator
|
||||
while tcount > 0:
|
||||
nextTermMatch = termRegExp.search(wikifier.source, wikifier.nextMatch)
|
||||
nextInitMatch = initRegExp.search(wikifier.source, wikifier.nextMatch)
|
||||
|
||||
if not nextTermMatch:
|
||||
# No terminator. Syntax error, just ignore it.
|
||||
matchEnd = matchStart
|
||||
tcount = 0
|
||||
break
|
||||
elif not nextInitMatch or nextTermMatch.start() <= nextInitMatch.start():
|
||||
# Terminator goes first.
|
||||
nextMatch = nextTermMatch
|
||||
tcount -= 1
|
||||
if tcount > 0:
|
||||
matchEnd = nextMatch.end()
|
||||
else:
|
||||
matchEnd = nextMatch.start()
|
||||
else:
|
||||
nextMatch = nextInitMatch
|
||||
tcount += 1
|
||||
matchEnd = nextMatch.end()
|
||||
|
||||
wikifier.nextMatch = nextMatch.end()
|
||||
|
||||
# Copy the content
|
||||
wikifier.outputText(output, matchStart, matchEnd)
|
||||
|
||||
if "\n" not in wikifier.source[matchStart:matchEnd]:
|
||||
output.tagname = "code"
|
||||
|
||||
def GoogleCode_WikiWord(wikifier, **kwargs):
|
||||
if wikifier.matchStart > 0:
|
||||
# Make sure we're at the start of a word?
|
||||
preRegExp = re.compile("[!A-Za-z0-9]", re.M)
|
||||
preMatch = preRegExp.search(wikifier.source, wikifier.matchStart-1)
|
||||
if (preMatch is not None and
|
||||
preMatch.start() == wikifier.matchStart-1):
|
||||
wikifier.outputText(wikifier.output,wikifier.matchStart,wikifier.nextMatch)
|
||||
return
|
||||
|
||||
if wikifier.source[wikifier.matchStart] == "!":
|
||||
wikifier.outputText(wikifier.output,wikifier.matchStart+1,wikifier.nextMatch)
|
||||
elif GoogleCode_Exists(wikifier, wikifier.matchText):
|
||||
# Full link, everybody sees it
|
||||
HTML.a(wikifier.output, text=wikifier.matchText, attribs={"href": wikifier.matchText + wikifier.suffix})
|
||||
elif wikifier.autolink:
|
||||
# Partial link - only authorized users
|
||||
wikifier.outputText(wikifier.output,wikifier.matchStart,wikifier.nextMatch)
|
||||
link = HTML.a(wikifier.output, text="?", attribs={"href": wikifier.matchText + wikifier.suffix})
|
||||
else:
|
||||
wikifier.outputText(wikifier.output,wikifier.matchStart,wikifier.nextMatch)
|
||||
|
||||
def GoogleCode_LineBreak(wikifier, **kwargs):
|
||||
sibs = wikifier.output.children
|
||||
if wikifier.multibreak:
|
||||
HTML.p(wikifier.output, **kwargs)
|
||||
elif len(sibs) and (not hasattr(sibs[-1], 'tagname') or
|
||||
sibs[-1].tagname == "img"):
|
||||
# Only after an inline or header block.
|
||||
HTML.p(wikifier.output, **kwargs)
|
||||
HTML.p(wikifier.output, **kwargs)
|
||||
|
||||
def GoogleCode_PrettyLink(wikifier, lookaheadRegExp=None, **kwargs):
|
||||
lookMatch = lookaheadRegExp.search(wikifier.source, wikifier.matchStart)
|
||||
if lookMatch and lookMatch.start() == wikifier.matchStart:
|
||||
text = lookMatch.group(1)
|
||||
if lookMatch.group(2):
|
||||
# Pretty bracketted link
|
||||
link = text
|
||||
text = lookMatch.group(2)
|
||||
if GoogleCode_IsExternalLink(wikifier, link):
|
||||
# External link
|
||||
attribs={"href":link, "target": "_blank" }
|
||||
else:
|
||||
# Internal link
|
||||
attribs={"href":link + wikifier.suffix}
|
||||
|
||||
e = HTML.a(wikifier.output, attribs=attribs)
|
||||
|
||||
if URL.match(text):
|
||||
HTML.img(e, attribs={'src':text,
|
||||
'border': '0'})
|
||||
HTML.br(wikifier.output)
|
||||
else:
|
||||
HTML.Text(e, text)
|
||||
else:
|
||||
if GoogleCode_IsExternalLink(wikifier, text):
|
||||
# External link
|
||||
attribs={"href":link, "target": "_blank" }
|
||||
else:
|
||||
# Internal link
|
||||
attribs={"href":text + wikifier.suffix}
|
||||
|
||||
# Simple bracketted link
|
||||
e = HTML.a(wikifier.output, text=text, attribs=attribs)
|
||||
wikifier.nextMatch = lookMatch.end()
|
||||
|
||||
def GoogleCode_UrlLink(wikifier, **kwargs):
|
||||
attribs = {"href": wikifier.matchText}
|
||||
if GoogleCode_IsExternalLink(wikifier, wikifier.matchText):
|
||||
attribs["target"] = "_blank"
|
||||
|
||||
if IMGURL.match(wikifier.matchText):
|
||||
HTML.img(wikifier.output, attribs={'src':wikifier.matchText})
|
||||
HTML.br(wikifier.output)
|
||||
elif YOUTUBEURL.match(wikifier.matchText):
|
||||
match = YOUTUBEURL.match(wikifier.matchText)
|
||||
# Raw html ;)
|
||||
wikifier.output.children.append(YOUTUBEREPL % match.group(1))
|
||||
elif VIDEOURL.match(wikifier.matchText):
|
||||
# Raw html ;)
|
||||
wikifier.output.children.append(VIDEOREPL % wikifier.matchText)
|
||||
elif CODEURL.match(wikifier.matchText):
|
||||
# Raw html ;)
|
||||
# http://([^\.]+).googlecode.com/svn/trunk/([^\#]+)#([^\:]+)(?:\:([^\W]+))?
|
||||
|
||||
codeMatch = CODEURL.match(wikifier.matchText)
|
||||
parts = { "class": (codeMatch.group(4) or "").lower()[1:],
|
||||
"file": codeMatch.group(2),
|
||||
"site": codeMatch.group(1)}
|
||||
|
||||
lines = codeMatch.group(3)
|
||||
if '-' in lines:
|
||||
lines = lines.split('-')
|
||||
lines[0] = int(lines[0])
|
||||
lines[1] = int(lines[1])
|
||||
else:
|
||||
lines = [int(lines), int(lines)]
|
||||
|
||||
parts['class'] += ":firstline[%i]" % lines[0]
|
||||
url, parts['lines'] = GoogleCode_ReadSVNFile(wikifier, parts['site'],
|
||||
parts['file'], *lines)
|
||||
parts['url'] = url
|
||||
parts['lines'] = "".join(parts['lines'])
|
||||
|
||||
wikifier.output.children.append(CODEREPL % parts)
|
||||
else:
|
||||
HTML.a(wikifier.output, text=wikifier.matchText, attribs=attribs)
|
||||
|
||||
|
||||
def GoogleCode_Table(wikifier, sepRegExp=None, termRegExp=None, **kwargs):
|
||||
sibs = wikifier.output.children
|
||||
if len(sibs) and getattr(sibs[-1], 'tagname', None) == "table":
|
||||
table = sibs[-1]
|
||||
else:
|
||||
table = HTML.table(wikifier.output)
|
||||
row = HTML.tr(table)
|
||||
|
||||
termMatch = termRegExp.search(wikifier.source, wikifier.matchStart)
|
||||
if termMatch is None:
|
||||
termEnd = termStart = len(wikifier.source)
|
||||
else:
|
||||
termStart, termEnd = termMatch.start(), termMatch.end()
|
||||
|
||||
# Skip over the leading separator
|
||||
sepMatch = sepRegExp.search(wikifier.source, wikifier.matchStart)
|
||||
wikifier.nextMatch = wikifier.matchStart = sepMatch.end()
|
||||
sepMatch = sepRegExp.search(wikifier.source, wikifier.matchStart)
|
||||
attribs = { "style": "border: 1px solid #aaa; padding: 5px;" }
|
||||
|
||||
while sepMatch and sepMatch.end() <= termStart:
|
||||
cell = HTML.td(row, attribs=attribs)
|
||||
wikifier.subWikifyTerm(cell, sepRegExp)
|
||||
wikifier.nextMatch = sepMatch.end()
|
||||
sepMatch = sepRegExp.search(wikifier.source, wikifier.nextMatch)
|
||||
|
||||
wikifier.nextMatch = termEnd
|
||||
|
||||
|
||||
def GoogleCode_List(wikifier, lookaheadRegExp=None, termRegExp=None, **kwargs):
|
||||
currLevel = 0
|
||||
currType = None
|
||||
stack = [wikifier.output]
|
||||
indents = [currLevel]
|
||||
wikifier.nextMatch = wikifier.matchStart
|
||||
|
||||
lookMatch = lookaheadRegExp.search(wikifier.source, wikifier.nextMatch)
|
||||
while lookMatch and lookMatch.start() == wikifier.nextMatch:
|
||||
# See what kind of list it is
|
||||
if lookMatch.group(1):
|
||||
listType = "ul"
|
||||
itemType = "li"
|
||||
elif lookMatch.group(2):
|
||||
listType = "ol"
|
||||
itemType = "li"
|
||||
|
||||
listLevel = len(lookMatch.group(0))
|
||||
wikifier.nextMatch += len(lookMatch.group(0))
|
||||
|
||||
# Check for any changes in list type or indentation
|
||||
if listLevel > currLevel:
|
||||
# Indent further
|
||||
indents.append(listLevel)
|
||||
if currLevel == 0:
|
||||
target = stack[-1]
|
||||
else:
|
||||
target = stack[-1].children[-1]
|
||||
|
||||
stack.append(HTML.Node(target, listType))
|
||||
|
||||
elif listLevel < currLevel:
|
||||
# Indent less
|
||||
while indents[-1] > listLevel:
|
||||
stack.pop(-1)
|
||||
indents.pop(-1)
|
||||
|
||||
elif listLevel == currLevel and listType != currType:
|
||||
# Same level, different kind of list
|
||||
stack.pop(-1)
|
||||
stack.append(HTML.Node(stack[-1].children[-1], listType))
|
||||
|
||||
currLevel = listLevel
|
||||
currType = listType
|
||||
|
||||
# Output the item
|
||||
output = HTML.Node(stack[-1],itemType)
|
||||
wikifier.subWikifyTerm(output,termRegExp)
|
||||
|
||||
# Roll again
|
||||
lookMatch = lookaheadRegExp.search(wikifier.source, wikifier.nextMatch)
|
||||
|
||||
|
||||
|
||||
GoogleCodeWikiFormat = [
|
||||
{
|
||||
"name": "tablerow",
|
||||
"match": r"^(?:\|\|.+\|\|)",
|
||||
"termRegExp": re.compile(r"(\n)", re.M),
|
||||
"sepRegExp": re.compile(r"(\|\|)", re.M),
|
||||
"handler": GoogleCode_Table
|
||||
},
|
||||
|
||||
{ "name": "heading",
|
||||
"match": r"^={1,6}",
|
||||
"termRegExp": re.compile(r"([=]+)", re.M),
|
||||
"handler": GoogleCode_Heading
|
||||
},
|
||||
|
||||
{ "name": "list",
|
||||
"match": r"^(?:[ ]+)(?:[\*#])",
|
||||
"lookaheadRegExp": re.compile(r"^(?:[ ]+)(?:(\*)|(#))",re.M),
|
||||
"termRegExp": re.compile(r"(\n)", re.M),
|
||||
"handler": GoogleCode_List
|
||||
},
|
||||
|
||||
|
||||
|
||||
{ "name": "blockquote",
|
||||
"match": r"^(?:[ ]+)",
|
||||
"termRegExp": re.compile(r"(\n)", re.M),
|
||||
"handler": GoogleCode_Blockquote,
|
||||
"tagName": "blockquote"
|
||||
},
|
||||
|
||||
{ "name": "codeword",
|
||||
"match": r"\`",
|
||||
"initRegExp": re.compile(r"(\`)", re.M),
|
||||
"termRegExp": re.compile(r"(\`)", re.M),
|
||||
"handler": GoogleCode_Codeblock,
|
||||
"tagName": "code"
|
||||
},
|
||||
|
||||
{ "name": "codeblock",
|
||||
"match": r"\{\{\{",
|
||||
"initRegExp": re.compile(r"(\{\{\{)", re.M),
|
||||
"termRegExp": re.compile(r"(\}\}\})", re.M),
|
||||
"handler": GoogleCode_Codeblock,
|
||||
"tagName": "pre",
|
||||
"attribs": { "class": "codeblock" }
|
||||
},
|
||||
|
||||
{ "name": "bold",
|
||||
"match": r"[\*]",
|
||||
"termRegExp": re.compile(r"([\*])", re.M),
|
||||
"handler": GoogleCode_SimpleElement,
|
||||
"tagName": "b"
|
||||
},
|
||||
|
||||
{ "name": "italic",
|
||||
"match": r"(?:[^\w\b]|^)[\_]",
|
||||
"termRegExp": re.compile(r"([\_])[^\w\b]", re.M),
|
||||
"handler": GoogleCode_SimpleElement,
|
||||
"tagName": "i"
|
||||
},
|
||||
|
||||
{ "name": "strike",
|
||||
"match": r"\~\~",
|
||||
"termRegExp": re.compile(r"(\~\~)", re.M),
|
||||
"handler": GoogleCode_SimpleElement,
|
||||
"tagName": "strike"
|
||||
},
|
||||
|
||||
{ "name": "superscript",
|
||||
"match": r"\^",
|
||||
"termRegExp": re.compile(r"(\^)", re.M),
|
||||
"handler": GoogleCode_SimpleElement,
|
||||
"tagName": "sup"
|
||||
},
|
||||
|
||||
{ "name": "subscript",
|
||||
"match": r",,",
|
||||
"termRegExp": re.compile(r"(,,)", re.M),
|
||||
"handler": GoogleCode_SimpleElement,
|
||||
"tagName": "sub"
|
||||
},
|
||||
|
||||
{ "name": "prettyLink",
|
||||
"match": r"\[(?:(?:[A-Za-z][A-Za-z0-9\_\-]+)|(?:(?:file|http|https|mailto|ftp|irc|news|data):[^\s'\"]+(?:/|\b)))(?: .*?)?\]",
|
||||
"lookaheadRegExp": re.compile(r'\[(.*?)(?: (.*?))?\]', re.M),
|
||||
"handler": GoogleCode_PrettyLink
|
||||
},
|
||||
|
||||
{ "name": "wikiword",
|
||||
"match": r"(?:\!?(?:[A-Z]+[a-z]+[A-Z][A-Za-z]*)|(?:[A-Z]{2,}[a-z]+))",
|
||||
"handler": GoogleCode_WikiWord
|
||||
},
|
||||
|
||||
{ "name": "urlLink",
|
||||
"match": URLSTR,
|
||||
"handler": GoogleCode_UrlLink
|
||||
},
|
||||
|
||||
{ "name": "linebreak",
|
||||
"match": r"\n\n",
|
||||
"handler": GoogleCode_LineBreak,
|
||||
"empty": True
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
||||
class Wikifier:
|
||||
|
||||
def __init__(self, formatters, autolink=False, srcdir=os.getcwd(),
|
||||
multibreak=False, tabwidth=8, suffix=".html",
|
||||
hiLang="Python"):
|
||||
# Create the master regex
|
||||
forms = [ "(%s)" % r['match'] for r in formatters ]
|
||||
self.formatterRegExp = re.compile("|".join(forms), re.M)
|
||||
# Save the individual format handlers
|
||||
self.formatters = formatters
|
||||
self.autolink = autolink
|
||||
self.srcdir = srcdir
|
||||
self.multibreak = multibreak and True or False
|
||||
self.tabwidth = tabwidth
|
||||
self.suffix = suffix
|
||||
self.defaultHiLang = hiLang
|
||||
|
||||
def _clean(self, text):
|
||||
text = text.replace("\r\n", "\n")
|
||||
|
||||
# Out, out, damned tabs
|
||||
text = text.replace("\t", " " * self.tabwidth)
|
||||
|
||||
if not self.multibreak:
|
||||
# Remove redundant line breaks
|
||||
tlen = len(text) + 1
|
||||
while tlen > len(text):
|
||||
tlen = len(text)
|
||||
text = text.replace("\n\n\n", "\n\n")
|
||||
|
||||
while text.startswith("#"):
|
||||
# Process any wiki-headers
|
||||
line, text = text.split("\n", 1)
|
||||
self._header(line)
|
||||
|
||||
return text
|
||||
|
||||
def _header(self, line):
|
||||
tagname, content = line.split(" ", 1)
|
||||
if tagname == "#summary":
|
||||
self.summary = content
|
||||
elif tagname == "#labels":
|
||||
self.labels = tuple(content.split(","))
|
||||
|
||||
def wikify(self, source, labels=None, summary=None):
|
||||
self.labels = labels
|
||||
self.summary = summary
|
||||
# Clean up the content
|
||||
self.source = self._clean(source)
|
||||
self.nextMatch = 0
|
||||
# Do it
|
||||
self.output = HTML.div(None)
|
||||
self.subWikifyUnterm()
|
||||
|
||||
return "".join([str(c) for c in self.output.children])
|
||||
|
||||
def findMatch(self, source, start):
|
||||
return self.formatterRegExp.search(source, start)
|
||||
|
||||
def subWikifyUnterm(self, output=None):
|
||||
oldOutput = self.output
|
||||
if output is not None:
|
||||
self.output = output
|
||||
|
||||
match = self.findMatch(self.source, self.nextMatch)
|
||||
while match:
|
||||
# Output any text before the match
|
||||
if match.start() > self.nextMatch:
|
||||
self.outputText(self.output, self.nextMatch, match.start())
|
||||
|
||||
# Set the match parameters for the handler
|
||||
self.matchStart = match.start()
|
||||
self.matchLength = len(match.group(0))
|
||||
self.matchText = match.group(0)
|
||||
self.nextMatch = match.end()
|
||||
|
||||
# Figure out which sub-group matched (zero-indexed)
|
||||
t,submatch = [ (t,s) for t, s in enumerate(match.groups()) if s ][0]
|
||||
|
||||
# Handle it
|
||||
self.formatters[t]['handler'](self, **self.formatters[t])
|
||||
|
||||
# Go back for more matches
|
||||
match = self.findMatch(self.source, self.nextMatch)
|
||||
|
||||
if self.nextMatch < len(self.source):
|
||||
self.outputText(self.output, self.nextMatch, len(self.source))
|
||||
self.nextMatch = len(self.source)
|
||||
|
||||
# Restore the destination node
|
||||
self.output = oldOutput
|
||||
|
||||
def subWikifyTerm(self, output, termRegExp):
|
||||
oldOutput = self.output
|
||||
if output is not None:
|
||||
self.output = output
|
||||
|
||||
# Get the first matches for the formatter and terminator RegExps
|
||||
termMatch = termRegExp.search(self.source, self.nextMatch)
|
||||
if termMatch:
|
||||
match = self.findMatch(self.source[:termMatch.start()], self.nextMatch)
|
||||
else:
|
||||
match = self.findMatch(self.source, self.nextMatch)
|
||||
|
||||
while termMatch or match:
|
||||
# If the terminator comes before the next formatter match, we're done
|
||||
if termMatch and (not match or termMatch.start() <= match.start()):
|
||||
if termMatch.start() > self.nextMatch:
|
||||
self.outputText(self.output,self.nextMatch,termMatch.start())
|
||||
self.matchText = termMatch.group(1)
|
||||
self.matchLength = len(self.matchText)
|
||||
self.matchStart = termMatch.start()
|
||||
self.nextMatch = self.matchStart + self.matchLength
|
||||
self.output = oldOutput
|
||||
return
|
||||
|
||||
# Output any text before the match
|
||||
if match.start() > self.nextMatch:
|
||||
self.outputText(self.output, self.nextMatch, match.start())
|
||||
|
||||
# Set the match parameters for the handler
|
||||
self.matchStart = match.start()
|
||||
self.matchLength = len(match.group(0))
|
||||
self.matchText = match.group(0)
|
||||
self.nextMatch = match.end()
|
||||
|
||||
# Figure out which sub-group matched (zero-indexed)
|
||||
t,submatch = [ (t,s) for t, s in enumerate(match.groups()) if s ][0]
|
||||
|
||||
# Handle it
|
||||
self.formatters[t]['handler'](self, **self.formatters[t])
|
||||
|
||||
termMatch = termRegExp.search(self.source, self.nextMatch)
|
||||
if termMatch:
|
||||
match = self.findMatch(self.source[:termMatch.start()], self.nextMatch)
|
||||
else:
|
||||
match = self.findMatch(self.source, self.nextMatch)
|
||||
|
||||
if self.nextMatch < len(self.source):
|
||||
self.outputText(self.output, self.nextMatch,len(self.source))
|
||||
self.nextMatch = len(self.source)
|
||||
|
||||
self.output = oldOutput
|
||||
|
||||
|
||||
def outputText(self, output, startPos, endPos):
|
||||
HTML.Text(output, self.source[startPos:endPos])
|
||||
|
||||
|
||||
DEFAULT_TEMPLATE = '''
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<br style="clear: both" /><br/>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
%(toc)s
|
||||
</div>
|
||||
|
||||
<i>%(title)s</i>
|
||||
|
||||
<div class="summary">
|
||||
%(summary)s
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
%(wiki)s
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
DEFAULT_TEMPLATE = '''
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div class="summary">
|
||||
%(summary)s
|
||||
</div>
|
||||
<div class="narrow">
|
||||
%(wiki)s
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
|
||||
def wikify(pages, options=None):
|
||||
# See options definition below.
|
||||
# Pass any object with those (potential) attributes
|
||||
srcdir = getattr(options, 'srcdir', os.getcwd())
|
||||
destdir = getattr(options, 'destdir', None)
|
||||
|
||||
# Find all requested files
|
||||
onlyStale = False
|
||||
if getattr(options, 'all', False):
|
||||
pages = [ k for k in os.listdir(srcdir)
|
||||
if k.endswith(".wiki") ]
|
||||
onlyStale = True
|
||||
if destdir is None:
|
||||
destdir = os.getcwd()
|
||||
|
||||
# Create the magic 8-ball
|
||||
w = Wikifier(GoogleCodeWikiFormat,
|
||||
autolink=getattr(options, 'autolink', False),
|
||||
tabwidth=getattr(options, 'tabwidth', 8),
|
||||
multibreak=getattr(options, 'multibreak', False),
|
||||
srcdir=srcdir,
|
||||
suffix=".html")
|
||||
|
||||
rets = []
|
||||
for wikiname in pages:
|
||||
# Clean up the page name
|
||||
if wikiname.endswith(".wiki"):
|
||||
wikiname = wikiname[:-5]
|
||||
|
||||
wikifilename = os.path.join(srcdir, "%s.wiki" % wikiname)
|
||||
if onlyStale:
|
||||
# See if the output is fresh, and if so, skip it
|
||||
wikidestname = os.path.join(destdir, "%s.html" % wikiname)
|
||||
try:
|
||||
sstat = os.stat(wikifilename)
|
||||
except:
|
||||
continue
|
||||
try:
|
||||
dstat = os.stat(wikidestname)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
if dstat.st_mtime > sstat.st_mtime:
|
||||
continue
|
||||
|
||||
# Load the wiki content
|
||||
wikifilename = os.path.join(srcdir, "%s.wiki" % wikiname)
|
||||
wikisrc = file(wikifilename).read()
|
||||
|
||||
# Ask a question
|
||||
wikified = w.wikify(wikisrc)
|
||||
|
||||
reFind = re.compile(r'<h(\d)>\s*([^\<]*[\S])\s*</h\d>')
|
||||
strRepl = r'<h\g<1>><a name="\g<2>">\g<2></a></h\g<1>>'
|
||||
|
||||
# Number the sections
|
||||
if getattr(options, 'number', True):
|
||||
sectstack = []
|
||||
matches = []
|
||||
curLevel = 0
|
||||
match = reFind.search(wikified)
|
||||
while match is not None:
|
||||
level = int(match.group(1))
|
||||
|
||||
while level > len(sectstack):
|
||||
sectstack.append(1)
|
||||
|
||||
while len(sectstack) > level:
|
||||
sectstack.pop(-1)
|
||||
|
||||
if curLevel >= level:
|
||||
sectstack[-1] += 1
|
||||
curLevel = len(sectstack)
|
||||
|
||||
sectnum = ".".join([str(n) for n in sectstack]) + "."
|
||||
matches.append((sectnum, match))
|
||||
match = reFind.search(wikified, match.end())
|
||||
|
||||
matches.reverse()
|
||||
for sectnum, match in matches:
|
||||
wikified = wikified[:match.start()+4] + sectnum + " " + wikified[match.start()+4:]
|
||||
|
||||
|
||||
# Generate the TOC
|
||||
if getattr(options, 'toc', True):
|
||||
matches = [ '<b>%s: Contents</b>' % wikiname ]
|
||||
for match in reFind.findall(wikified):
|
||||
if int(match[0]) > getattr(options, 'levels', 3): continue
|
||||
indent = " " * ((int(match[0])) * 2)
|
||||
|
||||
href = "#" + match[1]
|
||||
anchor = '%s<a href="%s">%s</a>' % (indent, href, match[1])
|
||||
matches.append(anchor)
|
||||
toc = "<br>".join(matches)
|
||||
else:
|
||||
toc = "" #-e -d /home/adam/src/CSpaceWiki/
|
||||
|
||||
# Generate the body links
|
||||
if getattr(options, 'links', True):
|
||||
wikified = reFind.sub(strRepl, wikified)
|
||||
|
||||
# Find a summary
|
||||
summary = ""
|
||||
if w.summary is not None:
|
||||
summary = w.summary
|
||||
|
||||
if not getattr(options, 'raw', False):
|
||||
# Fill the template
|
||||
wikified = options.template % {
|
||||
"toc": toc,
|
||||
"title": wikiname,
|
||||
"wiki": wikified,
|
||||
"summary": summary }
|
||||
|
||||
# Save it or write it
|
||||
if destdir is not None:
|
||||
outputname = os.path.join(destdir, "%s.html" % wikiname)
|
||||
file(outputname,"w").write(wikified)
|
||||
|
||||
mainpage = getattr(options, 'mainpage', 'MainPage')
|
||||
if wikiname == mainpage:
|
||||
rets.append((wikiname, outputname))
|
||||
outputname = os.path.join(destdir, "index.html")
|
||||
file(outputname,"w").write(wikified)
|
||||
|
||||
wikified = outputname
|
||||
rets.append((wikiname, wikified))
|
||||
return rets
|
||||
|
||||
if __name__ == "__main__":
|
||||
from optparse import OptionParser
|
||||
import sys
|
||||
|
||||
parser = OptionParser()
|
||||
|
||||
# Output format options
|
||||
parser.add_option("-t", "--template", dest="template",
|
||||
help="use TPLTFILE to wrap wiki output", metavar="TPLTFILE")
|
||||
parser.add_option("-n", "--number", dest="number", metavar="NUMSTART",
|
||||
help="number the headings in the body and table of contents starting with level NUMSTART")
|
||||
parser.add_option("-l", "--levels", dest="levels", type="int",
|
||||
help="create toc to depth LEVELS", metavar="LEVELS")
|
||||
parser.add_option("-c", "--skiptoc", dest="toc", action="store_false",
|
||||
help="leave toc out, even if template has slot")
|
||||
parser.add_option("-u", "--unlink", dest="links", action="store_false",
|
||||
help="don't create named anchors for toc links")
|
||||
parser.add_option("-a", "--autolink", dest="autolink", action="store_false",
|
||||
help="autolink wiki words that don't exist")
|
||||
parser.add_option("-w", "--tabwidth", dest="tabwidth", type="int",
|
||||
help="replace tabs by WIDTH spaces", metavar="WIDTH")
|
||||
parser.add_option("-m", "--multibreak", dest="multibreak", action="store_true",
|
||||
help="don't collapse multiple line breaks")
|
||||
parser.add_option("-r", "--raw", dest="raw", action="store_true",
|
||||
help="raw wiki translation -- no wrapping, no toc, no links")
|
||||
parser.add_option("-p", "--mainpage", dest="mainpage", metavar="PAGENAME",
|
||||
help="set main page to PAGENAME")
|
||||
|
||||
# Batch / Location options
|
||||
parser.add_option("-s", "--srcdir", dest="srcdir",
|
||||
help="wiki format sources in SRCDIR", metavar="SRCDIR")
|
||||
parser.add_option("-d", "--destdir", dest="destdir",
|
||||
help="write html output into DESTDIR", metavar="DESTDIR")
|
||||
parser.add_option("-e", "--stale", dest="all", action="store_true",
|
||||
help="convert all wiki files that are stale or missing from DESTDIR")
|
||||
|
||||
|
||||
parser.set_default('toc', True)
|
||||
parser.set_default('links', True)
|
||||
parser.set_default('template', None)
|
||||
parser.set_default('number', False)
|
||||
parser.set_default('levels', 3)
|
||||
parser.set_default('tabwidth', 8)
|
||||
parser.set_default('multibreak', False)
|
||||
parser.set_default('mainpage', "MainPage") # Identity of index
|
||||
|
||||
parser.set_default('srcdir', os.getcwd())
|
||||
parser.set_default('destdir', None)
|
||||
parser.set_default('all', False)
|
||||
|
||||
# Parse the command line
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if options.template is None:
|
||||
options.template = DEFAULT_TEMPLATE
|
||||
elif os.path.exists(options.template):
|
||||
options.template = file(options.template).read()
|
||||
else:
|
||||
print "Template not found: %s" % options.template
|
||||
parser.print_usage()
|
||||
sys.exit()
|
||||
#sys.exit()
|
||||
for wikiname, htmldata in wikify(args, options):
|
||||
if options.destdir:
|
||||
#print wikiname + ":",
|
||||
if htmldata is not None:
|
||||
pass
|
||||
#print htmldata
|
||||
else:
|
||||
print "Complete."
|
||||
elif htmldata is not None:
|
||||
print htmldata
|
||||
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue