diff --git a/docs/README b/docs/README.txt similarity index 100% rename from docs/README rename to docs/README.txt diff --git a/docs/sphinx/README.txt b/docs/sphinx/README.txt new file mode 100644 index 0000000000..a5425a8c1c --- /dev/null +++ b/docs/sphinx/README.txt @@ -0,0 +1,18 @@ + + +Wiki convertion and autodocs +---------------------------- + +The source/ directory contains Evennia's wiki documentation converted +to ReST form. This can be built to a html document by installing +python-sphinx (sphinx-doc.org) and running 'make html' from this +directory. The output will appear in under build/ - point your browser +to the index.html file. + +If you want to (re-)build the documentation yourself, wiki2rest/ +contains programs for converting Evennia's wiki documentation to ReST +files. Read the header of wiki2rest.py for setting up the converter. + +The src2rest folder contains a reprecated program for building +documented ReST source code from Evennia's documentation. You can +arguably get as good autodocs using doxygen. diff --git a/docs/sphinx/source/wiki/AddingObjectTypeclassTutorial.rst b/docs/sphinx/source/wiki/AddingObjectTypeclassTutorial.rst index 0399d9842c..6c5b323127 100644 --- a/docs/sphinx/source/wiki/AddingObjectTypeclassTutorial.rst +++ b/docs/sphinx/source/wiki/AddingObjectTypeclassTutorial.rst @@ -11,10 +11,10 @@ Evennia comes with a few very basic classes of in-game entities: Room Exit -So the more specific object-types are just children of the basic -``Object`` class (technically these are all -`Typeclasses `_ entities, but for this tutorial, just -treat them as normal Python classes). +The more specific object-types are just children of the basic ``Object`` +class (technically these are all `Typeclassed `_ +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 @@ -54,14 +54,14 @@ that characters should not have the ability to pick up. #. 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 +#. Code away in the ``heavy.py`` module, implementing the heavy functionality. See `Objects `_ for more details and the - example class below. Let's call the typeclass simply ``Heavy``. + 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. + object in your location. Next try to pick it up. *Note - the + superuser (User #1) will ignore all locks. Always test functionality + like this with a non-superuser character.* That's it. Below is a ``Heavy`` Typeclass that you could try. Note that the `lock `_ and `Attribute `_ here set in @@ -80,7 +80,7 @@ so this is a *very* simple example. # 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 + # to 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." @@ -114,8 +114,8 @@ 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 +new typeclass, set ``settings.BASE_OBJECT_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``, @@ -154,6 +154,42 @@ example of a new ``myroom.py``: "My own expandable room class" pass +Updating existing objects +========================= + +Let's say you have already created a slew of objects (Characters, Rooms, +what have you). Now you change the default typeclass for that type of +object (as described above). Unfortunately those old objects will not +know about this yet. If you want to update them you have to do this +manually. Luckily you only have to do this once, but it's a good case +for planning your game and its base typeclasses *before* starting to +build stuff. + +Typeclassed objects have a useful method called ``swap_typeclass``. All +you need to do is to flip through all existing objects, calling this. +Here is an example of how to do it using some Django magic: + +:: + + from django.conf import settings + import ev + + old_default = "src.objects.objects.Object" + new_default = "game.gamesrc.objects.myobj.MyObject" + + # use Django to query the database for all objects with the + # old typeclass path (typeclass path is stored in a database + # field 'db_typeclass_path)' + + for obj in ev.managers.objects.filter(db_typeclass_path=old_default): + obj.swap_typeclass(new_default) + +Above we use one of the Django database managers to query the database. +We are looking for the main thing typeclasses store in the database, +namely the full python path to the typeclass. We find all objects still +using the old typeclass and swap them to the new on. For more on Django +database access, see the Django manual and/or peruse ``ev.managers``. + Notes ===== diff --git a/docs/sphinx/source/wiki/ApacheConfig.rst b/docs/sphinx/source/wiki/ApacheConfig.rst index c634a8c4a5..5473138e46 100644 --- a/docs/sphinx/source/wiki/ApacheConfig.rst +++ b/docs/sphinx/source/wiki/ApacheConfig.rst @@ -1,22 +1,22 @@ Apache Configuration ==================== -*OBS: Evennia has a powerful in-built Twisted-based web server for -handling all web features. This works out of the box without any special -setup. This page is only of interest if you really want/need to run -Apache instead Evennia's in-built server. Note that the ajax web client -is not guaranteed to work (at least not without tweaking) on a -third-party server.* +This is an optional section only relevant for advanced users preferring +to use a third-party web server to power Evennia's front-end. For most +users the in-built Twisted web server should be enough. The in-built +server works out of the box without any extra configuration. Note that +the ajax web client will probably *not* work (at least not without +tweaking) on a third-party web server. -The suggested third-party stack for running Evennia's web front end is +You can run Evennia's web front end with `apache2 `_ and -`mod\_wsgi `_. However, the codebase -may run just fine on other servers and modules (apache2/nginx/lighttpd + -gunicorn, Tornado, uwsgi, etc.) Below are instructions on how to set -things up with various apache2 Python modules. If you get things working -using a different setup, please feel free to provide details below. +`mod\_wsgi `_. However, there seems +to be no reason why the codebase should not also work with other modern +web servers like nginx/lighttpd + gunicorn, Tornado, uwsgi, etc. ----- +Note that the Apache instructions below might be slightly outdated. If +something is not working right, or you use Evennia with a different +server, please let us know. SQLite Note ----------- @@ -29,8 +29,6 @@ the game and the web front-end. The best bet to any game wishing to power their web presence with Evennia is to use Postgres, MySQL, Oracle, or any other supported full-blown relational database. ----- - mod\_wsgi Setup --------------- diff --git a/docs/sphinx/source/wiki/AsyncProcess.rst b/docs/sphinx/source/wiki/AsyncProcess.rst index 64ad968fa1..cb9d584007 100644 --- a/docs/sphinx/source/wiki/AsyncProcess.rst +++ b/docs/sphinx/source/wiki/AsyncProcess.rst @@ -205,6 +205,25 @@ not be available to unprivileged users. Try version of Python for untrusted users. This will use ``run_async`` under the hood. +delay +----- + +The ``delay`` function is a much simpler sibling to ``run_async``. It is +in fact just a way to delay the execution of a command until a future +time. This is equivalent to something like ``time.sleep()`` except delay +is asynchronous while ``sleep`` would lock the entire server for the +duration of the sleep. + +:: + + def callback(obj): + obj.msg("Returning!") + delay(10, caller, callback=callback) + +This will delay the execution of the callback for 10 seconds. This +function is explored much more in `Command Duration +Tutorial `_. + Assorted notes -------------- diff --git a/docs/sphinx/source/wiki/Attributes.rst b/docs/sphinx/source/wiki/Attributes.rst index f5bfd82c09..d8cf60e242 100644 --- a/docs/sphinx/source/wiki/Attributes.rst +++ b/docs/sphinx/source/wiki/Attributes.rst @@ -75,58 +75,6 @@ If you use ``all`` as the name of an attribute, this will be used instead. Later deleting your custom ``all`` will return the default behaviour. -Fast assignment ---------------- - -*Depracation Warning: Fast assigment is deprecated and should not be -used - it will be removed in the future. Use the ``db`` operator -explicitly when saving to the database.* - -For quick testing you can in principle skip the ``db`` operator and -assign Attributes like you would any normal Python property: - -:: - - # saving - rose.has_thorns = True - # getting it back - is_ouch = rose.has_thorns - -This looks like any normal Python assignment, but calls ``db`` behind -the scenes for you. - -Note however that this form stands the chance of overloading already -existing properties on typeclasses and their database objects. Unless -you know what you are doing, this can cause lots of trouble. - -:: - - rose.msg("hello") # this uses the in-built msg() method - rose.msg = "Ouch!" # this OVERLOADS the msg() method with a string - rose.msg("hello") # this now a gives traceback! - -Overloading ``msg()`` with a string is a very bad idea since Evennia -uses this method all the time to send text to you. There are of course -situations when you *want* to overload default methods with your own -implementations - but then you'll hopefully do so intentionally and with -something that works. - -:: - - rose.db.msg = "Ouch" # this stands no risk of overloading msg() - rose.msg("hello") # this works as it should - -So using ``db``/``ndb`` will always do what you expect and is usually -the safer bet. It also makes it visually clear at all times when you are -saving to the database and not. - -Another drawback of this shorter form is that it will handle a non-found -Attribute as it would any non-found property on the object. The ``db`` -operator will instead return ``None`` if no matching Attribute is found. -So if an object has no attribute (or property) named ``test``, doing -``obj.test`` will raise an ``AttributeException`` error, whereas -``obj.db.test`` will return ``None``. - Persistent vs non-persistent ---------------------------- @@ -325,13 +273,12 @@ commands/code wherever it fits (such as before setting an Attribute). # 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: +lock was defined on the Attribute (which is the normal case). 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: :: diff --git a/docs/sphinx/source/wiki/BatchCodeProcessor.rst b/docs/sphinx/source/wiki/BatchCodeProcessor.rst index 745f0598df..02a9d28979 100644 --- a/docs/sphinx/source/wiki/BatchCodeProcessor.rst +++ b/docs/sphinx/source/wiki/BatchCodeProcessor.rst @@ -15,12 +15,14 @@ The batch-command processor is a superuser-only function, invoked by > @batchcode path.to.batchcodefile -Where ``path.to.batchcodefile`` is the path to a *batch-code file* with -the "``.py``\ " file ending. This path is given like a python path -relative to a folder you define to hold your batch files, set by -``BATCH_IMPORT_PATH`` in your settings. Default folder is -``game/gamesrc/world``. So if you want to run the example batch file in -``game/gamesrc/world/examples/batch_code.py``, you could simply use +Where ``path.to.batchcodefile`` is the path to a *batch-code file*. Such +a file should have a name ending in "``.py``\ " (but you shouldn't +include that when you batch-run the file from the game below). The path +is given like a python path relative to a folder you define to hold your +batch files, set by ``BATCH_IMPORT_PATH`` in your settings. Default +folder is ``game/gamesrc/world``. So if you want to run the example +batch file in ``game/gamesrc/world/examples/batch_code.py``, you could +simply use :: @@ -76,7 +78,7 @@ Here are the rules of syntax of the batch-command ``*.py`` file. pointing to the object executing the batchcommand. Below is a version of the example file found in -``game/gamesrc/commands/examples/batch_code.py``. +``game/gamesrc/world/examples/batch_code.py``. :: diff --git a/docs/sphinx/source/wiki/BuildingPermissions.rst b/docs/sphinx/source/wiki/BuildingPermissions.rst index d0bd62c9af..86c3b7a1cb 100644 --- a/docs/sphinx/source/wiki/BuildingPermissions.rst +++ b/docs/sphinx/source/wiki/BuildingPermissions.rst @@ -37,8 +37,8 @@ as well. By default Evennia creates the following hierarchy: #. *Players* is the default group that new players end up in. A new player have permission to use tells, to use and create new channels. -A user having a higher-level permission also automatically have access -to locks requiring only lower-level access. +A user having a certain level of permission automatically have access to +locks specifying access of a lower level. To assign a new permission from inside the game, you need to be able to use the ``@perm`` command. This is an *Immortal*-level command, but it @@ -50,5 +50,32 @@ staff with the command :: - @perm/add Tommy = Immortals + @perm/add *Tommy = Immortals +The ``*`` makes sure to put the permission on the *Player* and not on +any eventual *Character* that may also be named Tommy. This is usually +what you want since the Player will then remain an Immortal regardless +of which Character they are currently controlling. To limit permission +to a per-Character level you should instead use *quelling* (see below). + +Quelling your permissions +------------------------- + +When developing it can be useful to check just how things would look had +your permission-level been lower. For this you can use *quelling*. +Normally, when you puppet a Character you are using your Player-level +permission. So even if your Character only has *Players* level +permissions, your *Immortals*-level Player will take precedence. With +the ``@quell`` command you can change so that the Character's permission +takes precedence instead: + +:: + + @quell + +This will allow you to test out the game using the current Character's +permission level. A developer or builder can thus in principle maintain +several test characters, all using different permission levels. Note +that you cannot escalate your permissions this way; If the Character +happens to have a *higher* permission level than the Player, the +Player's permission will still be used. diff --git a/docs/sphinx/source/wiki/BuildingQuickstart.rst b/docs/sphinx/source/wiki/BuildingQuickstart.rst index 06621f321b..bbe7278b41 100644 --- a/docs/sphinx/source/wiki/BuildingQuickstart.rst +++ b/docs/sphinx/source/wiki/BuildingQuickstart.rst @@ -25,30 +25,28 @@ assigning something to an object. Below are some examples of commands. Use ``help `` for learning more about each command and their detailed options. -Making a Builder ----------------- +Stepping down from godhood +-------------------------- If you just installed Evennia, your very first player account is called user #1, also known as the *superuser* or *god user*. This user is very powerful, so powerful that it will override many game restrictions such as locks. This can be useful, but it also hides some functionality that -you might want to test. Let's create a more "normal" Builder player -account instead. +you might want to test. -Get to Evennia's login screen (log off with ``@quit`` if you are already -connected) and choose ``create`` from the login screen. Create a new -account (don't log in yet). You can use any e-mail address, it doesn't -have to be an existing one. Let's say we call the new account "Anna". -Next log in *on your superuser account* and give the recently created -player build rights: +To temporarily step down from your superuser position you can use the +``@quell`` command: :: - @perm Anna = Builders + @quell -You could give the permission "Immortals" instead, if you want to assign -full admin privileges. Log out of your superuser account (``@quit``) and -finally log back in again as your new builder account. +This will make you start using the permission of your current +`Character `_ instead of your superuser level. If you +didn't change any settings your game Character should have an *Immortal* +level permission - high as can be without bypassing locks like the +superuser does. This will work fine for the examples on this page. Use +``@unquell`` to get back to superuser status again afterwards. Creating an object ------------------ @@ -335,5 +333,5 @@ This will take a while, but you will see a lot of messages as the world is built for you. You will end up with a new exit from Limbo named *tutorial*. See more info about the tutorial world `here `_. Read -``contrib/tutorial/world/build.ev`` to see exactly how it's built, step +``contrib/tutorial_world/build.ev`` to see exactly how it's built, step by step. diff --git a/docs/sphinx/source/wiki/Caches.rst b/docs/sphinx/source/wiki/Caches.rst index 20529bb4e2..e3399ecaf1 100644 --- a/docs/sphinx/source/wiki/Caches.rst +++ b/docs/sphinx/source/wiki/Caches.rst @@ -23,6 +23,10 @@ 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. +All caching schemes except Idmapper is centralized in +``src/server.caches/py``. You can turn off all caches handled by that +module by use of the ``GAME_CACHE_TYPE`` setting. + The default ``@server`` command will give a brief listing of the memory usage of most relevant caches. @@ -68,16 +72,14 @@ All database fields on all objects in Evennia are cached by use of properties `_. 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. +actually to access a handler. This handler reads the field ``db_key`` +and caches its value for next time it using a call to the the +``server.caches`` module. -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). +The naming scheme is consistent, so a given field property ``obj.foo`` +is always a handler for 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"`` @@ -154,16 +156,12 @@ reloading the typeclass also next try, until it is fixed. On-object Attribute cache ------------------------- -`Attribute `_ lookups are cached by use of hidden -dictionaries on all `Typeclassed `_ 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 `_ lookups on objects are also cached. This +means that after the first access or assignment, ``obj.db.attrname`` is +as fast as accessing any normal python property - 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. +Apart from the lookup, each Attribute object itself caches the values +stored in it. Again this means that (after the first time) accessing an +attribute is equivalent to accessing any normal Python property. diff --git a/docs/sphinx/source/wiki/ChoosingAnSQLServer.rst b/docs/sphinx/source/wiki/ChoosingAnSQLServer.rst index 2fc1993dc9..29beadc4c6 100644 --- a/docs/sphinx/source/wiki/ChoosingAnSQLServer.rst +++ b/docs/sphinx/source/wiki/ChoosingAnSQLServer.rst @@ -13,17 +13,19 @@ SQLite3 ------- This is the default database used, and for the vast majority of Evennia -installs it will probably be more than adequate or even the best choice. -No server process is needed, the administrative overhead is tiny (as is -resource consumption). The database will appear as a simple file -(``game/evennia.db3``) and since we run SQLite as an in-memory process -without any socket overhead, it might well be faster than Postgres/MySQL -unless your database is huge. +installs it will probably be more than adequate. It's definitely +recommended for most of your development. No server process is needed, +the administrative overhead is tiny (as is resource consumption). The +database will appear as a simple file (``game/evennia.db3``) and since +we run SQLite as an in-memory process without any socket overhead, it +might well be faster than Postgres/MySQL unless your database is huge. -**Note:** If you for some reason need to use a third-party web server -like Apache rather than Evennia's internal web server, SQLite is -probably not the best choice. This is due to the possibility of clashes -with file-locking when using SQLite from more than one process. +The drawback with SQLite3 is that it does not work very well will +multiple concurrent threads or processes. This has to do with +file-locking clashes of the database file. So for a production server +making heavy use of process- or threadpools (or when using a third-party +webserver like Apache), a more full-featured database may be the better +choice. Postgres -------- @@ -54,3 +56,24 @@ No testing has been performed with Oracle, but it is also supported. There are community maintained drivers for `MS SQL `_ and possibly a few others (found via our friend, Google). + +Inspecting database data +======================== + +If you know SQL you can easily get command line access to your database +like this: + +:: + + python game/gamesrc.py dbshell + +This will drop you into the command line interface for your respective +database. + +There are also a host of easier graphical interfaces for the various +databases. For SQLite3 we recommend `SQLite +manager `_. +This is a plugin for the +`Firefox `_ web browser +making it usable across all operating systems. Just use it to open the +game/evennia.db3 file. diff --git a/docs/sphinx/source/wiki/CodingIntroduction.rst b/docs/sphinx/source/wiki/CodingIntroduction.rst index 79455fae28..7f75e689f5 100644 --- a/docs/sphinx/source/wiki/CodingIntroduction.rst +++ b/docs/sphinx/source/wiki/CodingIntroduction.rst @@ -32,7 +32,7 @@ Learn the `ev interface `_. 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 +You can complement your exploration by peeking at the sections of the much more detailed `Developer Central `_. The `Tutorials `_ section also contains a growing collection of system- or implementation-specific help. diff --git a/docs/sphinx/source/wiki/CodingUtils.rst b/docs/sphinx/source/wiki/CodingUtils.rst index 73903ac2af..fd3b5a93f6 100644 --- a/docs/sphinx/source/wiki/CodingUtils.rst +++ b/docs/sphinx/source/wiki/CodingUtils.rst @@ -104,18 +104,18 @@ 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. +distance* (as opposed to Python's in-built ``is_instance()`` that will +only catch immediate dependence). This function also accepts as input +any combination of classes, instances or python-paths-to-classes. Note that Python code should usually work with `duck typing `_. But in Evennia's case it can sometimes be useful to check if an object inherits from a -given `Typeclass `_ 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: +given `Typeclass `_ 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: :: @@ -123,6 +123,17 @@ will allow you to check for all animals in one go: 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.'") +delay() +------- + +This is a thin wrapper around a Twisted construct called a *deferred*. +It simply won't return until a given number of seconds have passed, at +which time it will trigger a given callback with whatever argument. This +is a small and lightweight (non-persistent) alternative to a full +`Script `_. Contrary to a Script it can also handle +sub-second timing precision (although this is not something you should +normally need to worry about). + Some text utilities ------------------- @@ -168,7 +179,7 @@ string to the left edge. :: - #python code is at this indentation + #python code is entered at a given indentation intxt = """ This is an example text that will end up with a lot of whitespace on the left. @@ -178,8 +189,8 @@ string to the left edge. # 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). +Normally you do the dedent in the display code (this is for example how +the help system homogenizes help entries). time\_format() ~~~~~~~~~~~~~~ diff --git a/docs/sphinx/source/wiki/CommandPrompt.rst b/docs/sphinx/source/wiki/CommandPrompt.rst index 2676ff39b8..7ac6901980 100644 --- a/docs/sphinx/source/wiki/CommandPrompt.rst +++ b/docs/sphinx/source/wiki/CommandPrompt.rst @@ -9,9 +9,9 @@ in-game time, weather and so on. Prompt after the command ------------------------ -The easiest form of prompt is one that is sent after every command you -send. So, say you enter the look command; you would then get the result -of the look command, followed by the prompt. As an example:  +One common form of prompt appears after every command you send. So, say +you enter the look command; you would then get the result of the look +command, followed by the prompt. As an example:  :: @@ -26,10 +26,9 @@ To add this kind of "after-every-command-prompt", you can use the ``at_post_cmd()`` hook. This is to be defined on the Command class and Evennia will always call it right after ``func()`` has finished executing. For this to appear after every command you enter, it's best -to put this in the parent for your commands (for the default commands -this would be ``MuxCommand``), but you could also put it only in certain -commands (might not be too useful to show it if you are doing game -administration for example). +to put this in the parent for your own commands. You can also put it +only in certain commands (might not be too useful to show it if you are +doing game administration for example). :: @@ -48,6 +47,32 @@ administration for example). self.caller.msg("HP: %i, SP: %i, MP: %i" % (hp, sp, mp)) +Note that if you are using the default commands, they will *not* display +a command prompt after this change - they are inheriting from +``src.commands.default.muxcommand.MuxCommand``. If you want to modify +default commands you need to copy&paste the default command definitions +to ``game/gamesrc/commands`` and modify them so they inherit from your +new parent (and re-point Evennia to use your custom versions as +described `here `_). If you only want to add +the hook and don't need to change anything else you can just create +stubs in modules in ``game/gamesrc/commands`` on this form: + +:: + + from ev import default_cmds + from game.gamesrc.commands.basecommand import MyCommand + + class CmdLook(MyCommand, default_cmds.CmdLook): + pass + + class CmdGet(MyCommand, default_cmds.CmdGet): + pass + +This multiple inheritance should make use of your custom ``at_post_cmd`` +hook while otherwise using the default command's code. This type of +overload is useful not only for adding prompts but for many different +forms of overloading default functionality. + Prompt on the same line ----------------------- @@ -70,14 +95,17 @@ before* the function return: HP:10, SP:20, MP: 5 You see nothing special. -... which might be cool too, but not what we wanted. To have the prompt -appear on the same line as the return this, we need to change how +... which might be cool too, but is not what we wanted. To have the +prompt appear on the same line as the return, we need to change how messages are returned to the player. This means a slight modification to -our *Character class* (see [Objects#Characters here] on how to change -the default Character class to your custom one). Now, all commands use -the ``object.msg()`` method for communicating with the player. This is -defined in ``src/objects/models.py``, on the ``ObjectDB`` base class. -This is how the ``msg()`` method is defined: +our *Character typeclass* (see [Objects#Characters here] on how to +change the default Character class to your custom one). + +All in-game commands use the ``object.msg()`` method for communicating +with the player (this is usually called as ``self.caller.msg()`` inside +command classes). This method is defined in ``src/objects/models.py``, +on the ``ObjectDB`` base class. This is the signature of ``msg()`` +method: :: @@ -113,4 +141,4 @@ some attribute defined on your character for turning on/off the prompt (the msg() method could look for it to determine if it should add the prompt or not). You can of course also name the above method ``msg_prompt()`` and make sure that only commands that *should* return a -prompt call this version. +prompt call that method. diff --git a/docs/sphinx/source/wiki/Commands.rst b/docs/sphinx/source/wiki/Commands.rst index 6428857d34..38004b584f 100644 --- a/docs/sphinx/source/wiki/Commands.rst +++ b/docs/sphinx/source/wiki/Commands.rst @@ -106,6 +106,8 @@ properties: (*Advanced note: the merged cmdset need NOT be the same as BigGuy.cmdset. The merged set can be a combination of the cmdsets from other objects in the room, for example*). +- ``sessid`` - this is an integer identifier for the Session triggering + this command, if any. This is seldomly needed directly. - ``raw_string`` - this is the raw input coming from the user, without stripping any surrounding whitespace. The only thing that is stripped is the ending newline marker. @@ -149,8 +151,14 @@ Beyond the properties Evennia always assigns to the command at runtime for this would be ``r"\s.*?|$"``). In that case, only ``"look me"`` will work whereas ``"lookme"`` will lead to an "command not found" error. -- auto\_help (optional boolean). Defaults to ``True``. This allows for - turning off the +- ``func_parts`` (optional list of methods). Not defined by default, + used if it exists. This list of methods will be called in sequence, + each given a chance to yield execution. This allows for multi-part + long-running commands. See `Commands with a + Duration `_ for a practial presentation of how + to use this. +- ``auto_help`` (optional boolean). Defaults to ``True``. This allows + for turning off the [`HelpSystem `_\ #Command\_Auto-help\_system auto-help system] on a per-command basis. This could be useful if you either want to write your help entries manually or hide the existence @@ -221,9 +229,9 @@ Below is how you define a simple alternative "``smile``\ " command: caller.location.msg_contents(string, exclude=caller) caller.msg("You smile.") else: - target = self.search(self.target) + target = caller.search(self.target) if not target: - # self.search handles error messages + # caller.search handles error messages return string = "%s smiles to you." % caller.name target.msg(string) diff --git a/docs/sphinx/source/wiki/Communications.rst b/docs/sphinx/source/wiki/Communications.rst index 43cce3cf52..e219ab368a 100644 --- a/docs/sphinx/source/wiki/Communications.rst +++ b/docs/sphinx/source/wiki/Communications.rst @@ -27,32 +27,52 @@ keep. Properties defined on \`Msg\` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- ``sender`` - this is a reference to a unique `Player `_ - object sending the message. -- ``receivers`` - a list of target `Players `_ to send - to. -- ``channels`` - a list of target Channels to send to. -- ``message`` - the actual text being sent +- ``senders`` - this is a reference to one or many + `Player `_ or `Objects `_ (normally + *Characters*) sending the message. This could also be an *External + Connection* such as a message coming in over IRC/IMC2 (see below). + There is usually only one sender, but the types can also be mixed in + any combination. +- ``receivers`` - a list of target `Players `_, + `Objects `_ (usually *Characters*) or *Channels* to + send the message to. The types of receivers can be mixed in any + combination. +- ``header`` - this has a max-length of 128 characters. This could be + used to store mime-type information for this type of message (such as + if it's a mail or a page), but depending on your game it could also + instead be used for the subject line or other types of header info + you want to track. Being an indexed field it can be used for quick + look-ups in the database. +- ``message`` - the actual text being sent. - ``date_sent`` - when message was sent (auto-created). - ``locks`` - a `lock definition `_. +- ``hide_from`` - this can optionally hold a list of objects, players + or channels to hide this ``Msg`` from. This relationship is stored in + the database primarily for optimization reasons, allowing for quickly + post-filter out messages not intended for a given target. There is no + in-game methods for setting this, it's intended to be done in code. You create new messages in code using ``ev.create_message`` (or ``src.utils.create.create_message.``) !TempMsg -~~~~~~~~ +-------- -``src.objects.models`` contains a class called ``TempMsg`` that mimics a -``Msg`` but does not get saved to the database and do not require a -sender object of a certain type. It's not used by default, but you could -use it in code to send one-off messages to systems expecting a ``Msg``. +``src.comms.models`` contains a class called ``TempMsg`` which mimics +the API of ``Msg`` but is not connected to the database. It's not used +by default but you could use it in code to send non-persistent messages +to systems expecting a ``Msg`` (like *Channels*, see the example in the +next section). Channels -------- -Channels act as generic distributors of messages. Players *subscribe* to -channels and can then send and receive message from it. Channels have -`Locks `_ to limit who may join them. +Channels act as generic distributors of messages. Think of them as +"switch boards" redistributing ``Msg`` objects. Internally they hold a +list of "listening" objects and any ``Msg`` sent to the channel will be +distributed out to all channel listeners. Channels have +`Locks `_ to limit who may listen and/or send messages +through them. There are three default channels created in stock Evennia - ``MUDinfo``, ``MUDconnections`` and ``Public``. Two first ones are server-related @@ -62,38 +82,50 @@ for asking questions). The default channels created are defined by ``settings.CHANNEL_PUBLIC``, ``settings.CHANNEL_MUDINFO`` and ``settings.CHANNEL_CONNECTINFO``. -You create new channels with ``ev.create_message`` (or +You create new channels with ``ev.create_channel`` (or ``src.utils.create.create_channel``). -In code, messages are sent to a channel using the -``msg(message, from_obj=None)`` method. The argument ``message`` can -either be a previously constructed ``Msg`` object or a message string. -If you send a text string, you should usually also define ``from_obj``; -a ``Msg`` object will then be created for you behind the scenes. If you -don't supply ``from_obj``, just the string will be sent to the channel -and nothing will be stored in the database (could be useful for certain -spammy error messages). You can also use ``channel.tempmsg()`` to always -send a non-persistent message, also if you send it a ``Msg`` object. +In code, messages are sent to a channel using the ``msg`` or ``tempmsg`` +methods of channels: + +:: + + channel.msg(msgobj, header=None, senders=None, persistent=True) + +The argument ``msgobj`` can be a previously constructed ``Msg`` or +``TempMsg`` - in that case all the following keywords are ignored. If +``msgobj`` is a string, the other keywords are used for creating a new +``Msg`` or ``TempMsg`` on the fly, depending on if ``persistent`` is set +or not. :: # assume we have a 'sender' object and a channel named 'mychan' - # send and store in database + # send and store Msg in database from src.utils import create mymsg = create.create_message(sender, "Hello!", channels=[mychan]) + # use the Msg object directly, no other keywords are needed mychan.msg(mymsg) - # send a one-time message - mychan.msg("Hello!") + # create a Msg automatically behind the scenes + mychan.msg("Hello!", senders=[sender]) - # send a one-time message created from a Msg object + # send a non-persistent TempMsg (note that the senders + # keyword can also be used without a list if there is + # only one sender) + mychan.msg("Hello!", senders=sender, persistent=False) + + # this is a shortcut that always sends a non-persistent TempMsg + # also if a full Msg was supplied to it (it also creates TempMsgs + # on the fly if given a string). mychan.tempmsg(mymsg) -As a more advanced note, sending text to channels is a "special -exception" as far as commands are concerned, and you may completely -customize how this works by defining a *system\_command* with your own -code. See `Commands `_ for more details. +On a more advanced note, when a player enters something like +``ooc Hello!`` (where ``ooc`` is the name/alias of a channel), this is +treated as a `System Command `_ by Evennia. You may +completely customize how this works by defining a system command with +your own code. See `Commands `_ for more details. Properties defined on \`Channel\` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/sphinx/source/wiki/ConnectionScreen.rst b/docs/sphinx/source/wiki/ConnectionScreen.rst index 3f289e8520..8b7a6e0fee 100644 --- a/docs/sphinx/source/wiki/ConnectionScreen.rst +++ b/docs/sphinx/source/wiki/ConnectionScreen.rst @@ -24,24 +24,27 @@ Effective, but not very exciting. You will most likely want to change this to be more unique for your game. You can customize the connection screen easily. If you look in -``game/gamesrc/world`` you will find a module named -``connection_screens.py``. Evennia looks into this module for globally -defined strings (only). These strings are used as connection screens and -shown to the user at startup. If more than one screen is defined in the -module, a random screen will be picked from among those available. +``game/gamesrc/conf/examples/`` you will find a module named +``connection_screens.py``. Copy this module up one level (to +``game/gamesrc/conf/``) and set ``settings.CONNECTION_SCREEN_MODULE`` to +point to your new module. + +Evennia looks into this module for globally defined strings (only). +These strings are used as connection screens and shown to the user at +startup. If more than one screen is defined in the module, a random +screen will be picked from among those available. Evennia's default screen is imported as ``DEFAULT_SCREEN`` from -``src.commands.connection_screen``. Remove the import or redefine -``DEFAULT_SCREEN`` to get rid of the default. There is a commented-out -example screen in the module that you can start from. You can define and -import things as normal into the module, but remember that *all* global -strings will be picked up and potentially used as a connection screen. -You can change which module Evennia uses by changing -``settings.CONNECTION_SCREEN_MODULE``. +``src.commands.connection_screen``. Remove the import at the top or +redefine ``DEFAULT_SCREEN`` to get rid of the default. There is a +commented-out example screen in the module that you can start from. You +can define and import things as normal into the module, but remember +that *all* global strings will be picked up and potentially used as a +connection screen. You can also customize the `commands `_ available during the connection screen (``connect``, ``create`` etc). These commands are a bit special since when the screen is running the player is not yet -identified. A command is made available at the login screen by adding +logged in. A command is made available at the login screen by adding them to the command set specified by settings.CMDSET\_UNLOGGEDIN. The default commands are found in ``src/commands/default/unloggedin.py``. diff --git a/docs/sphinx/source/wiki/Contributing.rst b/docs/sphinx/source/wiki/Contributing.rst index 30898b7427..23d2d9de62 100644 --- a/docs/sphinx/source/wiki/Contributing.rst +++ b/docs/sphinx/source/wiki/Contributing.rst @@ -3,6 +3,16 @@ Contributing to Evennia Wanna help out? Great! Here's how. +Contributing by spreading the word +---------------------------------- + +Even if you are not keen on working on the server code yourself, just +spreading the word is a big help - it will help attract more people +which leads to more feedback, motivation and interest. Rating and +writing a review on places like +`ohloh `_, talk about what you do in a +blog post or in mud forums, that kind of thing. + Contributing with Documentation ------------------------------- diff --git a/docs/sphinx/source/wiki/DefaultCommandHelp.rst b/docs/sphinx/source/wiki/DefaultCommandHelp.rst index 8d2da73f77..1b4679c7b6 100644 --- a/docs/sphinx/source/wiki/DefaultCommandHelp.rst +++ b/docs/sphinx/source/wiki/DefaultCommandHelp.rst @@ -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 86 commands in 6 categories. More information about how +contains 85 commands in 6 categories. More information about how commands work can be found in the `Command `_ documentation. @@ -137,7 +137,7 @@ module `_ = ``cmd:perm(emit) or perm(Builders)`` - `help\_category `_ = ``Admin`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -319,7 +319,7 @@ module `_ = ``cmd:perm(batchcommands) or superuser()`` - `help\_category `_ = ``Building`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -510,7 +510,7 @@ module `_ = ``cmd:perm(destroy) or perm(Builders)`` - `help\_category `_ = ``Building`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -570,7 +570,7 @@ module `_ = ``cmd:perm(examine) or perm(Builders)`` - `help\_category `_ = ``Building`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -600,7 +600,7 @@ module `_ = ``cmd:perm(find) or perm(Builders)`` - `help\_category `_ = ``Building`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -666,7 +666,7 @@ module `` - `locks `_ = ``cmd:perm(@home) or perm(Builders)`` - `help\_category `_ = ``Building`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -721,7 +721,7 @@ module `_ = ``cmd: perm(@locks) or perm(Builders)`` - `help\_category `_ = ``Building`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -910,29 +910,19 @@ module =] - - Examples: - @tel Limbo - @tel/quiet box Limbo - @tel/tonone box + @tel/switch [ =] 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, is ignored. - Note that the only way to retrieve - an object from a None location is by direct #dbref - reference. - Teleports an object somewhere. If no object is given, you yourself - is teleported to the target location. + Teleports an object or yourself somewhere. + @tunnel ~~~~~~~ @@ -1183,7 +1173,7 @@ module `_ = ``cmd: not pperm(channel_banned)`` - `help\_category `_ = ``Comms`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -1280,7 +1270,7 @@ module `_ = ``cmd: serversetting(IMC2_ENABLED) and pperm(Wizards)`` - `help\_category `_ = ``Comms`` @@ -1446,7 +1436,7 @@ imctell (OOC command) ~~~~~~~~~~~~~~~~~~~~~ - ``key`` = ``imctell`` -- ``aliases`` = ``imc2tell, imc2page, imcpage`` +- ``aliases`` = ``imcpage, imc2tell, imc2page`` - `locks `_ = ``cmd: serversetting(IMC2_ENABLED)`` - `help\_category `_ = ``Comms`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -1497,29 +1487,6 @@ General `Link to Python module `_ -@color -~~~~~~ - -- ``key`` = ``@color`` -- ``aliases`` = ```` -- `locks `_ = ``cmd:all()`` -- `help\_category `_ = ``General`` -- [`HelpSystem `_\ #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) ~~~~~~~~~~~~~~~~~~~~~~~ @@ -1642,7 +1609,7 @@ access ~~~~~~ - ``key`` = ``access`` -- ``aliases`` = ``hierarchy, groups`` +- ``aliases`` = ``groups, hierarchy`` - `locks `_ = ``cmd:all()`` - `help\_category `_ = ``General`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -1771,7 +1738,7 @@ inventory ~~~~~~~~~ - ``key`` = ``inventory`` -- ``aliases`` = ``i, inv`` +- ``aliases`` = ``inv, i`` - `locks `_ = ``cmd:all()`` - `help\_category `_ = ``General`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -1838,7 +1805,7 @@ nick ~~~~ - ``key`` = ``nick`` -- ``aliases`` = ``@nick, nicks, nickname, alias`` +- ``aliases`` = ``nickname, nicks, @nick, alias`` - `locks `_ = ``cmd:all()`` - `help\_category `_ = ``General`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -1975,7 +1942,7 @@ module `_ = ``cmd:perm(listobjects) or perm(Builders)`` - `help\_category `_ = ``System`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -2074,7 +2041,7 @@ module `_ = ``cmd:perm(listscripts) or perm(Wizards)`` - `help\_category `_ = ``System`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -2236,7 +2203,7 @@ connect (Unloggedin command) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - ``key`` = ``connect`` -- ``aliases`` = ``co, conn, con`` +- ``aliases`` = ``conn, con, co`` - `locks `_ = ``cmd:all()`` - `help\_category `_ = ``Unloggedin`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -2247,19 +2214,16 @@ connect (Unloggedin command) Connect to the game. Usage (at login screen): - connect playername password - connect "player name" "pass word" + connect Use the create command to first create an account before logging in. - - If you have spaces in your name, enclose it in quotes. create (Unloggedin command) ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - ``key`` = ``create`` -- ``aliases`` = ``cr, cre`` +- ``aliases`` = ``cre, cr`` - `locks `_ = ``cmd:all()`` - `help\_category `_ = ``Unloggedin`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -2270,12 +2234,10 @@ create (Unloggedin command) Create a new account. Usage (at login screen): - create - create "player name" "pass word" + create "playername" This creates a new player account. - If you have spaces in your name, enclose it in quotes. help (Unloggedin command) diff --git a/docs/sphinx/source/wiki/EvenniaIntroduction.rst b/docs/sphinx/source/wiki/EvenniaIntroduction.rst index e3275e4e5b..e69de29bb2 100644 --- a/docs/sphinx/source/wiki/EvenniaIntroduction.rst +++ b/docs/sphinx/source/wiki/EvenniaIntroduction.rst @@ -1,200 +0,0 @@ - "*A MUD (originally Multi-User Dungeon, with later variants - Multi-User Dimension and Multi-User Domain), pronounced 'mud', is a - multiplayer real-time virtual world described primarily in text. - MUDs combine elements of role-playing games, hack and slash, player - versus player, interactive fiction, and online chat. Players can - read or view descriptions of rooms, objects, other players, - non-player characters, and actions performed in the virtual world. - Players typically interact with each other and the world by typing - commands that resemble a natural language.*\ " - - `Wikipedia `_ - -Evennia introduction -==================== - -If you are reading this, it's quite likely you are dreaming of creating -and running a text-based massively-multiplayer game -(`MUD/MUX/MUSH `_ etc) of your very own. You -might just be starting to think about it, or you might have lugged -around that *perfect* game in your mind for years ... you know *just* -how good it would be, if you could only make it come to reality. We know -how you feel. That is, after all, why Evennia came to be. - -Evennia is in principle a MUD-building system: a bare-bones Python -codebase and server intended to be highly extendable for any style of -game. "Bare-bones" in this context means that we try to impose as few -game-specific things on you as possible. So whereas we for convenience -offer basic building blocks like objects, characters, rooms, default -commands for building and administration etc, we don't prescribe any -combat rules, mob AI, races, skills, character classes or other things -that will be different from game to game anyway. It is possible that we -will offer some such systems as contributions in the future, but these -will in that case all be optional. - -What we *do* however, is to provide a solid foundation for all the -boring database, networking, and behind-the-scenes administration stuff -that all online games need whether they like it or not. Evennia is -*fully persistent*, that means things you drop on the ground somewhere -will still be there a dozen server reboots later. Through Django we -support a large variety of different database systems (a database is -created for you automatically if you use the defaults). - -Using the full power of Python throughout the server offers some -distinct advantages. All your coding, from object definitions and custom -commands to AI scripts and economic systems is done in normal Python -modules rather than some ad-hoc scripting language. The fact that you -script the game in the same high-level language that you code it in -allows for very powerful and custom game implementations indeed. - -The server ships with a default set of player commands that are similar -to the MUX command set. We *do not* aim specifically to be a MUX server, -but we had to pick some default to go with (see `this `_ -for more about our original motivations). It's easy to remove or add -commands, or to have the command syntax mimic other systems, like Diku, -LP, MOO and so on. Or why not create a new and better command system of -your own design. - -Can I test it somewhere? ------------------------- - -There are Evennia-based muds under development but they are still not -publicly available. If you do try to install Evennia (it's not hard), it -comes with its own tutorial though - this shows off some of the -possibilities *and* gives you a small single-player quest to play. The -tutorial takes only one single in-game command to install as explained -`here `_. - -If you didn't see it before, here is also a -`screenshot `_ of Evennia running. - -Brief summary of features -========================= - -Technical ---------- - -- Game development is done by the server importing your normal Python - modules. Specific server features are implemented by overloading - hooks that the engine calls appropriately. -- All game entities are simply Python classes that handles database - negotiations behind the scenes without you needing to worry. -- Command sets are stored on individual objects (including characters) - to offer unique functionality and object-specific commands. Sets can - be updated and modified on the fly to expand/limit player input - options during play. -- Scripts are used to offer asynchronous/timed execution abilities. - Scripts can also be persistent. There are easy mechanisms to thread - particularly long-running processes. -- In-game communication channels are modular and can be modified to any - functionality, including mailing systems and full logging of all - messages. -- Server can be fully rebooted/reloaded without users disconnecting. -- A session (player) can freely connect/disconnect from game-objects, - offering an easy way to implement multi-character systems and - puppeting. -- All source code is extensively documented. -- Unit-testing suite, including tests of default commands and plugins - -Default content ---------------- - -- Basic classes for Objects, Characers, Rooms and Exits -- Basic login system, using the Player's login name as their in-game - Character's name for simplicity -- "MUX-like" command set with administration, building, puppeting, - channels and social commands -- In-game Tutorial -- Contributions folder with working, but optional, code such as - alternative login, menus, character generation and more - -Standards/Protocols supported ------------------------------ - -- Telnet with mud-specific extensions (MCCP, MSSP, TTYPE) -- SSH -- SSL -- TCP/Comet, JavaScript browser webclient included -- HTTP - Website served by in-built webserver and connected to same - database as game. -- IRC/IMC2 - external IRC and/or IMC2 channels can be connected to - in-game chat channels -- RSS feeds can be echoed to in-game channels -- ANSI, xterm256 colours -- Several different databases supported (SQLite3, MySQL, ...) - -For more extensive feature information, see -`here `_. - -What you need to know to work with Evennia -========================================== - -Assuming you have Evennia working (see the `quick start -instructions `_) and have gotten as far as to start -the server and connect to it with the client of your choice, here's what -you need to know depending on your skills and needs. - -I don't know (or don't want to do) any programming - I just want to run a game! -------------------------------------------------------------------------------- - -Evennia comes with a default set of commands for the Python newbies and -for those who need to get a game running *now*. Stock Evennia is enough -for running a simple 'Talker'-type game - you can build and describe -rooms and basic objects, have chat channels, do emotes and other things -suitable for a social or free-form MU\ ``*``. Combat, mobs and other -game elements are not included, so you'll have a very basic game indeed -if you are not willing to do at least *some* coding. - -I know basic Python, or am willing to learn -------------------------------------------- - -Evennia's source code is extensively documented and `viewable -online `_. We also have -a comprehensive `online -manual `_ with lots of -examples. But while Python is a relatively easy programming language, it -still represents a learning curve if you are new to programming. You -should probably sit down with a Python beginner's -`tutorial `_ (there are plenty of them -on the web if you look around) so you at least know what you are seeing. -To efficiently code your dream game in Evennia you don't need to be a -Python guru, but you do need to be able to read example code containing -at least these basic Python features: - -- Importing python modules -- Using variables, `conditional - statements `_, - `loops `_ - and - `functions `_ -- Using `lists, dictionaries and list - comprehensions `_ -- Doing `string handling and - formatting `_ -- Using `Classes `_, - their methods and properties - -Obviously, the more things you feel comfortable with, the easier time -you'll have to find your way. With just basic knowledge you should be -able to define your own `Commands `_, create custom -`Objects `_ as well as make your world come alive with -basic `Scripts `_. You can definitely build a whole -advanced and customized game from extending Evennia's examples only. - -I know my Python stuff and am willing to use it! ------------------------------------------------- - -Even if you started out as a Python beginner, you will likely get to -this point after working on your game for a while. With more general -knowledge in Python the full power of Evennia opens up for you. Apart -from modifying commands, objects and scripts, you can develop everything -from advanced mob AI and economic systems, through sophisticated combat -and social minigames, to redefining how commands, players, rooms or -channels themselves work. Since you code your game by importing normal -Python modules, there are few limits to what you can accomplish. - -If you *also* happen to know some web programming (HTML, CSS, -Javascript) there is also a web presence (a website and an mud web -client) to play around with ... - -From here you can continue to the `Index `_ to find more -info about Evennia. diff --git a/docs/sphinx/source/wiki/GettingStarted.rst b/docs/sphinx/source/wiki/GettingStarted.rst index ac8abdb2b2..56a77fe296 100644 --- a/docs/sphinx/source/wiki/GettingStarted.rst +++ b/docs/sphinx/source/wiki/GettingStarted.rst @@ -49,7 +49,7 @@ platform, please let us know. You'll need the following packages and minimum versions in order to run Evennia: -- **`Python `_** (v2.6+, not supporting v3.x) +- **`Python `_** (v2.6+, not supporting v3.x). - Windows users are recommended to use `ActivePython `_ @@ -62,95 +62,97 @@ Evennia: - Windows users might also need `pywin32 `_. -- **`Django `_** (v1.3+ or latest dev - build recommended) +- **`Django `_** (v1.4+) - `PIL `_ (Python Image Library) - often distributed with Django. +- **`South `_** (v0.7+) + + - South is used to track and apply changes to the database's + structure. + To download/update Evennia: - **`Mercurial `_** -Optional packages: +Optional: -- **`South `_** (v0.7+) +- **`PyPy `_** (v1.7+) - - Optional, but highly recommended. Makes it easy to keep up with - Evennia updates to the database schema. + - Optional faster implementation of Python. See + [`GettingStarted `_\ #Optional:\ *Running\_under\_PyPy + here] for how to run Evennia under PyPy.* -- **`Apache2 `_** + Installing pre-requisites + ------------------------- - - Optional. Only use if you don't want to use Evennia's own threaded - webserver. Other equivalent web servers with a Python interpreter - module can also be used. + **All platforms** can set up an \_virtual Python environment and + install Evennia to that. All you need pre-installed is Python. + Setup is described in detail + [`GettingStarted `_\ #Optional:\ *A\_separate\_installation\_environment\_with\_virtualenv + here]. Windows users will probably want to go the ActivePython + route instead (see below) since there are issues with installing + certain extensions under Windows.* -Installing pre-requisites -~~~~~~~~~~~~~~~~~~~~~~~~~ + **Linux** package managers should usually handle all this for you. + Python itself is definitely available through all distributions. + On Debian-derived systems (such as Ubuntu) you can do something + like this (as root) to get all you need: -**All platforms** can set up an *virtual Python environment* and install -Evennia to that. All you need pre-installed is Python. Setup is -described in detail -[`GettingStarted `_\ #Optional:\ *A\_separate\_installation\_environment\_with\_virtualenv -here]. Windows users will probably want to go the ActivePython way -instead though (see below), there are issues with installing certain -extensions in Windows.* + :: -**Linux** package managers should usually handle all this for you. -Python itself is definitely available through all distributions. On -Debian-derived systems (such as Ubuntu) you can do something like this -(as root) to get all you need: + apt-get install python python-django python-twisted mercurial python-django-south -:: + (Gentoo note: Gentoo (and maybe other distros?) seems to + distribute Twisted in multiple packages. Beyond the main twisted + package you will also need to get at least twisted-conch and + twisted-web too).\ ** - apt-get install python python-django python-twisted mercurial python-django-south + Distributions can usually not keep fully up-to-date with the + latest security fixes. So for an online server it is highly + recommended to use Python's + `easy\_install `_ + or the newer + `pip `_ to get + some or all of the dependencies instead: -(Gentoo note: Gentoo (and maybe other distros?) seems to distribute -Twisted in multiple packages. Beyond the main twisted package you will -also need to get at least twisted-conch and twisted-web too).\ ** + :: -Few distros actually keep the latest updated security updates (notably -django and twisted) in their repos though. So it might be worth to use -Python's -`easy\_install `_ -or the alternative -`pip `_ to get some -or all of these instead: + easy_install django twisted pil mercurial south -:: + :: - easy_install django twisted pil mercurial south + pip install django twisted pil mercurial south -:: + If you already have Python and have downloaded Evennia, the + package comes with a ``requirements.txt`` file. This can be used + with ``pip`` to install the remaining dependencies. This is useful + for automated build systems: - pip install django twisted pil mercurial south + :: -If you already have Python and mercurial, and have downloaded Evennia, -the package comes with a ``requirements.txt`` file. This can be used -with ``pip`` to install the remaining dependencies (possibly useful for -automated build systems): + pip install -r requirements.txt -:: + **Mac** users should be able to get most dependencies through + ``easy_install`` or ``pip`` like Linux users do. All interaction + is done from a terminal window. There are some reports that you + might need to get the + `Xcode `_ development system + to install the packages that requires extension compiling. You can + also retrieve the dependencies directly and install them through + their native installers or python setups. Some users have reported + problems compiling the ``PIL`` library on Mac, it's however not + strictly required in order to use Django (it's used for images). - pip install -r requirements.txt - -**Mac** users should be able to get most dependencies through -``easy_install`` or ``pip`` like Linux users do. All interaction is done -from a terminal window. There are some reports that you might need to -get the `Xcode `_ development system -to install the packages that requires extension compiling. You can also -retrieve the dependencies directly and install them through their native -installers or python setups. Some users have reported problems compiling -the ``PIL`` library on Mac, it's however not strictly required in order -to use Django (it's used for images). - -\_Note (June 2012): Some versions of MacOSX does not seem to have a -locale setting out of the box, and this causes a traceback during -database creation. This is a known upstream bug in Django 1.4, described -`here `_. -In the bug comments is also described how to add the locale and -circumvent this bug for now. This affects also Unix/Linux systems, but -those usually have the locale set out of the box. + \_Note (June 2012): Some versions of MacOSX does not seem to have + a locale setting out of the box, and this causes a traceback + during database creation. This is a known upstream bug in Django + 1.4, described + `here `_. + In the bug comments is also described how to add the locale and + circumvent this bug for now. This affects also Unix/Linux systems, + but those usually have the locale set out of the box. **Windows** users should first and foremost recognize that the Evennia server is run from the command line, something which some might not be @@ -167,8 +169,8 @@ one won't let you download any packages without paying for a "Business" license). If ActivePython is installed, you can use `pypm `_ in the same manner as ``easy_install``/``pip`` above. This *greatly* simplifies -getting started on Windows since that platform is by default missing -many of the sane developer systems that Linux users take for granted. +getting started on Windows - that platform defaults to missing many of +the sane developer tools that Linux users take for granted. After installing ActivePython you may need to restart the terminal/DOS window to make the pypm command available on the command line: @@ -328,8 +330,7 @@ set up a very easy self-contained Evennia install using the `virtualenv `_ program. If you are unsure how to get it, just grab the `virtualenv.py `_ -file from that page and run it directly in the terminal with -``python virtualenv.py``. +file and run it directly in the terminal with ``python virtualenv.py``. Virtualenv sets aside a folder on your harddrive as a stand-alone Python environment. It should work both on Linux/Unix and Windows. First, @@ -339,13 +340,13 @@ in an isolated new folder *mudenv*: :: - python virtualenv mudenv --no-site-packages + virtualenv mudenv Or, if you grabbed ``virtualenv.py`` and is running it directly: :: - python virtualenv.py mudenv --no-site-packages + python virtualenv.py mudenv Followed by @@ -363,22 +364,71 @@ virtual environment in here. # for Windows: \Scripts\activate.bat -The virtual environment within our *mudenv* folder is now active. Next -we get all the requirements with *pip*, which is included with +The virtual environment within our *mudenv* folder is now active. Things +will not seem to have changed very much, and indeed they haven't - the +only difference is that python programs will now look to the python +installation in this folder instead of the system-centric ones. + +Next we get all the requirements with *pip*, which is included with virtualenv: :: pip install django twisted pil mercurial south -The difference from the normal install described earlier is that these -installed packages are *only* localized to the virtual environment, they -do not affect the normal versions of programs you run in the rest of -your system. So you could for example experiment with bleeding-edge, -unstable libraries or go back to older versions without having to worry -about messing up other things. It's also very easy to uninstall the -whole thing in one go - just delete your ``mudenv`` folder. +These newly installed packages are *only* localized to the virtual +environment, they do not affect the normal versions of programs you run +in the rest of your system. So you could for example experiment with +bleeding-edge, unstable libraries or go back to older versions without +having to worry about messing up other things. It's also very easy to +uninstall the whole thing in one go - just delete your ``mudenv`` +folder. You can now refer to **Step 1** above and continue on from there to install Evennia into *mudenv*. In the future, just go into the folder and activate it before starting or working with Evennia. + +Optional: Running under !PyPy +============================= + +Evennia can also be run under `PyPy `_, a fast +alternative implementation of standard Python. Evennia under PyPy +generally takes longer to start but may run faster (just how much is +currently untested so feel free to report your findings). + +You first need to download and install PyPy. See the +`PyPy `_ homepage for instructions. This may be the +most tricky step depending on your operating system. + +The easiest way to set up Evennia for running under pypy is to do so in +a separate *virtualenv*. First get the virtualenv program as described +in the previous section and create your virtual environment like this: + +:: + + virtualenv mudenv --python=/usr/bin/pypy + +Replace ``/usr/bin/pypy`` with the full path to your PyPy binary on your +system. + +Next you activate and set up the virtual environment as normal. Make +sure to install all dependencies to the virtual environment. If ``pip`` +aborts with a message about "dependence is already satisfied", use the +--upgrade option. This way PyPy-enhanced versions of the dependencies +will be installed wherever applicable. + +Download and configure Evennia as usual. Then just start it like this: + +:: + + pypy evennia.py -i start + +Inside the game you can do the following (as superuser) to test that +PyPy is working: + +:: + + @py import __pypy__ + +If this works Evennia is running under pypy. If you get an ImportError +there was some problem and normal Python is still being used. diff --git a/docs/sphinx/source/wiki/Licensing.rst b/docs/sphinx/source/wiki/Licensing.rst index e2a4eb1d17..d34b4b1660 100644 --- a/docs/sphinx/source/wiki/Licensing.rst +++ b/docs/sphinx/source/wiki/Licensing.rst @@ -1,12 +1,14 @@ -Evennia Licence FAQ +Evennia License FAQ =================== -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 `_. +Evennia is licensed under the very friendly +`BSD `_ (3-clause) license. +You can find the license as ``LICENSE.txt`` in the Evennia root +directory. You can also read the full license file +`here `_ +(it's not long). -Q: When creating a game using Evennia, what does the licence permit me to do with it? +Q: When creating a game using Evennia, what does the license permit me to do with it? ------------------------------------------------------------------------------------- **A:** It's your own game world to do with as you please! Keep it to @@ -16,14 +18,14 @@ it and become filthy rich for all we care. Q: I have modified Evennia itself, what does the license say about that? ------------------------------------------------------------------------ -**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. +**A:** The BSD 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. -... 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 +... Of course, if you make bug fixes or add some new snazzy feature we +*softly nudge* you to make those changes available so they can be added +to the core Evennia package for everyone's benefit. The license don't require you to do it, but that doesn't mean we can't still greatly appreciate it if you do! @@ -31,3 +33,10 @@ Q: Can I re-distribute the Evennia server package along with my custom game impl ------------------------------------------------------------------------------------------- **A:** Sure. As long as the text in LICENSE.txt is included. + +Q: What about Contributions? +---------------------------- + +The contributions in ``evennia/contrib`` are considered to be released +under the same license as Evennia itself unless the individual +contributor has specifically defined otherwise. diff --git a/docs/sphinx/source/wiki/Links.rst b/docs/sphinx/source/wiki/Links.rst index c387c3329c..970f61cb10 100644 --- a/docs/sphinx/source/wiki/Links.rst +++ b/docs/sphinx/source/wiki/Links.rst @@ -18,6 +18,7 @@ Evennia links what was changed in each update) - `Evennia mailing list `_ (better web interface can be found from the portal) +- `Evennia development blog `_ Third-party Evennia links ------------------------- @@ -45,13 +46,13 @@ General mud/game development ideas and discussions - `MudLab `_ mud design discussion forum - `MudConnector `_ mud listing and forums - `MudBytes `_ mud listing and forums -- `Top Mud bytes `_ mud listing and forums +- `Top Mud Sites `_ mud listing and forums - `MudStandards wiki `_ is an attempt at gathering protocol standards for MUD servers. - `Planet Mud-Dev `_ is a blog - aggregator following blogs of current MUD development around the - 'net. + aggregator following blogs of current MUD development (including + Evennia) around the 'net. - Mud Dev mailing list archive (`mirror1 `_),(\ `mirror2 `_) - Influential mailing list active 1996-2004. Advanced game design diff --git a/docs/sphinx/source/wiki/Locks.rst b/docs/sphinx/source/wiki/Locks.rst index 86d2222e41..52ec6af998 100644 --- a/docs/sphinx/source/wiki/Locks.rst +++ b/docs/sphinx/source/wiki/Locks.rst @@ -126,8 +126,8 @@ Below are the access\_types checked by the default commandset. - ``get``- who may pick up the object and carry it around. - ``puppet`` - who may "become" this object and control it as their "character". - - ``attrcreate`` - allows to create new objects on object (default - True) + - ``attrcreate`` - who may create new attributes on the object + (default True) - [Objects#Characters Characters]: ```` - [Objects#Exits Exits]: ```` + ``traverse`` - who may @@ -200,10 +200,11 @@ Some useful default lockfuncs (see ``src/locks/lockfuncs.py`` for more): - ``true()/all()`` - give access to everyone - ``false()/none()/superuser()`` - give access to noone. Superusers bypass the check entirely. -- ``perm(perm)`` - this tries to match a given ``permission`` property. - See [Locks#Permissions below]. -- ``perm_above(perm)`` - requres a "higher" permission level than the - one given. +- ``perm(perm)`` - this tries to match a given ``permission`` property, + on a Player firsthand, on a Character second. See [Locks#Permissions + below]. +- ``perm_above(perm)`` - like ``perm`` but requires a "higher" + permission level than the one given. - ``id(num)/dbref(num)`` - checks so the access\_object has a certain dbref/id. - ``attr(attrname)`` - checks if a certain @@ -225,7 +226,7 @@ Default locks Evennia sets up a few basic locks on all new objects and players (if we didn't, noone would have any access to anything from the start). This is -all defined in the root `Typeclasses `_ of the +all defined in the root `Typeclasses `_ of the respective entity, in the hook method ``basetype_setup()`` (which you usually don't want to edit unless you want to change how basic stuff like rooms and exits store their internal variables). This is called @@ -245,10 +246,16 @@ set by the ``@perm`` command. :: - @perm Tommy = Builders + @perm *Tommy = Builders -All new players/character are given a default set of permissions defined -by ``settings.PERMISSION_PLAYER_DEFAULT``. +Note the use of the asterisk ``*`` above. For the ``@perm`` command it +means assigning to the `Player `_ Tommy instead of any +`Character `_ that also happens to be named Tommy. Putting +permissions on the Player guarantees that they are kept regardless of +which Character they are currently puppeting. + +All new players are given a default set of permissions defined by +``settings.PERMISSION_PLAYER_DEFAULT``. Selected permission strings can be organized in a *permission hierarchy* by editing the tuple ``settings.PERMISSION_HIERARCHY``. Evennia's @@ -265,11 +272,19 @@ default permission hierarchy is as follows: The main use of this is that if you use the lock function ``perm()`` mentioned above, a lock check for a particular permission in the hierarchy will *also* grant access to those with *higher* hierarchy -acces. So if you have the permission "Wizards" you will also pass a lock -defined as ``perm(Builders)`` or any of those levels below "Wizards". -The lock function ``perm_above(Players)`` require you to have a -permission level higher than ``Players`` and so on. If the permission -looked for is not in the hierarchy, an exact match is required. +access. So if you have the permission "Wizards" you will also pass a +lock defined as ``perm(Builders)`` or any of those levels below +"Wizards". When doing an access check from an `Object `_ +or Character, the ``perm()`` lock function will always first use the +permissions of any Player connected to that Object before checking for +permissions on the Object. In the case of hierarchical permissions +(Wizards, Builders etc), the Player permission will always be used (this +stops a Player from escalating their permission by puppeting a +high-level Character). If the permission looked for is not in the +hierarchy, an exact match is required, first on the Player and if not +found there (or if no Player is connected), then on the Object itself. + +Below is an example of an object without any connected player :: @@ -278,6 +293,16 @@ looked for is not in the hierarchy, an exact match is required. obj2.access(obj1, "enter") # this returns True! +And one example of a puppet with a connected player: + +:: + + player.permissions = ["Players"] + puppet.permissions = ["Builders", "cool_guy"] + obj2.locks.add("enter:perm_above(Players) and perm(cool_guy)") + + obj2.access(puppet, "enter") # this returns False! + Superusers ---------- @@ -290,6 +315,18 @@ But it also hides any eventual errors you might have made in your lock definitions. So when trying out game systems you should use a secondary character rather than #1 so your locks get tested correctly. +Quelling +-------- + +The ``@quell`` command can be used to enforce the ``perm()`` lockfunc to +ignore permissions on the Player and instead use the permissions on the +Character only. This can be used e.g. by staff to test out things with a +lower permission level. Return to the normal operation with +``@unquell``. Note that quelling will use the smallest of any +hierarchical permission on the Player or Character, so one cannot +escalate one's Player permission by quelling to a high-permission +Character. Also, the superuser cannot be quelled. + More Lock definition examples ============================= diff --git a/docs/sphinx/source/wiki/Players.rst b/docs/sphinx/source/wiki/Players.rst index 5128d97ac8..389d2b7b51 100644 --- a/docs/sphinx/source/wiki/Players.rst +++ b/docs/sphinx/source/wiki/Players.rst @@ -1,29 +1,57 @@ Players ======= -All users (in the sense of actual people) connecting to Evennia are +All gamers (real people) that opens a game *Session* on Evennia are doing so through an object called *Player*. The Player object has no -in-game representation; rather it is the out-of-character representation -of the user. The Player object is what is chatting on +in-game representation, it represents the account the gamer has on the +game. In order to actually get on the game the Player must *puppet* an +`Object `_ (normally a Character). + +Just how this works depends on the configuration option +``MULTISESSION_MODE``. There are three multisession modes, described in +the diagram below: + +|image0| + +From left to right, these show ``MULTISESSION_MODE`` 0, 1 and 2. In all +cases the gamer connects to the `Portal `_ with +one or more sessions - this could be a telnet connection, webclient, ssh +or some of the other protocols Evennia supports. + +- In mode 0 (leftmost), each Player can only hold one session at a + time. This is the normal mode for many legacy muds. +- In mode 1 (middle), each Player can hold any number of sessions but + they are all treated equal. This means all giving a command in one + client is doing exactly the same thing as doing so in any other + connected client. All sessions will see the same output and e.g. + giving the @quit command will kill all sessions. +- In mode 2 (right) eeach Player can hold any number of sessions and + they are kept separate from one another. This allows a single player + to puppet any number of Characters and Objects. + +Apart from storing login information and other account-specific data, +the Player object is what is chatting on `Channels `_. It is also a good place to store `Permissions `_ to be consistent between different in-game -characters, configuration options, account info and other things related -to the user. Players are `TypeClassed `_ entities and -can store a `CmdSet `_ of their own for OOC-type -commands. +characters as well as configuration options. Players are +`TypeClassed `_ entities defaulting to use +``settings.BASE_PLAYER_TYPECLASS``. They also hold a +`CmdSet `_ defaulting to the set defined by +``settings.CMDSET_PLAYER``. -If you are logged in into default Evennia, you can use the ``@ooc`` -command to leave your current `Character `_ and go into -OOC mode. You are quite limited in this mode, basically it works like a -simple chat program. It acts as a staging area for switching between -Characters (if your game supports that) or as a safety mode if your -Character gets deleted. . Use ``@ic`` to switch back "into" your -character. +If you are logged in into default Evennia under any multisession mode, +you can use the ``@ooc`` command to leave your current +`Character `_ and go into OOC mode. You are quite limited +in this mode, basically it works like a simple chat program. It acts as +a staging area for switching between Characters (if your game supports +that) or as a safety mode if your Character gets deleted. . Use ``@ic`` +attempt to puppet a Character. -Also note that the Player object can have a different set of -[Locks#Permissions Permissions] from the Character they control (in the -first character you create permissions for Player and Character are the -same, however). +Note that the Player object can and often do have a different set of +[Locks#Permissions Permissions] from the Character they control. +Normally you should put your permissions on the Player level - only if +your Player does not have a given permission will the permissions on the +Character be checked. How to create your own Player types ----------------------------------- @@ -50,7 +78,7 @@ Here's how to define a new Player typeclass in code: at_player_creation(self): "this is called only once, when player is first created" self.db.real_name = None # this is set later - self.db.real_address = None # '' + self.db.real_address = None # " self.db.config_1 = True # default config self.db.config_2 = False # " self.db.config_3 = 1 # " @@ -72,58 +100,32 @@ custom properties: - ``user`` - a unique link to a ``User`` Django object, representing the logged-in user. -- ``character`` - a reference to an associated *Character*-type - `Object `_. - ``obj`` - an alias for ``character``. - ``name`` - an alias for ``user.username`` - ``sessions`` - a list of all connected Sessions (physical - connections) this object listens to. + connections) this object listens to. The so-called session-id (used + in many places) is found as a property ``sessid`` on each Session + instance. - ``is_superuser`` (bool: True/False) - if this player is a superuser. Special handlers: - ``cmdset`` - This holds all the current `Commands `_ of this Player. By default these are the commands found in the cmdset - defined by ``settings.CMDSET_OOC``. + defined by ``settings.CMDSET_PLAYER``. - ``nicks`` - This stores and handles `Nicks `_, in the same way as nicks it works on Objects. For Players, nicks are primarily used to store custom aliases for [Communications#Channels Channels]. -How it all hangs together -------------------------- +Selection of special methods (see ``src.player.models`` for details): -Looking at the above list, it's clear there are more to ``Player``\ s -than what first meets the eye. +- ``get_puppet`` - get a currently puppeted object connected to the + Player and a given given session id, if any. +- ``puppet_object`` - connect a session to a puppetable Object. +- ``unpuppet_object`` - disconnect a session from a puppetable Object. +- ``msg`` - send text to the Player +- ``execute_cmd`` - runs a command as if this Player did it. +- ``search`` - search for Players. -What happens when a person connects to Evennia and logs in is that they -log in as a ``User`` object. This is a Django object that knows all -about keeping track of authentication - it stores the login name -(``username``), password, e-mail etc. - -We can't change ``User`` very much unless we want to start digging down -into Django source code (and we don't). Django however allows another -model (technically called a *profile*) to reference the User for -customization. This is our ``Player`` object. There is a one-to-one -relationship between ``User`` and Player, so we have tried to completely -hide the ``User`` interface throughout Evennia and left you to only have -to deal with ``Player``. - -So for all purposes, ``Player`` represents the OOC existence of a person -logged into Evennia. You e.g. connect to -`Channels `_ using your Player identity. The Player -object may store configurations and follows you wherever you go. You -will however never see the Player object in-game. This is handled by a -*Character*, a type of `Object `_ connected to the -``character`` property in the list above. - -So why don't we just use ``Player`` to walk around with too? The main -reason for this is flexibility. Your Player object won't change, but -your character *might*. By separating the two you could easily implement -a game where you can ``swap`` between different *Character* objects. All -you'd need to do is change the ``character`` property to point to -another suitable object (and change the values of the ``player`` -property on the affected objects). - -So the structure looks like ``User - Player - Character``, where the -last two are typeclassed, customizable objects. +.. |image0| image:: https://lh5.googleusercontent.com/-9XuiTr2UAbo/UZDxNLFUobI/AAAAAAAAB3I/1wArg9P-KnQ/w898-h293-no/evennia_player_sessions2.png diff --git a/docs/sphinx/source/wiki/Scripts.rst b/docs/sphinx/source/wiki/Scripts.rst index 3235df3272..6252b81580 100644 --- a/docs/sphinx/source/wiki/Scripts.rst +++ b/docs/sphinx/source/wiki/Scripts.rst @@ -17,8 +17,9 @@ Scripts can be used for many different things in Evennia: Time. But they can also have no time dependence at all. - They can describe State changes. - They can act as data stores for storing game data persistently in the - database -- They can be used to shared data between groups of objects + database. +- They can be used as OOC stores for sharing data between groups of + objects. The most obvious use of Scripts may be to use them as *timers* or *Events*. Consider a script running on the object ``Grandfather Clock``. @@ -52,62 +53,182 @@ references to. In short, Scripts can be used for a lot of things. -How to create your own Script types ------------------------------------ +How to create and test your own Script types +-------------------------------------------- -An Evennia Script is, per definition, a Python class that includes -``src.scripts.scripts.Script`` among its parents (if you are aware of -how typeclasses work, this is a typeclass linked to the ``ScriptDB`` -database model). Scripts have no in-game representation and you cannot -define them with any default commands. They have to be created in python -code modules and imported from there into the game. - -Scripts may run directly 'on' `Objects `_, affecting that -object and maybe its surroundings or contents. An alternative way to -affect many objects (rather than one script per object) is to create one -Script and have it call all objects that "subscribe" to it at regular -intervals (a *ticker*). Scripts not defined directly 'on' objects are -called *Global* scripts. - -Custom script modules are usually stored in ``game/gamesrc/scripts``. As -a convenience you can inherit sripts from ``ev.Script``. - -You can try out scripts an add them to objects by use of the ``@script`` -command (not to the confused with ``@scripts`` which lists scripts). You -can try it out with an example script: +In-game you can try out scripts using the ``@script`` command. Try the +following: :: > @script self = examples.bodyfunctions.BodyFunctions -This should cause some random messages. The ``/stop`` switch will kill -the script again. +This should cause some random messages. Add the ``/stop`` switch to the +above command to kill the script again. You can use the ``@scripts`` +command to list all active scripts in the game. Evennia creates a few +default ones. -In code, if you add scripts to `Objects `_ the script can -then manipulate the object as desired. The script is added to the -object's *script handler*, called simply ``scripts``. The handler takes -care of all initialization and startup of the script for you. +Custom script modules are usually stored in ``game/gamesrc/scripts``. As +a convenience you can inherit sripts from ``ev.Script``. + +If you add scripts to `Objects `_ the script can then +manipulate the object as desired. The script is added to the object's +*script handler*, called simply ``scripts``. The handler takes care of +all initialization and startup of the script for you. :: - # adding a script to an existing object 'myobj' + # add script to myobj's scripthandler myobj.scripts.add("game.gamesrc.scripts.myscripts.CoolScript") # alternative way - from src.utils.create import create_script + from ev import create_script create_script("game.gamesrc.scripts.myscripts.CoolScript", obj=myobj) -The creation method(s) takes an optional argument *key* that allows you -to name your script uniquely before adding it. This can be useful if you -add many scripts of the same type and later plan to use -``myobj.scripts.delete`` to remove individual scripts. +A script does not have to be connected to an in-game object. Such +scripts are called *Global scripts*. You can create global scripts by +simply not supplying an object to store it on: -You can create global scripts with -``ev.create_script (a shortcut to ``\ src.utils.create.create\_script()\ ``). Just don't supply an object to store it on. {{{ # adding a global script from ev import create_script create_script("game.gamesrc.scripts.globals.MyGlobalEconomy", key="economy", obj=None) }}} Assuming the Script ``\ game.gamesrc.scripts.globals.MyGlobalEconomy\ `` exists, this will create and start it as a global script. == Properties and functions defined on Scripts == It's important to know the variables controlling the script before one can create one. Beyond those properties assigned to all typeclassed objects (see [Typeclasses]), such as ``\ key\ ``, ``\ db\ ``, ``\ ndb\ `` etc, all Scripts also have the following properties: * ``\ desc\ `` - an optional description of the script's function. Seen in script listings. * ``\ interval\ `` - how often the script should run. If ``\ interval -== -0\ `` (default), it runs forever, without any repeating (it will not accept a negative value). * ``\ start\_delay\ `` - (bool), if we should wait ``\ interval\ `` seconds before firing for the first time or not. * ``\ repeats\ `` - How many times we should repeat, assuming ``\ interval -> 0\ ``. If repeats is set to ``\ <= -0\ ``, the script will repeat indefinitely. * ``\ persistent\ ``- if this script should survive a server reboot. There is one special property: * ``\ obj\ `` - the [Objects Object] this script is attached to (if any). You should not need to set this manually. If you add the script to the Object with ``\ myobj.scripts.add(myscriptpath)\ `` or give ``\ myobj\ `` as an argument to the ``\ utils.create.create\_script\ `` function, the ``\ obj\ `` property will be set to ``\ myobj\ `` for you. It's also imperative to know the hook functions. Normally, overriding these are all the customization you'll need to do in Scripts. You can find longer descriptions of these in ``\ src/scripts/scripts.py\ ``. * ``\ at\_script\_creation()\ `` - this is usually where the script class sets things like ``\ interval\ `` and ``\ repeats\ ``; things that control how the script runs. It is only called once - when the script is first created. * ``\ is\_valid()\ `` - determines if the script should still be running or not. This is called when running ``\ obj.scripts.validate()\ ``, which you can run manually, but which also Evennia calls during certain situations such as reloads. This is also useful for using scripts as state managers. If the method returns ``\ False\ ``, the script is stopped and cleanly removed. * ``\ at\_start()\ `` - this is called when the script first starts. For persistent scripts this is at least once ever server startup. Note that this will _always_ be called right away, also if ``\ start\_delay\ `` is ``\ True\ ``. * ``\ at\_repeat()\ `` - this is called every ``\ interval\ `` seconds, or not at all. It is called right away at startup, unless ``\ start\_delay\ `` is ``\ True\ ``, in which case the system will wait ``\ interval\ `` seconds before calling. * ``\ at\_stop()\ `` - this is called when the script stops for whatever reason. It's a good place to do custom cleanup. * ``\ at\_server\_reload()\ `` - this is called whenever the server is warm-rebooted (e.g. with the ``\ @reload\ `` command). It's a good place to save non-persistent data you might want to survive a reload. * ``\ at\_server\_shutdown()\ `` - this is called on a full systems shutdown. Running methods (usually called automatically by the engine, but possible to also invoke manually) * ``\ start()\ `` - this will start the script. This is called automatically whenever you add a new script to a handler. ``\ at\_start()\ `` will be called. * ``\ stop()\ `` - this will stop the script and delete it. Removing a script from a handler will stop it automatically. ``\ at\_stop()\ `` will be called. * ``\ pause()\ `` - this pauses a running script, rendering it inactive, but not deleting it. All properties are saved and timers can be resumed. This is called automatically when the server reloads. No hooks are called - as far as the script knows, it never stopped - this is a suspension of the script, not a change of state. * ``\ unpause()\ `` - resumes a previously paused script. Timers etc are restored to what they were before pause. The server unpauses all paused scripts after a server reload. No hooks are called - as far as the script is concerned, it never stopped running. * ``\ time\_until\_next\_repeat()\ `` - for timed scripts, this returns the time in seconds until it next fires. Returns ``\ None\ `` if ``\ interval==0\ ``. == Example script == {{{ import random from ev import Script class Weather(Script): "Displays weather info. Meant to be attached to a room." def at_script_creation(self): "Called once, during initial creation" self.key = "weather_script" self.desc = "Gives random weather messages." self.interval = 60 * 5 # every 5 minutes self.persistent = True self.at_repeat(self): "called every self.interval seconds." rand = random.random() if rand < 0.5: weather = "A faint breeze is felt." elif rand < 0.7: weather = "Clouds sweep across the sky." else: weather = "There is a light drizzle of rain." # send this message to everyone inside the object this # script is attached to (likely a room) self.obj.msg_contents(weather) }}} This is a simple weather script that we can put on an object. Every 5 minutes it will tell everyone inside that object how the weather is. To activate it, just add it to the script handler (``\ scripts\ ``) on an [Objects Room]. That object becomes ``\ self.obj\ `` in the example above. Here we put it on a room called ``\ myroom\ ``: {{{ myroom.scripts.add(weather.Weather) }}} In code you can also use the create function directly if you know how to locate the room you want: {{{ from ev import create_script create_script('game.gamesrc.scripts.weather.Weather', obj=myroom) }}} Or, from in-game, use the ``\ @script\ ```` -command: +:: + + # adding a global script + from ev import create_script + create_script("game.gamesrc.scripts.globals.MyGlobalEconomy", key="economy", obj=None) + +Assuming the Script ``game.gamesrc.scripts.globals.MyGlobalEconomy`` +exists, this will create and start it as a global script. + +Properties and functions defined on Scripts +------------------------------------------- + +A Script has all the properties of a typeclassed object, such as ``db`` +and ``ndb``\ (see `Typeclasses `_). Setting ``key`` is +useful in order to manage scripts (delete them by name etc). These are +usually set up in the Script's typeclass, but can also be assigned on +the fly as keyword arguments to ``ev.create_script``. + +- ``desc`` - an optional description of the script's function. Seen in + script listings. +- ``interval`` - how often the script should run. If ``interval == 0`` + (default), it runs forever, without any repeating (it will not accept + a negative value). +- ``start_delay`` - (bool), if we should wait ``interval`` seconds + before firing for the first time or not. +- ``repeats`` - How many times we should repeat, assuming + ``interval > 0``. If repeats is set to ``<= 0``, the script will + repeat indefinitely. +- ``persistent``- if this script should survive a server *reset* or + server *shutdown*. (You don't need to set this for it to survive a + normal reload - the script will be paused and seamlessly restart + after the reload is complete). + +There is one special property: + +- ``obj`` - the `Object `_ this script is attached to (if + any). You should not need to set this manually. If you add the script + to the Object with ``myobj.scripts.add(myscriptpath)`` or give + ``myobj`` as an argument to the ``utils.create.create_script`` + function, the ``obj`` property will be set to ``myobj`` for you. + +It's also imperative to know the hook functions. Normally, overriding +these are all the customization you'll need to do in Scripts. You can +find longer descriptions of these in ``src/scripts/scripts.py``. + +- ``at_script_creation()`` - this is usually where the script class + sets things like ``interval`` and ``repeats``; things that control + how the script runs. It is only called once - when the script is + first created. +- ``is_valid()`` - determines if the script should still be running or + not. This is called when running ``obj.scripts.validate()``, which + you can run manually, but which also Evennia calls during certain + situations such as reloads. This is also useful for using scripts as + state managers. If the method returns ``False``, the script is + stopped and cleanly removed. +- ``at_start()`` - this is called when the script first starts. For + persistent scripts this is at least once ever server startup. Note + that this will *always* be called right away, also if ``start_delay`` + is ``True``. +- ``at_repeat()`` - this is called every ``interval`` seconds, or not + at all. It is called right away at startup, unless ``start_delay`` is + ``True``, in which case the system will wait ``interval`` seconds + before calling. +- ``at_stop()`` - this is called when the script stops for whatever + reason. It's a good place to do custom cleanup. +- ``at_server_reload()`` - this is called whenever the server is + warm-rebooted (e.g. with the ``@reload`` command). It's a good place + to save non-persistent data you might want to survive a reload. +- ``at_server_shutdown()`` - this is called when a system reset or + systems shutdown is invoked. + +Running methods (usually called automatically by the engine, but +possible to also invoke manually) + +- ``start()`` - this will start the script. This is called + automatically whenever you add a new script to a handler. + ``at_start()`` will be called. +- ``stop()`` - this will stop the script and delete it. Removing a + script from a handler will stop it automatically. ``at_stop()`` will + be called. +- ``pause()`` - this pauses a running script, rendering it inactive, + but not deleting it. All properties are saved and timers can be + resumed. This is called automatically when the server reloads. No + hooks are called - as far as the script knows, it never stopped - + this is a suspension of the script, not a change of state. +- ``unpause()`` - resumes a previously paused script. Timers etc are + restored to what they were before pause. The server unpauses all + paused scripts after a server reload. No hooks are called - as far as + the script is concerned, it never stopped running. +- ``time_until_next_repeat()`` - for timed scripts, this returns the + time in seconds until it next fires. Returns ``None`` if + ``interval==0``. + +Example script +-------------- + +:: + + import random + from ev import Script + class Weather(Script): + "Displays weather info. Meant to be attached to a room." + def at_script_creation(self): + "Called once, during initial creation" + self.key = "weather_script" + self.desc = "Gives random weather messages." + self.interval = 60 * 5 # every 5 minutes + self.persistent = True + def at_repeat(self): + "called every self.interval seconds." + rand = random.random() + if rand < 0.5: + weather = "A faint breeze is felt." + elif rand < 0.7: + weather = "Clouds sweep across the sky." + else: + weather = "There is a light drizzle of rain." + # send this message to everyone inside the object this + # script is attached to (likely a room) + self.obj.msg_contents(weather) + +This is a simple weather script that we can put on an object. Every 5 +minutes it will tell everyone inside that object how the weather is. + +To activate it, just add it to the script handler (``scripts``) on an +`Room `_. That object becomes ``self.obj`` in the example +above. Here we put it on a room called ``myroom``: + +:: + + myroom.scripts.add(weather.Weather) + +In code you can also use the create function directly if you know how to +locate the room you want: + +:: + + from ev import create_script + create_script('game.gamesrc.scripts.weather.Weather', obj=myroom) + +Or, from in-game, use the ``@script`` command: :: diff --git a/docs/sphinx/source/wiki/TickerScripts.rst b/docs/sphinx/source/wiki/TickerScripts.rst index 2b2cd2c549..651023b8fc 100644 --- a/docs/sphinx/source/wiki/TickerScripts.rst +++ b/docs/sphinx/source/wiki/TickerScripts.rst @@ -2,38 +2,44 @@ 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 `_ are -powerful enough to act as any type of counter you want. +known as "heartbeats". A ticker is a timer that fires ("ticks" at a +given interval. The tick triggers updates in various game systems. +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'. Evennia has no such notion - the use of tickers (or not) is very +much up to your game and which requirements you have. +`Scripts `_ are powerful enough to act as any type of +counter you want and the "ticker recipe" is just one convenient (and +occationally effective) way of cranking the wheels. 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. +First, let's consider when *not* to use tickers. Even if you are used to +habitually relying on tickers for everything in other code bases, stop +and think about what you really need them for. Notably you should +*never* try implement a ticker to *catch changes*. Think about it - you +might have to run the ticker every second to react to the change fast +enough. Most likely nothing will have changed most of the time. So you +are doing pointless calls (since skipping the call gives the same result +as doing it). Making sure nothing's changed might even be +computationally 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). +"on demand". Evennia itself uses hook methods 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. +can handle things some other way. A self-reporting solution is usually +cheaper also for fast-updating properties. The main reason you do need a +ticker is rather when the timing itself is important. Ticker example - night/day system ================================= @@ -91,24 +97,26 @@ list and calls a given method on the subscribed object. 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 +It's worth noting that the simple recipe above can be used for all sorts +of tickers. 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. +This particular 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", ...] + ECHOES = ["The sun rises in the east.", + "It's mid-morning", + "It's mid-day", ...] + class TimerTicker(Script): ... def at_script_creation(self): diff --git a/docs/sphinx/source/wiki/Tutorials.rst b/docs/sphinx/source/wiki/Tutorials.rst index 84955766dc..71a4755a4a 100644 --- a/docs/sphinx/source/wiki/Tutorials.rst +++ b/docs/sphinx/source/wiki/Tutorials.rst @@ -36,6 +36,8 @@ the `Developer Central `_. - `Tutorial: Adding a Command prompt `_ - `Tutorial: Creating a Zoning system `_ - `Hints: Implementing cooldowns for commands `_ +- `Hints: Designing commands that take time to + finish `_ - `Hints: Ticker Scripts `_ Examples diff --git a/docs/sphinx/source/wiki/Typeclasses.rst b/docs/sphinx/source/wiki/Typeclasses.rst index b3679e3b59..978d4a4c94 100644 --- a/docs/sphinx/source/wiki/Typeclasses.rst +++ b/docs/sphinx/source/wiki/Typeclasses.rst @@ -23,6 +23,18 @@ inherited from etc just like normal Python classes. But whenever they store data they are infact transparently storing this data into the database. +|image0| + +In the above diagram, each of the Typeclassed entities are connected to +a *database model*. This handles all database interaction for you +without you needing to worry. The database model class is not changing. +But the Typeclass connected to it *can*. The typeclass roots (Object, +Script and Player) can have any number of subclasses to describe various +objects - above are some examples from the default distribution. + +The good news is that you should only need to worry about the typeclass +level, not about what happens behind the scenes with the database. + It's easy to work with Typeclasses - just create a new class inheriting from one of the base Typeclasses: @@ -76,13 +88,10 @@ a few things that you need to remember however: just initializing the typeclass. So use ``ev.create_object(MyTypeclass, ...)`` to create a new object of the type ``MyTypeclass``. Doing obj = - MyTypeclass()\ `` will not work. * Evennia will look for and call especially named _hook methods_ on the typeclass in different situations. Just define a new method on the class named correctly and Evennia will call it appropriately. Hooks are your main way to interact with the server. Available hook methods are listed in the resepective base modules in ``\ game/gamesrc/\ ``. * Don't use the normal ``\ \_\_init\_\_()\ `` to set up your typeclass. It is used by Evennia to set up the mechanics of the Typeclass system. Use the designated hook method instead, such as ``\ at\_object\_creation()\ ``, ``\ at\_player\_creation()\ `` or ``\ at\_script\_creation()\ ``. * Don't re-implement the python special class methods ``\ \_\_setattr\_\_()\ ``, ``\ \_\_getattribute\_\_()\ `` and ``\ \_\_delattr\_\_()\ ``. These are used extensively by the Typeclass system. * Some property names cannot be assigned to a Typeclassed entity due to being used for internal Typeclass operations. If you try, you will get an error. These property names are _id_, _dbobj_, _db_, _ndb_, _objects_, _typeclass_, _attr_, _save_ and _delete_. * Even if they are not explicitly protected, you should not redefine the "type" of the default typeclass properties listed above and on each typeclassed entity (such as trying to store an integer in the ``\ key\ `` property). These properties are often called by the engine expecting a certain type of return, and some are even tied directly to the database and will thus return errors if given a value of the wrong type. * _Advanced note_: If you are doing advanced coding you might (very rarely) find that overloading ``\ \_\_init\_\_\ ``, ``\ \_setattr\_\_\ `` etc allows for some functionality not possible with hooks alone. You _can_ do it if you know what you are doing, but you _must_ then remember to use Python's built-in function ``\ super()\ `` to call the parent method too, or you _will_ crash the server! You have been warned. = How typeclasses actually work = _This is considered an advanced section. Skip it on your first read-through unless you are really interested in what's going on under the hood._ All typeclassed entities actually consist of two (three) parts: # The _Typeclass_ (a normal Python class with customized get/set behaviour) # The _Database model_ (Django model) # ([Attributes]) The _Typeclass_ is an almost normal Python class, and holds all the flexibility of such a class. This is what makes the class special, some of which was already mentioned above: * It inherits from ``\ src.typeclasses.typeclass.TypeClass\ ``. * ``\ \_\_init\_\_()\ `` is reserved for various custom startup procedures. * It always initiates a property ``\ dbobj\ `` that points to a _Database model_. * It redefines python's normal ``\ \_\_getattribute\_\_()\ ``, ``\ \_\_setattr\_\_()\ `` and ``\ \_\_delattr\_\_\ `` on itself to relay all data on itself to/from ``\ dbobj\ `` (i.e. to/from the database model). The related _Database model_ in turn communicates data in and out of the the database. The Database model holds the following (typeclass-related) features: * It inherits from ``\ src.typeclasses.models.TypedObject\ `` (this actually implements a [http://github.com/dcramer/django-idmapper idmapper]-type model. If that doesn't mean anything to you, never mind). * It has a field ``\ typelclass\_path\ `` that gives the python path to the _Typeclass_ associated with this particular model instance. * It has a property _typeclass_ that dynamically imports and loads the _Typeclass_ from ``\ typeclass\_path\ ``, and assigns itself to the Typeclass' ``\ dbobj\ `` property. * It redefines ``\ \_\_getattribute\_\_()\ `` to search its typeclass too, while avoiding loops. This means you can search either object and find also data stored on the other. The _Attributes_ are not really part of the typeclass scheme, but are very important for saving data without having to change the database object itself. They are covered in a separate entry [Attributes here]. == Why split it like this? == The _Database model_ (Django model) allows for saving data to the database and is a great place for storing persistent data an object might need during and between sessions. But it is not suitable for representing all the various objects a game needs. You _don't_ want to have to redefine a new database representation just because a ``\ CarObject\ `` needs to look and behave differently than a ``\ ChairObject\ ``. So instead we keep the database model pretty "generic", and only put database Fields on it that we know that _all_ objects would need (or that require fast and regular database searches). Examples of such fields are "key" and "location". Enter the _Typeclass_. For lack of a better word, a typeclass "decorates" a Django database model. Through the re-definition of the class' get/set methods, the typeclass constantly communicates behind the scenes with the Django model. The beauty of it is that this is all hidden from you, the coder. As long as you don't overwrite the few magic methods listed above you can deal with the typeclass almost as you would any normal Python class. You can extend it, inherit from it, and so on, mostly without caring that it is infact hiding a full persistent database representation. So you can now create a typeclass-class _Flowers_ and then inherit a bunch of other typeclass-classes from that one, like _Rose_, _Tulip_, _Sunflower_. As your classes are instantiated they will each secretly carry a reference to a database model to which all data _actually_ goes. We, however, can treat the two as if they where one. Below is a schematic of the database/typeclass structure. http://d.imagehost.org/0784/typeclasses1.png Let's see how object creation looks like in an example. # We have defined a Typeclass called _Rose_ in ``\ game.gamesrc.objects.flower.Rose\ ``. It inherits from ``\ game.gamesrc.objects.baseobjects.Object\ ``, which is a grandchild of ``\ src.typeclasses.typeclass.TypeClass\ ``. So the rose a typeclassed object, just as it should be. # Using a command we create a new _Rose_ instance _!RedRose_ (e.g. with ``\ @create - redrose:flowers.Rose\ ``). # A new database model is created and given the key _!RedRose_. Since this is an [Objects Object] typeclass (rather than a Script or Player), the database model used is ``\ src.objects.models.ObjectDB\ ``, which inherits directly from ``\ src.typeclasses.models.TypedObject\ ``). # This new Django-model instance receives the python-path to the _Rose_ typeclass and stores it as a string on itself (in a database field ``\ typeclass\_path\ ``). When the server restarts in the future, the database model will restart from this point. # The database model next _imports_ the Typeclass from its stored path and creates a new instance of it in memory. It stores a reference to this instance of _Rose_ (_!RedRose_)in a property called ``\ typeclass\ ``. # As _Rose_ is instantiated, its ``\ \_\_init\_\_()\ `` method is called. What this does it to make sure to store the back-reference to the Django model on our new _Rose_ instance. This back-reference is called ``\ dbobj\ ``. # The creation method next runs the relevant startup hooks on the typeclass, such as ``\ at\_object\_creation()\ ``. Storing properties on the typeclass-instance will in fact transparently save to the database object. So ``\ RedRose.thorns - = True\ `` is the same as ``\ RedRose.dbobj.thorns = - True\ `` (this will in fact be saved in the database as an attribute "thorns"). Doing ``\ ouch + MyTypeclass()\ `` will not work. * Evennia will look for and call especially named _hook methods_ on the typeclass in different situations. Just define a new method on the class named correctly and Evennia will call it appropriately. Hooks are your main way to interact with the server. Available hook methods are listed in the resepective base modules in ``\ game/gamesrc/\ ``. * Don't use the normal ``\ \_\_init\_\_()\ `` to set up your typeclass. It is used by Evennia to set up the mechanics of the Typeclass system. Use the designated hook method instead, such as ``\ at\_object\_creation()\ ``, ``\ at\_player\_creation()\ `` or ``\ at\_script\_creation()\ ``. * Don't re-implement the python special class methods ``\ \_\_setattr\_\_()\ ``, ``\ \_\_getattribute\_\_()\ `` and ``\ \_\_delattr\_\_()\ ``. These are used extensively by the Typeclass system. * Some property names cannot be assigned to a Typeclassed entity due to being used for internal Typeclass operations. If you try, you will get an error. These property names are _id_, _dbobj_, _db_, _ndb_, _objects_, _typeclass_, _attr_, _save_ and _delete_. * Even if they are not explicitly protected, you should not redefine the "type" of the default typeclass properties listed above and on each typeclassed entity (such as trying to store an integer in the ``\ key\ `` property). These properties are often called by the engine expecting a certain type of return, and some are even tied directly to the database and will thus return errors if given a value of the wrong type. * _Advanced note_: If you are doing advanced coding you might (very rarely) find that overloading ``\ \_\_init\_\_\ ``, ``\ \_setattr\_\_\ `` etc allows for some functionality not possible with hooks alone. You _can_ do it if you know what you are doing, but you _must_ then remember to use Python's built-in function ``\ super()\ `` to call the parent method too, or you _will_ crash the server! You have been warned. = How typeclasses actually work = _This is considered an advanced section. Skip it on your first read-through unless you are really interested in what's going on under the hood._ All typeclassed entities actually consist of two (three) parts: # The _Typeclass_ (a normal Python class with customized get/set behaviour) # The _Database model_ (Django model) # ([Attributes]) The _Typeclass_ is an almost normal Python class, and holds all the flexibility of such a class. This is what makes the class special, some of which was already mentioned above: * It inherits from ``\ src.typeclasses.typeclass.TypeClass\ ``. * ``\ \_\_init\_\_()\ `` is reserved for various custom startup procedures. * It always initiates a property ``\ dbobj\ `` that points to a _Database model_. * It redefines python's normal ``\ \_\_getattribute\_\_()\ ``, ``\ \_\_setattr\_\_()\ `` and ``\ \_\_delattr\_\_\ `` on itself to relay all data on itself to/from ``\ dbobj\ `` (i.e. to/from the database model). The related _Database model_ in turn communicates data in and out of the the database. The Database model holds the following (typeclass-related) features: * It inherits from ``\ src.typeclasses.models.TypedObject\ `` (this actually implements a [http://github.com/dcramer/django-idmapper idmapper]-type model. If that doesn't mean anything to you, never mind). * It has a field ``\ typelclass\_path\ `` that gives the python path to the _Typeclass_ associated with this particular model instance. * It has a property _typeclass_ that dynamically imports and loads the _Typeclass_ from ``\ typeclass\_path\ ``, and assigns itself to the Typeclass' ``\ dbobj\ `` property. * It redefines ``\ \_\_getattribute\_\_()\ `` to search its typeclass too, while avoiding loops. This means you can search either object and find also data stored on the other. The _Attributes_ are not really part of the typeclass scheme, but are very important for saving data without having to change the database object itself. They are covered in a separate entry [Attributes here]. == Why split it like this? == The _Database model_ (Django model) allows for saving data to the database and is a great place for storing persistent data an object might need during and between sessions. But it is not suitable for representing all the various objects a game needs. You _don't_ want to have to redefine a new database representation just because a ``\ CarObject\ `` needs to look and behave differently than a ``\ ChairObject\ ``. So instead we keep the database model pretty "generic", and only put database Fields on it that we know that _all_ objects would need (or that require fast and regular database searches). Examples of such fields are "key" and "location". Enter the _Typeclass_. For lack of a better word, a typeclass "decorates" a Django database model. Through the re-definition of the class' get/set methods, the typeclass constantly communicates behind the scenes with the Django model. The beauty of it is that this is all hidden from you, the coder. As long as you don't overwrite the few magic methods listed above you can deal with the typeclass almost as you would any normal Python class. You can extend it, inherit from it, and so on, mostly without caring that it is infact hiding a full persistent database representation. So you can now create a typeclass-class _Flowers_ and then inherit a bunch of other typeclass-classes from that one, like _Rose_, _Tulip_, _Sunflower_. As your classes are instantiated they will each secretly carry a reference to a database model to which all data _actually_ goes. We, however, can treat the two as if they where one. Below is a schematic of the database/typeclass structure. https://lh4.googleusercontent.com/-HNUhh6xCYpY/UZISHoSucxI/AAAAAAAAB4I/2ThUbuAbosg/w865-h634-no/typeclasses2a.png Let's see how object creation looks like in an example. # We have defined a Typeclass called _Rose_ in ``\ game.gamesrc.objects.flower.Rose\ ``. It inherits from ``\ game.gamesrc.objects.baseobjects.Object\ ``, which is a grandchild of ``\ src.typeclasses.typeclass.TypeClass\ ``. So the rose a typeclassed object, just as it should be. # Using a command we create a new _Rose_ instance _!RedRose_ (e.g. with ``\ @create + redrose:flowers.Rose\ ``). # A new database model is created and given the key _!RedRose_. Since this is an [Objects Object] typeclass (rather than a Script or Player), the database model used is ``\ src.objects.models.ObjectDB\ ``, which inherits directly from ``\ src.typeclasses.models.TypedObject\ ``). # This new Django-model instance receives the python-path to the _Rose_ typeclass and stores it as a string on itself (in a database field ``\ typeclass\_path\ ``). When the server restarts in the future, the database model will restart from this point. # The database model next _imports_ the Typeclass from its stored path and creates a new instance of it in memory. It stores a reference to this instance of _Rose_ (_!RedRose_)in a property called ``\ typeclass\ ``. # As _Rose_ is instantiated, its ``\ \_\_init\_\_()\ `` method is called. What this does it to make sure to store the back-reference to the Django model on our new _Rose_ instance. This back-reference is called ``\ dbobj\ ``. # The creation method next runs the relevant startup hooks on the typeclass, such as ``\ at\_object\_creation()\ ``. Using the ``.db\ `` operator of Typeclasses will store Attributes of the right type in the database. So ``\ RedRose.db.thorns = - RedRose.thorns\ `` is however not really as symmetric. The system will in this case _first_ check the Typeclass instance and only if no property _thorns_ was found will go on to examine the database object. So ``\ ouch - = RedRose.thorns\ `` is not necessarily the same as ``\ ouch = - RedRose.dbobj.thorns\ `` in this case. The reason we don't assume everything to be on the database object is that you are likely to customize your _Rose_ typeclass with custom parameters and methods that are intended to _overload_ the default methods on the database object. These are thus searched and run first, and you can then safely use ``\ self.dbobj\ `` from the typeclass to call the original function if you want. An example of Typeclass overloading is found [CommandPrompt#Prompt_on_the_same_line here]. Another example: http://b.imagehost.org/0023/typeclasses2.png == Caveats of the typeclass system == While there are many advantages to the typeclass system over working with Django models directly, there are also some caveats to remember. Be careful when not using Evennia's search and create methods. Almost all code in evennia (including default commands) assume that what is returned from searches or creates are Typeclasses, not Django models (i.e. the first of the two in the pair). This is what you get if you use any of the model manager methods, and also the create/search functions in ``\ src.utils.create\ `` and ``\ src.utils.search\ ``. Old Django-gurus will find it tempting to use Django's in-build database query methods, such as ``\ ObjectDB.objects.filter()\ `` to get data. This works, but the result will then of course _not_ be a typeclass but a Django model object (a query). You can easily convert between them with ``\ dbobj.typeclass\ `` and ``\ typeclass.dbobj\ ``, but you should be aware of this distinction. {{{ obj = ObjectDB.objects.get_id(1) # custom evennia manager method. This returns the typeclass. obj = ObjectDB.objects.get(1) # standard Django. Returns a Django model object. }}} Even more important to know for Django affectionados: Evennia's custom methods return _lists_ where you with normal Django methods would expect ``\ Query\ `` objects (e.g. from the ``\ filter()\ `` method). As long as you don't confuse what result type you are dealing with (for example you cannot 'link' ``\ list\ ``s together the way you can ``\ Querysets\ ``), you should be fine. Read the ``\ manager.py\ `` files in each relevant folder under ``\ src/\ ```` + True\ `` will create a new Attribute named "thorns" where the boolean value ``\ True\ `` will be stored. On the other hand, storing RedRose.thorns will just store the data as a normal property (the Typeclass will actually transparently relay this so it's always stored on the database model). Due to caching reasons but also for the sake of clarity and readability, it's strongly recommended that you store temporary variables using the ``\ ndb\ `` operator, such as ``\ RedRose.ndb.newly\_planted=True\ ``. In the opposite direction, reading properties can also mean accessing methods that you want to overload. For example, the ``\ ObjectDB\ `` database model holds a method ``\ msg\ `` that you might want to overload with your own version. So accessing ``\ RedRose.msg\ `` will _first_ search the RedRose typeclass to see if it holds a custom ``\ msg\ `` and only if it fails it will continue on to search the properties on the database object. An example of a Typeclass overloading ``\ msg\ `` is found [CommandPrompt#Prompt_on_the_same_line here]. This is another good reason for using ``\ db/ndb\ `` handlers - they make it clear if you are creating/reading an Attribute and is not trying to access a method on the class. Here is a diagram exemplifying Attribute access: https://lh5.googleusercontent.com/-oCqy1f1ZFRA/UZIWeg0ll8I/AAAAAAAAB4g/-ewUvQ439y4/w681-h634-no/typeclasses2.png == Caveats of the typeclass system == While there are many advantages to the typeclass system over working with Django models directly, there are also some caveats to remember. Be careful when not using Evennia's search and create methods. Almost all code in evennia (including default commands) assume that what is returned from searches or creates are Typeclasses, not Django models (i.e. the first of the two in the pair). This is what you get if you use any of the model manager methods, and also the create/search functions in ``\ src.utils.create\ `` and ``\ src.utils.search\ ``. Old Django-gurus will find it tempting to use Django's in-build database query methods, such as ``\ ObjectDB.objects.filter()\ `` to get data. This works, but the result will then of course _not_ be a typeclass but a Django model object (a query). You can easily convert between them with ``\ dbobj.typeclass\ `` and ``\ typeclass.dbobj\ ``, but you should be aware of this distinction. {{{ obj = ObjectDB.objects.get_id(1) # custom evennia manager method. This returns the typeclass. obj = ObjectDB.objects.get(1) # standard Django. Returns a Django model object. }}} Even more important to know for Django affectionados: Evennia's custom methods return _lists_ where you with normal Django methods would expect ``\ Query\ `` objects (e.g. from the ``\ filter()\ `` method). As long as you don't confuse what result type you are dealing with (for example you cannot 'link' ``\ list\ ``s together the way you can ``\ Querysets\ ``), you should be fine. Read the ``\ manager.py\ `` files in each relevant folder under ``\ src/\ ```` to see which database access methods are available. +.. |image0| image:: https://lh4.googleusercontent.com/-jMrRjLRQiHA/UZIKiDgGECI/AAAAAAAAB3Y/YUzHZlgVFTY/w480-h282-no/typeclasses_overview.png diff --git a/docs/sphinx/source/wiki/UpdatingYourGame.rst b/docs/sphinx/source/wiki/UpdatingYourGame.rst index 6aacc93d91..7a1bb53620 100644 --- a/docs/sphinx/source/wiki/UpdatingYourGame.rst +++ b/docs/sphinx/source/wiki/UpdatingYourGame.rst @@ -52,6 +52,8 @@ clear your database. Once you are done, you just rebuild it from scratch as described in step 2 of the `Getting Started guide `_. +First stop a running server with ``game/python evennia.py stop``. + If you run the default ``SQlite3`` database (to change this you need to edit your ``settings.py`` file), the database is actually just a normal file in ``game/`` called ``evennia.db3``. Simply delete that file - diff --git a/docs/sphinx/source/wiki/WebFeatures.rst b/docs/sphinx/source/wiki/WebFeatures.rst index c8e6c78ea6..2882d6e9de 100644 --- a/docs/sphinx/source/wiki/WebFeatures.rst +++ b/docs/sphinx/source/wiki/WebFeatures.rst @@ -22,17 +22,27 @@ Since it's not recommended to edit files in ``src/`` directly, we need to devise a way to allow website customization from ``game/gamesrc``. This is not really finalized at the current time (it will be easier to share media directories in Django 1.3) so for now, your easiest course -of action is probably to copy the entire ``src/web`` directory into -``game/gamesrc/`` and modify the copy. Make sure to retain permissions -so the server can access the directory. +of action is as follows: -You also need to modify the settings file. Set ``ROOT_URLCONF`` to your -new ``game.gamesrc.web.urls`` and add an entry -`` os.path.join(GAME_DIR, "web", "templates", ACTIVE_TEMPLATE)`` to the -``TEMPLATE_DIRS`` tuple. You should now have a separate website setup -you can edit as you like. Be aware that updates we do to ``src/web`` -will not transfer automatically to your copy, so you'll need to apply -updates manually. +#. Copy the entire ``src/web`` directory into ``game/gamesrc/`` and do + your modifications to the copy. Make sure to retain permissions so + the server can access the directory (in linux you can do this with + something like ``cp -ra src/web game/gamesrc/``) +#. Re-link all relevant path variables to the new location. In settings + add the following lines: + +:: + + ROOT_URLCONF = "game.gamesrc.web.urls" + TEMPLATE_DIRS = (os.path.join(GAME_DIR, "gamesrc", "web", "templates", ACTIVE_TEMPLATE),) + MEDIA_ROOT = os.path.join(GAME_DIR, "gamesrc", "web", "media"`). + +#. Reload the server (you want to also restart the Portal at this point + to make sure it picks up the new web location). + +You should now have a separate website you can edit as you like. Be +aware that updates we do to ``src/web`` will not transfer automatically +to your copy, so you'll need to apply updates manually. Web client ---------- diff --git a/docs/sphinx/source/wiki/Zones.rst b/docs/sphinx/source/wiki/Zones.rst index 0f2490223b..9f6a2f690d 100644 --- a/docs/sphinx/source/wiki/Zones.rst +++ b/docs/sphinx/source/wiki/Zones.rst @@ -49,9 +49,14 @@ 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: +There is nothing special about this format; it's just a string we store +- a way to clump the zone-name and the room-key together in a "tag" we +can easily find and match against later. We could have used a format +like ``"zonename.key"`` or ``"ZONE:zonename,ROOMNAME:key"`` or some +other combination if we liked that better. So, using our suggested +format we assume we (arbitrarily) divide our forest example into the +zones ``magicforest`` and ``normalforest``. These are the added aliases +we use for the respective *Meadow* 's: :: @@ -67,8 +72,8 @@ 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 +Maybe you feel that this usage of aliases for zones is loose and ad-hoc. +It is indeed, 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 `_ to require the zone to be given: @@ -112,13 +117,13 @@ 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: +The aliasing system above doesn't instill any sort of functional +difference between a magical forest room and a normal one - it's just an +abitrary way to attach tags to objects for quick retrieval later. To +enforce differences 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: :: diff --git a/docs/sphinx/src2rest/src2rest.py b/docs/sphinx/src2rest/src2rest.py index e0eba812c7..42ddfea43b 100755 --- a/docs/sphinx/src2rest/src2rest.py +++ b/docs/sphinx/src2rest/src2rest.py @@ -1,8 +1,8 @@ -#! /usr/bin/python +# /usr/bin/python # # Auto-generate reST documentation for Sphinx from Evennia source # code. -# +# # Uses etinenned's sphinx autopackage script. Install it to folder # "autogen" in this same directory: # @@ -34,21 +34,21 @@ AUTOGEN_EXE = os.path.join(CONVERT_DIR, os.path.join("autogen", "generate_module def src2rest(): """ - Run import + Run import """ try: shutil.rmtree(SPHINX_CODE_DIR) print "Emptied old %s." % SPHINX_CODE_DIR except OSError: - pass + pass os.mkdir(SPHINX_CODE_DIR) - + inpath = EVENNIA_DIR outpath = SPHINX_CODE_DIR - excludes = [r".*/migrations/.*", r"evennia\.py$", r"manage\.py$", + excludes = [r".*/migrations/.*", r"evennia\.py$", r"manage\.py$", r"runner\.py$", r"server.py$", r"portal.py$"] - - subprocess.call(["python", AUTOGEN_EXE, + + subprocess.call(["python", AUTOGEN_EXE, "-n", "Evennia", "-d", outpath, "-s", "rst", diff --git a/docs/sphinx/wiki2rest/wikify.py b/docs/sphinx/wiki2rest/wikify.py index 3bcc164e34..fe88bca370 100644 --- a/docs/sphinx/wiki2rest/wikify.py +++ b/docs/sphinx/wiki2rest/wikify.py @@ -290,9 +290,9 @@ def GoogleCode_PrettyLink(wikifier, lookaheadRegExp=None, **kwargs): lookMatch = lookaheadRegExp.search(wikifier.source, wikifier.matchStart) if lookMatch and lookMatch.start() == wikifier.matchStart: text = lookMatch.group(1) + link = text if lookMatch.group(2): # Pretty bracketted link - link = text text = lookMatch.group(2) if GoogleCode_IsExternalLink(wikifier, link): # External link