Updated ReST documentation.

This commit is contained in:
Griatch 2012-10-28 16:39:18 +01:00
parent d885ef6ab3
commit b15d1fa683
8 changed files with 518 additions and 61 deletions

View file

@ -6,6 +6,7 @@ Alphabetical page index
:titlesonly:
wiki/AddingCommandTutorial
wiki/AddingObjectTypeclassTutorial
wiki/AdminDocs
wiki/ApacheConfig
wiki/AsyncProcess
@ -17,6 +18,7 @@ Alphabetical page index
wiki/BuilderDocs
wiki/BuildingPermissions
wiki/BuildingQuickstart
wiki/Caches
wiki/ChoosingAnSQLServer
wiki/CodingIntroduction
wiki/CodingUtils

View file

@ -0,0 +1,178 @@
Creating your own object classes
================================
Evennia comes with a few very basic classes of in-game entities:
::
Object
|
Character
Room
Exit
So the more specific object-types are just children of the basic
``Object`` class (technically these are all
`Typeclasses <Typeclassed.html>`_ entities, but for this tutorial, just
treat them as normal Python classes).
For your own game you will most likely want to expand on these very
simple beginnings. It's normal to want your Characters to have various
attributes. Maybe Rooms should hold extra information or even *all*
Objects in your game should have properties not included in basic
Evennia.
First a brief overview of how Evennia handles its object classes. The
default classes are defined under ``src/objects/objects.py``. You can
look at them there or you can bring up a Python prompt and interactively
examine ``ev.Object``, ``ev.Room`` etc.
You will create your own object classes in ``game/gamesrc/objects``. You
should normally inherit from the default classes (normal Python class
inheritance) and go from there. Once you have a working new class you
can immediately start to create objects in-game inheriting from that
class.
If you want to change the *default* object classes, there is one more
step. Evennia's default commands and internal creation mechanisms look
at a range of variables in your ``settings.py`` file to determine which
are the "default" classes. These defaults are used by the vanilla
creation commands if you don't specify the typeclass specifically. They
are also used as a fallback by Evennia if there are errors in other
typeclasses, so make sure that your new default class is bug-free.
The following sections spells this out more explicitly.
Create a new Typeclass
----------------------
This is the simplest case. Say you want to create a new "Heavy" object
that characters should not have the ability to pick up.
#. Go to ``game/gamesrc/objects/``. It should already contain a
directory ``examples/``.
#. Create a new module here, named ``heavy.py``. Alternatively you can
copy ``examples/object.py`` up one level and rename that file to
``heavy.py`` instead - you will then have a template to start from.
#. Code away in the ``heavy.py`` module, implementing the chair
functionality. See `Objects <Objects.html>`_ for more details and the
example class below. Let's call the typeclass simply ``Heavy``.
#. Once you are done, log into the game with a build-capable account and
do ``@create/drop rock:heavy.Heavy`` to drop a new heavy "rock"
object in your location. Note that you have to log in as a
non-superuser (i.e. not as User #1) when trying to get the rock in
order to see its heavy effects.
That's it. Below is a ``Heavy`` Typeclass that you could try. Note that
the `lock <Locks.html>`_ and `Attribute <Attribute.html>`_ here set in
the typeclass could just as well have been set using commands in-game,
so this is a *very* simple example.
::
# file game/gamesrc/objects/heavy.py
from ev import Object
class Heavy(Object):
"Heavy object"
at_object_creation(self):
"Called whenever a new object is created"
# lock the object down by default
self.locks.add("get:false()")
# the default "get" command looks for this Attribute in order
# return a customized error message (we just happen to know
# this, you'd have to look at the code of the 'get' command to
# find out).
self.db.get_err_msg = "This is too heavy for you to pick up."
Change Default Rooms, Exits, Character Typeclass
------------------------------------------------
This is only slightly more complex than creating any other Typeclass. In
fact it only includes one extra step - telling Evennia to use the new
default.
Let's say we want to change Rooms to use our new typeclass ``MyRoom``.
#. Create a new module in ``game/gamesrc/objects/myroom.py`` and code
your ``MyRoom`` typeclass as described in the previous section. Make
sure to test it by digging a few rooms of this class (e.g.
``@dig Hall:myroom.MyRoom``).
#. Once you are sure the new class works as it should, edit
``game/settings.py`` and add
``BASE_ROOM_TYPECLASS="game.gamesrc.objects.myroom.MyRoom"``.
#. Reload Evennia.
For example the ``@dig`` and ``@tunnel`` commands will henceforth use
this new default when digging new rooms whenever you don't give a
typeclass explicitly. For the other sub-types, change
``BASE_CHARACTER_TYPECLASS`` (used by character creation commands) and
``BASE_EXIT_TYPECLASS`` (used by ``@dig``/``@tunnel`` etc) respectively.
Change the default Object Typeclass
-----------------------------------
Changing the root ``Object`` class works identically to changing the
``Character``, ``Room`` or ``Exit`` typeclass. After having created your
new typeclass, set ``settings.BASE_EXIT_TYPECLASS`` to point to your new
class. Let's say you call your new default ``Object`` class
``MyObject``.
There however one important further thing to remember: ``Characters``,
``Rooms`` and ``Exits`` will still inherit from the *old* ``Object`` at
this point (not ``MyObject``). This is by design - depending on your
type of game, you may not need some or all of these subclasses to
inherit any of the new stuff you put in ``MyObject``.
If you do want that however, you need to also overload these subclasses.
For each of the ``Character``, ``Room`` and ``Exit`` you want to
customize, do the following:
#. Create a new module in ``game/gamesrc/``, e.g. ``mycharacter.py``
etc. A good flexible solution for overloading only parts of the
default is to make inheriting classes *multi-inherited* (see below).
As a place-holder you can make the class empty for now (just put
``pass`` in it).
#. In your ``settings.py`` file, add and define
``BASE_CHARACTER_TYPECLASS``, ``BASE_ROOM_TYPECLASS`` and
``BASE_EXIT_TYPECLASS`` to point to your new typeclasses.
#. Reload Evennia.
This will give you maximum flexibility with creating and expanding your
own types of rooms, characters and exit objects (or not). Below is an
example of a new ``myroom.py``:
::
# file gamesrc/objects/myroom.py
from ev import Object
from gamesrc.objects.myobject import MyObject
# we use multi-inheritance, this will primarily use MyObject,
# falling back to the default Object for things MyObject do
# not overload
class MyRoom(MyObject, Object):
"My own expandable room class"
pass
Notes
=====
All above examples puts each class in its own module. This makes it easy
to find, but it is really up to you how you organize things. There is
nothing stopping you from putting all base classes into one module, for
example.
Also remember that Python may dynamically rename module classes as they
are imported. So if you feel it annoying to have to refer to your new
default as ``MyObject`` all the time, you can also import them to
another name like in the below example:
::
from ev import Object as BaseObject
from gamesrc.objects.myobject import MyObject as Object
class MyRoom(Object, BaseObject):
[...]
This doesn't actually change the meaning of the code, but might make the
relationships clearer inside a module.

View file

@ -161,10 +161,17 @@ situations though.
What types of data can I save in an Attribute?
----------------------------------------------
If you store a single object (that is, not an iterable list of objects),
you can practically store any Python object that can be
`pickled <http://docs.python.org/library/pickle.html>`_. Evennia uses
the ``pickle`` module to serialize Attribute data into the database.
Evennia uses the ``pickle`` module to serialize Attribute data into the
database. So if you store a single object (that is, not an iterable list
of objects), you can practically store any Python object that can be
`pickled <http://docs.python.org/library/pickle.html>`_.
If you store many objects however, you can only store them using normal
Python structures (i.e. in either a *tuple*, *list*, *dictionary* or
*set*). All other iterables (such as custom containers) are converted to
*lists* by the Attribute (see next section for the reason for this).
Since you can nest dictionaries, sets, lists and tuples together in any
combination, this is usually not much of a limitation.
There is one notable type of object that cannot be pickled - and that is
a Django database object. These will instead be stored as a wrapper
@ -177,19 +184,12 @@ recursively traverse all iterables to make sure all database objects in
them are stored safely. So for efficiency, it can be a good idea to
avoid deeply nested lists with objects if you can.
To store several objects, you may only use python *lists*,
*dictionaries* or *tuples* to store them. If you try to save any other
form of iterable (like a ``set`` or a home-made class), the Attribute
will convert, store and retrieve it as a list instead. Since you can
nest dictionaries, lists and tuples together in any combination, this is
usually not a limitation you need to worry about.
*Note that you could fool the safety check if you for example created
custom, non-iterable classes and stored database objects in them. So to
make this clear - saving such an object is **not supported** and will
probably make your game unstable. Store your database objects using
lists, tuples, dictionaries or a combination of the three and you should
be fine.*
lists, tuples, dictionaries, sets or a combination of the four and you
should be fine.*
Examples of valid attribute data:
@ -232,13 +232,13 @@ Example of non-supported save:
Retrieving Mutable objects
--------------------------
A side effect of the way Evennia stores Attributes is that Python Lists
and Dictionaries (only) are handled by custom objects called PackedLists
and PackedDicts. These behave just like normal lists and dicts except
they have the special property that they save to the database whenever
new data gets assigned to them. This allows you to do things like
``self.db.mylist[4]`` = val without having to extract the mylist
Attribute into a temporary variable first.
A side effect of the way Evennia stores Attributes is that Python Lists,
Dictionaries and Sets are handled by custom objects called PackedLists,
PackedDicts and PackedSets. These behave just like normal lists and
dicts except they have the special property that they save to the
database whenever new data gets assigned to them. This allows you to do
things like ``self.db.mylist[4]`` = val without having to extract the
mylist Attribute into a temporary variable first.
There is however an important thing to remember. If you retrieve this
data into another variable, e.g. ``mylist2 = obj.db.mylist``, your new
@ -287,9 +287,92 @@ stop any changes to the structure from updating the database.
# iterable is a tuple, so we can edit the internal list as we want
# without affecting the database.
Notes
-----
Locking and checking Attributes
-------------------------------
Attributes are normally not locked down by default, but you can easily
change that for individual Attributes (like those that may be
game-sensitive in games with user-level building).
First you need to set a *lock string* on your Attribute. Lock strings
are specified `here <Locks.html>`_. The relevant lock types are
- *attrread* - limits who may read the value of the Attribute
- *attredit* - limits who may set/change this Attribute
You cannot use e.g. ``obj.db.attrname`` handler to modify Attribute
objects (such as setting a lock on them - you will only get the
Attribute *value* that way, not the actual Attribute *object*. You get
the latter with ``get_attribute_obj`` (see next section) which allows
you to set the lock something like this:
::
obj.get_attribute_obj.locks.add("attread:all();attredit:perm(Wizards)")
A lock is no good if nothing checks it -- and by default Evennia does
not check locks on Attributes. You have to add a check to your
commands/code wherever it fits (such as before setting an Attribute).
::
# in some command code where we want to limit
# setting of a given attribute name on an object
attr = obj.get_attribute_obj(attrname, default=None)
if not (attr and attr.locks.check(caller, 'attredit', default=True)):
caller.msg("You cannot edit that Attribute!")
return
# edit the Attribute here
Note that in this example this lock check will default to ``True`` if no
lock was defined on the Attribute (which is the case by default). You
can set this to False if you know all your Attributes always check
access in all situations. If you want some special control over what the
default Attribute access is (such as allowing everyone to view, but
never allowing anyone to edit unless explicitly allowing it with a
lock), you can use the ``secure_attr`` method on Typeclassed objects
like this:
::
obj.secure_attr(caller, attrname, value=None,
delete=False,
default_access_read=True,
default_access_edit=False,
default_access_create=True)
The secure\_attr will try to retrieve the attribute value of an existing
Attribute if the ``value`` keyword is not set and create/set/delete it
otherwise. The *default\_access* keywords specify what should be the
default policy for each operation if no appropriate lock string is set
on the Attribute.
Other ways to access Attributes
-------------------------------
Normally ``db`` is all you need. But there there are also several other
ways to access information about Attributes, some of which cannot be
replicated by ``db``. These are available on all Typeclassed objects:
- ``has_attribute(attrname)`` - checks if the object has an attribute
with the given name. This is equivalent to doing ``obj.db.attrname``.
- ``set_attribute(attrname, value)`` - equivalent to
``obj.db.attrname = value``.
- ``get_attribute(attrname)`` - returns the attribute value. Equivalent
to ``obj.db.attrname``.
- ``get_attribute_raise(attrname)`` - returns the attribute value, but
instead of returning ``None`` if no such attribute is found, this
method raises ``AttributeError``.
- ``get_attribute_obj(attrname)`` - returns the attribute *object*
itself rather than the value stored in it.
- ``del_attribute(attrname)`` - equivalent to ``del obj.db.attrname``.
Quietly fails if ``attrname`` is not found.
- ``del_attribute_raise(attrname)`` - deletes attribute, raising
``AttributeError`` if no matching Attribute is found.
- ``get_all_attributes`` - equivalent to ``obj.db.all``
- ``attr(attrname, value=None, delete=False)`` - this is a convenience
function for getting, setting and deleting Attributes. It's
recommended to use ``db`` instead.
- ``secure_attr(...)`` - lock-checking version of ``attr``. See example
in previous section.
There are several other ways to assign Attributes to be found on the
typeclassed objects, all being more 'low-level' underpinnings to
``db``/``ndb``. Read their descriptions in the respective modules.

View file

@ -0,0 +1,169 @@
Caches
======
*Note: This is an advanced topic. You might want to skip it on a first
read-through.*
Evennia is a fully persistent system, which means that it will store
things in the database whenever its state changes. Since accessing the
database i comparably expensive, Evennia uses an extensive *caching*
scheme. Caching normally means that once data is read from the database,
it is stored in memory for quick retrieval henceforth. Only when data
changes will the database be accessed again (and the cache updated).
With a few exceptions, caching are primarily motivated by speed and to
minimize bottlenecks found by profiling the server. Some systems must
access certain pieces of data often, and going through the django API
over and over builds up. Depending on operation, individual speedups of
hundreds of times can be achieved by clever caching.
The price for extended caching is memory consumption and added
complexity. This page tries to explain the various cache strategies in
place. Most users should not have to worry about them, but if you ever
try to "bang the metal" with Evennia, you should know what you are
seeing.
The default ``@server`` command will give a brief listing of the memory
usage of most relevant caches.
Idmapper
--------
Evennia's django object model is extended by *idmapper* functionality.
The idmapper is an external third-party system that sits in
``src/utils/idmapper``. The idmapper is an on-demand memory mapper for
all database models in the game. This means that a given database object
is represented by the same memory area whenever it is accessed. This may
sound trivial but it is not - in plain Django there is no such
guarantee. Doing something like ``objdb.test = "Test"`` (were objdb is a
django model instance) would be unsafe and most likely the ``test``
variable would be lost next time the model is retrieved from the
database. As Evennia ties `Typeclasses <Typeclasses.html>`_ to django
models, this would be a catastophy.
Idmapper is originally a memory saver for django websites. In the case
of a website, object access is brief and fleeting - not so for us. So we
have extended idmapper to never loose its reference to a stored object
(not doing this was the cause of a very long-standing, very hard-to-find
bug).
Idmapper is an on-demand cache, meaning that it will only cache objects
that are actually accessed. Whereas it is possible to clean the idmapper
cache via on-model methods, this does not necessarily mean its memory
will be freed - this depends on any lingering references in the system
(this is how Python's reference counting works). If you ever need to
clean the idmapper cache, the safest way is therefore a soft-reload of
the server (via e.g. the ``@reload`` command).
Most developers will not need to care with the idmapper cache - it just
makes models work intuitively. It is visible mostly in that all database
models in Evennia inherits from
``src.utils.idmapper.models.SharedMemoryModel``.
On-object variable cache
------------------------
All database fields on all objects in Evennia are cached by use of
`Python
properties <http://docs.python.org/library/functions.html#property>`_.
So when you do ``name = obj.key``, you are actually *not* directly
accessing a database field "key" on the object. What you are doing is
actually to access a handler. This handler looks for hidden variable
named ``_cached_db_key``. If that can be found, it is what is returned.
If not, the actual database field, named ``db_key`` are accessed. The
result is returned and cached for next time.
The naming scheme is consistent, so a given property ``obj.foo`` is a
handler with a cache named ``obj._cached_db_foo`` and a database field
``obj.db_key.`` The handler methods for the property are always named
``obj.foo_get()``, ``obj.foo_set()`` and ``obj.foo_del()`` (all are not
always needed).
Apart from caching, property handlers also serves another function -
they hide away Django administration. So doing ``obj.key = "Peter"``
will not only assign (and cache) the string "Peter" in the database
field ``obj.db_key``, it will also call ``obj.save()`` for you in order
to update the database.
Hiding away the model fields presents one complication for developers,
and that is searching using normal django methods. Basically, if you
search using e.g. the standard django ``filter`` method, you must search
for ``db_key``, not ``key``. Only the former is known by django, the
latter will give an invalid-field error. If you use Evennia's own search
methods you don't need to worry about this, they look for the right
things behind the scenes for you.
Mutable variable caches
~~~~~~~~~~~~~~~~~~~~~~~
Some object properties may appear mutable - that is, they return lists.
One such example is the ``permissions`` property. This is however not
actually a list - it's just a handler that *returns* and *accepts*
lists. ``db_permissions`` is actually stored as a comma-separated
string. The uptake of this is that you cannot do list operations on the
handler. So ``obj.permissions.append('Immortals')`` will not work.
Rather, you will have to do such operations on what is returned. Like
this:
::
perms = obj.permissions # this returns a list!
perms.append("Immortals")
obj.permissions = perms # overwrites with new list
Content cache
~~~~~~~~~~~~~
A special case of on-object caching is the *content* cache. Finding the
"contents" of a particular location turns out to be a very common and
pretty expensive operation. Whenever a person moves, says something or
does other things, "everyone else" in a given location must be informed.
This means a database search for which objects share the location.
``obj.contents`` is a convenient container that at every moment contains
a cached list of all objects "inside" that particular object. It is
updated by the ``location`` property. So ``obj1.location = obj2`` will
update ``obj2.contents`` on the fly to contain ``obj1``. It will also
remove ``obj1`` from the ``contents`` list of its previous location.
Testing shows that when moving from one room to another, finding and
messaging everyone in the room took up as much as *25%* of the total
computer time needed for the operation. After caching ``contents``,
messaging now takes up *0.25%* instead ...
The contents cache should be used at all times. The main thing to
remember is that if you were to somehow bypass the ``location`` handler
(such as by setting the ``db_location`` field manually), you will bring
the cache and database out of sync until you reload the server.
Typeclass cache
~~~~~~~~~~~~~~~
All typeclasses are cached on the database model. This allows for quick
access to the typeclass through ``dbobj.typeclass``. Behind the scenes
this operation will import the typeclass definition from a path stored
in ``db_typeclass_path`` (available via the property handler
``typeclass_path``). All checks and eventual debug messages will be
handled, and the result cached.
The only exception to the caching is if the typeclass module had some
sort of syntax error or other show-stopping bug. The default typeclass
(as defined in ``settings``) will then be loaded instead. The error will
be reported and *no* caching will take place. This is in order to keep
reloading the typeclass also next try, until it is fixed.
On-object Attribute cache
-------------------------
`Attribute <Attributes.html>`_ lookups are cached by use of hidden
dictionaries on all `Typeclassed <Typeclasses.html>`_ objects - this
removes the necessity for subsequent database look-ups in order to
retrieve attributes. Both ``db`` and ``ndn`` work the same way in this
regard.
Attribute value cache
---------------------
Each Attribute object also caches the values stored in it. Whenever
retrieving an attribute value, it is also cached for future accesses. In
effect this means that (after the first time) accessing an attribute is
equivalent to accessing any normal property.

View file

@ -27,7 +27,7 @@ defined on.
DefaultCmdset and available in the game.
The full set of available commands (all three sub-sets above) currently
contains 85 commands in 6 categories. More information about how
contains 86 commands in 6 categories. More information about how
commands work can be found in the `Command <Commands.html>`_
documentation.
@ -666,7 +666,7 @@ module <https://code.google.com/p/evennia/source/browse/src/commands/default/bui
~~~~~
- ``key`` = ``@home``
- ``aliases`` = ``<None>``
- ``aliases`` = ``@sethome``
- `locks <Locks.html>`_ = ``cmd:perm(@home) or perm(Builders)``
- `help\_category <HelpSystem.html>`_ = ``Building``
- [`HelpSystem <HelpSystem.html>`_\ #Auto-help\_system Auto-help]
@ -910,19 +910,29 @@ module <https://code.google.com/p/evennia/source/browse/src/commands/default/bui
::
teleport
teleport object to another location
Usage:
@tel/switch [<object> =] <location>
@tel/switch [<object> =] <target location>
Examples:
@tel Limbo
@tel/quiet box Limbo
@tel/tonone box
Switches:
quiet - don't echo leave/arrive messages to the source/target
locations for the move.
intoexit - if target is an exit, teleport INTO
the exit object instead of to its destination
tonone - if set, teleport the object to a None-location. If this
switch is set, <target location> is ignored.
Note that the only way to retrieve
an object from a None location is by direct #dbref
reference.
Teleports an object or yourself somewhere.
Teleports an object somewhere. If no object is given, you yourself
is teleported to the target location.
@tunnel
~~~~~~~
@ -1487,6 +1497,29 @@ General
`Link to Python
module <https://code.google.com/p/evennia/source/browse/src/commands/default/general.py>`_
@color
~~~~~~
- ``key`` = ``@color``
- ``aliases`` = ``<None>``
- `locks <Locks.html>`_ = ``cmd:all()``
- `help\_category <HelpSystem.html>`_ = ``General``
- [`HelpSystem <HelpSystem.html>`_\ #Auto-help\_system Auto-help]
(``__doc__ string``) =
::
testing colors
Usage:
@color ansi|xterm256
Print a color map along with in-mud color codes, while testing what is supported in your client.
Choices are 16-color ansi (supported in most muds) or the 256-color xterm256 standard.
No checking is done to determine your client supports color - if not you will
see rubbish appear.
@encoding (OOC command)
~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -1,47 +1,33 @@
Evennia Licence FAQ
===================
Evennia is licensed under the very friendly *Modified Clarified Artistic
License*. You can find the license as ``LICENCE`` in the Evennia root
directory. You can also read the full license file
Evennia is licensed under the very friendly BSD License. You can find
the license as ``LICENCE.txt`` in the Evennia root directory. You can
also read the full license file
`here <http://code.google.com/p/evennia/source/browse/LICENSE.txt>`_.
You should read the full license text to know what it says exactly, but
here are some answers to common questions.
Q: When creating a game using Evennia, what does the licence permit me to do with it?
-------------------------------------------------------------------------------------
**A:** It's your own game world to do with as you please! To summarize,
a MUD world you create using Evennia (i.e. the files you create in
``/game/``) falls under **§6** of the license (it's a sort of
"library"). So your game world and all its contents belongs to you (as
it should be). Keep it to yourself or re-distribute it under any license
of your choice - or sell it and become filthy rich for all we care.
**A:** It's your own game world to do with as you please! Keep it to
yourself or re-distribute it under any license of your choice - or sell
it and become filthy rich for all we care.
Q: I have modified Evennia itself, what does the license say about that?
------------------------------------------------------------------------
**A:** The Evennia package itself (i.e. the stuff you download from us)
is referred to as "The Package, Standard version" in the license.
**A:** The License allows you to do whatever you want with your modified
Evennia, including re-distributing or selling it, as long as you include
our license and copyright info in the ``LICENSE.txt`` file along with
your distribution.
- If you just fixed a typo or bug, that falls under **§2** - that is,
you don't *have* to do anything to appease the license. Regardless,
we'd of course appreciate it if you submitted bugs/fixes to us so
Evennia becomes more complete!.
- If you made bigger modifications or added new features to the server,
that's also ok, but falls under **§3** - you must make a clear note
of the changes you did and put those changes into public domain
(since it's then no longer a "Standard version"). You could also
contact the Evennia developers to make separate arrangements ... but
of course, if you plan to add new features to the server, the easiest
way to do so is to simply become an Evennia developer!
... Of course, if you add bug fixes or add some new snazzy features we
still *softly nudge* you to make those changes available upstream so
they could be added to the core Evennia package. The license don't
require you to do it, but that doesn't mean we can't still greatly
appreciate it if you do!
Q: Can I re-distribute the Evennia server package along with my custom game implementation?
-------------------------------------------------------------------------------------------
**A:** Sure. This is covered in **§4** - just package the "Standard
version" (that is, the one you download from us) with your game files.
Also make sure to include the original license and disclaimers and note
where users may get "plain" Evennia should they want to download it of
their own.
**A:** Sure. As long as the text in LICENSE.txt is included.

View file

@ -34,6 +34,10 @@ Third-party Evennia links
- `Avaloria <http://code.google.com/p/avaloria/>`_ (MUD under
development, using Evennia)
- `Winter's Oasis <http://blog.wintersoasis.com/>`_ (MUCK under
development, using Evennia)
- `Latitude <https://github.com/dbenoy/latitude>`_ (MUCK under
development, using Evennia)
General mud/game development ideas and discussions
--------------------------------------------------

View file

@ -20,6 +20,8 @@ Central <DeveloperCentral.html>`_.
- `Tutorial: Adding a new default
command <AddingCommandTutorial.html>`_
- `Tutorial: Adding new Object typeclasses and
defaults <AddingObjectTypeclassTutorial.html>`_
Implementation ideas
--------------------