Updating ReST docs.

This commit is contained in:
Griatch 2012-03-15 15:26:07 +01:00
parent f46a9a1280
commit 2eb5c4fc8c
39 changed files with 410 additions and 1203 deletions

View file

@ -12,6 +12,7 @@ Installation and Early Life
- `Starting, stopping, reloading and resetting
Evennia <StartStopReload.html>`_
- `Keeping your game up to date <UpdatingYourGame.html>`_
- `Making your game available online <OnlineSetup.html>`_
Customizing the server
----------------------

View file

@ -113,8 +113,5 @@ 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>

View file

@ -17,9 +17,7 @@ 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
@ -45,10 +43,7 @@ use of the ``run_async()`` function in ``src/utils/utils.py``.
::
from src.utils import utils
print "before call ..."
utils.run_async(long_running_function)
print "after call ..."
from src.utils 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
@ -79,8 +74,7 @@ called automatically.
::
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
@ -99,19 +93,7 @@ An example of making an asynchronous call from inside a
::
from src.utils 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 src.utils 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
@ -121,12 +103,19 @@ for us to see. If not we will see an error message.
Assorted notes
--------------
Be careful with choosing when to use asynchronous calls. It is mainly
useful for large administration operations that has no direct influence
on the game world (imports and backup operations come to mind). Since
there is no telling exactly when an asynchronous call actually ends,
using them for in-game commands is to potentially invite confusion and
inconsistencies (and very hard-to-reproduce bugs).
Note that the ``run_async`` will try to launch a separate thread behind
the scenes. Some databases, notably our default database SQLite3, does
*not* allow concurrent read/writes. So if you do a lot of database
access (like saving to an Attribute) in your function, your code might
actually run *slower* using this functionality if you are not careful.
Extensive real-world testing is your friend here.
Overall, be careful with choosing when to use asynchronous calls. It is
mainly useful for large administration operations that has no direct
influence on the game world (imports and backup operations come to
mind). Since there is no telling exactly when an asynchronous call
actually ends, using them for in-game commands is to potentially invite
confusion and inconsistencies (and very hard-to-reproduce bugs).
The very first synchronous example above is not *really* correct in the
case of Twisted, which is inherently an asynchronous server. Notably you

View file

@ -29,9 +29,7 @@ To save persistent data on a Typeclassed object you normally use the
::
# 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
@ -44,9 +42,7 @@ 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
@ -68,9 +64,7 @@ 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.
@ -85,6 +79,13 @@ uses ``msg()`` a lot to send text to you). Using
the safer bet. And it also makes it visually clear at all times when you
are saving to the database and not.
Another drawback of this shorter form is that it will handle a non-found
Attribute as it would any non-found property on the object. The ``db``
operator will instead return ``None`` if no matching Attribute is found.
So if an object has no attribute (or property) named ``test``, doing
``obj.test`` will raise an ``AttributeException`` error, whereas
``obj.db.test`` will return ``None``.
Persistent vs non-persistent
----------------------------
@ -151,96 +152,33 @@ 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])
# 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])
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
Storing nested data directly on the variable
--------------------------------------------
Retrieving Mutable objects
--------------------------
Evennia needs to do a lot of work behind the scenes in order to save and
retrieve data from the database. Most of the time, things work just like
normal Python, but there is one further exception except the one about
storing database objects above. It is related to updating already
existing attributes in-place. Normally this works just as it should. For
example, you can do
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 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 <4.html>`_
::
val without having to extract the mylist Attribute into a temporary
variable first.
# saving data
obj.db.mydict = "key":"test0"
obj.db.mydict["key"] = "test1"
obj.db.mylist = [0,1,2,3]
obj.db.mylist[3] = "test2"
obj.db.mylist.append("test3")
# retrieving data
obj.db.mydict["key"] # returns "test1"
obj.db.mylist[3] # returns "test2
obj.db.mylist[-1] # returns "test3"
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 will *still* be a PackedList, and if you assign things to it,
it will save to the database! To "disconnect" it from the database
system, you need to convert it to a normal list with mylist2
and it will work fine, thanks to a lot of magic happening behind the
scenes. What will *not* work however is *assigning nested
lists/dictionaries in-place*. This is due to the way Python referencing
works, no way around it alas. Consider the following:
::
obj.db.mydict = 1:2:3
This is a perfectly valid nested dictionary and Evennia will store it
just fine. Retrieving this data will also work normally:
::
val = obj.db.mydict[1][2] # correctly returns 3
However:
::
obj.db.mydict[1][2] = "test" # silently fails!
val = obj.db.mydict[1][2] # still returns 3
will not work - trying to edit the nested structure will fail silently
and nothing will have changed. No, this is not consistent with normal
Python operation, it's where the database magic fails. Sorry, but there
does not seem to be a way around this (if you know one, let us know!)
All is not lost however. In order to change a nested structure, you
simply need to use a temporary variable to update:
::
temp = obj.db.mydict
temp[1][2] = "test"
obj.db.mydict = temp
val = obj.db.mydict[1][2] # now correctly returns "test"
This is cumbersome, but always works as expected.
list(mylist2).
Notes
-----

View file

@ -35,21 +35,23 @@ The batch file
--------------
A batch-code file is mostly a normal Python source file. The only thing
separating a batch file from any Python module is that the code are
wrapped into *blocks* using a special syntax. These blocks allow the
separating a batch file from any standard Python module is that the code
is wrapped into *blocks* using a special syntax. These blocks allow the
batch processor more control over execution, especially when using the
processor's *interactive* mode, where they allow the default
``@batchcommand`` to pause and only execute certain blocks at a time.
processor's *interactive* mode. In interactive mode these blocs allow
the batchcode runner to pause and only execute certain blocks at a time.
There is however nothing stopping you from coding everything in one
single block if you don't want to split things up into chunks like this.
Here are the rules of syntax of the batch-command ``*.py`` file.
- ``#HEADER`` as the first on a line marks the start of a *header*
block. This is intended to hold imports and variables that might be
of use for for other blocks. All python code defined in a header
block will always be inserted at the top of all ``#CODE`` blocks in
the file. You may have more than one ``#HEADER`` block, but that is
equivalent to having just one big one. Comments in ``#HEADER`` blocks
are stripped out before merging.
of use for other blocks. All python code defined in a header block
will always be inserted at the top of all ``#CODE`` blocks in the
file. You may have more than one ``#HEADER`` block, but that is
equivalent to having one big one. Comments in ``#HEADER`` blocks are
stripped out before merging.
- ``#CODE`` as the first on a line marks the start of a *code* block.
Code blocks contain functional python code. ``#HEADER`` blocks are
added to the top of code blocks at runtime.
@ -57,7 +59,7 @@ Here are the rules of syntax of the batch-command ``*.py`` file.
block header. The ``(info)`` field gives extra info about what's
going on in the block and is displayed by the batch processor. The
``obj1, obj2, ...`` parts are optional object labels used by the
processors *debug* mode in order to auto-delete objects after a test
processor's *debug* mode in order to auto-delete objects after a test
run.
- A new ``#HEADER`` or ``#CODE`` (or the end of the file) ends the
previous block. Text before the first block are ignored.
@ -75,13 +77,7 @@ 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.")#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 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.")#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))
This uses Evennia's Python API to create three objects in sequence.
@ -138,16 +134,12 @@ This shows that you are on the first ``#CODE`` block, the first of only
two commands in this batch file. Observe that the block has *not*
actually been executed at this point!
To take a look at the full command you are about to run, use ``ll`` (a
batch-processor version of ``look``).
To take a look at the full code snippet you are about to run, use ``ll``
(a batch-processor version of ``look``).
::
from src.utils import create, search
from game.gamesrc.objects.examples import red_button
from game.gamesrc.objects import 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 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.")
Compare with the example code given earlier. Notice how the content of
``#HEADER`` has been pasted at the top of the ``#CODE`` block. Use

View file

@ -26,13 +26,13 @@ relative to a folder you define to hold your batch files, set with
> @batchcommand examples.batch_cmds
A batch-command file contains a list of Evennia commands that you have
previously entered. The processor will run the batch file from beginning
to end. Note that *it will not stop if commands in it fail* (there is no
universal way for the processor to know what a failure looks like for
all different commands). So keep a close watch on the output, or use
*Interactive mode* (see below) to run the file in a more controlled,
gradual manner.
A batch-command file contains a list of Evennia in-game commands
separated by comments. The processor will run the batch file from
beginning to end. Note that *it will not stop if commands in it fail*
(there is no universal way for the processor to know what a failure
looks like for all different commands). So keep a close watch on the
output, or use *Interactive mode* (see below) to run the file in a more
controlled, gradual manner.
The batch file
--------------
@ -41,40 +41,31 @@ The batch file is a simple plain-text file containing Evennia commands.
Just like you would write them in-game, except you have more freedom
with line breaks.
Here's the rules of syntax of an ``*.ev`` file. You'll find it's really,
really simple:
Here are the rules of syntax of an ``*.ev`` file. You'll find it's
really, really simple:
- All lines having the # (hash)-symbol *as the first one on the line*
are considered *comments*.
- Comments also have an actual function -- they mark the *end of the
- All lines having the ``#`` (hash)-symbol *as the first one on the
line* are considered *comments*. All non-comment lines are treated as
a command and/or their arguments.
- Comment lines have an actual function -- they mark the *end of the
previous command definition*. So never put two commands directly
after one another in the file - separate them with a comment, or the
second of the two will be considered an argument to the first one
(regardless, using plenty of comments is a good practice anyway).
- Extra whitespace in a command definition are ignored. If you want a
line break in texts, leave an empty line. Two empty lines thus means
a new paragraph (for commands accepting formatting, that is).
second of the two will be considered an argument to the first one.
Besides, using plenty of comments is good practice anyway.
- Extra whitespace in a command definition is *ignored*.
- A completely empty line translates in to a line break in texts. Two
empty lines thus means a new paragraph (this is obviously only
relevant for commands accepting such formatting, such as the
``@desc`` command).
- The very last command in the file is not required to end with a
comment.
Below is a version of the example file found in
``game/gamesrc/commands/examples/batch_cmds.ev``.
::
#
# This is an example batch build file for Evennia.
## This creates a red button 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 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 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
@ -174,3 +165,9 @@ to allow others than superusers to call the processor. Since normal
access-checks are still performed, a malevolent builder with access to
the processor should not be able to do all that much damage (this is the
main drawback of the `batch-code processor <BatchCodeProcessor.html>`_)
`GNU Emacs <http://en.wikipedia.org/wiki/Emacs>`_ users might find it
interesting to use emacs' *evennia mode*. This is an Emacs major mode
found in ``src/utils/evennia-mode.el``. It offers correct syntax
highlighting and indentation with ``<tab>`` when editing ``.ev`` files
in Emacs. See the header of that file for installation instructions.

View file

@ -124,8 +124,7 @@ 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

View file

@ -9,32 +9,29 @@ about supported
Databases <http://docs.djangoproject.com/en/dev/ref/databases/#ref-databases>`_
page.
SQLite
------
SQLite3
-------
This is the default database used, and for the vast majority of Evennia
installs it will probably be more than adequate for a long time. No
server process is needed, the administrative overhead is tiny (as is
installs it will probably be more than adequate or even the best choice.
No server process is needed, the administrative overhead is tiny (as is
resource consumption). The database will appear as a simple file
(``game/evennia.db3``). SQLite is excellent for development and easy
testing. The database is however hampered in speed by not allowing
concurrent reads. For a full production game with many users accessing
the database, a more fully featured database engine (MySQL, Postgres
etc) is probably better.
(``game/evennia.db3``) and since we run SQLite as an in-memory process
without any socket overhead, it might well be faster than Postgres/MySQL
unless your database is huge.
**Note:** If you run Windows and for some reason need to use a
third-party web server like Apache rather than Evennia's internal web
server, sqlite is probably also not be the best choice. This is due to
the possibility of clashes with file-locking of the database file under
Windows.
**Note:** If you for some reason need to use a third-party web server
like Apache rather than Evennia's internal web server, SQLite is
probably not be the best choice. This is due to the possibility of
clashes with file-locking when using SQLite from more than one process.
Postgres
--------
This is Django's recommended database engine, usable for all sites
aspiring to grow to a larger size. While not as fast as SQLite for
simple purposes, it will scale infinitely better than SQLite, especially
if your game has an extensive web presence.
This is Django's recommended database engine, While not as fast as
SQLite for normal usage, it will scale better than SQLite, especially if
your game has an very large database and/or extensive web presence
through a separate server process.
**Warning:** Postgres has issues with Evennia on some installs at the
moment. "http://code.google.com/p/evennia/issues/detail?id
@ -44,8 +41,8 @@ moment. "http://code.google.com/p/evennia/issues/detail?id
MySQL
-----
MySQL **may** be slightly faster than Postgres depending on your setup
and software versions involved. Older versions of MySQL had some
MySQL *may* be slightly faster than Postgres depending on your setup and
software versions involved. Older versions of MySQL had some
peculiarities though, so check out Django's `Notes about supported
Databases <http://docs.djangoproject.com/en/dev/ref/databases/#ref-databases>`_
to make sure you use the correct version.

View file

@ -26,8 +26,7 @@ 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).

View file

@ -15,9 +15,7 @@ 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.
@ -33,12 +31,7 @@ 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
-----------------------
@ -48,8 +41,7 @@ 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
@ -58,9 +50,7 @@ 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
@ -73,8 +63,7 @@ player. This is defined in ``src/objects/models.py``, on the
::
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
@ -85,13 +74,7 @@ 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

View file

@ -111,24 +111,46 @@ Defining your own command classes
Beyond the properties Evennia always assigns to the command at runtime
(listed above), your job is to define the following class properties:
- ``key`` - 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". Maximum number of space-separated words that can
be part of a command name is given by ``settings.CMD_MAXLEN``.
- ``aliases`` (optional) - a list of alternate names for the command
(``["l", "glance", "see"]``). Same name rules as for ``key`` applies.
- ``locks`` - 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.
- ``help_category`` (optional) - setting this helps to structure the
auto-help into categories. If none is set, this will be set to
- ``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". Maximum number of space-separated
words that can be part of a command name is given by
``settings.CMD_MAXLEN``.
- ``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.
- ``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). This defaults to ``False``. If
- ``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
run command will either clear or replace the storage.
- '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%3Ci%3EAuto-help%3C/i%3Esystem.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.
You should also implement at least two methods, ``parse()`` and
``func()`` (You could also implement ``perm()``, but that's not needed
@ -155,31 +177,7 @@ Below is how you define a simple alternative "``look at``" command:
::
from game.gamesrc.commands.basecommand 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 game.gamesrc.commands.basecommand 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'...")
The power of having commands as classes and to separate ``parse()`` and
``func()`` lies in the ability to inherit functionality without having
@ -236,17 +234,7 @@ rules <Commands#Merge_rules.html>`_ section).
::
from src.commands.cmdset import CmdSet
from game.gamesrc.commands import mycommandsclass 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 src.commands.cmdset import CmdSet from game.gamesrc.commands import mycommandsclass 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
this case all the commands from that CmdSet will be appended to this one
@ -254,10 +242,7 @@ 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
@ -320,19 +305,7 @@ look:
::
from game.gamesrc.commands.basecommand import MuxCommandclass MyCommand(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)
from game.gamesrc.commands.basecommand import MuxCommandclass MyCommand(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
@ -349,10 +322,7 @@ This is what we have now:
::
from game.gamesrc.commands.basecmdset import CmdSet
from game.gamesrc.commands import mycommandclass MyCmdSet(CmdSet):
key = "MyCmdSet" def at_cmdset_creation(self):
self.add(mycommand.MyCommand())
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
@ -416,15 +386,7 @@ class and you will in fact append it to the existing command set.
::
# file gamesrc/commands/basecmdset.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())
# file gamesrc/commands/basecmdset.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.
@ -513,8 +475,7 @@ 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
@ -522,8 +483,7 @@ 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
@ -531,8 +491,7 @@ 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
@ -541,8 +500,7 @@ 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:
@ -569,17 +527,7 @@ 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
---------------
@ -633,12 +581,7 @@ command must be added to a cmdset as well before it will work.
::
from src.commands import cmdhandler
from game.gamesrc.commands.basecommand import Commandclass MyNoInputCommand(Command):
"Usage: Just press return, I dare you"
key = cmdhandler.CMD_NOINPUT
def func(self):
self.caller.msg("Don't just press return like that, talk to me!")
from src.commands import cmdhandler from game.gamesrc.commands.basecommand import Commandclass MyNoInputCommand(Command): "Usage: Just press return, I dare you" key = cmdhandler.CMD_NOINPUT def func(self): self.caller.msg("Don't just press return like that, talk to me!")
Exits
-----

View file

@ -76,12 +76,7 @@ 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

View file

@ -8,11 +8,7 @@ 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-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. ==============================================================
Effective, but not very exciting. You will most likely want to change
this to be more unique for your game.

View file

@ -10,7 +10,11 @@ Evennia depends heavily on good documentation and we are always looking
for extra eyes and hands to improve it. Even small things such as fixing
typos 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.
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.
Contributing with Code through a clone repository
-------------------------------------------------
@ -43,7 +47,9 @@ Once you have an online clone and a local copy of it:
#. Code away on your computer, fixing bugs or whatnot (you can be
offline for this). Commit your code to your local clone as you work,
as often as you like.
as often as you like. There are some suggestions for setting up a
sane local work environment with Mercurial
`here <http://code.google.com/p/evennia/wiki/VersionControl>`_.
#. When you have something you feel is worthwhile (or just want to ask
people's opinions or make an online backup), *push* your local code
up to your online repository with Mercurial.

File diff suppressed because it is too large Load diff

View file

@ -74,6 +74,7 @@ Programming Evennia
Game implementation hints
-------------------------
- `Planning your own Evennia game <GamePlanning.html>`_
- `Creating a Zoning system <Zones.html>`_
- `Implementing cooldowns for commands <CommandCooldown.html>`_
@ -85,7 +86,8 @@ features and ideas. Items here may or may not make it into Evennia down
the road.*
- `Basic game system implementation <WorkshopDefaultGame.html>`_
- `Rtclient protocol <Workshop.html>`_
(inactive)
- `Rtclient protocol <Workshop.html>`_ (deprecated)
- `Summary of changes <EvenniaDevel.html>`_ of latest version vs old
Evennia (pre aug2010)
Evennia (implemented in aug2010)

View file

@ -76,16 +76,7 @@ the server.
game/
evennia.py
manage.py gamesrc/
commands/
examples/
scripts/
examples/
objects/
examples/
world/
examples/
conf/
manage.py gamesrc/ commands/ examples/ scripts/ examples/ objects/ examples/ world/ examples/ conf/
``game/gamesrc/``
~~~~~~~~~~~~~~~~~
@ -151,12 +142,12 @@ syntax.
``gamesrc/conf/``
^^^^^^^^^^^^^^^^^
``gamesrc/world/`` holds optional extension modules for the Evennia
``gamesrc/conf/`` holds optional extension modules for the Evennia
engine. Certain functionality in the server is meant to be extended, and
in order to allow you to do this without modifying the server itself, it
imports files from this directory. Modifying these are optionally and
you can usually change a variable in ``game/settings.py`` to change
which module Evennia is looking for. There are dummy example files in
imports files from this directory. Modifying these are optional and you
can usually change a variable in ``game/settings.py`` to change exactly
which module Evennia actually uses. There are dummy example files in
here, read their headers for usage instructions.
The ``src/`` directory
@ -171,17 +162,7 @@ bugs or features missing, file a bug report or send us a message.
::
src/
settings_defaults.py commands/
comms/
help/
objects/
locks/
players/
scripts/
server/
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

View file

@ -116,9 +116,7 @@ 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
--------------------------
@ -264,16 +262,14 @@ 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

View file

@ -40,7 +40,7 @@ created for you automatically if you use the defaults).
Using the full power of Python throughout the server offers some
distinct advantages. All your coding, from object definitions and custom
commands to AI scripts and economic systems are done in normal Python
commands to AI scripts and economic systems is done in normal Python
modules rather than some ad-hoc scripting language. The fact that you
script the game in the same high-level language that you code it in
allows for very powerful and custom game implementations indeed.

View file

@ -10,8 +10,7 @@ entrust to just anybody.
::
@py 1+2
<<< 3
@py 1+2 <<< 3
Available variables
-------------------
@ -37,8 +36,7 @@ found in ``src/utils/utils.py``:
::
@py from src.utils import utils; utils.time_format(33333)
<<< Done.
@py from src.utils 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
@ -48,9 +46,7 @@ system to echo it to us explicitly with ``self.msg()``.
::
@py from src.utils import utils; self.msg(utils.time_format(33333))
09:15
<<< Done.
@py from src.utils 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
@ -67,10 +63,7 @@ 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
@ -79,8 +72,7 @@ entries found in ``src.utils.search``.
::
@py from src.utils import search; self.msg(search.scripts("sys_game_time"))
<<< [<src.utils.gametime.GameTime object at 0x852be2c>]
@py from src.utils import search; self.msg(search.scripts("sys_game_time")) <<< [<src.utils.gametime.GameTime object at 0x852be2c>]
You can also use the database model managers directly (accessible
through the ``objects`` properties of database models). This is a bit
@ -89,8 +81,7 @@ search methods defined in each manager.
::
@py ScriptDB.objects.script_search("sys_game_time")
<<< [<src.utils.gametime.GameTime object at 0x852be2c>]
@py ScriptDB.objects.script_search("sys_game_time") <<< [<src.utils.gametime.GameTime object at 0x852be2c>]
(Note that since this second example becomes a simple statement, we
don't have to wrap it in ``self.msg()`` to get the output). If you want
@ -101,8 +92,7 @@ contents of the database using normal Django query operations:
::
@py ConfigValue.objects.all()
<<< [<ConfigValue: default_home]>, <ConfigValue:site_name>, ...]
@py ConfigValue.objects.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
@ -114,13 +104,7 @@ most situations.
::
# this uses Evennia's manager method get_id().
# It returns a Character typeclass instance
@py ObjectDB.objects.get_id(1).__class__
<<< Character# this uses the standard Django get() query.
# It returns a django database model instance.
@py ObjectDB.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 ObjectDB.objects.get_id(1).__class__ <<< Character# this uses the standard Django get() query. # It returns a django database model instance. @py ObjectDB.objects.get(id=1).__class__ <<< <class 'src.objects.models.ObjectDB'>
Running a Python Parser outside the game
========================================
@ -145,8 +129,5 @@ tab-completion and ``__doc__``-string reading.
::
$ python manage.py shellIPython 0.10 -- An enhanced Interactive Python
...In [1]: from src.objects.models import ObjectDB
In [2]: ObjectDB.objects.all()
Out[3]: [<ObjectDB: Harry>, <ObjectDB: Limbo>, ...]
$ python manage.py shellIPython 0.10 -- An enhanced Interactive Python ...In [1]: from src.objects.models import ObjectDB In [2]: ObjectDB.objects.all() Out[3]: [<ObjectDB: Harry>, <ObjectDB: Limbo>, ...]

View file

@ -67,7 +67,8 @@ Twisted also requires:
**Django** (http://www.djangoproject.com)
- Version 1.2.5+ or latest development versions highly recommended.
- PIL library (http://www.pythonware.com/products/pil)
- PIL (Python Imaging Library) (http://www.pythonware.com/products/pil)
- not strictly required unless you use images in Django.
To download/update Evennia:
@ -129,17 +130,19 @@ or all of these instead:
``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 requites extension compiling. You can also
install the packages that requires extension compiling. You can also
retrieve the dependencies directly and install them through their native
installers or python setups.
installers or python setups. Some users have reported problems compiling
the ``PIL`` library on Mac, it's however not strictly required to use
Django.
**Windows** users may choose to install
`ActivePython <http://www.activestate.com/activepython/downloads>`_
instead of the usual Python. If ActivePython is installed, you can use
`pypm <http://docs.activestate.com/activepython/2.6/pypm.html>`_ in the
same manner as ``easy_install``/``pip`` above. This *greatly* simplifies
getting started on Windows since that platform is otherwise missing many
of the background developer systems that Linux users take for granted.
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:
@ -171,20 +174,21 @@ trick (first place yourself in a directory where you want a new folder
hg clone https://code.google.com/p/evennia/ evennia
(``hg`` is the chemical abbreviation of mercury, hence the use of ``hg``
for ``mercurial``)
(Mercurial is abbreviated ``hg`` since this is the chemical symbol for
mercury).
In the future, you just do
::
hg pull
hg update
hg pull hg update
from your ``evennia/`` directory to obtain the latest updates.
If you use a graphical Mercurial client, use the equivalent buttons to
perform the above operations.
perform the above operations. See
`here <http://code.google.com/p/evennia/wiki/VersionControl>`_ for more
advanced suggestions to set up a development environment with Mercurial.
Step 2: Setting up the Server
-----------------------------
@ -324,10 +328,7 @@ 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

View file

@ -49,16 +49,7 @@ 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
@ -98,10 +89,7 @@ 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.

View file

@ -6,17 +6,18 @@ 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 don't understand a concept or think the docs are not clear
enough, fill in our quick little
"https://docs.google.com/spreadsheet/viewform?hl
- 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 - maybe the docs need to be improved
or a tutorial added!
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.
- If you have trouble with a missing feature or a problem you think is
a bug, go to the `issue

View file

@ -85,8 +85,7 @@ 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:

View file

@ -25,8 +25,7 @@ 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

View file

@ -54,9 +54,7 @@ how it would (and do) look from inside the ``@delete`` command:
::
if not obj.access(accessing_obj, 'delete'):
accessing_obj.msg("Sorry, you may not delete that.")
return
if not obj.access(accessing_obj, 'delete'): accessing_obj.msg("Sorry, you may not delete that.") return
Defining locks
--------------
@ -69,9 +67,7 @@ Here are some examples of lock strings:
::
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:
@ -178,11 +174,7 @@ 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
@ -250,11 +242,7 @@ 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
@ -267,8 +255,7 @@ 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
----------
@ -339,8 +326,7 @@ 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
@ -359,12 +345,7 @@ 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
@ -398,9 +379,7 @@ like this:
::
from src.utils import create
box = create.create_object(None, key="box", locks="get:attr_gt(strength, 50)")# or, if we don't set the locks right awaybox.locks.add("get:attr_gt(strength, 50)")# set the attributesbox.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 src.utils import create box = create.create_object(None, key="box", locks="get:attr_gt(strength, 50)")# or, if we don't set the locks right awaybox.locks.add("get:attr_gt(strength, 50)")# set the attributesbox.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
=============================

View file

@ -50,7 +50,7 @@ becomes equivalent to "``look The red sports car``".
::
nick/players tom = TommyBoy
nick/players tom = Thomas Johnsson
This is useful for commands searching for players explicitly:
@ -83,13 +83,7 @@ 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

View file

@ -19,14 +19,7 @@ Here's how to define a new Object typeclass in code:
::
from game.gamesrc.objects.baseobjects import Objectclass Rose(Object):
"""
This creates a simple rose object
"""
def at_object_creation(self):
"this is called only once, when object is first created"
# add a persistent attribute 'desc' to object.
self.db.desc = "This is a pretty rose with thorns."
from game.gamesrc.objects.baseobjects import Objectclass Rose(Object): """ This creates a simple rose object """ def at_object_creation(self): "this is called only once, when object is first created" # add a persistent attribute 'desc' to object. self.db.desc = "This is a pretty rose with thorns."
Save your class to a module under ``game/gamesrc/objects``, say
``flowers.py``. Now you just need to point to the class *Rose* with the
@ -41,8 +34,7 @@ To create a new object in code, use the method
::
from src.utils import create
new_rose = create.create_object("game.gamesrc.objects.flowers.Rose", key="MyRose")
from src.utils import create new_rose = create.create_object("game.gamesrc.objects.flowers.Rose", key="MyRose")
(You have to give the full path to the class in this case -
``create.create_object`` is a powerful function that should be used for

View file

@ -41,19 +41,7 @@ Here's how to define a new Player typeclass in code:
::
from src.players.player 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 src.players.player 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. Either make your own folder or store it in

View file

@ -34,10 +34,7 @@ inheriting from ``game.gamesrc.objects.baseobjecs.Character``.
::
from game.gamesrc.objects.baseobjects import Characterclass ColourableCharacter(Character):
at_object_creation(self):
# set a colour config value
self.db.config_colour = True
from game.gamesrc.objects.baseobjects import Characterclass 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>`_.
@ -51,7 +48,7 @@ everything works - you don't want to render your root user unusable!).
::
@typeclass/reset/force mycharacter.ColourableCharacter
@typeclass/reset/force Bob = mycharacter.ColourableCharacter
The ``/reset`` switch clears all attributes and properties back to the
default for the new typeclass and forces the object to re-run all its
@ -77,11 +74,7 @@ original. Here's how it could look:
::
from src.utils 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 src.utils 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)
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
@ -93,7 +86,13 @@ Since we put this custom ``msg()`` in our typeclass
the default method on ``ObjectDB`` (which we now instead call manually).
There we go! Just flip the attribute ``config_colour`` to False and your
users will not see any colour.
users will not see any colour. As superuser (assuming you use the
Typeclass ``ColourableCharacter``) you can test this with the ``@py``
command:
::
@py self.db.config_colour = False
Custom colour config command
----------------------------
@ -107,22 +106,7 @@ for configuration down the line).
::
from game.gamesrc.commands.basecommand import MuxCommandclass ConfigColourCmd(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)
from game.gamesrc.commands.basecommand import MuxCommandclass ConfigColourCmd(MuxCommand): """ Configures your colour Usage: @setcolour on|off This turns ansii-colours on/off. Default is on. """ key = "@setcolour" aliases = ["@setcolor"] def func(self): "Implements the command" if not self.args or not self.args in ("on", "off"): self.caller.msg("Usage: @setcolour on|off") return if self.args == "on": self.caller.db.config_colour = True else: self.caller.db.config_colour = False self.caller.msg("Colour was turned %s." % self.args)
Lastly, we make this command available to the user by adding it to the
default command set. Easiest is to add it to the end of
@ -130,14 +114,7 @@ default command set. Easiest is to add it to the end of
::
from game.gamesrc.commands import configcmds
class DefaultCmdSet(cmdset_default.DefaultCmdSet):
key = "DefaultMUX"
def at_cmdset_creation(self):
super(DefaultCmdSet, self).at_cmdset_creation()
self.add(configcmds.ConfigColourCmd())
from game.gamesrc.commands import configcmds class DefaultCmdSet(cmdset_default.DefaultCmdSet): key = "DefaultMUX" def at_cmdset_creation(self): super(DefaultCmdSet, self).at_cmdset_creation() self.add(configcmds.ConfigColourCmd())
When adding a new command to a cmdset like this you need to run the
``@reload`` command (or reboot the server). From here on out, your users

View file

@ -72,11 +72,7 @@ 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
@ -88,9 +84,7 @@ Just don't supply an object to store it on.
::
# adding a global script
from src.utils.create import create_script
create_script("game.gamesrc.scripts.globals.MyGlobalEconomy", key="economy", obj=None)
# adding a global script from src.utils.create 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.
@ -218,8 +212,7 @@ locate the room you want:
::
from src.utils.create import create_script
create_script('game.gamesrc.scripts.weather.Weather', obj=myroom)
from src.utils.create import create_script create_script('game.gamesrc.scripts.weather.Weather', obj=myroom)
Or, from in-game, use the ``@script`` command:

View file

@ -160,20 +160,7 @@ 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
@ -181,21 +168,7 @@ 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
@ -205,15 +178,7 @@ 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.

View file

@ -51,8 +51,7 @@ 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

View file

@ -43,8 +43,7 @@ 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.
@ -85,8 +84,7 @@ 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
-------------

View file

@ -56,9 +56,10 @@ see what happens in detail.
To play the tutorial "correctly", you should *not* do so as superuser.
The reason for this is that many game systems ignore the presence of a
superuser and will thus not work as normal. Create a new, non-superuser
character for playing instead. As superuser you can of course examine
things "under the hood" later if you want.
superuser and will thus not work as normal. Log out, then reconnect.
From the login screen, create a new, non-superuser character for playing
instead. As superuser you can of course examine things "under the hood"
later if you want.
Gameplay
--------
@ -102,8 +103,7 @@ 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

View file

@ -255,8 +255,7 @@ 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

View file

@ -57,25 +57,7 @@ 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 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)
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

View file

@ -16,8 +16,7 @@ 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
@ -77,8 +76,7 @@ database is named "Evennia":
::
mysql> DROP DATABASE Evennia;
mysql> exit
mysql> DROP DATABASE Evennia; mysql> exit
A Note on Schema Migration
--------------------------

View file

@ -1,10 +1,18 @@
Workshop: Default-game whitepage
**Status Update**:*There does not seem to be any active development on
this by the original initiator (rcaskey). As far as I know there is no
active game code written apart from a Smaug area converter (how
complete?). If anyone is willing to continue with this particular idea,
they are welcome to do so. I will help out but I don't know anything
about Smaug myself. In the interim I will chalk this one down as being a
stalled project. /Griatch*
Introduction
============
This is an initiative to create a "base" game system to be shipped with
Evennia in a "contrib" folder. The game is an independent
This is(was?) an initiative to create a "base" game system to be shipped
with Evennia in a "contrib" folder. The game is an independent
re-implementation of the basic stuff of the
`SMAUG <http://www.smaug.org>`_ system. No code from the original will
be used, and no licensed content will be included in the release. For