docs: Adding missing rest pages to mercurial.
This commit is contained in:
parent
f95fac1f51
commit
ae65438c9b
16 changed files with 2114 additions and 0 deletions
114
docs/sphinx/source/wiki/AddingCommandTutorial.rst
Normal file
114
docs/sphinx/source/wiki/AddingCommandTutorial.rst
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
Adding a new command - a step by step guide
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
This is a quick first-time tutorial expanding on the
|
||||||
|
`Commands <Commands.html>`_ documentation.
|
||||||
|
|
||||||
|
Let's assume you have just downloaded Evennia and want to try to add a
|
||||||
|
new command. This is the fastest way to do it.
|
||||||
|
|
||||||
|
Tell Evennia where to look for custom commands/cmdsets
|
||||||
|
------------------------------------------------------
|
||||||
|
|
||||||
|
We will tell Evennia that you want to override the default cmdset with
|
||||||
|
new additional commands of your own.
|
||||||
|
|
||||||
|
#. Go to ``game/gamesrc/commands``.
|
||||||
|
#. There is a subfolder here named ``examples``. *Copy* the files
|
||||||
|
``examples/command.py`` and ``examples/cmdset.py`` to where you are.
|
||||||
|
You can rename them as you please, but in this example we assume you
|
||||||
|
don't.
|
||||||
|
#. Edit ``game/settings.py``, adding the following line:
|
||||||
|
|
||||||
|
``CMDSET_DEFAULT="game.gamesrc.commands.cmdset.DefaultCmdSet"``
|
||||||
|
|
||||||
|
Evennia will now look for default commands in the ``DefaultCmdSet``
|
||||||
|
class of your newly copied module. You only need to do this once.
|
||||||
|
|
||||||
|
Creating a custom command
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
#. Edit your newly copied ``game/gamesrc/commands/command.py``. This
|
||||||
|
template already imports everything you need.
|
||||||
|
#. Create a new class in ``command.py`` that inherits from
|
||||||
|
``MuxCommand``. Let's call it ``CmdEcho`` in this example.
|
||||||
|
#. Set the class variable ``key`` to a good command name, like ``echo``.
|
||||||
|
#. Set the ``locks`` property on the command to a suitable
|
||||||
|
[Locks#Defining\_locks lockstring]. If you are unsure, use
|
||||||
|
``"cmd:all()"``.
|
||||||
|
#. Give your class a useful *\_doc\_* string, this acts as the help
|
||||||
|
entry for the command.
|
||||||
|
#. Define a class method ``func()`` that does stuff. Below is an example
|
||||||
|
how it all could look.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
# file game/gamesrc/commands/command.py
|
||||||
|
#[...]
|
||||||
|
class CmdEcho(MuxCommand):
|
||||||
|
"""
|
||||||
|
Simple command example
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
echo <text>
|
||||||
|
|
||||||
|
This command simply echoes text back to the caller.
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = "echo"
|
||||||
|
locks = "cmd:all()"
|
||||||
|
|
||||||
|
def func(self):
|
||||||
|
"This actually does things"
|
||||||
|
if not self.args:
|
||||||
|
self.caller.msg("You didn't enter anything!")
|
||||||
|
else:
|
||||||
|
self.caller.msg("You gave the string: '%s'" % self.args)
|
||||||
|
|
||||||
|
Adding the Command to a Cmdset
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
The command is not available to use until it is part of a Command Set.
|
||||||
|
In this example we will go the easiest route and add it to the default
|
||||||
|
command set we already prepared.
|
||||||
|
|
||||||
|
#. Edit your recently copied ``game/gamesrc/commands/cmdset.py``
|
||||||
|
#. In this copied module you will find the ``DefaultCmdSet`` class
|
||||||
|
already imported and prepared for you. Import your new command module
|
||||||
|
here with ``from game.gamesrc.commands import echo``.
|
||||||
|
#. Add a line ``self.add(echo.CmdEcho())`` to ``DefaultCmdSet``, in the
|
||||||
|
``at_cmdset_creation`` method (the template tells you where). This is
|
||||||
|
approximately how it should look at this point:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
# file gamesrc/commands/examples/cmdset.py
|
||||||
|
#[...]
|
||||||
|
from game.gamesrc.commands import echo
|
||||||
|
#[...]
|
||||||
|
class DefaultCmdSet(default_cmds.DefaultCmdSet):
|
||||||
|
|
||||||
|
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(echo.CmdEcho())
|
||||||
|
|
||||||
|
#. Reboot/restart Evennia (``@reload`` from inside the game). You should
|
||||||
|
now be able to use your new ``echo`` command from inside the game.
|
||||||
|
Use ``help echo`` to see the documentation for the command.
|
||||||
|
|
||||||
|
If you have trouble, make sure to check the log for error messages
|
||||||
|
(probably due to syntax errors in your command definition).
|
||||||
|
|
||||||
|
Adding new commands to the default cmdset in the future now only
|
||||||
|
involves creating the function class and adding it to the same place. If
|
||||||
|
you want to overload existing default commands (such as ``look`` or
|
||||||
|
``get``), just add your new command with the same key as the old one -
|
||||||
|
it will overload the default one. Just remember that you must
|
||||||
|
``@reload`` the server before you see any changes.
|
||||||
132
docs/sphinx/source/wiki/Banning.rst
Normal file
132
docs/sphinx/source/wiki/Banning.rst
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
Sometimes it's just not worth the grief ...
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
Whether due to abuse, blatant breaking of your rules, or some other
|
||||||
|
reason you will eventually find no other recourse but to kick out a
|
||||||
|
particularly troublesome player. The default command set has admin tools
|
||||||
|
to handle this, primarily ``@ban, @unban`` and ``@boot``.
|
||||||
|
|
||||||
|
Creating a ban
|
||||||
|
==============
|
||||||
|
|
||||||
|
Say we have a troublesome player "YouSuck" - this is a guy that refuse
|
||||||
|
common courtesy - an abusive and spammy account that is clearly created
|
||||||
|
by some bored internet hooligan only to cause grief. You have tried to
|
||||||
|
be nice. Now you just want this troll gone.
|
||||||
|
|
||||||
|
Name ban
|
||||||
|
--------
|
||||||
|
|
||||||
|
The easiest is to block the account YouSuck from ever connecting again.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@ban YouSuck
|
||||||
|
|
||||||
|
This will lock the name YouSuck (as well as 'yousuck' and any other
|
||||||
|
combination), and next time they try to log in with this name the server
|
||||||
|
will not let them!
|
||||||
|
|
||||||
|
You can also give a reason so you remember later why this was a good
|
||||||
|
thing (the banned player will never see this)
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@ban YouSuck:This is just a troll.
|
||||||
|
|
||||||
|
If you are sure this is just a spam account, you might even consider
|
||||||
|
deleting the player account outright:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@delplayer YouSuck
|
||||||
|
|
||||||
|
Generally banning the name is the easier and safer way to stop the use
|
||||||
|
of an account -- if you change your mind you can always remove the block
|
||||||
|
later whereas a deletion is permanent.
|
||||||
|
|
||||||
|
IP ban
|
||||||
|
------
|
||||||
|
|
||||||
|
Just because you block YouSuck's name might not mean the trolling human
|
||||||
|
behind that account gives up. They can just create a new account
|
||||||
|
YouSuckMore and be back at it. One way to make things harder for them is
|
||||||
|
to tell the server to not allow connections from their particular IP
|
||||||
|
address.
|
||||||
|
|
||||||
|
First, when the offending player is online, check which IP address they
|
||||||
|
use. This you can do with the ``who`` command, which will show you
|
||||||
|
something like this:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
Player Name On for Idle Room Cmds Host
|
||||||
|
YouSuck 01:12 2m 22 212 237.333.0.223
|
||||||
|
|
||||||
|
The "Host" bit is the IP address from which the player is connecting.
|
||||||
|
Use this to define the ban instead of the name:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@ban 237.333.0.223
|
||||||
|
|
||||||
|
This will stop YouSuck connecting from his computer. Note however that
|
||||||
|
IP addresses might change easily - either due to how the player's
|
||||||
|
Internet Service Provider operates or by the user simply changing
|
||||||
|
computer. You can make a more general ban by putting asterisks ``*`` as
|
||||||
|
wildcards for the groups of three digits in the address. So if you
|
||||||
|
figure out that YouSuck mainly connects from 237.333.0.223,
|
||||||
|
237.333.0.225 and 237.333.0.256 (only changes in the local subnet), it
|
||||||
|
might be an idea to put down a ban like this to include any number in
|
||||||
|
that subnet:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@ban 237.333.0.*
|
||||||
|
|
||||||
|
You should combine the IP ban with a name-ban too of course, so the
|
||||||
|
account YouSuck is truly locked regardless of from where they connect.
|
||||||
|
|
||||||
|
Be careful with too general IP bans however (more asterisks above). If
|
||||||
|
you are unlucky you could be blocking out innocent players who just
|
||||||
|
happen to connect from the same subnet as the offender.
|
||||||
|
|
||||||
|
Booting
|
||||||
|
=======
|
||||||
|
|
||||||
|
YouSuck is not really noticing all this banning yet though - and won't
|
||||||
|
until having logged out and tries to log back in again. Let's help the
|
||||||
|
troll along.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@boot YouSuck
|
||||||
|
|
||||||
|
Good riddance. You can give a reason for booting too (to be echoed to
|
||||||
|
the player before getting kicked out).
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@boot YouSuck:Go troll somewhere else.
|
||||||
|
|
||||||
|
Lifting a ban
|
||||||
|
=============
|
||||||
|
|
||||||
|
Give the ``@unban`` (or ``@ban``) command without any arguments and you
|
||||||
|
will see a list of all currently active bans:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
Active bans
|
||||||
|
id name/ip date reason
|
||||||
|
1 yousuck Fri Jan 3 23:00:22 2020 This is just a Troll.
|
||||||
|
2 237.333.0.* Fri Jan 3 23:01:03 2020 YouSuck's IP.
|
||||||
|
|
||||||
|
Use the ``id`` from this list to find out which ban to lift.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@unban 2
|
||||||
|
|
||||||
|
Cleared ban 2: 237.333.0.*
|
||||||
|
|
||||||
63
docs/sphinx/source/wiki/CodingIntroduction.rst
Normal file
63
docs/sphinx/source/wiki/CodingIntroduction.rst
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
Evennia coding introduction
|
||||||
|
===========================
|
||||||
|
|
||||||
|
Evennia allows for a lot of freedom when designing your game - but to
|
||||||
|
code efficiently you still need to adopt some best practices as well as
|
||||||
|
find a good place to start to learn.
|
||||||
|
|
||||||
|
Here are some pointers to get you going.
|
||||||
|
|
||||||
|
Code in \`game/gamesrc\`, not in \`src/\`
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
|
You will create and code your game by adding Python modules in
|
||||||
|
``game/gamesrc/`` (see the `directory
|
||||||
|
overview <DirectoryOverview.html>`_). This is your home. You should
|
||||||
|
*never* need to modify anything under ``src/`` (anything you download
|
||||||
|
from us, really). Treat ``src/`` as a kind of library. You import useful
|
||||||
|
functionality from here. If you see code you like, copy&paste it out
|
||||||
|
into ``game/gamesrc`` and edit it there.
|
||||||
|
|
||||||
|
If you find that ``src/`` *doesn't* support some functionality you need,
|
||||||
|
make a `Feature
|
||||||
|
Request <https://code.google.com/p/evennia/issues/list>`_ about it. Same
|
||||||
|
goes for `bugs <https://code.google.com/p/evennia/issues/list>`_. If you
|
||||||
|
add features or fix bugs yourself, please consider
|
||||||
|
`contributing <Contributing.html>`_ your changes upstream!
|
||||||
|
|
||||||
|
Learn with \`ev\`
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Learn the `ev interface <evAPI.html>`_. This is a great way to explore
|
||||||
|
what Evennia has to offer. For example, start an interactive python
|
||||||
|
shell, import ``ev`` and just look around.
|
||||||
|
|
||||||
|
You can compliment your exploration by peeking at the sections of the
|
||||||
|
much more detailed `Developer Central <DeveloperCentral.html>`_. The
|
||||||
|
`Tutorials <Tutorials.html>`_ section also contains a growing collection
|
||||||
|
of system- or implementation-specific help.
|
||||||
|
|
||||||
|
Plan before you code
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Before you start coding away at your dream game, take a look at our
|
||||||
|
`game planning hints and tips <GamePlanning.html>`_ page. It might
|
||||||
|
hopefully help you avoid some common pitfalls and time sinks.
|
||||||
|
|
||||||
|
Docs are here to help you
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Some people find reading documentation extremely dull and shun it out of
|
||||||
|
principle. That's your call, but reading docs really *does* help you,
|
||||||
|
promise! Evennia's documentation is pretty thorough and knowing what is
|
||||||
|
possible can often give you a lot of new cool game ideas. That said, if
|
||||||
|
you can't find the answer in the docs, don't be shy to ask questions!
|
||||||
|
The `discussion
|
||||||
|
group <https://sites.google.com/site/evenniaserver/discussions>`_ and
|
||||||
|
the `irc chat <http://webchat.freenode.net/?channels=evennia>`_ are
|
||||||
|
there for you.
|
||||||
|
|
||||||
|
The most important point
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
And finally, of course, have fun!
|
||||||
245
docs/sphinx/source/wiki/CodingUtils.rst
Normal file
245
docs/sphinx/source/wiki/CodingUtils.rst
Normal file
|
|
@ -0,0 +1,245 @@
|
||||||
|
Utils
|
||||||
|
=====
|
||||||
|
|
||||||
|
Evennia comes with many utilities to help with common coding tasks. Some
|
||||||
|
of these are part of the command interface but most can be found in the
|
||||||
|
``src/utils/`` folder. They are used all over the server, but offer help
|
||||||
|
for many common tasks when coding your own game as well. This is not a
|
||||||
|
complete list, check the module for more goodies.
|
||||||
|
|
||||||
|
Search
|
||||||
|
------
|
||||||
|
|
||||||
|
A common thing to do is to search for objects. The most common time one
|
||||||
|
needs to do this is inside a command body. There it's easiest to use the
|
||||||
|
``search`` method defined on all objects. This will search for objects
|
||||||
|
in the same location and inside the caller:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
obj = self.caller.search(objname)
|
||||||
|
|
||||||
|
Give the keyword ``global_search=True`` to extend search to encompass
|
||||||
|
entire database. Also aliases with be matched by this search. You will
|
||||||
|
find multiple examples of this functionality in the default command set.
|
||||||
|
|
||||||
|
If you need to search for objects in a code module you can use the
|
||||||
|
functions in ``src.utils.search``. You can access these as shortcuts
|
||||||
|
``ev.search_*``.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
from ev import search_object
|
||||||
|
obj = search_object(objname)
|
||||||
|
|
||||||
|
``utils.search`` contains properties to the relevant database search
|
||||||
|
method. They are really just shortcuts to the django-database search
|
||||||
|
methods, like ``ObjectDB.objects.search()``.
|
||||||
|
|
||||||
|
**Note:** If you are a Django wizz, you might be tempted to use Django's
|
||||||
|
database search functions directly (using ``filter``, ``get`` etc). Just
|
||||||
|
remember that such operations will give you a django model whereas
|
||||||
|
Evennia's manager methods will give you a typeclass. It's easy to
|
||||||
|
convert between them with ``dbobj.typeclass`` and ´typeclass.dbobj´,
|
||||||
|
but you should remember this distinction. If you stick with Evennia's
|
||||||
|
search methods you will always get typeclasses back.
|
||||||
|
|
||||||
|
Create
|
||||||
|
------
|
||||||
|
|
||||||
|
Apart from the in-game build commands (``@create`` etc), you can also
|
||||||
|
build all of Evennia's game entities directly in code (for example when
|
||||||
|
defining new create commands). This *must* be done using
|
||||||
|
``src.utils.create`` or their shortcuts ``ev.create_*``- these functions
|
||||||
|
are responsible for setting up all the background intricacies of the
|
||||||
|
typeclass system and other things. Creating database instances using raw
|
||||||
|
Django will *not* work. Examples:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
import ev
|
||||||
|
#
|
||||||
|
myobj = ev.create_objects("game.gamesrc.objects.myobj.MyObj", key="MyObj")
|
||||||
|
myscr = ev.create_script("game.gamesrc.scripts.myscripts.MyScript", obj=myobj)
|
||||||
|
help = ev.create_help_entry("Emoting", "Emoting means that ...")
|
||||||
|
msg = ev.create_message(senderobj, [receiverobj], "Hello ...")
|
||||||
|
chan = ev.create_channel("news")
|
||||||
|
player = ev.create_player("Henry", "henry@test.com", "H@passwd")
|
||||||
|
|
||||||
|
Each of these create functions have a host of arguments to further
|
||||||
|
customize the created entity. See ``src/utils/create.py`` for more
|
||||||
|
information.
|
||||||
|
|
||||||
|
Logging
|
||||||
|
-------
|
||||||
|
|
||||||
|
Normally you can use Python ``print`` statements to see output to the
|
||||||
|
terminal (if you started Evennia in *interactive mode* with the -i
|
||||||
|
switch). This should only be used for debugging though, for real output,
|
||||||
|
use the logger - it will log to the terminal in interactive mode, to the
|
||||||
|
log file otherwise.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
from ev import logger
|
||||||
|
#
|
||||||
|
logger.log_errmsg("This is an Error!")
|
||||||
|
logger.log_warnmsg("This is a Warning!")
|
||||||
|
logger.log_infomsg("This is normal information")
|
||||||
|
logger.log_depmsg("This feature is deprecated")
|
||||||
|
|
||||||
|
There is also a special log-message type that is intended to be called
|
||||||
|
from inside a traceback - this can be very useful for relaying the
|
||||||
|
traceback message back to log without having it kill the server.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
try:
|
||||||
|
# [some code that may fail...]
|
||||||
|
except Exception:
|
||||||
|
logger.log_trace("This text will be appended to the traceback info")
|
||||||
|
|
||||||
|
inherits\_from()
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This useful function takes two arguments - an object to check and a
|
||||||
|
parent. It returns ``True`` if object inherits from parent *at any
|
||||||
|
distance* (as opposed to Python's ``is_instance()`` that will only catch
|
||||||
|
immediate dependence. This function also accepts any combination of
|
||||||
|
classes, instances or python paths to classes as inputs.
|
||||||
|
|
||||||
|
Note that Python code should usually work with `duck
|
||||||
|
typing <http://en.wikipedia.org/wiki/Duck_typing>`_. But in Evennia's
|
||||||
|
case it can sometimes be useful to check if an object inherits from a
|
||||||
|
given `Typeclass <Typelasses.html>`_ as a way of identification. Say for
|
||||||
|
example that we have a typeclass *Animal*. This has a subclass *Felines*
|
||||||
|
which in turns is a parent to *HouseCat*. Maybe there are a bunch of
|
||||||
|
other animal types too, like horses and dogs. Using ``inherits_from``
|
||||||
|
will allow you to check for all animals in one go:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
from ev import utils
|
||||||
|
if (utils.inherits_from(obj, "game.gamesrc.objects.animals.Animal"):
|
||||||
|
obj.msg("The bouncer stops you in the door. He says: 'No talking animals allowed.'")
|
||||||
|
|
||||||
|
Some text utilities
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
In a text game, you are naturally doing a lot of work shuffling text
|
||||||
|
back and forth. Here is a *non-complete* selection of text utilities
|
||||||
|
found in ``src/utils/utils.py`` (shortcut ``ev.utils``). If nothing else
|
||||||
|
it can be good to look here before starting to develop a solution of
|
||||||
|
your own.
|
||||||
|
|
||||||
|
fill()
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
This flood-fills a text to a given width (shuffles the words to make
|
||||||
|
each line evenly wide). It also indents as needed.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
outtxt = fill(intxt, width=78, indent=4)
|
||||||
|
|
||||||
|
crop()
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
This function will crop a very long line, adding a suffix to show the
|
||||||
|
line actually continues. This can be useful in listings when showing
|
||||||
|
multiple lines would mess up things.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
intxt = "This is a long text that we want to crop."
|
||||||
|
outtxt = crop(intxt, width=19, suffix="[...]")
|
||||||
|
# outtxt is now "This is a long text[...]"
|
||||||
|
|
||||||
|
dedent()
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
This solves what may at first glance appear to be a trivial problem with
|
||||||
|
text - removing indentations. It is used to shift entire paragraphs to
|
||||||
|
the left, without disturbing any further formatting they may have. A
|
||||||
|
common case for this is when using Python triple-quoted strings in code
|
||||||
|
- they will retain whichever indentation they have in the code, and to
|
||||||
|
make easily-readable source code one usually don't want to shift the
|
||||||
|
string to the left edge.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
#python code is at this indentation
|
||||||
|
intxt = """
|
||||||
|
This is an example text that will end
|
||||||
|
up with a lot of whitespace on the left.
|
||||||
|
It also has indentations of
|
||||||
|
its own."""
|
||||||
|
outtxt = dedent(intxt)
|
||||||
|
# outtxt will now retain all internal indentation
|
||||||
|
# but be shifted all the way to the left.
|
||||||
|
|
||||||
|
Normally you do the dedent in the display code (this is the way the help
|
||||||
|
system homogenizes help entries).
|
||||||
|
|
||||||
|
time\_format()
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This function takes a number of seconds as input and converts it to a
|
||||||
|
nice text output in days, hours etc. It's useful when you want to show
|
||||||
|
how old something is. It converts to four different styles of output
|
||||||
|
using the *style* keyword:
|
||||||
|
|
||||||
|
- style 0 - ``5d:45m:12s`` (standard colon output)
|
||||||
|
- style 1 - ``5d`` (shows only the longest time unit)
|
||||||
|
- style 2 - ``5 days, 45 minutes`` (full format, ignores seconds)
|
||||||
|
- style 3 - ``5 days, 45 minutes, 12 seconds`` (full format, with
|
||||||
|
seconds)
|
||||||
|
|
||||||
|
text conversion()
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Evennia supplies two utility functions for converting text to the
|
||||||
|
correct encodings. ``to_str()`` and ``to_unicode()``. The difference
|
||||||
|
from Python's in-built ``str()`` and ``unicode()`` operators are that
|
||||||
|
the Evennia ones makes use of the ``ENCODINGS`` setting and will try
|
||||||
|
very hard to never raise a traceback but instead echo errors through
|
||||||
|
logging. See `TextEncodings <TextEncodings.html>`_ for more info.
|
||||||
|
|
||||||
|
format\_table()
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This function creates nicely formatted tables - columns of text all
|
||||||
|
lined up. It will automatically widen each column so all entries fit.
|
||||||
|
|
||||||
|
To use it, you need to create a list of lists - each sublist contains
|
||||||
|
the content of one column. The result will be a list of ready-formatted
|
||||||
|
strings to print.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
# title line
|
||||||
|
cols = [["num"],["x"],["y"]]
|
||||||
|
# creating a dummy table with integers
|
||||||
|
for i in range(3):
|
||||||
|
cols[0].append(i)
|
||||||
|
cols[1].append(i+1)
|
||||||
|
cols[2].append(i+2)
|
||||||
|
# format the table (returns list with rows)
|
||||||
|
ftable = format_table(cols, extra_space=3)
|
||||||
|
# print the rows, making header bright white
|
||||||
|
for irow, row in enumerate(ftable):
|
||||||
|
if irow == 0: # header
|
||||||
|
print "{w%s{x" % row
|
||||||
|
else:
|
||||||
|
print row
|
||||||
|
# Output (no colors shown):
|
||||||
|
#
|
||||||
|
# num x y
|
||||||
|
# 1 2 3
|
||||||
|
# 2 3 4
|
||||||
|
# 3 4 5
|
||||||
|
#
|
||||||
|
|
||||||
|
Note that you cannot add colour codes to the input to ``format_table`` -
|
||||||
|
these would mess up the width of each column. Instead you can add this
|
||||||
|
to the output when printing.
|
||||||
92
docs/sphinx/source/wiki/CommandCooldown.rst
Normal file
92
docs/sphinx/source/wiki/CommandCooldown.rst
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
Cooldowns
|
||||||
|
=========
|
||||||
|
|
||||||
|
Some types of games want to limit how often a command can be run. If a
|
||||||
|
character casts the spell *Firestorm*, you might not want them to spam
|
||||||
|
that command over and over. Or in an advanced combat system, a massive
|
||||||
|
swing may offer a chance of lots of damage at the cost of not being able
|
||||||
|
to re-do it for a while. Such effects are called *cooldowns*.
|
||||||
|
|
||||||
|
Evennia allows for many ways to implement cooldowns. Here are some
|
||||||
|
ideas.
|
||||||
|
|
||||||
|
Simplest way - single-command, non-persistent cooldown
|
||||||
|
------------------------------------------------------
|
||||||
|
|
||||||
|
This little recipe will limit how often a particular command can be run.
|
||||||
|
Since Commands are class instances, and those are cached in memory, a
|
||||||
|
command instance will remember things you store on it. So just store the
|
||||||
|
current time of execution! Next time the command is run, it just needs
|
||||||
|
to check if it has that time stored, and compare it with the current
|
||||||
|
time to see if a desired delay has passed.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
import time
|
||||||
|
from ev import default_cmds
|
||||||
|
|
||||||
|
class CmdSpellFirestorm(default_cmds.MuxCommand):
|
||||||
|
"""
|
||||||
|
Spell - Firestorm
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
cast firestorm <target>
|
||||||
|
|
||||||
|
This will unleash a storm of flame. You can only release one
|
||||||
|
firestorm every five minutes (assuming you have the mana).
|
||||||
|
"""
|
||||||
|
key = "cast firestorm"
|
||||||
|
locks = "cmd:isFireMage()"
|
||||||
|
|
||||||
|
def func(self):
|
||||||
|
"Implement the spell"
|
||||||
|
|
||||||
|
# check cooldown (5 minute cooldown)
|
||||||
|
if hasattr(self, "lastcast") and time.time()-self.lastcast < 5*60:
|
||||||
|
self.caller.msg("You need to wait before casting this spell again.")
|
||||||
|
return
|
||||||
|
|
||||||
|
#[the spell effect is implemented]
|
||||||
|
|
||||||
|
# if the spell was successfully cast, store the casting time
|
||||||
|
self.lastcast = time.time()
|
||||||
|
|
||||||
|
We just check the ``lastcast`` flag, and update it if everything works
|
||||||
|
out. Simple and very effective since everything is just stored in
|
||||||
|
memory. The drawback of this simple scheme is that it's non-persistent.
|
||||||
|
If you do ``@reload``, the cache is cleaned and all such ongoing
|
||||||
|
cooldowns will be forgotten. It is also limited only to this one
|
||||||
|
command, other commands cannot (easily) check for this value.
|
||||||
|
|
||||||
|
Persistent cooldown
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
This is essentially the same mechanism as the simple one above, except
|
||||||
|
we use the database to store the information which means the cooldown
|
||||||
|
will survive a server reload/reboot. Since commands themselves have no
|
||||||
|
representation in the database, you need to use the caster for the
|
||||||
|
storage.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
#[...]
|
||||||
|
# check cooldown (5 minute cooldown)
|
||||||
|
lastcall = self.caller.db.firestorm_lastcast # returns None if not exist yet
|
||||||
|
if lastcast and time.time() - lastcast < 5*60:
|
||||||
|
self.caller.msg("You need to wait before casting this spell again.")
|
||||||
|
return
|
||||||
|
|
||||||
|
#[the spell effect is implemented]
|
||||||
|
|
||||||
|
# if the spell was successfully cast, store the casting time
|
||||||
|
self.caller.db.firestorm_lastcast = time.time()
|
||||||
|
|
||||||
|
Since we are storing as an `Attribute <Attributes.html>`_, we need to
|
||||||
|
identify the variable as ``firestorm_lastcast`` so we are sure we get
|
||||||
|
the right one (we'll likely have other skills with cooldowns after all).
|
||||||
|
But this method of using cooldowns also has the advantage of working
|
||||||
|
*between* commands - you can for example let all fire-related spells
|
||||||
|
check the same cooldown to make sure the casting of *Firestorm* blocks
|
||||||
|
all fire-related spells for a while. Or, in the case of taking that big
|
||||||
|
swing with the sword, this could now block all other types of attacks
|
||||||
|
for a while before the warrior can recover.
|
||||||
185
docs/sphinx/source/wiki/GamePlanning.rst
Normal file
185
docs/sphinx/source/wiki/GamePlanning.rst
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
Game development tips and tricks
|
||||||
|
================================
|
||||||
|
|
||||||
|
So you have Evennia up and running. You have a great game idea in mind.
|
||||||
|
Now it's time to start cracking! But where to start? Here are some ideas
|
||||||
|
for a workflow. Note that the suggestions on this page are just that -
|
||||||
|
suggestions. Also, they are primarily aimed at a lone hobby designer or
|
||||||
|
a small team developing a game in their free time.
|
||||||
|
|
||||||
|
Phases of Evennia game development
|
||||||
|
==================================
|
||||||
|
|
||||||
|
Below are some minimal steps for getting the first version of a new game
|
||||||
|
world going with players. It's worth to at least make the attempt to do
|
||||||
|
these steps in order even if you are itching to jump ahead in the
|
||||||
|
development cycle. On the other hand, you should also make sure to keep
|
||||||
|
your work fun for you, or motivation will falter. Making a full game is
|
||||||
|
a lot of work as it is, you'll need all your motivation to make it a
|
||||||
|
reality.
|
||||||
|
|
||||||
|
Remember that *99.99999% of all great game ideas never leads to an
|
||||||
|
online game*. So your first all overshadowing goal is to beat those odds
|
||||||
|
and get *something* out the door! *Even* if it's a scaled-down version
|
||||||
|
of your dream game, lacking many "must-have" features! It's better to
|
||||||
|
get it out there and expand on it later than to code in isolation
|
||||||
|
forever until you burn out, loose interest or your hard drive crashes.
|
||||||
|
|
||||||
|
Like is common with online games, getting a game out the door does not
|
||||||
|
mean you are going to be "finished" with the game - most MUDs add
|
||||||
|
features gradually over the course of years - it's often part of the
|
||||||
|
fun!
|
||||||
|
|
||||||
|
1: Planning
|
||||||
|
-----------
|
||||||
|
|
||||||
|
This is what you do before having coded a single line or built a single
|
||||||
|
room. Many prospective game developers are very good at *parts* of this
|
||||||
|
process, namely in defining what their world is "about": The theme, the
|
||||||
|
world concept, cool monsters and so on. This is by all means important -
|
||||||
|
yes critical to the appeal of your game. But it's unfortunately not
|
||||||
|
enough to make your game a reality. To do that you must have an idea of
|
||||||
|
how to actually map those great ideas onto Evennia.
|
||||||
|
|
||||||
|
A good start is to begin by planning out the basic primitives of the
|
||||||
|
game and what they need to be able to do.
|
||||||
|
|
||||||
|
- **Rooms** - consider the most basic room in your game. How "big" is
|
||||||
|
it in a game sense? What should Players be able to do inside it? Is a
|
||||||
|
simple description enough? Can it be dark (description
|
||||||
|
changed/hidden)? Should it have smells, sounds? Weather? Different
|
||||||
|
terrain? How are those to be conveyed? Are there special "magic"
|
||||||
|
rooms that does things to people entering? Can a person hide in the
|
||||||
|
room? Should all rooms have the ability to be this complex or should
|
||||||
|
there be different types of rooms? Evennia allows you to change the
|
||||||
|
very concept of rooms should you be very ambitious, but is that a
|
||||||
|
road you really want to go down for your project?
|
||||||
|
- **Objects** - consider the most basic (non-player-controlled) object
|
||||||
|
in your game. What should a Player be able to do with it? Smash it?
|
||||||
|
If so, will it need some measure of its health? Does it have weight
|
||||||
|
or volume (so you cannot carry an infinite amount of them)? How do
|
||||||
|
you handle multiple identical objects? Try to give rough
|
||||||
|
classifications. Is a weapon a different type of object or are you
|
||||||
|
supposed to be able to fight with a chair as well? What about
|
||||||
|
NPCs/mobs, should they have some sort of AI?
|
||||||
|
- **Systems** - These are the behind-the-scenes features that exist in
|
||||||
|
your game, often without being represented by a specific in-game
|
||||||
|
object. For a role playing game, you need to define chances of
|
||||||
|
success ("rolls") for example. Will weather messages be random in
|
||||||
|
every room or should it follow some sort of realistic pattern over
|
||||||
|
all rooms? Do you have an game-wide economy - if so, how is that
|
||||||
|
supposed to work? If magic is dependent on the position of the
|
||||||
|
planets, the planets must change with time. What about spreading
|
||||||
|
rumors? Mail boxes? Bulletin boards?
|
||||||
|
- **Characters** - to do all those things with the rooms, objects and
|
||||||
|
systems in the game, what will the Characters need to have? What
|
||||||
|
skill will decide if they can "hide" in a room? Wield a chair as a
|
||||||
|
weapon? How to tell how much they can carry or which objects they can
|
||||||
|
smash? Can they gain experience and how? How about skills, classes,
|
||||||
|
attributes?
|
||||||
|
|
||||||
|
A MUD's a lot more involved than you would think and these things hang
|
||||||
|
together in a complex web. It can easily become overwhelming and it's
|
||||||
|
tempting to want *all* functionality right out of the door. Try to
|
||||||
|
identify the basic things that "make" your game and focus on them for
|
||||||
|
the first release. Make a list. Keep future expansions in mind but limit
|
||||||
|
yourself.
|
||||||
|
|
||||||
|
2: Coding
|
||||||
|
---------
|
||||||
|
|
||||||
|
This is the actual work of creating the "game" part of your game. Many
|
||||||
|
"game-designer" types tend to gloss over this bit and jump directly to
|
||||||
|
**Building**. Vice-versa, many "game-coder" types tend to jump directly
|
||||||
|
to this part without doing the **Planning** first. Neither is good and
|
||||||
|
*will* lead to you having to redo all your hard work at least once,
|
||||||
|
probably more.
|
||||||
|
|
||||||
|
Evennia's `Developer Central <DeveloperCentral.html>`_ is focused on how
|
||||||
|
to perform this bit of the development. Evennia tries hard to make this
|
||||||
|
part easier for you, but there is no way around the fact that if you
|
||||||
|
want anything but a very basic Talker-type game you *will* have to bite
|
||||||
|
the bullet and code your game (or find a coder willing to do it for
|
||||||
|
you). Even if you won't code anything yourself, as a designer you need
|
||||||
|
to at least understand the basic paradigms of Evennia, such as objects,
|
||||||
|
commands and scripts and how they hang together. We recommend you go
|
||||||
|
through the `Tutorial World <TutorialWorldIntroduction.html>`_ in detail
|
||||||
|
(as well as skimming its code) to get at least a feel for what is
|
||||||
|
involved behind the scenes.
|
||||||
|
|
||||||
|
During Coding you look back at the things you wanted during the
|
||||||
|
**Planning** phase and try to implement them. Don't be shy to update
|
||||||
|
your plans if you find things easier/harder than you thought. The
|
||||||
|
earlier you revise problems, the easier they will be to fix.
|
||||||
|
`Here <VersionControl.html>`_ are some hints for setting up a sane
|
||||||
|
coding environment.
|
||||||
|
|
||||||
|
"Tech Demo" Building
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This is an integral part of your Coding. It might seem obvious to
|
||||||
|
experienced coders, but it cannot be emphasized enough that you should
|
||||||
|
*test* things on a *small* scale before putting your untested code into
|
||||||
|
a large game-world. The earlier you test, the easier and cheaper it will
|
||||||
|
be to fix bugs and even rework things that didn't work out the way you
|
||||||
|
thought they would. You might even have to go back to the **Planning**
|
||||||
|
phase if your ideas can't handle their meet with reality.
|
||||||
|
|
||||||
|
This means building singular in-game examples. Make one room and one
|
||||||
|
object of each important type and test they work as they should in
|
||||||
|
isolation, then add more if they are supposed to interact with each
|
||||||
|
other in some way. Build a small series of rooms to test how mobs move
|
||||||
|
around ... and so on. In short, a test-bed for your growing code. It
|
||||||
|
should be done gradually until you have a fully functioning (if not
|
||||||
|
guaranteed bug-free) miniature tech demo that shows *all* the features
|
||||||
|
you want in the first release of your game. There does not need to be
|
||||||
|
any game play or even a theme to your tests, but the more testing you do
|
||||||
|
on this small scale, the less headaches you will have in the next phase.
|
||||||
|
|
||||||
|
3: World Building
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Up until this point we've only had a few tech-demo objects in the
|
||||||
|
database. This step is the act of populating the database with a larger,
|
||||||
|
thematic world. Too many would-be developers jump to this stage too soon
|
||||||
|
and then have to go back and rework things on already existing objects.
|
||||||
|
Evennia's typeclass system does allow you to edit the properties of
|
||||||
|
existing objects, but some hooks are only called at object creation, and
|
||||||
|
you are in for a *lot* of unnecessary work if you build stuff en masse
|
||||||
|
without having the underlying code systems in some reasonable shape
|
||||||
|
first.
|
||||||
|
|
||||||
|
So, at this point the "game" bit (Coding + Testing) should be more or
|
||||||
|
less complete, *at least to the level of your initial release*. Building
|
||||||
|
often involves non-coders, so you also get to test whatever custom build
|
||||||
|
systems you have made at this point. You don't have to complete your
|
||||||
|
entire world in one go - just enough to make the game's "feel" come
|
||||||
|
across - an actual world where the intended game play can be tested and
|
||||||
|
roughly balanced. You can always add new areas later, so limit yourself.
|
||||||
|
|
||||||
|
Alpha Release
|
||||||
|
-------------
|
||||||
|
|
||||||
|
As mentioned, don't hold onto your world more than necessary. *Get it
|
||||||
|
out there* with a huge *Alpha* flag and let people try it! Call upon
|
||||||
|
your alpha-players to try everything - they *will* find ways to break
|
||||||
|
your game in ways that you never could have imagined. In Alpha you might
|
||||||
|
be best off to focus on inviting friends and maybe other MUD developers,
|
||||||
|
people who you can pester to give proper feedback and bug reports (there
|
||||||
|
*will* be bugs, there is no way around it). Follow the quick
|
||||||
|
instructions `here <OnlineSetup.html>`_ to make your game visible
|
||||||
|
online.
|
||||||
|
|
||||||
|
Beta Release/Perpetual Beta
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Once things stabilize in Alpha you can move to *Beta* and let more
|
||||||
|
people in. Many MUDs are in `perpetual
|
||||||
|
beta <http://en.wikipedia.org/wiki/Perpetual_beta>`_, meaning they are
|
||||||
|
never considered "finished", but just repeat the cycle of Planning,
|
||||||
|
Coding, Testing and Building over and over as new features get
|
||||||
|
implemented or Players come with suggestions. As the game designer it's
|
||||||
|
up to you to perfect your vision.
|
||||||
|
|
||||||
|
Congratulations, at this point you have joined the small, exclusive
|
||||||
|
crowd who have made their dream game a reality!
|
||||||
15
docs/sphinx/source/wiki/GettingHelp.rst
Normal file
15
docs/sphinx/source/wiki/GettingHelp.rst
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
Getting Help
|
||||||
|
============
|
||||||
|
|
||||||
|
The best way to get help is via our `Google
|
||||||
|
Group <http://evennia.com>`_. Simply post a message with a clear
|
||||||
|
question and you are more than likely to receive a response. This way
|
||||||
|
issues can be tracked over time too.
|
||||||
|
|
||||||
|
If you want more direct discussions with developers and other users,
|
||||||
|
consider dropping into our IRC chat channel
|
||||||
|
`#evennia <http://webchat.freenode.net/?channels=evennia>`_ on the
|
||||||
|
Freenode network. Please note however that you have to be patient if you
|
||||||
|
don't get any response immediately; we are all in very different time
|
||||||
|
zones and many have busy personal lifes. So you might have to lurk about
|
||||||
|
for a while - but if you are patient you'll get noticed eventually!
|
||||||
199
docs/sphinx/source/wiki/OnlineSetup.rst
Normal file
199
docs/sphinx/source/wiki/OnlineSetup.rst
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
Making your game available online
|
||||||
|
=================================
|
||||||
|
|
||||||
|
Evennia development can be made also without any internet connection
|
||||||
|
(except to download updates). At some point however, you are likely to
|
||||||
|
want to make your game visible online, either as part of making it
|
||||||
|
public or to allow other developers or beta testers access to it.
|
||||||
|
|
||||||
|
Using your own computer as a server
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
By far the simplest and probably cheapest option. Evennia will run on
|
||||||
|
your own, home computer. Moreover, since Evennia is its own web server,
|
||||||
|
you don't need to install anything extra to also run its website.
|
||||||
|
|
||||||
|
**Advantages**
|
||||||
|
|
||||||
|
- Free (except for internet cost and electrical bill)
|
||||||
|
- Full control over the server/hardware (it sits right there!)
|
||||||
|
- Easy to set up.
|
||||||
|
- Also suitable for quick setups - e.g. to briefly show off results to
|
||||||
|
your collaborators.
|
||||||
|
|
||||||
|
**Disadvantages**
|
||||||
|
|
||||||
|
- You need a good internet connection, ideally without any
|
||||||
|
upload/download limits/costs.
|
||||||
|
- If you want to run a full game this way, your computer needs to
|
||||||
|
always be on. It could be noisy, and as mentioned, the electrical
|
||||||
|
bill is worth considering.
|
||||||
|
- No support or safety - if your house burns down, so will your game.
|
||||||
|
Also, you are yourself responsible for backups etc (some would
|
||||||
|
consider this an advantage).
|
||||||
|
- Home IP numbers are often dynamically allocated, so for permanent
|
||||||
|
online time you need to set up a DNS to always re-point to the right
|
||||||
|
place (see below).
|
||||||
|
|
||||||
|
Set up your own machine as a server
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Making Evennia available from your own machine is mainly a matter of
|
||||||
|
configuring eventual firewalls to let Evennia's ports through. With
|
||||||
|
Evennia running, note which ports it is using (defaults are 4000 for
|
||||||
|
telnet and 8000 for webclient, we assume them below).
|
||||||
|
|
||||||
|
#. Go to `http://www.whatismyip.com/ <http://www.whatismyip.com/>`_ (or
|
||||||
|
similar site). They should tell you which IP address you are
|
||||||
|
connecting from, e.g. ``230.450.0.222``.
|
||||||
|
#. In your web browser, go to ``http://230.450.0.222:8000``, where the
|
||||||
|
last ``:8000`` is the webclient port Evennia uses. If you see
|
||||||
|
Evennia's website and can connect to the webclient - -congrats,
|
||||||
|
that's it! Try to connect with a traditional MUD-client to the telnet
|
||||||
|
port too, just to make sure.
|
||||||
|
#. Most likely you won't see the Evennia website right away though. This
|
||||||
|
is probably because you have a firewall blocking the ports we need.
|
||||||
|
There could also be a hardware-router between your computer and the
|
||||||
|
Internet - in that case the IP address we see "from the outside" is
|
||||||
|
actually the router's IP, not that of your computer on your local
|
||||||
|
network.
|
||||||
|
|
||||||
|
- You need to let Evennia data out through your router/firewall. How
|
||||||
|
you do that varies with manufacturer and software. But in
|
||||||
|
principle you should look for something called "Port forwarding"
|
||||||
|
or similar. You want to route port 8000/4000 from your computer to
|
||||||
|
an "outgoing port" that the world can see. That latter port does
|
||||||
|
*not* have to have the same number as the internal port! For
|
||||||
|
example, you might want to connect port 8000 to an outgoing port
|
||||||
|
80 - this is the port HTTP requests use and web browsers
|
||||||
|
automatically look for. If you use port 80 you won't have to
|
||||||
|
specify the port number in the url of your browser. If you run
|
||||||
|
other web servers on your machine, that could be an issue though.
|
||||||
|
- I found that I had to reboot my router for this to take effect, so
|
||||||
|
worth trying if you still cannot get to the Evennia website
|
||||||
|
through your browser.
|
||||||
|
|
||||||
|
#. At this point you should be able to invite people to play your game
|
||||||
|
on ``http://230.450.0.222:8000`` or via telnet to ``230.450.0.222``
|
||||||
|
on port ``4000``.
|
||||||
|
|
||||||
|
A complication with using a specific IP address like this is that your
|
||||||
|
home IP might not remain the same. Many ISPs (Internet Service
|
||||||
|
Providers) allocates a dynamic IP to you which could change at any time.
|
||||||
|
When that happens, that IP you told people to go to will be worthless.
|
||||||
|
Also, that long string of numbers is not very pretty, is it? It's hard
|
||||||
|
to remember and not easy to use in marketing your game. What you need is
|
||||||
|
to alias it to a more sensible domain name - an alias that follows you
|
||||||
|
around also when the IP changes.
|
||||||
|
|
||||||
|
#. To set up a domain name alias, we recommend starting with a free
|
||||||
|
domain name from `FreeDNS <http://freedns.afraid.org/>`_. Once you
|
||||||
|
register there (it's free) you have access to tens of thousands
|
||||||
|
domain names that people have "donated" to allow you to use for your
|
||||||
|
own sub domain. For example, ``strangled.net`` is one of those
|
||||||
|
available domains. So tying our IP address to ``strangled.net`` using
|
||||||
|
the subdomain ``evennia`` would mean that one could henceforth direct
|
||||||
|
people to
|
||||||
|
`http://evennia.strangled.net:8000 <http://evennia.strangled.net:8000>`_
|
||||||
|
for their gaming needs - far easier to remember!
|
||||||
|
#. So how do we make this new, nice domain name follow us also if our IP
|
||||||
|
changes? For this we need to set up a little program on our computer.
|
||||||
|
It will check whenever our ISP decides to change our IP and tell
|
||||||
|
FreeDNS that. There are many alternatives to be found from FreeDNS:s
|
||||||
|
homepage, one that works on multiple platforms is
|
||||||
|
`inadyn <http://www.inatech.eu/inadyn/>`_. Get it from their page or,
|
||||||
|
in Linux, through something like
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
apt-get install inadyn
|
||||||
|
|
||||||
|
#. Next, you login to your account on FreeDNS and go to the
|
||||||
|
`Dynamic <http://freedns.afraid.org/dynamic/>`_ page. You should have
|
||||||
|
a list of your subdomains. Click the ``Direct URL`` link and you'll
|
||||||
|
get a page with a text message. Ignore that and look at the URL of
|
||||||
|
the page. It should be ending in a lot of random letters. Everything
|
||||||
|
after the question mark is your unique "hash". Copy this string.
|
||||||
|
#. You now start inadyn with the following command (Linux):
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
inadyn --dyndns_system default@freedns.afraid.org -a <my.domain>,<hash> &
|
||||||
|
|
||||||
|
where ``<my.domain>`` would be ``evennia.strangled.net`` and
|
||||||
|
``<hash>`` the string of numbers we copied from FreeDNS. The ``&``
|
||||||
|
means we run in the background (might not be valid in other
|
||||||
|
operating systems). ``inadyn`` will henceforth check for changes
|
||||||
|
every 60 seconds. You should put the ``inadyn`` command string in a
|
||||||
|
startup script somewhere so it kicks into gear whenever your
|
||||||
|
computer starts.
|
||||||
|
|
||||||
|
Remote hosting
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Your normal "web hotel" will probably not be enough to run Evennia. A
|
||||||
|
web hotel is normally aimed at a very specific usage - delivering web
|
||||||
|
pages, at the most with some dynamic content. The "Python scripts" they
|
||||||
|
refer to on their home pages are usually only intended to be CGI-like
|
||||||
|
scripts launched by their webserver. Even if they allow you shell access
|
||||||
|
(so you can install the Evennia dependencies in the first place),
|
||||||
|
resource usage will likely be very restricted. Running a full-fledged
|
||||||
|
game server like Evennia will probably be shunned upon or be outright
|
||||||
|
impossible. If you are unsure, contact your web hotel and ask about
|
||||||
|
their policy on you running third-party servers that will want to open
|
||||||
|
custom ports.
|
||||||
|
|
||||||
|
The options you probably need to look for are *shell account services*
|
||||||
|
or *VPS:es*. A "Shell account" service means that you get a shell
|
||||||
|
account on a server and can log in like any normal user. By contrast, a
|
||||||
|
*VPS* (Virtual Private Server) service usually means that you get
|
||||||
|
``root`` access, but in a virtual machine.
|
||||||
|
|
||||||
|
**Advantages**
|
||||||
|
|
||||||
|
- Shell accounts/VPS offer more flexibility than your average web hotel
|
||||||
|
- it's the ability to log onto a shared computer away from home.
|
||||||
|
- Usually runs a Linux flavor, making it easy to install Evennia.
|
||||||
|
- Support. You don't need to maintain the server hardware. If your
|
||||||
|
house burns down, at least your game stays online. Many services
|
||||||
|
guarantee a certain level of up-time and might also do regular
|
||||||
|
backups for you (this varies).
|
||||||
|
- Gives a fixed domain name, so no need to mess with IP addresses.
|
||||||
|
|
||||||
|
**Disadvantages**
|
||||||
|
|
||||||
|
- Might be pretty expensive (more so than a web hotel)
|
||||||
|
- Linux flavors might feel unfamiliar to users not used to ssh/PuTTy
|
||||||
|
and the Linux command line.
|
||||||
|
- You are probably sharing the server with many others, so you are not
|
||||||
|
completely in charge. CPU usage might be limited. Also, if the server
|
||||||
|
people decides to take the server down for maintenance, you have no
|
||||||
|
choice but to sit it out (but you'll hopefully be warned ahead of
|
||||||
|
time).
|
||||||
|
|
||||||
|
Set up Evennia on a remote shell account/VPS
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Assuming you know how to connect to your account over ssh/PuTTy you
|
||||||
|
should be able to follow the `Getting Started <GettingStarted.html>`_
|
||||||
|
instructions normally. Ports might be an issue, so make sure you know
|
||||||
|
which ports are available to use.
|
||||||
|
|
||||||
|
If you don't have root access in a virtual machine (but just a normal
|
||||||
|
user-shell account), you will probably *not* have all resources easily
|
||||||
|
available. You need root to use ``apt-get`` for example. In that case
|
||||||
|
you should be able set up a virtualenv install instead, see the last
|
||||||
|
section of `Getting Started <GettingStarted.html>`_ for more info.
|
||||||
|
|
||||||
|
To find commercial solutions, just scour the web for shell access/VPS in
|
||||||
|
your region. One user has for example reported some success with
|
||||||
|
`Webfaction <http://www.webfaction.com/>`_.
|
||||||
|
|
||||||
|
Worth checking out is a free hosting offer especially aimed at MUDs
|
||||||
|
`here <http://zeno.biyg.org/portal.php>`_. An account and some activity
|
||||||
|
at `MUDbytes <http://www.mudbytes.net>`_ is required (that's a good
|
||||||
|
forum to join anyway if you are interested in MUDs). On this
|
||||||
|
mud-specific server you can reserve ports to use as well. From their
|
||||||
|
page it's however unclear which resources are available (only gcc is
|
||||||
|
listed), so one probably needs to use a virtualenv setup to get all
|
||||||
|
dependencies.
|
||||||
138
docs/sphinx/source/wiki/Quirks.rst
Normal file
138
docs/sphinx/source/wiki/Quirks.rst
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
Evennia Quirks
|
||||||
|
==============
|
||||||
|
|
||||||
|
This is a list of various problems or stumbling blocks that people often
|
||||||
|
ask about or report when using (or trying to use) Evennia. These are
|
||||||
|
common stumbling blocks, non-intuitive behaviour and common newbie
|
||||||
|
mistakes when working with Evennia. They are not bugs.
|
||||||
|
|
||||||
|
Actual Evennia bugs should be reported in
|
||||||
|
`Issues <https://code.google.com/p/evennia/issues/list>`_.
|
||||||
|
|
||||||
|
Editing code directly in \`src/\`
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
Don't do this. Doing local changes to ``src`` will eventually conflict
|
||||||
|
with changes done by the Evennia developers. Rather you should import
|
||||||
|
``src``-modules into your own custom modules in ``game/gamesrc`` (or
|
||||||
|
copy&paste them over if you want to expand on the defaults). Next you
|
||||||
|
re-point the relevant links in your ``settings`` file to point to your
|
||||||
|
custom modules instead of the default ones.
|
||||||
|
|
||||||
|
If you want to expand the web interface, copy the entire ``src/web``
|
||||||
|
folder over to ``game/gamesrc`` and change the media links in your
|
||||||
|
``settings`` file.
|
||||||
|
|
||||||
|
If you find that ``src`` lacks some functionality you need, make an
|
||||||
|
`Issue <https://code.google.com/p/evennia/issues/list>`_ of the type
|
||||||
|
*Feature Request*. Or become a part of the Evennia development and
|
||||||
|
submit your own additions to the core.
|
||||||
|
|
||||||
|
Create typeclassed object by calling the typeclass
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
Alas, you cannot create a new Typeclass by just initializing the
|
||||||
|
classname. So ``obj = Object()`` won't do what you want. Whereas
|
||||||
|
Evennia's Typeclasses behave *pretty much* like normal Python classes,
|
||||||
|
this is one of the situations where they don't. You need to use
|
||||||
|
Evennia's create functions to add new objects. So use e.g.
|
||||||
|
``ev.create_object()``, ``ev.create_script()`` etc. (these are defined
|
||||||
|
in ``src/utils/create.py``). This will set up the Typeclass database
|
||||||
|
connection behind the scenes.
|
||||||
|
|
||||||
|
In the same vein, you cannot overload ``__init__()``, ``__setattr__`` or
|
||||||
|
``__getattribute__`` on Typeclasses. Use the hook methods
|
||||||
|
(``at_object_creation()`` etc) instead.
|
||||||
|
|
||||||
|
Web admin to create new Player
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
If you use the default login system and is trying to use the Web admin
|
||||||
|
to create a new Player account, you need to thread carefully. The
|
||||||
|
default login system *requires* the Player object to already have a
|
||||||
|
connected Character object - there is no character creation screen by
|
||||||
|
default. If using the normal mud login screen, a Character with the same
|
||||||
|
name is automatically created and connected to your Player. From the web
|
||||||
|
interface you must do this manually.
|
||||||
|
|
||||||
|
So, when creating the Player, make sure to also create the Character
|
||||||
|
*from the same form* as you create the Player from. This should set
|
||||||
|
everything up for you. Otherwise you need to manually set the "player"
|
||||||
|
property on the Character and the "character" property on the Player to
|
||||||
|
point to each other. You must also set the lockstring of the Character
|
||||||
|
to allow the Player to "puppet" this particular character.
|
||||||
|
|
||||||
|
The default login system is very simple and intended for you to easily
|
||||||
|
get into the game. The more advanced login system in ``contrib/`` along
|
||||||
|
with the example character-creation system does not require such an
|
||||||
|
initial coupling (i.e. you can create the coupling in-game). For your
|
||||||
|
initial experiments, it's easist to create your characters from the
|
||||||
|
normal MUD connection screen instead.
|
||||||
|
|
||||||
|
Mutable attributes and their connection to the database
|
||||||
|
-------------------------------------------------------
|
||||||
|
|
||||||
|
When storing a mutable object (usually a list or a dictionary) in an
|
||||||
|
Attribute
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
object.db.mylist = [1,2,3]
|
||||||
|
|
||||||
|
you should know that the connection to the database is retained also if
|
||||||
|
you later extract that Attribute into another variable (what is stored
|
||||||
|
and retrieved is actually a ``PackedList`` or a ``PackedDict`` that
|
||||||
|
works just like their namesakes except they save themselves to the
|
||||||
|
database when changed). So if you do
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
alist = object.db.mylist
|
||||||
|
alist.append(4)
|
||||||
|
|
||||||
|
this updates the database behind the scenes, so both ``alist`` and
|
||||||
|
``object.db.mylist`` are now ``[1,2,3,4]``. If you don't want this,
|
||||||
|
convert the mutable object to its normal Python form.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
blist = list(object.db.mylist)
|
||||||
|
blist.append(4)
|
||||||
|
|
||||||
|
The property ``blist`` is now ``[1,2,3,4]`` whereas ``object.db.mylist``
|
||||||
|
remains unchanged. You'd need to explicitly re-assign to the ``mylist``
|
||||||
|
Attribute in order to update the database. If you store nested mutables
|
||||||
|
you only need to convert the "outermost" one in order to "break" the
|
||||||
|
connection to the database like this.
|
||||||
|
|
||||||
|
General shakiness of the web admin
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
Since focus has been on getting the underlying python core to work
|
||||||
|
efficiently, the web admin is not quite as stable nor easy to use as
|
||||||
|
we'd like at this point. Also, the web-based html code is, while
|
||||||
|
working, not very pretty or clean. These are areas where we'd much
|
||||||
|
appreciate getting more input and help.
|
||||||
|
|
||||||
|
Known upstream bugs
|
||||||
|
===================
|
||||||
|
|
||||||
|
These are known bugs in in the libraries Evennia uses, i.e. things out
|
||||||
|
of our control.
|
||||||
|
|
||||||
|
Error during manage.py syncdb
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
This error can be seen using Django 1.4 without a *locale* set. It
|
||||||
|
causes a traceback during the ``manage.py syncdb`` phase, just when
|
||||||
|
trying to create the superuser.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
TypeError: decode() argument 1 must be string, not None
|
||||||
|
|
||||||
|
This opaque error means no locale could be found. Not properly handling
|
||||||
|
this is a bug in Django 1.4 reported
|
||||||
|
`here <https://code.djangoproject.com/ticket/16017>`_. You resolve it by
|
||||||
|
setting your locale (this is a good thing to have in any case). See the
|
||||||
|
comments to that bug report for how to do this.
|
||||||
66
docs/sphinx/source/wiki/RSS.rst
Normal file
66
docs/sphinx/source/wiki/RSS.rst
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
RSS
|
||||||
|
===
|
||||||
|
|
||||||
|
`RSS <http://en.wikipedia.org/wiki/RSS>`_ is a format for easily
|
||||||
|
tracking updates on websites. The principle is simple - whenever a site
|
||||||
|
is updated, a small text file is updated. An RSS reader can then
|
||||||
|
regularly go online, check this file for updates and let the user know
|
||||||
|
what's new.
|
||||||
|
|
||||||
|
Evennia allows for connecting any number of RSS feeds to any number of
|
||||||
|
in-game channels. Updates to the feed will be conveniently echoed to the
|
||||||
|
channel. There are many potential uses for this: For example the MUD
|
||||||
|
might use a separate website to host its forums. Through RSS, the
|
||||||
|
players can then be notified when new posts are made. Another example is
|
||||||
|
to let everyone know you updated your dev blog. Admins might also want
|
||||||
|
to track the latest Evennia updates through our own RSS feed
|
||||||
|
`here <http://code.google.com/feeds/p/evennia/updates/basic>`_.
|
||||||
|
|
||||||
|
Configuring RSS
|
||||||
|
---------------
|
||||||
|
|
||||||
|
To use RSS, you first need to install the
|
||||||
|
`feedparser <http://code.google.com/p/feedparser/>`_ python module. It
|
||||||
|
should be easily available through most distributions as
|
||||||
|
*python-feedparser*, otherwise you can download it directly.
|
||||||
|
|
||||||
|
Next you activate RSS support in your config file by settting
|
||||||
|
``RSS_ENABLED=True``.
|
||||||
|
|
||||||
|
Start/reload Evennia as a privileged user. You should now have a new
|
||||||
|
command available, ``@rss2chan``:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@rss2chan <evennia_channel> = <rss_url>
|
||||||
|
|
||||||
|
Setting up RSS, step by step
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
You can connect RSS to any Evennia channel, but for testing, let's set
|
||||||
|
up a new channel "rss".
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@ccreate rss = RSS feeds are echoed to this channel!
|
||||||
|
|
||||||
|
Let's connect Evennia's code-update feed to this channel. Its full url
|
||||||
|
is ``http://code.google.com/feeds/p/evennia/updates/basic``.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@rss2chan rss = http://code.google.com/feeds/p/evennia/updates/basic
|
||||||
|
|
||||||
|
That's it, really. New Evennia updates will now show up as a one-line
|
||||||
|
title and link in the channel. Give the ``@rss2chan`` command on its own
|
||||||
|
to show all connections. To remove a feed from a channel, you specify
|
||||||
|
the connection again (see the list) but add the ``/delete`` switch:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@rss2chan/delete rss = http://code.google.com/feeds/p/evennia/updates/basic
|
||||||
|
|
||||||
|
You can connect any number of RSS feeds to a channel this way. You could
|
||||||
|
also connect them to the same channels as `IRC <IRC.html>`_ and/or
|
||||||
|
`IMC2 <IMC2.html>`_ to have the feed echo to external chat channels as
|
||||||
|
well.
|
||||||
105
docs/sphinx/source/wiki/ServerConf.rst
Normal file
105
docs/sphinx/source/wiki/ServerConf.rst
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
Evennia Server Configurations
|
||||||
|
=============================
|
||||||
|
|
||||||
|
Evennia runs out of the box without any changes to its settings. But
|
||||||
|
there are several important ways to customize the server and expand it
|
||||||
|
with your own plugins.
|
||||||
|
|
||||||
|
Settings file
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The "Settings" file referenced throughout the documentation is the file
|
||||||
|
``game/settings.py``. This is automatically created on the first run of
|
||||||
|
``manage.py syncdb`` (see the `GettingStarted <GettingStarted.html>`_
|
||||||
|
page). The settings file is actually a normal Python module. It's pretty
|
||||||
|
much empty from the start, it just imports all default values from
|
||||||
|
``src/settings_default.py`` into itself.
|
||||||
|
|
||||||
|
You should never edit ``src/settings_default.py``. Rather you should
|
||||||
|
copy&paste the variables you want to change into ``settings.py`` and
|
||||||
|
edit them there. This will overload the previously imported defaults.
|
||||||
|
|
||||||
|
In code, the settings is accessed through
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
# or (shorter):
|
||||||
|
from ev import settings
|
||||||
|
#
|
||||||
|
servername = settings.SERVER_NAME
|
||||||
|
|
||||||
|
Each setting appears as a property on the imported ``settings`` object.
|
||||||
|
You can also explore all possible options with ``ev.settings_full``
|
||||||
|
(this also includes advanced Django defaults that are not not touched in
|
||||||
|
default Evennia).
|
||||||
|
|
||||||
|
It should be pointed out that when importing ``settings`` into your code
|
||||||
|
like this, it will be *read only*. You cannot edit your settings in
|
||||||
|
code. The only way to change an Evennia setting is to edit
|
||||||
|
``game/settings.py`` directly. You most often need to restart the server
|
||||||
|
(possibly also the Portal) before a changed setting becomes available.
|
||||||
|
|
||||||
|
\`game/gamesrc/conf\` directory
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
The ``game/gamesrc/conf/`` directory contains module templates for
|
||||||
|
customizing Evennia. Common for all these is that you should *copy* the
|
||||||
|
template up one level (to ``game/gamesrc/conf/``) and edit the copy, not
|
||||||
|
the original. You then need to change your settings file to point the
|
||||||
|
right variable at your new module. Each template header describes
|
||||||
|
exactly how to use it and which settings variable needs to be changed
|
||||||
|
for Evennia to be able to locate it.
|
||||||
|
|
||||||
|
- ``at_initial_setup.py`` - this allows you to add a custom startup
|
||||||
|
method to be called (only) the very first time Evennia starts (at the
|
||||||
|
same time as user #1 and Limbo is created). It can be made to start
|
||||||
|
your own global scripts or set up other system/world-related things
|
||||||
|
your game needs to have running from the start.
|
||||||
|
- ``at_server_startstop.py`` - this module contains two functions that
|
||||||
|
Evennia will call every time the Server starts and stops respectively
|
||||||
|
- this includes stopping due to reloading and resetting as well as
|
||||||
|
shutting down completely. It's a useful place to put custom startup
|
||||||
|
code for handlers and other things that must run in your game but
|
||||||
|
which has no database persistence.
|
||||||
|
- ``connection_screens.py`` - all global string variables in this
|
||||||
|
module are interpreted by Evennia as a greeting screen to show when a
|
||||||
|
Player first connects. If more than one string variable is present in
|
||||||
|
the module a random one will be picked.
|
||||||
|
- ``lockfuncs.py`` - this is one of many possible modules to hold your
|
||||||
|
own "safe" *lock functions* to make available to Evennia's `lock
|
||||||
|
system <Locks.html>`_.
|
||||||
|
- ``mssp.py`` - this holds meta information about your game. It is used
|
||||||
|
by MUD search engines (which you often have to register with) in
|
||||||
|
order to display what kind of game you are running along with
|
||||||
|
statistics such as number of online players and online status.
|
||||||
|
|
||||||
|
Some other Evennia systems can be customized by plugin modules but has
|
||||||
|
no explicit template in ``conf/examples``:
|
||||||
|
|
||||||
|
- *command parser* - a custom module can be used to totally replace
|
||||||
|
Evennia's default command parser. All this does is to split the
|
||||||
|
incoming string into "command name" and "the rest". It also handles
|
||||||
|
things like error messages for no-matches and multiple-matches among
|
||||||
|
other things that makes this more complex than it sounds. The default
|
||||||
|
parser is *very* generic, so you are most often best served by
|
||||||
|
modifying things further down the line (on the command parse level)
|
||||||
|
than here.
|
||||||
|
- *search-return handler* - this can be used to replace how Evennia
|
||||||
|
handles search results from most of Evennia's in-game searches (most
|
||||||
|
importantly ``self.caller.search`` in commands). It handles the
|
||||||
|
echoing of errors.
|
||||||
|
- *multimatch handler* - this plugin replaces the handling of multiple
|
||||||
|
match errors in searching. By default it allows for separating
|
||||||
|
between same-named matches by use of numbers. Like understanding that
|
||||||
|
"2-ball" should match the second "ball" object if there are two of
|
||||||
|
them.
|
||||||
|
|
||||||
|
!ServerConf
|
||||||
|
-----------
|
||||||
|
|
||||||
|
There is a special database model called ServerConf that stores server
|
||||||
|
internal data and settings such as current player count (for interfacing
|
||||||
|
with the webserver), startup status and many other things. It's rarely
|
||||||
|
of use outside the server core itself but may be good to know about if
|
||||||
|
you are an Evennia developer.
|
||||||
224
docs/sphinx/source/wiki/TickerScripts.rst
Normal file
224
docs/sphinx/source/wiki/TickerScripts.rst
Normal file
|
|
@ -0,0 +1,224 @@
|
||||||
|
Ticker Scripts ("Heartbeats")
|
||||||
|
=============================
|
||||||
|
|
||||||
|
A common way to implement a dynamic MUD is by using "tickers", also
|
||||||
|
known as "heartbeats". Tickers are very common or even unavoidable in
|
||||||
|
other mud code bases, where many systems are hard coded to rely on the
|
||||||
|
concept of the global 'tick'. In Evennia this is very much up to your
|
||||||
|
game and which requirements you have. `Scripts <Scripts.html>`_ are
|
||||||
|
powerful enough to act as any type of counter you want.
|
||||||
|
|
||||||
|
When \_not\_ to use tickers
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Even if you are used to habitually relying on tickers in other code
|
||||||
|
bases, stop and think about what you really need them for. Notably you
|
||||||
|
should think very, *very* hard before implementing a ticker to *catch
|
||||||
|
changes in something that rarely changes*. Think about it - you might
|
||||||
|
have to run the ticker every second to react to changes fast enough.
|
||||||
|
This means that most of the time *nothing* will have changed. You are
|
||||||
|
doing pointless calls (since skipping the call gives the same result as
|
||||||
|
doing it). Making sure nothing's changed might even be a tad expensive
|
||||||
|
depending on the complexity of your system. Not to mention that you
|
||||||
|
might need to run the check on every object in the database. Every
|
||||||
|
second. Just to maintain status quo.
|
||||||
|
|
||||||
|
Rather than checking over and over on the off-chance that something
|
||||||
|
changed, consider a more proactive approach. Can you maybe implement
|
||||||
|
your rarely changing system to *itself* report its change *whenever* it
|
||||||
|
happens? It's almost always much cheaper/efficient if you can do things
|
||||||
|
"on demand". Evennia itself uses hook methods all over for this very
|
||||||
|
reason. The only real "ticker"-like thing in the default set is the one
|
||||||
|
that saves the uptime (which of course *always* changes every call).
|
||||||
|
|
||||||
|
So, in summary, if you consider a ticker script that will fire very
|
||||||
|
often but which you expect to do nothing 99% of the time, ponder if you
|
||||||
|
can handle things some other way.
|
||||||
|
|
||||||
|
Ticker example - night/day system
|
||||||
|
=================================
|
||||||
|
|
||||||
|
Let's take the example of a night/day system. The way we want to use
|
||||||
|
this is to have all outdoor rooms echo time-related messages to the
|
||||||
|
room. Things like "The sun sets", "The church bells strike midnight" and
|
||||||
|
so on.
|
||||||
|
|
||||||
|
One could imagine every `Room <Objects.html>`_ in the game having a
|
||||||
|
script running on themselves that fire regularly. It's however much
|
||||||
|
better (easier to handle and using a lot less computing resources) to
|
||||||
|
use a single, global, ticker script. You create a "global" Script the
|
||||||
|
way you would any Script except you don't assign it to any particular
|
||||||
|
object.
|
||||||
|
|
||||||
|
To let objects use this global ticker, we will utilize a *subscription
|
||||||
|
model*. In short this means that our Script holds an internal list of
|
||||||
|
"subscribing" rooms. Whenever the Script fires it loops through this
|
||||||
|
list and calls a given method on the subscribed object.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
from ev import Script
|
||||||
|
class TimeTicker(Script):
|
||||||
|
"""
|
||||||
|
This implements a subscription model
|
||||||
|
"""
|
||||||
|
def at_script_creation(self):
|
||||||
|
"Called when script is created"
|
||||||
|
self.key = "daynight_ticker"
|
||||||
|
self.interval = 60 * 60 * 2 # every two hours
|
||||||
|
self.persistent = True
|
||||||
|
# storage of subscriptions
|
||||||
|
self.db.subscriptions = []
|
||||||
|
def subscribe(self, obj):
|
||||||
|
"add object to subscription"
|
||||||
|
if obj not in self.db.subscriptions:
|
||||||
|
self.db.subscriptions.append(obj)
|
||||||
|
def unsubscribe(self, obj):
|
||||||
|
"remove object from subscription"
|
||||||
|
try:
|
||||||
|
del_ind = self.db.subscriptions.index(obj)
|
||||||
|
del self.db.subscriptions[del_ind]
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
def list_subscriptions(self):
|
||||||
|
"echo all subscriptions"
|
||||||
|
return self.db.subscriptions
|
||||||
|
def at_repeat(self):
|
||||||
|
"called every self.interval seconds"
|
||||||
|
for obj in self.db.subscriptions:
|
||||||
|
obj.echo_daynight()
|
||||||
|
|
||||||
|
This depends on your subscribing weather rooms defining the
|
||||||
|
``echo_daynight()`` method (presumably outputting some sort of message).
|
||||||
|
|
||||||
|
It's worth noting that this simple recipe can be used for all sorts of
|
||||||
|
tickers objects. Rooms are maybe not likely to unsubscribe very often,
|
||||||
|
but consider a mob that "deactivates" when Characters are not around for
|
||||||
|
example.
|
||||||
|
|
||||||
|
The above TimeTicker-example could be further optimized. All subscribed
|
||||||
|
rooms are after all likely to echo the same time related text. So this
|
||||||
|
text can be pre-set already at the Script level and echoed to each room
|
||||||
|
directly. This way the subscribed objects won't need a custom
|
||||||
|
``echo_daynight()`` method at all.
|
||||||
|
|
||||||
|
Here's the more efficient example (only showing the new stuff).
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
...
|
||||||
|
ECHOES = ["The sun rises in the east.", "It's mid-morning",
|
||||||
|
"It's mid-day", ...]
|
||||||
|
class TimerTicker(Script):
|
||||||
|
...
|
||||||
|
def at_script_creation(self):
|
||||||
|
...
|
||||||
|
self.db.timeindex = 0
|
||||||
|
...
|
||||||
|
def at_repeat(self):
|
||||||
|
"called every self.repeat seconds"
|
||||||
|
echo = ECHOES[self.db.timeindex]
|
||||||
|
# msg_contents() is a standard method, so this
|
||||||
|
# ticker works with any object.
|
||||||
|
for obj in self.db.subscriptions:
|
||||||
|
obj.msg_contents(echo)
|
||||||
|
# resetting/stepping the counter
|
||||||
|
if self.db.timeindex == len(ECHOES) - 1:
|
||||||
|
self.db.timeindex = 0
|
||||||
|
else:
|
||||||
|
self.db.timeindex += 1
|
||||||
|
|
||||||
|
Note that this ticker is unconnected to Evennia's default global in-game
|
||||||
|
time script, and will thus be out of sync with that. A more advanced
|
||||||
|
example would entail this script checking the current game time (in
|
||||||
|
``at_script_creation()`` or in ``at_start()``) so it can pick a matching
|
||||||
|
starting point in its cycle.
|
||||||
|
|
||||||
|
Testing the night/day ticker
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Tickers are really intended to be created and handled from your custom
|
||||||
|
commands or in other coded systems. An "outdoor" room typeclass would
|
||||||
|
probably subscribe to the ticker itself from its
|
||||||
|
``at_object_creation()`` hook. Same would be true for mobs and other
|
||||||
|
objects that could respond to outside stimuli (such as the presence of a
|
||||||
|
player) in order to subscribe/unsubscribe.
|
||||||
|
|
||||||
|
There is no way to create a global script using non-superuser commands,
|
||||||
|
and even if you could use ``@script`` to put it on an object just to
|
||||||
|
test things out, you also need a way to subscribe objects to it.
|
||||||
|
|
||||||
|
With ``@py`` this would be something like this:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@py ev.create_script(TimeTicker) # if persistent=True, this only needs to be done once
|
||||||
|
@py ev.search_script("daynight_ticker").subscribe(self.location)
|
||||||
|
|
||||||
|
|
||||||
|
If you think you will use these kind of ticker scripts a lot, you might
|
||||||
|
want to create your own command for adding/removing subscriptions to
|
||||||
|
them. Here is a complete example:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
import ev
|
||||||
|
class CmdTicker(ev.default_cmds.MuxCommand):
|
||||||
|
"""
|
||||||
|
adds/remove an object to a given ticker
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
@ticker[/switches] tickerkey [= object]
|
||||||
|
Switches:
|
||||||
|
add (default) - subscribe object to ticker
|
||||||
|
del - unsubscribe object from ticker
|
||||||
|
|
||||||
|
This adds an object to a named ticker Script,
|
||||||
|
if such a script exists. Such a script must have
|
||||||
|
subsribe/unsubscripe functionality. If no object is
|
||||||
|
supplied, a list of subscribed objects for this ticker
|
||||||
|
will be returned instead.
|
||||||
|
"""
|
||||||
|
key = "@ticker"
|
||||||
|
locks = "cmd:all()"
|
||||||
|
help_category = "Building"
|
||||||
|
|
||||||
|
def func(self):
|
||||||
|
if not self.args:
|
||||||
|
self.caller.msg("Usage: @ticker[/switches] tickerkey [= object]")
|
||||||
|
return
|
||||||
|
tickerkey = self.lhs
|
||||||
|
# find script
|
||||||
|
script = ev.search_scripts(tickerkey)
|
||||||
|
if not script:
|
||||||
|
self.caller.msg("Ticker %s could not be found." % tickerkey)
|
||||||
|
return
|
||||||
|
# all ev.search_* methods always return lists
|
||||||
|
script = script[0]
|
||||||
|
# check so the API is correct
|
||||||
|
if not (hasattr(script, "subscribe")
|
||||||
|
and hasattr(script, "unsubscribe")
|
||||||
|
and hasattr(script, "list_subscriptions"):
|
||||||
|
self.caller.msg("%s can not be subscribed to." % tickerkey)
|
||||||
|
return
|
||||||
|
if not self.rhs:
|
||||||
|
# no '=' found, just show the subs
|
||||||
|
subs = [o.key for o in script.list_subscripionts()]
|
||||||
|
self.caller.msg(", ".join(subs))
|
||||||
|
return
|
||||||
|
# get the object to add
|
||||||
|
obj = self.caller.search(self.rhs)
|
||||||
|
if not obj:
|
||||||
|
# caller.search handles error messages
|
||||||
|
return
|
||||||
|
elif 'del' in self.switches:
|
||||||
|
# remove a sub
|
||||||
|
script.unsubscribe(obj)
|
||||||
|
self.caller.msg("Unsubscribed %s from %s." % (obj.key, tickerkey)
|
||||||
|
else:
|
||||||
|
# default - add subscription
|
||||||
|
script.subscribe(obj)
|
||||||
|
self.caller.msg("Subscribed %s to ticker %s." % (obj.key, tickerkey))
|
||||||
|
|
||||||
|
This looks longer than it is, most of the length comes from comments and
|
||||||
|
the doc string.
|
||||||
33
docs/sphinx/source/wiki/Tutorials.rst
Normal file
33
docs/sphinx/source/wiki/Tutorials.rst
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
Evennia Tutorials
|
||||||
|
=================
|
||||||
|
|
||||||
|
This is a summary of Evennia documentation available on a step-by-step
|
||||||
|
or tutorial-like format.
|
||||||
|
|
||||||
|
Building
|
||||||
|
--------
|
||||||
|
|
||||||
|
- `Tutorial: Building Quick-start <BuildingQuickstart.html>`_
|
||||||
|
|
||||||
|
Coding basics
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- `Tutorial: Adding a new default
|
||||||
|
command <AddingCommandTutorial.html>`_
|
||||||
|
|
||||||
|
Implementation ideas
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
- `Tutorial: Removing Colour from your game (typeclass method
|
||||||
|
overloading) <RemovingColour.html>`_
|
||||||
|
- `Tutorial: Adding a Command prompt <CommandPrompt.html>`_
|
||||||
|
- `Tutorial: Creating a Zoning system <Zones.html>`_
|
||||||
|
- `Hints: Implementing cooldowns for commands <CommandCooldown.html>`_
|
||||||
|
- `Hints: Ticker Scripts <TickerScripts.html>`_
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
- `The Tutorial
|
||||||
|
World <http://code.google.com/p/evennia/wiki/TutorialWorldIntroduction>`_
|
||||||
|
|
||||||
257
docs/sphinx/source/wiki/VersionControl.rst
Normal file
257
docs/sphinx/source/wiki/VersionControl.rst
Normal file
|
|
@ -0,0 +1,257 @@
|
||||||
|
Setting up a coding environment with version control
|
||||||
|
====================================================
|
||||||
|
|
||||||
|
Version control software allows you to easily backtrack changes to your
|
||||||
|
code, help with sharing your development efforts and more. Even if you
|
||||||
|
are not contributing to Evennia itself, but is "just" developing your
|
||||||
|
own game, having a version control system in place is a good idea. If
|
||||||
|
you want more info, start with the wikipedia article about it
|
||||||
|
`here <http://en.wikipedia.org/wiki/Version_control>`_. Note that this
|
||||||
|
page deals with commands in the Linux operating system. Details may vary
|
||||||
|
for other systems.
|
||||||
|
|
||||||
|
Note: This is only one suggested way to use Mercurial by using separate
|
||||||
|
local clones. You could set up any number of different workflows if it
|
||||||
|
suited you better. See `here <http://mercurial.selenic.com/guide/>`_ for
|
||||||
|
some more examples.
|
||||||
|
|
||||||
|
Using Mercurial
|
||||||
|
===============
|
||||||
|
|
||||||
|
`Mercurial <http://mercurial.selenic.com/>`_ (abbreviated as ``hg``
|
||||||
|
after the chemical symbol for mercury) is a version control system
|
||||||
|
written mainly in Python. It's available for all major platforms.
|
||||||
|
|
||||||
|
First, identify to mercurial by creating a new file ``.hgrc`` in your
|
||||||
|
home directory and put the following content in it:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
[ui]
|
||||||
|
username = MyName <myemail@mail.com>
|
||||||
|
|
||||||
|
You can put a nickname here too if you want. This is just so the system
|
||||||
|
knows how to credit new revisions.
|
||||||
|
|
||||||
|
Setting up
|
||||||
|
----------
|
||||||
|
|
||||||
|
We will here assume you are downloading Evennia for the first time. We
|
||||||
|
will set up a simple environment for hacking your game in. In the end it
|
||||||
|
will look like this:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
evennia/
|
||||||
|
evennia-main
|
||||||
|
evennia-mygame
|
||||||
|
|
||||||
|
Create a new folder ``evennia`` and clone Evennia into it as
|
||||||
|
``evennia-main``:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
hg clone https://code.google.com/p/evennia/ evennia-main
|
||||||
|
|
||||||
|
A new folder ``evennia-main`` has appeared. In it you will find the
|
||||||
|
entire Evennia source repository, including all its commit history -
|
||||||
|
it's really a full copy of what's available on the web.
|
||||||
|
|
||||||
|
We'll let ``evennia-main`` only contain the "clean" Evennia install -
|
||||||
|
it's a good debugging tool to tell you if a bug you find is due to your
|
||||||
|
changes or also visible in the core server. We'll develop our game in
|
||||||
|
another repository instead:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
hg clone evennia-main evennia-mygame
|
||||||
|
|
||||||
|
This will create a new repository ``evennia-mygame`` on your machine. In
|
||||||
|
this directory you now code away, adding and editing things to your
|
||||||
|
heart's content to make your dream game.
|
||||||
|
|
||||||
|
Example work flow
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
First we make sure our copy of Evennia is up-to-date. Go to
|
||||||
|
``evennia-main``:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
cd evennia-main
|
||||||
|
hg pull
|
||||||
|
|
||||||
|
Mercurial goes online and gets the latest Evennia revision from the
|
||||||
|
central repository, merging it automatically into your repository. It
|
||||||
|
will tell you that you need to ``update`` to incoorporate the latest
|
||||||
|
changes. Do so.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
hg update
|
||||||
|
|
||||||
|
So ``evennia-main`` is now up-to-date. If you want, you can review the
|
||||||
|
changes and make sure things work as they should. Finally go to
|
||||||
|
``evennia-mygame`` and pull the changes into that as well.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
|
||||||
|
cd ../evennia-mygame
|
||||||
|
hg commit # (only if you had any changes)
|
||||||
|
hg pull ../evennia-main
|
||||||
|
hg update
|
||||||
|
|
||||||
|
You can now continue to hack away in ``evennia-mygame`` to build your
|
||||||
|
game. Maybe you define new commands, economic systems, create batchfiles
|
||||||
|
or what have you. If you create any new files, you must tell Mercurial
|
||||||
|
to track them by using the ``add`` command:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
hg add <filename(s)>
|
||||||
|
|
||||||
|
Check the current status of the version control with
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
hg status
|
||||||
|
|
||||||
|
If you don't get any return value, you haven't made any changes since
|
||||||
|
last commit. Otherwise you will get a list of modified files.
|
||||||
|
|
||||||
|
It's usually a good idea to commit your changes often - it's fast and
|
||||||
|
only local - you will never commit anything online. This gives you a
|
||||||
|
"save" snapshot of your work that you can get back to.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
hg commit
|
||||||
|
|
||||||
|
This will open a text editor where you can add a message detailing your
|
||||||
|
changes. These are the messages you see in the Evennia update/log list.
|
||||||
|
If you don't want to use the editor you can set the message right away
|
||||||
|
with the ``-m`` flag:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
hg commit -m "This should fix the bug Sarah talked about."
|
||||||
|
|
||||||
|
If you did changes that you wish you hadn't, you can easily get rid of
|
||||||
|
everything since your latest commit:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
hg revert --all
|
||||||
|
|
||||||
|
Instead of ``--all`` you can also choose to revert individual files.
|
||||||
|
|
||||||
|
You can view the full log of committed changes with
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
hg log
|
||||||
|
|
||||||
|
See the Mercurial manuals for learning more about useful day-to-day
|
||||||
|
commands, and special situations such as dealing with text collisions
|
||||||
|
etc.
|
||||||
|
|
||||||
|
Sharing your code with the world
|
||||||
|
================================
|
||||||
|
|
||||||
|
The most common case of this is when you have fixed an Evennia bug and
|
||||||
|
want to make the fix available to Evennia maintainers. But you can also
|
||||||
|
share your work with other people on your game-development team if you
|
||||||
|
don't worry about the changes being publicly visible.
|
||||||
|
|
||||||
|
Let's take the example of debugging Evennia. Go online and create an
|
||||||
|
"online clone" of Evennia as described `here <Contributing.html>`_. Pull
|
||||||
|
this repo to your local machine -- so if your clone is named
|
||||||
|
``my-evennia-fixes``, you do something like this:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
hg clone https://<yourname>@code.google.com/r/my-evennia-fixes evennia-fixes
|
||||||
|
|
||||||
|
You will now have a new folder ``evennia-fixes``. Let's assume we want
|
||||||
|
to use this to push bug fixes to Evennia. It works like any other
|
||||||
|
mercurial repository except you also have push-rights to your online
|
||||||
|
clone from it. When working, you'd first update it to the latest
|
||||||
|
upstream Evennia version:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
cd evennia-main
|
||||||
|
hg pull
|
||||||
|
hg update
|
||||||
|
cd ../evennia-fixes
|
||||||
|
hg pull ../evennia-main
|
||||||
|
hg update
|
||||||
|
|
||||||
|
Now you fix things in ``evennia-fixes``. Commit your changes as
|
||||||
|
described above. Make sure to make clear and descriptive commit messages
|
||||||
|
so it's easy to see what you intended. You can do any number of commits
|
||||||
|
as you work. Once you are at a stage where you want to show what you did
|
||||||
|
to the world, you push all the so-far committed changes to your online
|
||||||
|
clone:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
hg push
|
||||||
|
|
||||||
|
(You'd next need to tell Evennia devs that they should merge your
|
||||||
|
brilliant changes into Evennia proper. Create a new
|
||||||
|
`Issue <https://code.google.com/p/evennia/issues/list>`_ of type *Merge
|
||||||
|
Request*, informing them of this.)
|
||||||
|
|
||||||
|
Apart from supporting Evennia itself you can have any number of online
|
||||||
|
clones for different purposes, such as sharing game code or collaborate
|
||||||
|
on solutions. Just pull stuff from whichever relevant local repository
|
||||||
|
you have (like ``evennia-mygame``) and push to a suitably named online
|
||||||
|
clone so people can get to it.
|
||||||
|
|
||||||
|
Sharing your code only with a small coding team
|
||||||
|
===============================================
|
||||||
|
|
||||||
|
Creating a publicly visible online clone might not be what you want for
|
||||||
|
all parts of your development process - you may prefer a more private
|
||||||
|
venue when sharing your revolutionary work with your team.
|
||||||
|
|
||||||
|
An online hosting provider offering private repositories is probably
|
||||||
|
your best bet. For example, if all your contributors are registered on
|
||||||
|
`BitBucket <https://bitbucket.org/>`_, that service offers free
|
||||||
|
"private" repositories that you could use for this.
|
||||||
|
|
||||||
|
An alternative simple way to share your work with a limited number of
|
||||||
|
people is to use mercurial's own simple webserver and let them connect
|
||||||
|
directly to your machine:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
cd evennia-mygame
|
||||||
|
hg serve -p 8500
|
||||||
|
|
||||||
|
(the port was changed because the default port is 8000 and that is
|
||||||
|
normally used by Evennia's own webserver). Find out the IP address of
|
||||||
|
your machine visible to the net (make sure you know your firewall setup
|
||||||
|
etc). Your collaborators will then be able to first review the changes
|
||||||
|
in their browser:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
firefox http://192.168.178.100:8500
|
||||||
|
|
||||||
|
and pull if they like what they see:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
hg pull http://192.168.178.100:8500
|
||||||
|
|
||||||
|
See `here <http://mercurial.selenic.com/wiki/hgserve>`_ for more
|
||||||
|
information on using ``hg serve``.
|
||||||
|
|
||||||
|
Mercurial's in-built webserver is *very* simplistic and not particularly
|
||||||
|
robust. It only allows one connection at a time, lacks authorization and
|
||||||
|
doesn't even allow your collaborators to ``push`` data to you (there is
|
||||||
|
nothing stopping them to set up a server of their own so you can pull
|
||||||
|
from them though).
|
||||||
141
docs/sphinx/source/wiki/Zones.rst
Normal file
141
docs/sphinx/source/wiki/Zones.rst
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
Zones
|
||||||
|
=====
|
||||||
|
|
||||||
|
Problem
|
||||||
|
-------
|
||||||
|
|
||||||
|
Say you create a room named *Meadow* in your nice big forest MUD. That's
|
||||||
|
all nice and dandy, but what if you, in the other end of that forest
|
||||||
|
want another *Meadow*? As a game creator, this can cause all sorts of
|
||||||
|
confusion. For example, teleporting to *Meadow* will now give you a
|
||||||
|
warning that there are two *Meadow* s and you have to select which one.
|
||||||
|
It's no problem to do that, you just choose for example to go to
|
||||||
|
``2-meadow``, but unless you examine them you couldn't be sure which of
|
||||||
|
the two sat in the magical part of the forest and which didn't.
|
||||||
|
|
||||||
|
Another issue is if you want to group rooms in geographic regions for
|
||||||
|
example. Let's say the "normal" part of the forest should have separate
|
||||||
|
weather patterns from the magical part. Or maybe a magical disturbance
|
||||||
|
echoes through all magical-forest rooms. It would then be convenient to
|
||||||
|
be able to simply find all rooms that are "magical" so you could send
|
||||||
|
messages to them.
|
||||||
|
|
||||||
|
Zones in Evennia
|
||||||
|
----------------
|
||||||
|
|
||||||
|
*Zones* try to separate rooms by global location. In our example we
|
||||||
|
would separate the forest into two parts - the magical and the
|
||||||
|
non-magical part. Each have a *Meadow* and rooms belonging to each part
|
||||||
|
should be easy to retrieve.
|
||||||
|
|
||||||
|
Many MUD codebases hardcode zones as part of the engine and database.
|
||||||
|
Evennia does no such distinction due to the fact that rooms themselves
|
||||||
|
are meant to be customized to any level anyway. Below is just *one*
|
||||||
|
example of how one could add zone-like functionality to a game.
|
||||||
|
|
||||||
|
All objects have a *key* property, stored in the database. This is the
|
||||||
|
primary name of the object. But it can also have any number of *Aliases*
|
||||||
|
connected to itself. This allows Players to refer to the object using
|
||||||
|
both key and alias - a "big red door" can also be referred to as the
|
||||||
|
alias "door". Aliases are actually separate database entities and are as
|
||||||
|
such very fast to search for in the database, about as fast as searching
|
||||||
|
for the object's primary key in fact.
|
||||||
|
|
||||||
|
This makes Aliases prime candiates for implementing zones. All you need
|
||||||
|
to do is to come up with a consistent aliasing scheme. Here's one
|
||||||
|
suggestion:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
#zonename|key
|
||||||
|
|
||||||
|
So say we (arbitrarily) divide our forest example into the zones
|
||||||
|
``magicforest`` and ``normalforest``. These are the added aliases we use
|
||||||
|
for the respective *Meadow* 's:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
#magicforest|meadow
|
||||||
|
#normalforest|meadow
|
||||||
|
|
||||||
|
The primary key of each will still be *Meadow*, and players will still
|
||||||
|
see that name. We can also add any number of other Aliases to each
|
||||||
|
meadow if we want. But we will also henceforth always be able to
|
||||||
|
uniquely identify the right meadow by prepending its primary key name
|
||||||
|
with ``#zonename|``.
|
||||||
|
|
||||||
|
Enforcing zones
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Maybe you feel that this usage of aliases for zones is somehow loose and
|
||||||
|
ad-hoc. It is, and there is no guarantee that a builder would follow the
|
||||||
|
naming convention - unless you force them. And you can do that easily by
|
||||||
|
changing for example the ``@dig`` `Command <Commands.html>`_ to require
|
||||||
|
the zone to be given:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@dig zone|roomname:typeclass = north;n, south;s
|
||||||
|
|
||||||
|
Just have the ``@dig`` command auto-add an alias of the correct format
|
||||||
|
and hey-presto! A functioning zone system! An even more convenient way
|
||||||
|
to enforce zones would be for the new room to inherit the zone from the
|
||||||
|
room we are building from.
|
||||||
|
|
||||||
|
Overload the default ``search`` method on a typeclass for further
|
||||||
|
functionality:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
def search(self, ostring, zone=None, *args, **kwargs):
|
||||||
|
if zone:
|
||||||
|
ostring = "#%s|%s" % (ostring, zone)
|
||||||
|
return self.dbobj.search(ostring, *args, **kwargs)
|
||||||
|
|
||||||
|
You will then be able to do, from commands:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
meadow_obj = self.caller.search("meadow", zone="magicforest")
|
||||||
|
|
||||||
|
and be sure you are getting the magical meadow, not the normal one.
|
||||||
|
|
||||||
|
You could also easily build search queries searching only for rooms with
|
||||||
|
aliases starting with ``#magicforest|``. This would allow for easy
|
||||||
|
grouping and retrieving of all rooms in a zone for whatever need to
|
||||||
|
have.
|
||||||
|
|
||||||
|
Evennia's open solution to zones means that you have much more power
|
||||||
|
than in most MUD systems. There is for example no reason why you have to
|
||||||
|
group and organize only rooms with this scheme.
|
||||||
|
|
||||||
|
Using typeclasses and inheritance for zoning
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
The above alias scheme is basically a way to easily find and group
|
||||||
|
objects together at run-time - a way to label, if you will. It doesn't
|
||||||
|
instill any sort of functional difference between a magical forest room
|
||||||
|
and a normal one. For this you will need to use Typeclasses. If you know
|
||||||
|
that a certain typeclass of room will always be in a certain zone you
|
||||||
|
could even hard-code the zone in the typeclass rather than enforce the
|
||||||
|
``@dig`` command to do it:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
class MagicalForestRoom(Room)
|
||||||
|
def at_object_creation(self):
|
||||||
|
...
|
||||||
|
self.aliases = "#magicforest|%s" % self.key
|
||||||
|
...
|
||||||
|
class NormalForestRoom(Room)
|
||||||
|
def at_object_creation(self):
|
||||||
|
...
|
||||||
|
self.aliases = "#normalforest|%s" % self.key
|
||||||
|
...
|
||||||
|
|
||||||
|
Of course, an alternative way to implement zones themselves is to have
|
||||||
|
all rooms/objects in a zone inherit from a given typeclass parent - and
|
||||||
|
then limit your searches to objects inheriting from that given parent.
|
||||||
|
The effect would be the same and you wouldn't need to implement any
|
||||||
|
ad-hoc aliasing scheme; but you'd need to expand the search
|
||||||
|
functionality to properly search the inheritance tree.
|
||||||
105
docs/sphinx/source/wiki/evAPI.rst
Normal file
105
docs/sphinx/source/wiki/evAPI.rst
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
\`ev\` - Evennia's flat API
|
||||||
|
===========================
|
||||||
|
|
||||||
|
Evennia consists of many components, some of which interact in rather
|
||||||
|
complex ways. One such example is the Typeclass system which is
|
||||||
|
implemented across four different folders in ``src/``. This is for
|
||||||
|
efficiency reasons and to avoid code duplication, but it means that it
|
||||||
|
can be a bit of a hurdle to understand just what connects to what and
|
||||||
|
which properties are actually available/inherited on a particular game
|
||||||
|
entity you want to use.
|
||||||
|
|
||||||
|
Evennia's ``ev`` API (Application Programming Interface) tries to help
|
||||||
|
with this. ``ev.py`` sits in evennia's root directory which means you
|
||||||
|
can import it from your code simply with ``import ev``. The ``ev``
|
||||||
|
module basically implements shortcuts to the innards of ``src/``. The
|
||||||
|
goal is to give a very "flat" structure (as opposed to a deeply nested
|
||||||
|
one). Not only is this a Python recommendation, it also makes it much
|
||||||
|
easier to see what you have.
|
||||||
|
|
||||||
|
Exploring \`ev\`
|
||||||
|
----------------
|
||||||
|
|
||||||
|
To check out evennia interactively, it's recommended you use a more
|
||||||
|
advanced Python interpreter, like `ipython <http://ipython.org/>`_. With
|
||||||
|
ipython you can easily read module headers and help texts as well as
|
||||||
|
list possible completions.
|
||||||
|
|
||||||
|
Start a python interactive shell, then get ``ev``:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
import ev
|
||||||
|
|
||||||
|
In ipython we can now do for example ``ev?`` to read the API's help
|
||||||
|
text. Using eg. ``ev.Object?`` will read the documentation for the
|
||||||
|
``Object`` typeclass. Use ``??`` to see source code. Tab on ``ev.`` to
|
||||||
|
see what ``ev`` contains.
|
||||||
|
|
||||||
|
Some highlights
|
||||||
|
---------------
|
||||||
|
|
||||||
|
- ``Object, Player, Script, Room, Character, Exit`` - direct links to
|
||||||
|
the most common base classes in Evennia.
|
||||||
|
- ``search_*`` - shortcuts to the search functions in
|
||||||
|
``src.utils.search``, such as ``search_object()`` or
|
||||||
|
``search_script()``
|
||||||
|
- ``create_*`` - are convenient links to all object-creation functions.
|
||||||
|
Note that all Typeclassed objects *must* be created with methods such
|
||||||
|
as these (or their parents in ``src.utils.create``) to make
|
||||||
|
Typeclasses work.
|
||||||
|
- ``managers`` - this is a container object that groups shortcuts to
|
||||||
|
initiated versions of Evennia's django *managers*. So
|
||||||
|
``managers.objects`` is in fact equivalent to ``ObjectDB.objects``
|
||||||
|
and you can do ``managers.objects.all()`` to get a list of all
|
||||||
|
database objects. The managers allows to explore the database in
|
||||||
|
various ways. To use, do ``from ev import manager`` and access the
|
||||||
|
desired manager on the imported ``managers`` object.
|
||||||
|
- default\_cmds - this is a container on ``ev`` that groups all default
|
||||||
|
commands and command sets under itself. Do
|
||||||
|
``from ev import default_cmds`` and you can then access any default
|
||||||
|
command from the imported ``default_cmds`` object.
|
||||||
|
- ``utils, logger, gametime, ansi`` are various utilities. Especially
|
||||||
|
utils contains many useful functions described
|
||||||
|
`here <CodingUtils.html>`_.
|
||||||
|
- ``syscmdkeys`` is a container that holds all the system-command keys
|
||||||
|
needed to define system commands. Similar to the ``managers``
|
||||||
|
container, you import this and can then access the keys on the
|
||||||
|
imported ``syscmdkeys`` object.
|
||||||
|
|
||||||
|
To remember when importing from \`ev\`
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
Properties on ``ev`` are *not* modules in their own right. They are just
|
||||||
|
shortcut properties stored in the ``ev.py`` module. That means that you
|
||||||
|
cannot use dot-notation to ``import`` nested module-names over ``ev``.
|
||||||
|
The rule of thumb is that you cannot use ``import`` for more than one
|
||||||
|
level down. Hence you can do
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
import ev
|
||||||
|
print ev.default_cmds.CmdLook
|
||||||
|
|
||||||
|
or import one level down
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
from ev import default_cmds
|
||||||
|
print default_cmds.CmdLook
|
||||||
|
|
||||||
|
but you *cannot* import two levels down
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
from ev.default_cmds import CmdLook # error!
|
||||||
|
|
||||||
|
This will give you an ``ImportError`` telling you that the module
|
||||||
|
``default_cmds`` cannot be found. This is not so strange -
|
||||||
|
``default_cmds`` is just a variable name in the ``ev.py`` module, it
|
||||||
|
does not exist outside of it.
|
||||||
|
|
||||||
|
As long as you keep this in mind, you should be fine. If you really want
|
||||||
|
full control over which level of package you import you can always
|
||||||
|
bypass ``ev`` and import directly from ``src/``. If so, look at
|
||||||
|
``ev.py`` to see where it imports from.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue