diff --git a/docs/sphinx/source/wiki/AddingCommandTutorial.rst b/docs/sphinx/source/wiki/AddingCommandTutorial.rst index 23e47f0efa..2909791dea 100644 --- a/docs/sphinx/source/wiki/AddingCommandTutorial.rst +++ b/docs/sphinx/source/wiki/AddingCommandTutorial.rst @@ -20,9 +20,9 @@ new additional commands of your own. but in this example we assume you don't. #. Edit ``game/settings.py``, adding the following line: - ``CMDSET_DEFAULT="game.gamesrc.commands.cmdset.DefaultCmdSet"`` + ``CMDSET_CHARACTER="game.gamesrc.commands.cmdset.CharacterCmdSet"`` -Evennia will now look for default commands in the ``DefaultCmdSet`` +Evennia will now look for default commands in the ``CharacterCmdSet`` class of your newly copied module. You only need to do this once. Creating a custom command @@ -45,6 +45,7 @@ Creating a custom command # file game/gamesrc/commands/command.py #[...] + from ev import default_cmds class CmdEcho(default_cmds.MuxCommand): """ Simple command example @@ -65,30 +66,30 @@ Creating a custom command else: self.caller.msg("You gave the string: '%s'" % self.args) -Adding the Command to a Cmdset ------------------------------- +Adding the Command to a default Cmdset +-------------------------------------- The command is not available to use until it is part of a Command Set. In this example we will go the easiest route and add it to the default -command set we already prepared. +Character command set we already prepared. #. Edit your recently copied ``game/gamesrc/commands/cmdset.py`` #. In this copied module you will find the ``DefaultCmdSet`` class already imported and prepared for you. Import your new command module here with ``from game.gamesrc.commands.command import CmdEcho``. -#. Add a line ``self.add(CmdEcho())`` to ``DefaultCmdSet``, in the +#. Add a line ``self.add(CmdEcho())`` to ``CharacterCmdSet``, in the ``at_cmdset_creation`` method (the template tells you where). This is approximately how it should look at this point: :: - # file gamesrc/commands/examples/cmdset.py + # file gamesrc/commands/cmdset.py #[...] from game.gamesrc.commands.command import CmdEcho #[...] - class DefaultCmdSet(default_cmds.DefaultCmdSet): + class CharacterCmdSet(default_cmds.CharacterCmdSet): - key = DefaultMUX + key = DefaultCharacter def at_cmdset_creation(self): @@ -115,3 +116,73 @@ old one - it will overload the default one. Just remember that you must See `Commands `_ for many more details and possibilities when defining Commands and using Cmdsets in various ways. + +Adding the command to specific object types +------------------------------------------- + +You do not *have* to expand the ``CharacterCmdSet``, it's just the +easiest example. The cmdset system is very generic. You can create your +own cmdsets and add them to objects as you please (just how to control +how they merge with the existing set is described in detail in the +[Commands#Command\_Sets Command Set documentation]). + +:: + + # file gamesrc/commands/cmdset.py + #[...] + from game.gamesrc.commands.command import CmdEcho + #[...] + class MyCmdSet(default_cmds.CmdSet): + + key = MyCmdSet + + def at_cmdset_creation(self): + self.add(CmdEcho()) + +Now you just need to add this to an object. To test things (as +superuser) you can do + +:: + + @py self.cmdset.add("cmdset.MyCmdSet") + +This will add the cmdset (and the echo command) to yourself so you can +test it. This is not permanent though, if you do a ``@reload`` the +merger will be gone. You *can* add the ``permanent=True`` keyword to the +``cmdset.add`` call. This will however only make the new merged cmdset +permanent on that single object, not on other objects of that type, +which is usually what you want. + +To make sure all new created objects get your new merged set, put the +``cmdset.add`` call in your custom `Typeclass `_' +``at_object_creation`` method: + +:: + + from ev import Object + class MyObject(Object): + + def at_object_creation(self): + "called when the object is first created" + self.cmdset.add("cmdset.MyCmdSet") + + +All new objects of this typeclass will now start with this cmdset. + +*Note:* An important caveat with this is that ``at_object_creation`` is +only called *once*, when the object is first created. This means that if +you already have existing objects in your databases using that +typeclass, they will not have been initiated the same way. There are +many ways to update them; since it's a one-time update you can usually +just simply loop through them. As superuser, try the following: + +:: + + @py [obj.cmdset.add("cmdset.MyCmdSet") for obj in + ev.managers.typeclass_search("game.gamesrc.objects.objects.mytypeclass.MyTypeClass"] + +This goes through all objects in your database having the right +typeclass, adding the new cmdset to each. The good news is that you only +have to do this if you want to post-add cmdsets. If you just want to add +a new command, you can just add that command to the cmdset's +``at_cmdset_creation`` and @reload. diff --git a/docs/sphinx/source/wiki/AddingObjectTypeclassTutorial.rst b/docs/sphinx/source/wiki/AddingObjectTypeclassTutorial.rst index 6c5b323127..4807ee6260 100644 --- a/docs/sphinx/source/wiki/AddingObjectTypeclassTutorial.rst +++ b/docs/sphinx/source/wiki/AddingObjectTypeclassTutorial.rst @@ -66,16 +66,19 @@ that characters should not have the ability to pick up. That's it. Below is a ``Heavy`` Typeclass that you could try. Note that the `lock `_ and `Attribute `_ here set in the typeclass could just as well have been set using commands in-game, -so this is a *very* simple example. +so this is a *very* simple example. We also add a custom +[Commands#Command\_Sets Command set] to it. :: # file game/gamesrc/objects/heavy.py from ev import Object + # let's assume we defined our own cmdset earlier + from game.gamesrc.commands.mycmdsets import HeavySet class Heavy(Object): "Heavy object" - at_object_creation(self): + def at_object_creation(self): "Called whenever a new object is created" # lock the object down by default self.locks.add("get:false()") @@ -84,6 +87,8 @@ so this is a *very* simple example. # 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." + # expand the default cmdset with your own custom set (defined elsewhere) + self.cmdset.add(HeavySet) Change Default Rooms, Exits, Character Typeclass ------------------------------------------------ diff --git a/docs/sphinx/source/wiki/AdminDocs.rst b/docs/sphinx/source/wiki/AdminDocs.rst index dbaa82dd73..9b7e35d056 100644 --- a/docs/sphinx/source/wiki/AdminDocs.rst +++ b/docs/sphinx/source/wiki/AdminDocs.rst @@ -17,6 +17,7 @@ Installation and Early Life Customizing the server ---------------------- +- `Changing the Settings `_ - `Change Evennia's language `_ - `Apache webserver configuration `_ (optional) - `Changing text encodings used by the server `_ @@ -34,4 +35,5 @@ Working with Evennia - `Setting up your work environment with version control `_ +- `First steps coding with Evennia `_ diff --git a/docs/sphinx/source/wiki/Attributes.rst b/docs/sphinx/source/wiki/Attributes.rst index d8cf60e242..28923b0eff 100644 --- a/docs/sphinx/source/wiki/Attributes.rst +++ b/docs/sphinx/source/wiki/Attributes.rst @@ -62,19 +62,84 @@ will for example delete an ``Attribute``: del rose.db.has_thorns -Both ``db`` and ``ndb`` defaults to offering an ``all`` property on +Both ``db`` and ``ndb`` defaults to offering an ``all()`` method on themselves. This returns all associated attributes or non-persistent properties. :: - list_of_all_rose_attributes = rose.db.all - list_of_all_rose_ndb_attrs = rose.ndb.all + list_of_all_rose_attributes = rose.db.all() + list_of_all_rose_ndb_attrs = rose.ndb.all() If you use ``all`` as the name of an attribute, this will be used instead. Later deleting your custom ``all`` will return the default behaviour. +Properties of Attributes +------------------------ + +An Attribute object is stored in the database. It has the following +properties: + +- ``key`` - the name of the Attribute. When doing e.g. + ``obj.db.attrname = value``, this property is set to ``attrname``. +- ``value`` - this is the value of the Attribute. This value can be + anything which can be pickled - objects, lists, numbers or what have + you (see + [Attributes#What\_types\_of\_data\_can\_I\_save\_in\_an\_Attribute + this section] for more info). In the example + ``obj.db.attrname = value``, the ``value`` is stored here. +- ``category`` - this is an optional property that is set to None for + most Attributes. Setting this allows to use Attributes for different + functionality. This is usually not needed unless you want to use + Attributes for very different functionality (`Nicks `_ is + an example of using Attributes in this way). To modify this property + you need to use the [Attributes#The\_Attribute\_Handler Attribute + Handler]. +- ``strvalue`` - this is a separate value field that only accepts + strings. This severaly limits the data possible to store, but allows + for easier database lookups. This property is usually not used except + when re-using Attributes for some other purpose + (`Nicks `_ use it). It is only accessible via the + [Attributes#The\_Attribute\_Handler Attribute Handler]. + +Non-database attributes have no equivalence to category nor strvalue. + +The Attribute Handler +--------------------- + +The Attribute handler is what is used under the hood to manage the +Attributes on an object. It is accessible as ``obj.attributes``. For +most operations, the ``db`` or ``ndb`` wrappers are enough. But +sometimes you won't know the attribute name beforehand or you need to +manipulate your Attributes in more detail. The Attribute handler has the +following methods (the argument lists are mostly shortened; you can see +the full call signatures in ``src.typeclasses.models``): + +- ``attributes.has(...)`` - this checks if the object has an Attribute + with this key. This is equivalent to doing ``obj.db.key``. +- ``get(...)`` - this retrieves the given Attribute. Normally the + ``value`` property of the Attribute is returned, but the method takes + keywords for returning the Attribute object itself. By supplying an + ``accessing_object`` oto the call one can also make sure to check + permissions before modifying anything. +- ``add(...)`` - this adds a new Attribute to the object. An optional + `lockstring `_ can be supplied here to restrict future + access and also the call itself may be checked against locks. +- ``remove(...)`` - Remove the given Attribute. This can optionally be + made to check for permission before performing the deletion. +- ``clear(...)`` - removes all Attributes from object. +- ``all(...)`` - returns all Attributes (of the given category) + attached to this object. + +See [Attributes#Locking\_and\_checking\_Attributes this section] for +more about locking down Attribute access and editing. + +There is an equivalent ``nattribute`` handler for managing non-database +Attributes. This has the same methods but is much simpler since it does +not concern itself with category nor strvalue. It also offers no concept +of access control. + Persistent vs non-persistent ---------------------------- @@ -248,15 +313,15 @@ are specified `here `_. The relevant lock types are - *attrread* - limits who may read the value of the Attribute - *attredit* - limits who may set/change this Attribute -You cannot use e.g. ``obj.db.attrname`` handler to modify Attribute -objects (such as setting a lock on them - you will only get the -Attribute *value* that way, not the actual Attribute *object*. You get -the latter with ``get_attribute_obj`` (see next section) which allows -you to set the lock something like this: +You cannot use the ``db`` handler to modify Attribute object (such as +setting a lock on them) - The ``db`` handler will return the Attribute's +*value*, not the Attribute object itself. Instead you use +``get_attribute_obj`` (see next section) which allows you to set the +lock something like this: :: - obj.get_attribute_obj.locks.add("attread:all();attredit:perm(Wizards)") + obj.attributes.get("myattr", return_obj=True).locks.add("attread:all();attredit:perm(Wizards)") A lock is no good if nothing checks it -- and by default Evennia does not check locks on Attributes. You have to add a check to your @@ -266,60 +331,12 @@ commands/code wherever it fits (such as before setting an Attribute). # in some command code where we want to limit # setting of a given attribute name on an object - attr = obj.get_attribute_obj(attrname, default=None) - if not (attr and attr.locks.check(caller, 'attredit', default=True)): + attr = obj.attributes.get(attrname, return_obj=True, accessing_obj=caller, default=None, default_access=False) + if not attr: caller.msg("You cannot edit that Attribute!") return # edit the Attribute here -Note that in this example this lock check will default to ``True`` if no -lock was defined on the Attribute (which is the 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: - -:: - - obj.secure_attr(caller, attrname, value=None, - delete=False, - default_access_read=True, - default_access_edit=False, - default_access_create=True) - -The secure\_attr will try to retrieve the attribute value of an existing -Attribute if the ``value`` keyword is not set and create/set/delete it -otherwise. The *default\_access* keywords specify what should be the -default policy for each operation if no appropriate lock string is set -on the Attribute. - -Other ways to access Attributes -------------------------------- - -Normally ``db`` is all you need. But there there are also several other -ways to access information about Attributes, some of which cannot be -replicated by ``db``. These are available on all Typeclassed objects: - -- ``has_attribute(attrname)`` - checks if the object has an attribute - with the given name. This is equivalent to doing ``obj.db.attrname``. -- ``set_attribute(attrname, value)`` - equivalent to - ``obj.db.attrname = value``. -- ``get_attribute(attrname)`` - returns the attribute value. Equivalent - to ``obj.db.attrname``. -- ``get_attribute_raise(attrname)`` - returns the attribute value, but - instead of returning ``None`` if no such attribute is found, this - method raises ``AttributeError``. -- ``get_attribute_obj(attrname)`` - returns the attribute *object* - itself rather than the value stored in it. -- ``del_attribute(attrname)`` - equivalent to ``del obj.db.attrname``. - Quietly fails if ``attrname`` is not found. -- ``del_attribute_raise(attrname)`` - deletes attribute, raising - ``AttributeError`` if no matching Attribute is found. -- ``get_all_attributes`` - equivalent to ``obj.db.all`` -- ``attr(attrname, value=None, delete=False)`` - this is a convenience - function for getting, setting and deleting Attributes. It's - recommended to use ``db`` instead. -- ``secure_attr(...)`` - lock-checking version of ``attr``. See example - in previous section. - +The same keywords are available to use with ``obj.attributes.set()`` and +``obj.attributes.remove()``, those will check for the *attredit* lock +type. diff --git a/docs/sphinx/source/wiki/BuildingQuickstart.rst b/docs/sphinx/source/wiki/BuildingQuickstart.rst index bbe7278b41..35da4ff20a 100644 --- a/docs/sphinx/source/wiki/BuildingQuickstart.rst +++ b/docs/sphinx/source/wiki/BuildingQuickstart.rst @@ -39,7 +39,7 @@ To temporarily step down from your superuser position you can use the :: - @quell + > @quell This will make you start using the permission of your current `Character `_ instead of your superuser level. If you @@ -64,19 +64,18 @@ rather short name, let's is give a few aliases. :: - > @name box = very large box;box;very;bo;crate + > @name box = very large box;box;very;crate We now actually renamed the box to *very large box* (and this is what we -will see when looking at the room), but we will also recognize it by any -of the other names we give - like *crate* or simply *box* as before. We +will see when looking at it), but we will also recognize it by any of +the other names we give - like *crate* or simply *box* as before. We could have given these aliases directly after the name in the ``@create`` command, this is true for all creation commands - you can always tag on a list of ;-separated aliases to the name of your new object. If you had wanted to not change the name itself, but to only add aliases, you could have used the ``@alias`` command. -We are currently carrying the box, which you can see if you give the -command ``inventory`` (or ``i``). Let's drop it. +We are currently carrying the box. Let's drop it. :: @@ -118,8 +117,7 @@ box was dropped in the room, then try this: Locks are a rather `big topic `_, but for now that will do what we want. This will lock the box so noone can lift it. The exception is superusers, they override all locks and will pick it up anyway. Make -sure you are using your builder account and not the superuser account -and try to get the box now: +sure you are quelling your superuser powers and try to get the box now: :: @@ -134,7 +132,7 @@ attributes using the ``@set`` command: :: - > @set box/get_err_msg = The box is way too heavy for you to lift. + > @set box/get_err_msg = It's way too heavy for you to lift. Try to get it now and you should see a nicer error message echoed back to you. @@ -178,7 +176,7 @@ Pushing your buttons If we get back to the box we made, there is only so much fun you can do with it at this point. It's just a dumb generic object. If you renamed -it ``carpet`` and changed its description noone would be the wiser. +it to ``stone`` and changed its description noone would be the wiser. However, with the combined use of custom `Typeclasses `_, `Scripts `_ and object-based `Commands `_, you could expand it and other @@ -201,7 +199,7 @@ Python except Evennia defaults to looking in ``game/gamesrc/objects/`` so you don't have to write the full path every time. There you go - one red button. -The RedButton is an example object intended to show off many of +The RedButton is an example object intended to show off a few of Evennia's features. You will find that the `Scripts `_ and `Commands `_ controlling it are scattered in ``examples``-folders all across ``game/gamesrc/``. @@ -210,8 +208,8 @@ If you wait for a while (make sure you dropped it!) the button will blink invitingly. Why don't you try to push it ...? Surely a big red button is meant to be pushed. You know you want to. -Creating a room called 'house' ------------------------------- +Making yourself a house +----------------------- The main command for shaping the game world is ``@dig``. For example, if you are standing in Limbo you can dig a route to your new house location @@ -240,8 +238,7 @@ This will create a new room "cliff" with an exit "southwest" leading there and a path "northeast" leading back from the cliff to your current location. -You can create exits from anywhere at any time using the ``@open`` -command: +You can create new exits from where you are using the ``@open`` command: :: @@ -251,9 +248,9 @@ This opens an exit ``north`` to the previously created room ``house``. If you have many rooms named ``house`` you will get a list of matches and have to select which one you want to link to. You can also give its -database ref number, which is unique to every object. This can be found -with the ``examine`` command or by looking at the latest constructions -with ``@objects``. +database (#dbref) number, which is unique to every object. This can be +found with the ``examine`` command or by looking at the latest +constructions with ``@objects``. Follow the north exit to your 'house' or ``@teleport`` to it: @@ -276,37 +273,46 @@ To manually open an exit back to Limbo (if you didn't do so with the (or give limbo's dbref which is #2) -Finding and manipulating existing objects ------------------------------------------ +Reshuffling the world +--------------------- -To re-point an exit at another room or object, you can use +You can find things using the ``@find`` command. Assuming you are back +at ``Limbo``, let's teleport the *large box to our house*. :: - > @link = + > @teleport box = house + very large box is leaving Limbo, heading for house. + Teleported very large box -> house. -To find something, use +We can still find the box by using @find: :: - > @find + > @find box + One Match(#1-#8): + very large box(#8) - src.objects.objects.Object -This will return a list of dbrefs that have a similar name. - -To teleport something somewhere, one uses +Knowing the #dbref of the box (#8 in this example), you can grab the box +and get it back here without actually yourself going to ``house`` first: :: - > @teleport = + > @teleport #8 = here -To destroy something existing, use +(You can usually use ``here`` to refer to your current location. To +refer to yourself you can use ``self`` or ``me``). The box should now be +back in Limbo with you. + +We are getting tired of the box. Let's destroy it. :: - > @destroy + > @destroy box You can destroy many objects in one go by giving a comma-separated list -of objects to the command. +of objects (or their #dbrefs, if they are not in the same location) to +the command. Adding a help entry ------------------- @@ -322,16 +328,27 @@ command. Adding a World -------------- -Evennia comes with a tutorial world for you to build. To build this you -need to log back in as *superuser*. Place yourself in Limbo and do: +After this brief introduction to building you may be ready to see a more +fleshed-out example. Evennia comes with a tutorial world for you to +explore. + +First you need to switch back to *superuser* by using the ``@unquell`` +command. Next, place yourself in ``Limbo`` and run the following +command: :: - @batchcommand contrib.tutorial_world.build + > @batchcommand contrib.tutorial_world.build -This will take a while, but you will see a lot of messages as the world -is built for you. You will end up with a new exit from Limbo named -*tutorial*. See more info about the tutorial world -`here `_. Read -``contrib/tutorial_world/build.ev`` to see exactly how it's built, step -by step. +This will take a while (be patient and don't re-run the command). You +will see all the commands used to build the world scroll by as the world +is built for you. + +You will end up with a new exit from Limbo named *tutorial*. Apart from +being a little solo-adventure in its own right, the tutorial world is a +good source for learning Evennia building (and coding). + +Read +`contrib/tutorial\_world/build.ev `_ +to see exactly how it's built, step by step. See also more info about +the tutorial world `here `_. diff --git a/docs/sphinx/source/wiki/Caches.rst b/docs/sphinx/source/wiki/Caches.rst index e3399ecaf1..5d86fa0888 100644 --- a/docs/sphinx/source/wiki/Caches.rst +++ b/docs/sphinx/source/wiki/Caches.rst @@ -60,8 +60,8 @@ clean the idmapper cache, the safest way is therefore a soft-reload of the server (via e.g. the ``@reload`` command). Most developers will not need to care with the idmapper cache - it just -makes models work intuitively. It is visible mostly in that all database -models in Evennia inherits from +makes models work intuitively. It is visible mostly in that many +database models in Evennia inherit from ``src.utils.idmapper.models.SharedMemoryModel``. On-object variable cache @@ -95,24 +95,6 @@ latter will give an invalid-field error. If you use Evennia's own search methods you don't need to worry about this, they look for the right things behind the scenes for you. -Mutable variable caches -~~~~~~~~~~~~~~~~~~~~~~~ - -Some object properties may appear mutable - that is, they return lists. -One such example is the ``permissions`` property. This is however not -actually a list - it's just a handler that *returns* and *accepts* -lists. ``db_permissions`` is actually stored as a comma-separated -string. The uptake of this is that you cannot do list operations on the -handler. So ``obj.permissions.append('Immortals')`` will not work. -Rather, you will have to do such operations on what is returned. Like -this: - -:: - - perms = obj.permissions # this returns a list! - perms.append("Immortals") - obj.permissions = perms # overwrites with new list - Content cache ~~~~~~~~~~~~~ @@ -162,6 +144,8 @@ 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. -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. +Due to the possibility of storing objects in Attributes, the system +cannot cache the value of data stored in the Attribute (this would allow +for the system not detecting a stored Object being deleted elsewhere - +it would still be accessible from the Attribute). So having to re-access +such objects every load does incur a minor speed penalty. diff --git a/docs/sphinx/source/wiki/CommandCooldown.rst b/docs/sphinx/source/wiki/CommandCooldown.rst index 378637d45a..c6d288abf8 100644 --- a/docs/sphinx/source/wiki/CommandCooldown.rst +++ b/docs/sphinx/source/wiki/CommandCooldown.rst @@ -1,3 +1,6 @@ +*This tutorial requires that you first well understand how +`Commands `_ work.* + Cooldowns ========= diff --git a/docs/sphinx/source/wiki/Commands.rst b/docs/sphinx/source/wiki/Commands.rst index 38004b584f..6d951534a0 100644 --- a/docs/sphinx/source/wiki/Commands.rst +++ b/docs/sphinx/source/wiki/Commands.rst @@ -255,7 +255,8 @@ Command Sets All commands in Evennia are always grouped together into *Command Sets* (CmdSets). A particular ``Command`` class definition can be part of any number of different CmdSets. CmdSets can be stored either on game -`Objects `_ or on `Players `_. +`Sessions `_, `Objects `_ or on +`Players `_. When a user issues a command, it is matched against the contents of all cmdsets available to the user at the moment, @@ -270,19 +271,18 @@ in this order: exits) - The channel commandset - The cmdset defined on the Player object controlling the character - (OOC cmdset) The default ``CmdSet`` shipping with Evennia is automatically added to all new characters and contains commands such as ``look``, ``drop``, ``@dig`` etc. You can find it defined in -``src/commands/default/cmdset_default.py``, but it is also referenced by -importing ``ev.default_cmds`` and accessing its property -``DefaultCmdset``. Players have an Out-of-character cmdset called -``cmdset_ooc`` that can also be found from the same place. There is -finally an "unloggedin" cmdset that is used before the Player has +``src/commands/default/cmdset_character.py``, but it is also referenced +by importing ``ev.default_cmds`` and accessing its property +``CharacterCmdset``. Players have a cmdset called ``PlayerCmdSet`` that +can also be found from the same place. There is finally an "unloggedin" +cmdset, "UnloggedinCmdSet", that is used before the Player has authenticated to the game. The path to these three standard command sets -are defined in settings, as ``CMDSET_UNLOGGEDIN``, ``CMDSET_DEFAULT`` -and ``CMDSET_OOC``. You can create any number of command sets besides +are defined in settings, as ``CMDSET_UNLOGGEDIN``, ``CMDSET_CHARACTER`` +and ``CMDSET_PLAYER``. You can create any number of command sets besides those to fit your needs. A CmdSet is, as most things in Evennia, defined as a Python class @@ -350,314 +350,26 @@ for a different way of approaching it. Generally you can customize which command sets are added to your objects by using ``self.cmdset.add()`` or ``self.cmdset.add_default()``. -Adding and merging command sets -------------------------------- +Properties on command sets +~~~~~~~~~~~~~~~~~~~~~~~~~~ -*Note: This is an advanced topic. It's useful to know about, but you -might want to skip it if this is your first time learning about -commands.* +There are a few extra flags one can set on CmdSets in order to modify +how they work. All are optional and will be set to defaults otherwise. +Since many of these relate to *merging* cmdsets you might want to read +up on [Commands#Adding\_and\_merging\_command\_sets next section] for +some of these to make sense. -CmdSets have the special ability that they can be *merged* together into -new sets. This would happen if you, for example, did -``object.cmdset.add(MyCmdSet)`` on an object that already had a command -set defined on it. The two sets will be evaluated and a temporary, -*merged set* will be created out of the commands in both sets. Only the -commands in this merged set is from that point available to use. Which -of the ingoing commands end up in the merged set is defined by the -*merge rule* and the relative *priorities* of the two sets. Removing the -latest added set will restore things back to the way it was before the -addition. +- ``key`` (string) - an identifier for the cmdset. This is optional but + should be unique; it is used for display in lists but also to + identify special merging behaviours using the + ``key_mergetype' dictionary below. * ``\ mergetype\ `` (string) - one of "_Union_", "_Intersect_", "_Replace_" or "_Remove_". * ``\ priority\ `` (int) - Higher priority sets will take priority during merging. Evennia default sets have priorities between ``\ 0\ `` and ``\ 9\ ``, where ``\ 0\ `` are most commands and ``\ 8\ `` or ``\ 9\ `` are used for special things like channel-commands and exit-commands. * ``\ key\_mergetype\ `` (dict) - a dict of ``\ key:mergetype\ `` pairs. This allows this cmdset to merge differently with certain named cmdsets. If the cmdset to merge with has a ``\ key\ `` matching an entry in ``\ key\_mergetype\ ``, it will not be merged according to the setting in ``\ mergetype\ `` but according to the mode in this dict. * ``\ duplicates\ `` (bool, default False) - when merging same-priority cmdsets containing same-key commands, the cmdset being merged "onto" the old one will take precedence. The result will be unique commands. If this flag is set the merged set can have multiple commands with the same key. This will usually lead to multi-match errors for the player. This is is useful e.g. for on-object cmdsets (example: There is a ``\ red + button\ `` and a ``\ green + button\ `` in the room. Both have a ``\ press + button\ `` command, in cmdsets with the same priority. This flag makes sure that just writing ``\ press + button\ `` will force the Player to define just which object's command was intended). * ``\ no\_objs\ `` this is a flag for the cmdhandler that builds the set of commands available at every moment. It tells the handler not to include cmdsets from objects around the player (nor from rooms) when building the merged set. Exit commands will still be included. * ``\ no\_exits\ `` - this is a flag for the cmdhandler that builds the set of commands available at every moment. It tells the handler not to include cmdsets from exits. * ``\ is\_exit\ `` (bool) - this marks the cmdset as being used for an in-game exit. This allows the cmdhandler to easily disregard this cmdset when other cmdsets have their ``\ no\_exits\ `` flag set. It is set directly by the Exit object as part of initializing its cmdset. * ``\ no\_channels\ `` (bool) - this is a flag for the cmdhandler that builds the set of commands available at every moment. It tells the handler not to include cmdsets from available in-game channels. * ``\ is\_channel\ `` (bool)- this marks the cmdset as being used for an in-game exit. It allows the cmdhandler to easily disregard this cmdset when other cmdsets have their ``\ no\_channels\ `` flag set. It is set directly by the Channel object as part of initializing its cmdset. == Default command sets == Evennia comes with four default cmdsets, used at different parts of the game. You can freely add more and/or expand on these as you see fit. * _DefaultUnloggedin_ (``\ src.commands.default.cmdset\_unloggedin.UnloggedinCmdSet\ ``) - this cmdset holds the commands used before you have authenticated to the server, such as the login screen, connect, create character and so on. * _DefaultSession_ (``\ src.commands.default.cmdset\_session.SessionCmdSet\ `` - this is stored on the [Session] once authenticated. This command set holds no commands by default. It is meant to hold session-specific OOC things, such as character-creation systems. * _DefaultPlayer_ (``\ src.commands.default.cmdset\_player.PlayerCmdSet\ ``) - this is stored on the [Player] and contains account-centric OOC commands, such as the commands for sending text to channels or admin commands for staff. * _DefaultCharacter_ (``\ src.commands.default.cmdset\_character.CharacterCmdSet\ ``) - this is finally stored on Character objects and holds all IC commands available to that Character. This is the biggest cmdset. For it to be available to the player, the Character must be puppeted. Except for the unloggedin cmdset, cmdsets stored on these various levels dre merged "downward" in the connection hierarchy. So when merging (with the same priority), Session cmdsets are merged first, followed by Player and finally Character (so Character command overrule Player commands which overrule Session commands as it were). See the next section for details about merge rules. ==Adding and merging command sets == _Note: This is an advanced topic. It's useful to know about, but you might want to skip it if this is your first time learning about commands._ !CmdSets have the special ability that they can be _merged_ together into new sets. This would happen if you, for example, did ``\ object.cmdset.add(MyCmdSet)\ `` on an object that already had a command set defined on it. The two sets will be evaluated and a temporary, _merged set_ will be created out of the commands in both sets. Only the commands in this merged set is from that point available to use. Which of the ingoing commands end up in the merged set is defined by the _merge rule_ and the relative _priorities_ of the two sets. Removing the latest added set will restore things back to the way it was before the addition. !CmdSets are non-destructively stored in a stack inside the cmdset handler on the object. This stack is parsed to create the "combined" cmdset active at the moment. The very first cmdset in this stack is called the _Default cmdset_ and is protected from accidental deletion. Running ``\ obj.cmdset.delete()\ `` will never delete the default set. Instead one should add new cmdsets on top of the default to "hide" it, as described below. Use the special ``\ obj.cmdset.delete\_default()\ `` only if you really know what you are doing. !CmdSet merging is an advanced feature useful for implementing powerful game effects. Imagine for example a player entering a dark room. You don't want the player to be able to find everything in the room at a glance - maybe you even want them to have a hard time to find stuff in their backpack! You can then define a different !CmdSet with commands that override the normal ones. While they are in the dark room, maybe the ``\ look\ `` and ``\ inv\ `` commands now just tell the player they cannot see anything! Another example would be to offer special combat commands only when the player is in combat. Or when being on a boat. Or when having taken the super power-up. All this can be done on the fly by merging command sets. === Merge rules === To understand how sets merge, we need to define a little lingo. Let's call the first command set *A* and the second *B*. We will merge *A* onto *B*, so in code terms the command would be ``\ object.cdmset.add(A)\ ``, where we assume *B* was already the active cmdset on ``\ object\ `` since earlier. We let the *A* set have higher priority than *B*. A priority is simply an integer number. Default is 0, Evennia's in-built high-prio commands (intended to overrule others) have values of 9 or 10. Both sets contain a number of commands named by numbers, like ``\ A1, + A2\ `` for set *A* and ``\ B1, B2, B3, + B4\ `` for *B*. So for that example both sets contain commands with the same keys 1 and 2, whereas commands 3 and 4 are unique to *B*. To describe a merge between these sets, we would write {{{A1,A2 + B1,B2,B3,B4 = ?}}} where ``?\ `` is a list of commands that depend on which merge type *A* has, and which relative priorities the two sets have. By convention, we read this statement as "New command set *A* is merged onto the old command set *B* to form *?*". Below are the available merge types and how they work. Names are partly borrowed from [http://en.wikipedia.org/wiki/Set_theory Set theory]. *Union* (default) - The two cmdsets are merged so that as many commands as possible from each cmdset ends up in the merged cmdset. Same-key commands are merged by priority. {{{ # Union A1,A2 + B1,B2,B3,B4 = A1,A2,B3,B4 }}} *Intersect* - Only commands found in _both_ cmdsets (i.e. which have the same keys) end up in the merged cmdset, with the higher-priority cmdset replacing the lower one's commands. {{{ # Intersect A1,A3,A5 + B1,B2,B4,B5 = A1,A5 }}} *Replace* - The commands of the higher-prio cmdset completely replaces the lower-priority cmdset's commands, regardless of if same-key commands exist or not. {{{ # Replace A1,A3 + B1,B2,B4,B5 = A1,A3 }}} *Remove* - The high-priority command sets removes same-key commands from the lower-priority cmdset. They are not replaced with anything, so this is a sort of filter that prunes the low-prio set using the high-prio one as a template. {{{ # Remove A1,A3 + B1,B2,B3,B4,B5 = B2,B4,B5 }}} Besides ``\ priority\ `` and ``\ mergetype\ ``, a command set also takes a few other variables to control how they merge: * _duplicates_ (bool) - determines what happens when two sets of equal priority merge. Default is that the new set in the merger (i.e. *A* above) automatically takes precedence. But if _duplicates_ is true, the result will be a merger with more than one of each name match. This will usually lead to the player receiving a multiple-match error higher up the road, but can be good for things like cmdsets on non-player objects in a room, to allow the system to warn that more than one 'ball' in the room has the same 'kick' command defined on it, so it may offer a chance to select which ball to kick ... Allowing duplicates only makes sense for _Union_ and _Intersect_, the setting is ignored for the other mergetypes. * _key_mergetypes_ (dict) - allows the cmdset to define a unique mergetype for particular cmdsets, identified by their cmdset-key. Format is ``\ {CmdSetkey:mergetype}``. Priorities still apply. Example: ``\ {'Myevilcmdset','Replace'}\ `` which would make sure for this set to always use 'Replace' on ``\ Myevilcmdset\ `` only, no matter what _mergetype_ is set to. More advanced cmdset example: {{{ class MyCmdSet(CmdSet): key = "MyCmdSet" priority = 4 mergetype = "Replace" key_mergetypes = {'MyOtherCmdSet':'Union'} def at_cmdset_creation(self): """ The only thing this method should need to do is to add commands to the set. """ self.add(mycommands.MyCommand1()) self.add(mycommands.MyCommand2()) self.add(mycommands.MyCommand3()) }}} == System commands == _Note: This is an advanced topic. Skip it if this is your first time learning about commands._ There are several command-situations that are exceptional in the eyes of the server. What happens if the player enters an empty string? What if the 'command' given is infact the name of a channel the user wants to send a message to? Or if there are multiple command possibilities? Such 'special cases' are handled by what's called _system commands_. A system command is defined in the same way as other commands, except that their name (key) must be set to one reserved by the engine (the names are defined at the top of ``\ src/commands/cmdhandler.py\ ``). You can find (unused) implementations of the system commands in ``\ src/commands/default/system\_commands.py\ ``. Since these are not (by default) included in any ``\ CmdSet\ `` they are not actually used, they are just there for show. When the special situation occurs, Evennia will look through all valid ``\ CmdSet\ ``s for your custom system command. Only after that will it resort to its own, hard-coded implementation. Here are the exceptional situations that triggers system commands. You can find the command keys they use as properties on ``\ ev.syscmdkeys\ `` * No input (``\ syscmdkeys.CMD\_NOINPUT\ ``) - the player just pressed return without any input. Default is to do nothing, but it can be useful to do something here for certain implementations such as line editors that interpret non-commands as text input (an empty line in the editing buffer). * Command not found (``\ syscmdkeys.CMD\_NOMATCH\ ``) - No matching command was found. Default is to display the "Huh?" error message. * Several matching commands where found (``\ syscmdkeys.CMD\_MULTIMATCH\ ``) - Default is to show a list of matches. * User is not allowed to execute the command (``\ syscmdkeys.CMD\_NOPERM\ ``) - Default is to display the "Huh?" error message. * Channel (``\ syscmdkeys.CMD\_CHANNEL\ ``) - This is a [Communications Channel] name of a channel you are subscribing to - Default is to relay the command's argument to that channel. Such commands are created by the Comm system on the fly depending on your subscriptions. * New session connection ('syscmdkeys.CMD_LOGINSTART'). This command name should be put in the ``\ settings.CMDSET\_UNLOGGEDIN\ ``. Whenever a new connection is established, this command is always called on the server (default is to show the login screen). Below is an example of redefining what happens when the player don't give any input (e.g. just presses return). Of course the new system command must be added to a cmdset as well before it will work. {{{ from ev import syscmdkeys, Command class MyNoInputCommand(Command): "Usage: Just press return, I dare you" key = syscmdkeys.CMD_NOINPUT def func(self): self.caller.msg("Don't just press return like that, talk to me!") }}} == Dynamic Commands == _Note: This is an advanced topic._ Normally Commands are created as fixed classes and used without modification. There are however situations when the exact key, alias or other properties is not possible (or impractical) to pre-code ([Commands#Exits Exits] is an example of this). To create a dynamic command use the following call: {{{ cmd = MyCommand(key="newname", aliases=["test", "test2"], locks="cmd:all()", ...) }}} _All_ keyword arguments you give to the Command constructor will be stored as a property on the command object. This will overload eventual existing properties defined on the parent class. Normally you would define your class as normal and only overload things like ``\ key\ `` and ``\ aliases\ `` at run-time. But you could in principle also send method objects as keyword arguments in order to make your command completely customized at run-time. == Exits == _Note: This is an advanced topic._ Exits are examples of the use of a [Commands#Dynamic_Commands Dynamic Command]. The functionality of [Objects Exit] objects in Evennia is not hard-coded in the engine. Instead Exits are normal [Typeclasses typeclassed] objects that auto-creates a [Commands#CmdSets CmdSet] on themselves when they load. This cmdset has a single dynamically created Command with the same properties (key, aliases and locks) as the Exit object itself. When entering the name of the exit, this dynamic exit-command is triggered and (after access checks) moves the Character to the exit's destination. Whereas you could customize the Exit object and its command to achieve completely different behaviour, you will usually be fine just using the appropriate ``\ traverse\_\ **`` hooks on the Exit object. But if you are interested in really changing how things work under the hood, check out ``\ src.objects.objects\ `` for how the ``\ Exit\ `` typeclass is set up. == How commands actually work == _Note: This is an advanced topic mainly of interest to server developers._ Any time the user sends text to Evennia, the server tries to figure out if the text entered corresponds to a known command. This is how the command handler sequence looks for a logged-in user: # A user (the _caller_) enters a string of text and presses enter. * If input is an empty string, resend command as ``\ CMD\_NOINPUT\ ``. If no such command is found in cmdset, ignore. * If command.key matches ``\ settings.IDLE\_COMMAND\ ``, update timers but don't do anything more. # Evennia's _commandhandler_ gathers the !CmdSets available to _caller_ at the time: * The caller's own currently active !CmdSet. * The active !CmdSets of eventual objects in the same location (if any). This includes commands on [Objects#Exits Exits]. * Sets of dynamically created _System commands_ representing available [Communications Channels]. * !CmdSet defined on the _caller.player_ (OOC cmdset). # All !CmdSets _of the same priority_ are merged together in groups. Grouping avoids order-dependent issues of merging multiple same-prio sets onto lower ones. # All the grouped !CmdSets are _merged_ in reverse priority into one combined !CmdSet according to each set's merge rules. # Evennia's _command parser_ takes the merged cmdset and matches each of its commands (using its key and aliases) against the beginning of the string entered by _caller_. This produces a set of candidates. # The _cmd parser_ next rates the matches by how many characters they have and how many percent matches the respective known command. Only if candidates cannot be separated will it return multiple matches. * If multiple matches were returned, resend as ``\ CMD\_MULTIMATCH\ ``. If no such command is found in cmdset, return hard-coded list of matches. * If no match was found, resend as ``\ CMD\_NOMATCH\ ``. If no such command is found in cmdset, give hard-coded error message. # If a single command was found by the parser, the correct command class is plucked out of storage and instantiated. # It is checked that the caller actually has access to the command by validating the _lockstring_ of the command. If not, it is not considered as a suitable match it is resent as ``\ CMD\_NOPERM\ `` is created. If no such command is found in cmdset, use hard-coded error message. # If the new command is tagged as a channel-command, resend as ``\ CMD\_CHANNEL\ ``. If no such command is found in cmdset, use hard-coded implementation. # Assign several useful variables to the command instance. # Call ``\ at\_pre\_command()\ `` on the command instance. # Call ``\ parse()\ `` on the command instance. This is is fed the remainder of the string, after the name of the command. It's intended to pre-parse the string int a form useful for the ``\ func()\ `` method. # Call ``\ func()\ `` on the command instance. This is the functional body of the command, actually doing useful things. # Call ``\ at\_post\_command()\ `` on the command instance. ==Assorted notes== The return value of ``\ Command.func()\ `` is a Twisted [http://twistedmatrix.com/documents/current/core/howto/defer.html deferred]. Evennia does not use this return value at all by default. If you do, you must thus do so asynchronously, using callbacks. {{{ # in command class func() def callback(ret, caller): caller.msg("Returned is %s" % ret) deferred = self.execute_command("longrunning") deferred.addCallback(callback, self.caller) }}} This is probably not relevant to any but the most advanced/exotic designs (one might use it to create a "nested" command structure for example). The ``\ save\_for\_next\ ```` + class variable can be used to implement state-persistent commands. + For example it can make a command operate on "it", where it is + determined by what the previous command operated on.** -CmdSets are non-destructively stored in a stack inside the cmdset -handler on the object. This stack is parsed to create the "combined" -cmdset active at the moment. The very first cmdset in this stack is -called the *Default cmdset* and is protected from accidental deletion. -Running ``obj.cmdset.delete()`` will never delete the default set. -Instead one should add new cmdsets on top of the default to "hide" it, -as described below. Use the special ``obj.cmdset.delete_default()`` only -if you really know what you are doing. - -CmdSet merging is an advanced feature useful for implementing powerful -game effects. Imagine for example a player entering a dark room. You -don't want the player to be able to find everything in the room at a -glance - maybe you even want them to have a hard time to find stuff in -their backpack! You can then define a different CmdSet with commands -that override the normal ones. While they are in the dark room, maybe -the ``look`` and ``inv`` commands now just tell the player they cannot -see anything! Another example would be to offer special combat commands -only when the player is in combat. Or when being on a boat. Or when -having taken the super power-up. All this can be done on the fly by -merging command sets. - -Merge rules -~~~~~~~~~~~ - -To understand how sets merge, we need to define a little lingo. Let's -call the first command set **A** and the second **B**. We will merge -**A** onto **B**, so in code terms the command would be -``object.cdmset.add(A)``, where we assume **B** was already the active -cmdset on ``object`` since earlier. - -We let the **A** set have higher priority than **B**. A priority is -simply an integer number. Default is 0, Evennia's in-built high-prio -commands (intended to overrule others) have values of 9 or 10. - -Both sets contain a number of commands named by numbers, like ``A1, A2`` -for set **A** and ``B1, B2, B3, B4`` for **B**. So for that example both -sets contain commands with the same keys 1 and 2, whereas commands 3 and -4 are unique to **B**. To describe a merge between these sets, we would -write ``A1,A2 + B1,B2,B3,B4 = ?`` where ``?`` is a list of commands that -depend on which merge type **A** has, and which relative priorities the -two sets have. By convention, we read this statement as "New command set -**A** is merged onto the old command set **B** to form **?**". - -Below are the available merge types and how they work. Names are partly -borrowed from `Set theory `_. - -**Union** (default) - The two cmdsets are merged so that as many -commands as possible from each cmdset ends up in the merged cmdset. -Same-key commands are merged by priority. - -:: - - # Union - A1,A2 + B1,B2,B3,B4 = A1,A2,B3,B4 - -**Intersect** - Only commands found in *both* cmdsets (i.e. which have -the same keys) end up in the merged cmdset, with the higher-priority -cmdset replacing the lower one's commands. - -:: - - # Intersect - A1,A3,A5 + B1,B2,B4,B5 = A1,A5 - -**Replace** - The commands of the higher-prio cmdset completely replaces -the lower-priority cmdset's commands, regardless of if same-key commands -exist or not. - -:: - - # Replace - A1,A3 + B1,B2,B4,B5 = A1,A3 - -**Remove** - The high-priority command sets removes same-key commands -from the lower-priority cmdset. They are not replaced with anything, so -this is a sort of filter that prunes the low-prio set using the -high-prio one as a template. - -:: - - # Remove - A1,A3 + B1,B2,B3,B4,B5 = B2,B4,B5 - -Besides ``priority`` and ``mergetype``, a command set also takes a few -other variables to control how they merge: - -- *allow\_duplicates* (bool) - determines what happens when two sets of - equal priority merge. Default is that the new set in the merger (i.e. - **A** above) automatically takes precedence. But if - *allow\_duplicates* is true, the result will be a merger with more - than one of each name match. This will usually lead to the player - receiving a multiple-match error higher up the road, but can be good - for things like cmdsets on non-player objects in a room, to allow the - system to warn that more than one 'ball' in the room has the same - 'kick' command defined on it, so it may offer a chance to select - which ball to kick ... Allowing duplicates only makes sense for - *Union* and *Intersect*, the setting is ignored for the other - mergetypes. -- *key\_mergetype* (dict) - allows the cmdset to define a unique - mergetype for particular cmdsets, identified by their cmdset-key. - Format is ``{CmdSetkey:mergetype}``. Priorities still apply. Example: - ``{'Myevilcmdset','Replace'}`` which would make sure for this set to - always use 'Replace' on ``Myevilcmdset`` only, no matter what - *mergetype* is set to. - -More advanced cmdset example: - -:: - - class MyCmdSet(CmdSet): - - key = "MyCmdSet" - priority = 4 - mergetype = "Replace" - key_mergetype = {'MyOtherCmdSet':'Union'} - - def at_cmdset_creation(self): - """ - The only thing this method should need - to do is to add commands to the set. - """ - self.add(mycommands.MyCommand1()) - self.add(mycommands.MyCommand2()) - self.add(mycommands.MyCommand3()) - -System commands ---------------- - -*Note: This is an advanced topic. Skip it if this is your first time -learning about commands.* - -There are several command-situations that are exceptional in the eyes of -the server. What happens if the player enters an empty string? What if -the 'command' given is infact the name of a channel the user wants to -send a message to? Or if there are multiple command possibilities? - -Such 'special cases' are handled by what's called *system commands*. A -system command is defined in the same way as other commands, except that -their name (key) must be set to one reserved by the engine (the names -are defined at the top of ``src/commands/cmdhandler.py``). You can find -(unused) implementations of the system commands in -``src/commands/default/system_commands.py``. Since these are not (by -default) included in any ``CmdSet`` they are not actually used, they are -just there for show. When the special situation occurs, Evennia will -look through all valid ``CmdSet``\ s for your custom system command. -Only after that will it resort to its own, hard-coded implementation. - -Here are the exceptional situations that triggers system commands. You -can find the command keys they use as properties on ``ev.syscmdkeys`` - -- No input (``syscmdkeys.CMD_NOINPUT``) - the player just pressed - return without any input. Default is to do nothing, but it can be - useful to do something here for certain implementations such as line - editors that interpret non-commands as text input (an empty line in - the editing buffer). -- Command not found (``syscmdkeys.CMD_NOMATCH``) - No matching command - was found. Default is to display the "Huh?" error message. -- Several matching commands where found (``syscmdkeys.CMD_MULTIMATCH``) - - Default is to show a list of matches. -- User is not allowed to execute the command - (``syscmdkeys.CMD_NOPERM``) - Default is to display the "Huh?" error - message. -- Channel (``syscmdkeys.CMD_CHANNEL``) - This is a - `Channel `_ name of a channel you are - subscribing to - Default is to relay the command's argument to that - channel. Such commands are created by the Comm system on the fly - depending on your subscriptions. -- New session connection ('syscmdkeys.CMD\_LOGINSTART'). This command - name should be put in the ``settings.CMDSET_UNLOGGEDIN``. Whenever a - new connection is established, this command is always called on the - server (default is to show the login screen). - -Below is an example of redefining what happens when the player don't -give any input (e.g. just presses return). Of course the new system -command must be added to a cmdset as well before it will work. - -:: - - from ev import syscmdkeys, Command - - class MyNoInputCommand(Command): - "Usage: Just press return, I dare you" - key = syscmdkeys.CMD_NOINPUT - def func(self): - self.caller.msg("Don't just press return like that, talk to me!") - -Exits ------ - -*Note: This is an advanced topic.* - -The functionality of `Exit `_ objects in Evennia is not -hard-coded in the engine. Instead Exits are normal typeclassed objects -that auto-creates a ``CmdSet`` on themselves when they are loaded. This -cmdset has a single command with the same name (and aliases) as the Exit -object itself. So what happens when a Player enters the name of the Exit -on the command line is simply that the command handler, in the process -of searching all available commands, also picks up the command from the -Exit object(s) in the same room. Having found the matching command, it -executes it. The command then makes sure to do all checks and eventually -move the Player across the exit as appropriate. This allows exits to be -extremely flexible - the functionality can be customized just like one -would edit any other command. - -Admittedly, you will usually be fine just using the appropriate -``traverse_*`` hooks. But if you are interested in really changing how -things work under the hood, check out ``src.objects.objects`` for how -the default ``Exit`` typeclass is set up. - -How commands actually work --------------------------- - -*Note: This is an advanced topic mainly of interest to server -developers.* - -Any time the user sends text to Evennia, the server tries to figure out -if the text entered corresponds to a known command. This is how the -command handler sequence looks for a logged-in user: - -#. A user (the *caller*) enters a string of text and presses enter. - - - If input is an empty string, resend command as ``CMD_NOINPUT``. If - no such command is found in cmdset, ignore. - - If command.key matches ``settings.IDLE_COMMAND``, update timers - but don't do anything more. - -#. Evennia's *commandhandler* gathers the CmdSets available to *caller* - at the time: - - - The caller's own currently active CmdSet. - - The active CmdSets of eventual objects in the same location (if - any). This includes commands on [Objects#Exits Exits]. - - Sets of dynamically created *System commands* representing - available `Channels `_. - - CmdSet defined on the *caller.player* (OOC cmdset). - -#. All the CmdSets are *merged* into one combined CmdSet according to - each set's merge rules. -#. Evennia's *command parser* takes the merged cmdset and matches each - of its commands (using its key and aliases) against the beginning of - the string entered by *caller*. This produces a set of candidates. -#. The *cmd parser* next rates the matches by how many characters they - have and how many percent matches the respective known command. Only - if candidates cannot be separated will it return multiple matches. - - - If multiple matches were returned, resend as ``CMD_MULTIMATCH``. - If no such command is found in cmdset, return hard-coded list of - matches. - - If no match was found, resend as ``CMD_NOMATCH``. If no such - command is found in cmdset, give hard-coded error message. - -#. If a single command was found by the parser, the correct command - class is plucked out of storage and instantiated. -#. It is checked that the caller actually has access to the command by - validating the *lockstring* of the command. If not, it is not - considered as a suitable match it is resent as ``CMD_NOPERM`` is - created. If no such command is found in cmdset, use hard-coded error - message. -#. If the new command is tagged as a channel-command, resend as - ``CMD_CHANNEL``. If no such command is found in cmdset, use - hard-coded implementation. -#. Assign several useful variables to the command instance. -#. Call ``at_pre_command()`` on the command instance. -#. Call ``parse()`` on the command instance. This is is fed the - remainder of the string, after the name of the command. It's intended - to pre-parse the string int a form useful for the ``func()`` method. -#. Call ``func()`` on the command instance. This is the functional body - of the command, actually doing useful things. -#. Call ``at_post_command()`` on the command instance. - -Assorted notes --------------- - -The return value of ``Command.func()`` is a Twisted -`deferred `_. -Evennia does not use this return value at all by default. If you do, you -must thus do so asychronously, using callbacks. - -:: - - # in command class func() - def callback(ret, caller): - caller.msg("Returned is %s" % ret) - deferred = self.execute_command("longrunning") - deferred.addCallback(callback, self.caller) - -This is probably not relevant to any but the most advanced/exotic -designs (one might use it to create a "nested" command structure for -example). - -The ``save_for_next`` class variable can be used to implement -state-persistent commands. For example it can make a command operate on -"it", where it is determined by what the previous command operated on. diff --git a/docs/sphinx/source/wiki/Communications.rst b/docs/sphinx/source/wiki/Communications.rst index e219ab368a..3c473e2db2 100644 --- a/docs/sphinx/source/wiki/Communications.rst +++ b/docs/sphinx/source/wiki/Communications.rst @@ -7,7 +7,9 @@ system. Stock evennia implements a 'MUX-like' system of channels, but there is nothing stopping you from changing things to better suit your taste. -Comms rely on two main database objects - ``Msg`` and ``Channel``. +Comms rely on two main database objects - ``Msg`` and ``Channel``. There +is also the ``TempMsg`` which mimics the API of a ``Msg`` but has no +connection to the database. Msg --- @@ -67,10 +69,15 @@ next section). Channels -------- +Channels are `Typeclassed `_ entities, which mean they +can be easily extended and their functionality modified. To change which +channel typeclass Evennia uses, change +settings.BASE\_CHANNEL\_TYPECLASS. + 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 +list of "listening" objects and any ``Msg`` (or ``TempMsg`` 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. @@ -92,11 +99,12 @@ 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. +The argument ``msgobj`` can be either a string, a previously constructed +``Msg`` or a ``TempMsg`` - in the latter cases 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. Default is to use ``TempMsg`` for +channel communication (i.e. not save everything to the database). :: @@ -108,18 +116,13 @@ or not. # use the Msg object directly, no other keywords are needed mychan.msg(mymsg) - # create a Msg automatically behind the scenes + # Send a non-persistent message to a channel mychan.msg("Hello!", senders=[sender]) - # 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) + # send a message to list, save it to the database + # (note how the senders keyword can also be used + # without a list if there is only one sender) + mychan.msg("Hello!", senders=sender, persistent=True) On a more advanced note, when a player enters something like ``ooc Hello!`` (where ``ooc`` is the name/alias of a channel), this is diff --git a/docs/sphinx/source/wiki/Contributing.rst b/docs/sphinx/source/wiki/Contributing.rst index 23d2d9de62..33a3a25c81 100644 --- a/docs/sphinx/source/wiki/Contributing.rst +++ b/docs/sphinx/source/wiki/Contributing.rst @@ -58,6 +58,12 @@ do this once): Once you have an online clone and a local copy of it: +#. Make sure that you have edited Mercurial's config file (``hgrc``) and + under the header ``[ui]`` added the line + ``username=Yourname ``. This is important for proper + crediting and eventual conversions. See the first point of the + `Mercurial + Quickstart `_. #. Code away on your computer, fixing bugs or whatnot (you can be offline for this). Commit your code to your local clone as you work, as often as you like. There are some suggestions for setting up a diff --git a/docs/sphinx/source/wiki/DefaultCommandHelp.rst b/docs/sphinx/source/wiki/DefaultCommandHelp.rst index 1b4679c7b6..f45a128dc2 100644 --- a/docs/sphinx/source/wiki/DefaultCommandHelp.rst +++ b/docs/sphinx/source/wiki/DefaultCommandHelp.rst @@ -13,21 +13,21 @@ The commands that make up the default [Commands#Command\_Sets command set] are divided into three sub-sets after which objects they are defined on. -- An *OOC Command* is a command in the OOCCmdset, available only on - Players, not on Objects/Characters. Since Players control Characters, - the OOC and default cmdset are normally merged together and the - difference is not noticeable. Use e.g. the ``@ooc`` command to - disconnect from the current character and see only the OOC cmdset. - Same-keyed command on the Character has higher priority than its OOC - equivalent, allowing to overload the OOC commands on a per-Character - basis. +- A *Player Command* is a command in the PlayerCmdset, available only + on Players, not on Objects/Characters. Since Players control + Characters, the Player and Character cmdsets are normally merged + together and the difference is not noticeable. Use e.g. the ``@ooc`` + command to disconnect from the current character and see only the + Player cmdset. Same-keyed command on the Character has higher + priority than its Player equivalent, allowing to overload the OOC + commands on a per-Character basis. - An *Unloggedin Command* sits in UnloggedinCmdset. They are specific to the login screen, before the session (User) has authenticated. - All other commands are *On-Character* commands, commands defined in - DefaultCmdset and available in the game. + CharacterCmdset and available in the game. The full set of available commands (all three sub-sets above) currently -contains 85 commands in 6 categories. More information about how +contains 92 commands in 6 categories. More information about how commands work can be found in the `Command `_ documentation. @@ -108,8 +108,8 @@ module `` @@ -137,7 +137,7 @@ module `_ = ``cmd:perm(emit) or perm(Builders)`` - `help\_category `_ = ``Admin`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -214,8 +214,8 @@ module `` @@ -226,7 +226,7 @@ module = @@ -319,7 +319,7 @@ module `_ = ``cmd:perm(batchcommands) or superuser()`` - `help\_category `_ = ``Building`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -384,8 +384,8 @@ module `` -- `locks `_ = ``cmd:perm(debug) or perm(Builders)`` -- `help\_category `_ = ``Building`` -- [`HelpSystem `_\ #Auto-help\_system Auto-help] - (``__doc__ string``) = - -:: - - Debug game entities - - Usage: - @debug[/switch] - - Switches: - obj - debug an object - script - debug a script - - Examples: - @debug/script game.gamesrc.scripts.myscript.MyScript - @debug/script myscript.MyScript - @debug/obj examples.red_button.RedButton - - This command helps when debugging the codes of objects and scripts. - It creates the given object and runs tests on its hooks. - - @desc ~~~~~ @@ -510,7 +481,7 @@ module `_ = ``cmd:perm(destroy) or perm(Builders)`` - `help\_category `_ = ``Building`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -524,8 +495,8 @@ module `_ = ``cmd:perm(examine) or perm(Builders)`` +- `help\_category `_ = ``Building`` +- [`HelpSystem `_\ #Auto-help\_system Auto-help] + (``__doc__ string``) = + +:: + + examine - detailed info on objects + + Usage: + examine [[/attrname]] + examine [*[/attrname]] + + Switch: + player - examine a Player (same as adding *) + + The examine command shows detailed game info about an + object and optionally a specific attribute on it. + If object is not specified, the current location is examined. + + Append a * before the search string to examine a player. + + + +@examine (Player command) +~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ``key`` = ``@examine`` +- ``aliases`` = ``examine, @ex, ex, exam`` - `locks `_ = ``cmd:perm(examine) or perm(Builders)`` - `help\_category `_ = ``Building`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -586,7 +588,6 @@ module `_ = ``cmd:perm(find) or perm(Builders)`` - `help\_category `_ = ``Building`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -621,7 +622,8 @@ module `` +- ``aliases`` = ``@sethome`` - `locks `_ = ``cmd:perm(@home) or perm(Builders)`` - `help\_category `_ = ``Building`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -713,15 +715,16 @@ module is an exit, set its destination to . Two-way operation instead sets the destination to the *locations* of the respective given arguments. - The second form (a lone =) sets the destination to None (same as the @unlink command) - and the third form (without =) just shows the currently set destination. + The second form (a lone =) sets the destination to None (same as + the @unlink command) and the third form (without =) just shows the + currently set destination. @lock ~~~~~ - ``key`` = ``@lock`` -- ``aliases`` = ``@locks, lock, locks`` +- ``aliases`` = ``lock, @locks, locks`` - `locks `_ = ``cmd: perm(@locks) or perm(Builders)`` - `help\_category `_ = ``Building`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -782,8 +785,8 @@ module / = @set / = @set / + @set */attr = Sets attributes on objects. The second form clears a previously set attribute while the last form @@ -889,7 +894,8 @@ module `` +- `locks `_ = ``cmd:perm(tag) or perm(Builders)`` +- `help\_category `_ = ``Building`` +- [`HelpSystem `_\ #Auto-help\_system Auto-help] + (``__doc__ string``) = + +:: + + handles tagging + + Usage: + @tag[/del] [= [:]] + @tag/search + + Switches: + search - return all objects + del - remove the given tag. If no tag is specified, + clear all tags. + + Manipulates and lists tags on objects. Tags allow for quick + grouping of and searching for objects. If only is given, + list all tags on the object. If /search is used, list objects + with the given tag. + The category can be used for grouping tags themselves. + + @tel ~~~~ @@ -910,19 +946,29 @@ module =] + @tel/switch [ =] + + Examples: + @tel Limbo + @tel/quiet box Limbo + @tel/tonone box Switches: quiet - don't echo leave/arrive messages to the source/target locations for the move. intoexit - if target is an exit, teleport INTO the exit object instead of to its destination + tonone - if set, teleport the object to a None-location. If this + switch is set, is ignored. + Note that the only way to retrieve + an object from a None location is by direct #dbref + reference. - Teleports an object or yourself somewhere. - + Teleports an object somewhere. If no object is given, you yourself + is teleported to the target location. @tunnel ~~~~~~~ @@ -1055,8 +1101,8 @@ Comms `Link to Python module `_ -@cboot (OOC command) -~~~~~~~~~~~~~~~~~~~~ +@cboot (Player command) +~~~~~~~~~~~~~~~~~~~~~~~ - ``key`` = ``@cboot`` - ``aliases`` = ```` @@ -1079,8 +1125,8 @@ module `` @@ -1120,8 +1166,8 @@ module `` @@ -1140,8 +1186,8 @@ module `_ = ``cmd: not pperm(channel_banned)`` - `help\_category `_ = ``Comms`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -1188,15 +1234,16 @@ module `` - `locks `_ = ``cmd:not pperm(channel_banned)`` - `help\_category `_ = ``Comms`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -1204,17 +1251,17 @@ module [= ] + @clock [= ] Changes the lock access restrictions of a channel. If no lockstring was given, view the current lock definitions. -@cwho (OOC command) -~~~~~~~~~~~~~~~~~~~ +@cwho (Player command) +~~~~~~~~~~~~~~~~~~~~~~ - ``key`` = ``@cwho`` - ``aliases`` = ```` @@ -1233,8 +1280,8 @@ module `` @@ -1266,11 +1313,11 @@ module `_ = ``cmd: serversetting(IMC2_ENABLED) and pperm(Wizards)`` - `help\_category `_ = ``Comms`` @@ -1296,8 +1343,8 @@ module `` @@ -1315,22 +1362,24 @@ module = <#irchannel> Switches: - /disconnect - this will delete the bot and remove the irc connection to the channel. + /disconnect - this will delete the bot and remove the irc connection + to the channel. /remove - " /list - show all irc<->evennia mappings Example: @irc2chan myircchan = irc.dalnet.net 6667 myevennia-channel evennia-bot - This creates an IRC bot that connects to a given IRC network and channel. It will - relay everything said in the evennia channel to the IRC channel and vice versa. The - bot will automatically connect at server start, so this comman need only be given once. - The /disconnect switch will permanently delete the bot. To only temporarily deactivate it, - use the @services command instead. + This creates an IRC bot that connects to a given IRC network and channel. + It will relay everything said in the evennia channel to the IRC channel and + vice versa. The bot will automatically connect at server start, so this + comman need only be given once. The /disconnect switch will permanently + delete the bot. To only temporarily deactivate it, use the {w@services{n + command instead. -@rss2chan (OOC command) -~~~~~~~~~~~~~~~~~~~~~~~ +@rss2chan (Player command) +~~~~~~~~~~~~~~~~~~~~~~~~~~ - ``key`` = ``@rss2chan`` - ``aliases`` = ```` @@ -1348,23 +1397,25 @@ module = Switches: - /disconnect - this will stop the feed and remove the connection to the channel. + /disconnect - this will stop the feed and remove the connection to the + channel. /remove - " /list - show all rss->evennia mappings Example: @rss2chan rsschan = http://code.google.com/feeds/p/evennia/updates/basic - This creates an RSS reader that connects to a given RSS feed url. Updates will be - echoed as a title and news link to the given channel. The rate of updating is set - with the RSS_UPDATE_INTERVAL variable in settings (default is every 10 minutes). + This creates an RSS reader that connects to a given RSS feed url. Updates + will be echoed as a title and news link to the given channel. The rate of + updating is set with the RSS_UPDATE_INTERVAL variable in settings (default + is every 10 minutes). - When disconnecting you need to supply both the channel and url again so as to identify - the connection uniquely. + When disconnecting you need to supply both the channel and url again so as + to identify the connection uniquely. -addcom (OOC command) -~~~~~~~~~~~~~~~~~~~~ +addcom (Player command) +~~~~~~~~~~~~~~~~~~~~~~~ - ``key`` = ``addcom`` - ``aliases`` = ``aliaschan, chanalias`` @@ -1386,8 +1437,8 @@ addcom (OOC command) aliases to an already joined channel. -allcom (OOC command) -~~~~~~~~~~~~~~~~~~~~ +allcom (Player command) +~~~~~~~~~~~~~~~~~~~~~~~ - ``key`` = ``allcom`` - ``aliases`` = ```` @@ -1410,8 +1461,8 @@ allcom (OOC command) Without argument, works like comlist. -delcom (OOC command) -~~~~~~~~~~~~~~~~~~~~ +delcom (Player command) +~~~~~~~~~~~~~~~~~~~~~~~ - ``key`` = ``delcom`` - ``aliases`` = ``delaliaschan, delchanalias`` @@ -1432,11 +1483,11 @@ delcom (OOC command) unsubscribe. -imctell (OOC command) -~~~~~~~~~~~~~~~~~~~~~ +imctell (Player command) +~~~~~~~~~~~~~~~~~~~~~~~~ - ``key`` = ``imctell`` -- ``aliases`` = ``imcpage, imc2tell, imc2page`` +- ``aliases`` = ``imc2tell, imc2page, imcpage`` - `locks `_ = ``cmd: serversetting(IMC2_ENABLED)`` - `help\_category `_ = ``Comms`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -1454,8 +1505,8 @@ imctell (OOC command) over IMC2. -page (OOC command) -~~~~~~~~~~~~~~~~~~ +page (Player command) +~~~~~~~~~~~~~~~~~~~~~ - ``key`` = ``page`` - ``aliases`` = ``tell`` @@ -1487,9 +1538,55 @@ General `Link to Python module `_ -@encoding (OOC command) +@charcreate (Player command) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ``key`` = ``@charcreate`` +- ``aliases`` = ```` +- `locks `_ = ``cmd:all()`` +- `help\_category `_ = ``General`` +- [`HelpSystem `_\ #Auto-help\_system Auto-help] + (``__doc__ string``) = + +:: + + Create a character + + Usage: + @charcreate [= desc] + + Create a new character, optionally giving it a description. You + may use upper-case letters in the name - you will nevertheless + always be able to access your character using lower-case letters + if you want. + + +@color (Player command) ~~~~~~~~~~~~~~~~~~~~~~~ +- ``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 (Player command) +~~~~~~~~~~~~~~~~~~~~~~~~~~ + - ``key`` = ``@encoding`` - ``aliases`` = ``@encode`` - `locks `_ = ``cmd:all()`` @@ -1508,18 +1605,20 @@ module can be - any in-game object as long as you have access right to puppet it. + the right to do so. Note that it's the PLAYER character that puppets + characters/objects and which needs to have the correct permission! + + You cannot become an object that is already controlled by another + player. In principle can be any in-game object as long + as you the player have access right to puppet it. -@ooc (OOC command) -~~~~~~~~~~~~~~~~~~ +@ooc (Player command) +~~~~~~~~~~~~~~~~~~~~~ - ``key`` = ``@ooc`` - ``aliases`` = ``@unpuppet`` @@ -1555,7 +1657,7 @@ module `` @@ -1585,8 +1687,57 @@ module `_ = ``cmd:perm(listplayers) or perm(Wizards)`` +- `help\_category `_ = ``General`` +- [`HelpSystem `_\ #Auto-help\_system Auto-help] + (``__doc__ string``) = + +:: + + @players - give a summary of all registed Players + + Usage: + @players [nr] + + Lists statistics about the Players registered with the game. + It will list the amount of latest registered players + If not given, defaults to 10. + + +@quell (Player command) +~~~~~~~~~~~~~~~~~~~~~~~ + +- ``key`` = ``@quell`` +- ``aliases`` = ``@unquell`` +- `locks `_ = ``cmd:all()`` +- `help\_category `_ = ``General`` +- [`HelpSystem `_\ #Auto-help\_system Auto-help] + (``__doc__ string``) = + +:: + + Quelling permissions + + Usage: + quell + unquell + + Normally the permission level of the Player is used when puppeting a + Character/Object to determine access. This command will switch the lock + system to make use of the puppeted Object's permissions instead. This is + useful mainly for testing. + Hierarchical permission quelling only work downwards, thus a Player cannot + use a higher-permission Character to escalate their permission level. + Use the unquell command to revert back to normal operation. + + +@quit (Player command) +~~~~~~~~~~~~~~~~~~~~~~ - ``key`` = ``@quit`` - ``aliases`` = ```` @@ -1602,14 +1753,18 @@ module `_ = ``cmd:all()`` - `help\_category `_ = ``General`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -1668,6 +1823,27 @@ get your inventory. +give +~~~~ + +- ``key`` = ``give`` +- ``aliases`` = ```` +- `locks `_ = ``cmd:all()`` +- `help\_category `_ = ``General`` +- [`HelpSystem `_\ #Auto-help\_system Auto-help] + (``__doc__ string``) = + +:: + + give away things + + Usage: + give = + + Gives an items from your inventory to another character, + placing it in their inventory. + + help ~~~~ @@ -1691,8 +1867,8 @@ help topics related to the game. -help (OOC command) -~~~~~~~~~~~~~~~~~~ +help (Player command) +~~~~~~~~~~~~~~~~~~~~~ - ``key`` = ``help`` - ``aliases`` = ```` @@ -1738,7 +1914,7 @@ inventory ~~~~~~~~~ - ``key`` = ``inventory`` -- ``aliases`` = ``inv, i`` +- ``aliases`` = ``i, inv`` - `locks `_ = ``cmd:all()`` - `help\_category `_ = ``General`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -1777,8 +1953,8 @@ look Observes your location or objects in your vicinity. -look (OOC command) -~~~~~~~~~~~~~~~~~~ +look (Player command) +~~~~~~~~~~~~~~~~~~~~~ - ``key`` = ``look`` - ``aliases`` = ``l, ls`` @@ -1794,18 +1970,14 @@ look (OOC command) Usage: look - This is an OOC version of the look command. Since a - Player doesn't have an in-game existence, there is no - concept of location or "self". If we are controlling - a character, pass control over to normal look. - + Look in the ooc state. nick ~~~~ - ``key`` = ``nick`` -- ``aliases`` = ``nickname, nicks, @nick, alias`` +- ``aliases`` = ``@nick, nicks, nickname, alias`` - `locks `_ = ``cmd:all()`` - `help\_category `_ = ``General`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -1890,8 +2062,8 @@ say Talk to those in your current location. -who -~~~ +who (Player command) +~~~~~~~~~~~~~~~~~~~~ - ``key`` = ``who`` - ``aliases`` = ``doing`` @@ -1942,7 +2114,7 @@ module `_ = ``cmd:perm(listobjects) or perm(Builders)`` - `help\_category `_ = ``System`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -1950,7 +2122,7 @@ module ] @@ -1977,6 +2149,9 @@ module + Switch: + time - output an approximate execution time for + Separate multiple commands by ';'. A few variables are made available for convenience in order to offer access to the system (you can import more at execution time). @@ -1987,13 +2162,54 @@ module `_ = ``cmd:perm(py) or perm(Immortals)`` +- `help\_category `_ = ``System`` +- [`HelpSystem `_\ #Auto-help\_system Auto-help] + (``__doc__ string``) = + +:: + + Execute a snippet of python code + + Usage: + @py + + Switch: + time - output an approximate execution time for + + Separate multiple commands by ';'. A few variables are made + available for convenience in order to offer access to the system + (you can import more at execution time). + + Available variables in @py environment: + self, me : caller + here : caller.location + ev : the evennia API + inherits_from(obj, parent) : check object inheritance + + You can explore The evennia API from inside the game by calling + ev.help(), ev.managers.help() etc. + + {rNote: In the wrong hands this command is a severe security risk. + It should only be accessible by trusted server admins/superusers.{n + + + +@reload (Player command) +~~~~~~~~~~~~~~~~~~~~~~~~ - ``key`` = ``@reload`` - ``aliases`` = ```` @@ -2007,15 +2223,15 @@ module `_ = ``cmd:perm(listscripts) or perm(Wizards)`` - `help\_category `_ = ``System`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -2127,16 +2343,19 @@ module `` @@ -2172,7 +2391,7 @@ module `_ = ``cmd:all()`` - `help\_category `_ = ``Unloggedin`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -2214,16 +2433,19 @@ connect (Unloggedin command) Connect to the game. Usage (at login screen): - connect + connect playername password + connect "player name" "pass word" 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`` = ``cre, cr`` +- ``aliases`` = ``cr, cre`` - `locks `_ = ``cmd:all()`` - `help\_category `_ = ``Unloggedin`` - [`HelpSystem `_\ #Auto-help\_system Auto-help] @@ -2234,10 +2456,12 @@ create (Unloggedin command) Create a new account. Usage (at login screen): - create "playername" + create + create "player name" "pass word" 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/DeveloperCentral.rst b/docs/sphinx/source/wiki/DeveloperCentral.rst index 76a7a15214..10f31665bf 100644 --- a/docs/sphinx/source/wiki/DeveloperCentral.rst +++ b/docs/sphinx/source/wiki/DeveloperCentral.rst @@ -26,6 +26,7 @@ General Evennia development information - `Setting up a Mercurial environment for coding `_ - `Planning your own Evennia game `_ +- `First steps coding Evennia `_ Evennia Component Documentation ------------------------------- @@ -34,21 +35,25 @@ Evennia Component Documentation - `Directory Overview `_ - `Portal and Server `_ +- `Session `_ - `Commands `_ - `Typeclass system `_ - `Objects `_ - `Scripts `_ - `Players `_ + - [Communications#Channels Channels] - `Attributes `_ - `Locks and Permissions `_ - `Communications `_ - `Help System `_ - `Nicks `_ +- `Tags `_ - `Sessions and Protocols `_ - `Caches `_ - `Web features `_ +- `Out-of-band communication `_ - `Configuration and module plugins `_ Programming Evennia @@ -59,6 +64,7 @@ Programming Evennia - `Useful coding utilities `_ - `Running and writing unit tests for Evennia `_ - `Running processes asynchronously `_ +- `Expanding Evennia with new database models `_ Work in Progress - Developer brainstorms and whitepages ------------------------------------------------------- @@ -70,6 +76,5 @@ the road.* - `Basic game system implementation `_ (inactive) - `Rtclient protocol `_ (deprecated) -- `Summary of changes `_ of latest version vs old - Evennia (implemented in aug2010) +- `Change log `_ of big Evennia updates over time diff --git a/docs/sphinx/source/wiki/DirectoryOverview.rst b/docs/sphinx/source/wiki/DirectoryOverview.rst index 6764fb8726..473bcb765d 100644 --- a/docs/sphinx/source/wiki/DirectoryOverview.rst +++ b/docs/sphinx/source/wiki/DirectoryOverview.rst @@ -188,6 +188,7 @@ or features missing, file a bug report or send us a message. players/ scripts/ server/ + portal/ typeclasses/ utils/ web/ @@ -277,10 +278,10 @@ connection timeouts) are also defined here. ~~~~~~~~~~~~~~~ This directory is the heart of Evennia. It holds the server process -itself (started from ``game/evennia.py``), the portal and all `sessions -and protocols `_ that allow users to connect to -the game. It also knows how to store dynamic server info in the -database. +itself (started from ``game/evennia.py``). Its subfolder ``portal/`` +holds the portal and all `sessions and +protocols `_ that allow users to connect to the +game. \`src/typeclasses/\` ~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/sphinx/source/wiki/EvenniaDevel.rst b/docs/sphinx/source/wiki/EvenniaDevel.rst index 8278cc2bc0..a79398b9a5 100644 --- a/docs/sphinx/source/wiki/EvenniaDevel.rst +++ b/docs/sphinx/source/wiki/EvenniaDevel.rst @@ -1,373 +1,149 @@ -*Note: The devel branch merged with trunk as of r970 (aug2010). So if -you are new to Evennia, this page is of no real interest to you.* +This page serves as a changelog of the various bigger updates of Evennia +over time. + +Devel-clone as of October 2013 +============================== + +*This update focused on moving the webserver into Server as well as +functioning OOB and reworked Attributes and Tags. Channels became +Typeclassed.* + +*This clone has **not** yet merged with main. This text is copied from +the mailing list post.* + +New features +------------ + +These are features that either don't affect existing APIs or introduce +new, non-colliding ones. + +- The webserver was moved from Portal into Server, for reasons outlined + in `earlier + posts `_. +- Out-Of-Band (OOB) functionality. This uses the MSDP protocol to + communicate with supported third-party clients (the webclient does + not currently support OOB). The new OOBhandler supports tracking of + variables and most of the default commands recommended by the MSDP + protocol. GMCP support is not part of this update. From the API side, + it means the msg() method have a new keyword 'oob', such as + msg(oob=("send",{"key":"val"}) +- Comm Channels are now Typeclassed entities. This means they can be + customized much more than before using hooks and inheritance. + src.comms.comms.py contains the new default channel typeclass and + hooks. Settings. DEFAULT\_COMM\_TYPECLASS define the default + typeclass. +- Most database field wrappers have been moved into the + SharedMemoryObject metaclass. This makes the handling of database + fields consistent and also makes the source code of models + considerably shorter with less boiler plate. All database fields are + updated individually now instead of having to save the entire + database object every time a field changes. The API is otherwise + unchanged - you still use obj.key="name" to save to the obj.db\_key + database field, for example. A new feature is that you can now give + dbrefs to fields holding objects in order to store that object in the + field. So self.location = "#44" should work. +- Attributes have three new fields: data, strvalue and category. All + are optional. The first can be used for arbitrary string data (it is + used by nick for the nick replacement). The second field, strvalue, + is used for storing a value known to always be a string (as opposed + to the normal value field which is pickled). This offers easier + optimization and makes Attributes useful for more things. Category + can be used to group Attributes (for example when they are used as + Nicks by the nickhandler). Normal operations are not affected. + Attributes are also now stored as a m2m fields on objects rather than + via a reverse lookup. +- obj.tags is a new handler on all typeclassed objects. A Tag is unique + and indexed and can be attached to any number of objects. It allows + to tag and group any entity/entities for quick lookup later. Like all + handlers you use get/add/remove/clear/all to manipulate tags. +- obj.nicks works similarly to before but it uses Attributes under the + hood (using strvalue and data fields for nick replacement and + category to determine which type of replacement to do). +- Sessions can also have their own cmdsets when the player has logged + in. + +There are a few other new settings in settings\_default, notably related +to OOB and caching. + +- New, reworked cache system. + +Deprecations +------------ + +These are features that have changed but where the old way still works - +for now. + +- Attributes are handled by the attributehandler (obj.attributes or + obj.db), which means that the old on-object methods are all + deprecated. Use of an deprecated method will result in a + DeprecationWarning in your log. Note that obj.db works the same as + before, it can (and should) replace all of these unless you are + looking to operate on an Attribute you don't know the name of before + execution. + + - obj.has\_attribute(attrname) -> obj.attributes.has(attrname) + - obj.get\_attribute(attrname) -> obj.attributes.get(attrname) + - obj.set\_attribute(attrname, value) -> + obj.attributes.add(attrname, value) + - obj.del\_attribute(attrname) -> obj.attributes.remove(attrname). + There is also obj.attributes.clear() to remove all Attributes from + obj. + - obj.get\_all\_attributes() -> obj.attributes.all() + - obj.secure\_attr(attrname) -> obj.attributes.get(attrname, + accessing\_obj=aobj, default\_access=True). The new + get/set/remove/clear/all methods have these optional keywords to + turn it into an access check. Setting default\_access=False will + fail the check if no accessing\_obj is given. + - obj.attr() - this was just a wrapper for the above commands, use + the new ones instead. + - obj.nattr() is replaced by the obj.nattributes handler instead. + obj.ndb works the same as before. + +The usage of Aliases as 'tags' alluded to in the tutorials (e.g. for +zones) should now be handled by Tags instead, they are intended for this +purpose. + +Incompatibilities +----------------- + +These are features/APIs that have changed to behave differently from +before. Using the old way will lead to errors. + +- Minimum Django version was upped from 1.4 to 1.5. +- User+PlayerDB -> PlayerDB. This means that + django.contrib.auth.models.User is no longer used and all references + to it should be changed to src.players.models.PlayerDB, which now + holds all authorization information for a player account. Note that + not all 3rd party Django apps have yet updated to allow a custom + User-model. So there may be issues there (one such app known to have + issues is DjangoBB). +- msg(text, data=None) has changed its API to + ``msg(text=None, ``\ args, + ****\ kwargs)\ ``. This makes no difference for most calls (basically anything just sending text). But if you used protocol options, such as msg(text,data={"raw":True}) you should now instead use msg(text, raw=True). * obj.permissions="perm" used to add "perm" to a hidden list of permissions behind the scenes. This no longer works since permissions is now a full handler and should be called like this: obj.permissions.set("perm"). The handler support the normal get/add/remove/all as other handlers. Permissions now use Tags under the hood. * obj.aliases="alias" used to add 'alias' to a hidden handler. This no longer works as obj.aliases is now a full handler: obj.aliases.set("alias"). This works like other handlers. Aliases now use Tags under the hood. * All portal-level modules have moved from being spread out all over src.server into a new sub-folder src.server.portal. Change your imports as required. * The default search/priority order for cmdsets have changed now that Sessions may also have cmdsets. Cmdsets are merged in the order session-player-puppet, which means that the puppet-level cmdset will default to overiding player-level cmdsets which in turn overrides session-level ones. * Messages (using the msg() method) used to relay data puppet->player->session. Now, puppet-level relays data directly to the session level, without passing the player-level. This makes it easier to customize msg at each respective level separately, but if you overloaded player.msg() with the intent to affect all puppeted objects, you need to change this. * If you used src.server.caches for anything (unlikely if you are not a core dev), the APIs of that has changed a lot. See that module. == Known Issues == * Whereas this merge will resolve a number of Issues from the list, most fixed ones will be feature requests up to this point. There are many known Issues which have not been touched. Some may be resolved as a side effect of other changes but many probably won't. This will come gradually. The wiki is of course also not updated yet, this will likely not happen until after this clone has been merged into main branch. For now, if you have usage questions, ask them here or on IRC. = Devel clone as of May 2013 = _This update centered around making a player able to control multiple characters at the same time (the multplayer_mode=2 feature)._ _ This clone was merged with main branch. This text is copied from the mailing list post._ == Things you have to update manually:== If you have partially overloaded and import the default cmdsets into game/gamesrc, you have to update to their new names and locations: * src.commands.default.cmdset_default.DefaultCmdSet changed name to src.commands.default.cmdset_character.CharacterCmdSet * src.commands.default.cmdset_ooc.OOCCmdSet changed name to src.commands.default.cmdset_player.PlayerCmdSet (in the same way ev.default_cmds now holds CharacterCmdSet and PlayerCmdSet instead of the old names) Note that if you already named your own cmdset class differently and have objects using those cmdsets in the database already, you should keep the old name for your derived class so as to not confuse existing objects. Just change the imports. The migrations will detect if any objects are using the old defaults and convert them to the new paths automatically. Also the settings file variable names have changed: * settings.CMDSET_DEFAULT has changed to settings.CMDSET_CHARACTER * settings.CMDSET_OOC has changed to settings.CMDSET_PLAYER The system will warn you at startup if your settings file contains the old names. If you have extensively modified Object Typeclasses, you need to update your hooks: * obj.at_first_login(), at_pre_login(), at_post_login() and at_disconnect() are removed. They no longer make sense since the Player is no longer auto-tied to a Character (except in MULTISESSION_MODE=0 and 1 where this is retained as a special case). All "first time" effects and "at login" effects should now only be done on the same-named hooks on the Player, not on the Character/Object. * New hooks on the Object are obj.at_pre_puppet(player), at_post_puppet(), at_pre_unpuppet() and at_post_unpuppet(player). These are now used for effects involving the Character going "into" the game world. So the default move from a None-location (previously in at_pre_login()) is now located in at_pre_puppet() instead and will trigger when the Player connects/disconnects to/from the Object/Character only. The Permission Hierarchy lock function (perm) has changed in an important way: * Previously, the perm() lock function checked permission only on the Character, even if a Player was connected. This potentially opens up for escalation exploits and is also rather confusing now that the Player and Character is more decoupled (which permission is currently used?) * perm() now checks primarily the Player for a hierarchy permission (Players, Builders, Admins etc, the stuff in settings.PERMISSION_HIERARCHY). Other types of permissions (non-hierarchical) are checked first against Player and then, if the Player does not have it, on the Character. * The @quell command was moved from a contrib into the main distribution. It allows Players to force hierarchical permission checks to only take the currently puppeted Character into account and not the Player. This is useful for staff testing features with lower permissions than normal. Note that one can only downgrade one's Player permission this way - this avoids Player's escalating their permissions through controlling a high-perm Character. Superusers can never be quelled, same as before. This is not a show-stopper, but nevertheless an important change: * settings.ALLOW_MULTISESSION was removed and is now replaced with MULTISESSION_MODE which can have a value of 0, 1 or 2. == Other Changes to be aware of== * Many-Characters-per-Player multisession mode. See the previous post here. * Player.character does still exist for backwards compatability but it is now only valid in MULTISESSION_MODE 0 or 1. Also this link will be meaninless when the Player goes OOC - the Player-Object link is now completely severed (before it remained). For MULTISESSION_MODE=2, you must use Player.get_character(sessid). See src.commands.default.player.py for details on how to get the Character now. * The @ic and @ooc and @ooclook commands use an Attribute ``\ \_playable\_characters\ `` to store a list of "your" characters. This is not hard-coded but only used by those commands. This is by default only used for listing convenience - locks are now the only thing blocking other users from puppeting your characters when you are not around. Keeping a list like this is now the only safe way to relate Characters with a given Player when that Player is offline. * Character typeclass has new hooks at_pre_puppet * ObjectDB.search() has a changed api: search(ostring, global_search=False, use_nicks=False, typeclass=None, location=None, attribute_name=None, quiet=False, exact=False. The changes here are the removal of the global_dbref keyword and that ignore_errors keyword was changed to quiet. More importantly the search function now always only return Objects (it could optionally return Players before). This means it no longer accepts the ``\ **playername\ `` syntax out of the box. To search for Players, use src.utils.search.player_search (you can always look for the asterisk manually in the commands where you want it). This makes the search method a lot more streamlined and hopefully consistent with expectations. * object.player is now only defined when the Player is actually online (before the connection would remain also when offline). Contrary to before it now always returns a Player typeclass whenever it's defined (Issue 325) * object.sessid is a new field that is always set together with character.player. * object.msg() has a new api: msg(self, message, from_obj=None, data=None, sessid=0). In reality this is used mostly the same as before unless wanting to send to an unexpected session id. Since the object stores the sessid of the connected Player's session, leaving the keywords empty will populate them with sensible defaults. * player.msg() also has changed: msg(self, outgoing_string, from_obj=None, data=None, sessid=None). The Player cannot easily determine the valid sessid on its own, so for Player commands, the sessid needs to be supplied or the msg will go to all sessions connected to the Player. In practice however, one uses the new Command.msg wrapper below: * command.msg is a new wrapper. It's call api looks like this: msg(self, msg="", to_obj=None, from_obj=None, data=None, sessid=Noneall_sessions=False). This will solve the problem of having to remember any sessids for Player commands, since the command object itself remembers the sessid of its caller now. In a Player command, just use self.msg(string). To clarify, this is just a convenience wrapper instead of calling self.caller.msg(string, sessid=self.sessid) - that works identically but is a little more to write. * The prettytable module is now included with Evennia. It was modified to handle Evennia's special ANSI color markers and is now the recommended way to output good-looking ASCII tables over using the old src.utils.format_table (which is still around) == Other changes == * New internal Attribute storage, using PickledFields rather than a custom solution; this now also allows transparent lookups of Attribute data directly on the database level (you could not do this (easily) before since the data is internally pickled). * Updated all unittests to cover the default commands again, also with a considerably speedup. * Plenty of cleanups and bug fixes all over * Removed several deprecation warnings from moving to Django 1.4+ and a few others. * Updated all examples in game/gamesrc and the various APIs = Status update as of December 2012 = _Mostly bug fixes and various cleanup this update. This is copied from the mailing list post._ Latest pushes to the repository fixes a few things in the Tutorial world. Notably the torch/splinter will light properly again now - which means you will be not be forever entombed under ground. Also I sometimes found that I couldn't solve the final puzzle. This is now fixed and you will now again be able to finish your quest by wreaking some well-deserved vengeance on that pesky Ghostly Apparition. I hadn't looked at the tutorial in a while which revealed a bunch of other small inconsistencies in how the Character was cleaned up afterwards, as well as some other small things, all now fixed. The tutorial world is meant to be a nice first look into what Evennia can do, so if you do come across further strangeness in it, don't be shy to report it. Also, it may be worth lingering on the west half of the swaying bridge longer than you should, just to see what happens. In other news, there is now a "give" command in the default cmdset; it's very simple (for example the receiver have no choice but to accept what is given to them) but it helped debug the Tutorial world and is a neat command to build from anyway. If you didn't notice, the latest changes places more strict regulation on how to reference database references from the default cmdset. Before you could do things like "ex 2" and expect to get Limbo. You will now have to do "ex #2", allowing objects to have numbered names as well (this was a feature request). The upshot is that the explicit dbref-search can be made global whereas key-searches can remain local. This is handled by a new keyword to object.search called "global_dbref". This means you can do things like "ex #23" and examine the object with dbref=23 wherever it is in the game. But you can also do "ex north" and not get a multi-match for every north exit in the game, but only the north in your current location. Thanks to Daniel Benoy for the feature request suggesting this. There might be more build commands were this is useful, they will be updated as I come across them or people report it. = Status update as of October 2011 = _This was an update related to the changes to persistence and other things on the docket. This text is copied from the mailing list post._ Here are some summaries of what's going on in the Evennia source at the moment: ==Admin interface == The admin interface backend is being revamped as per issue 174. Interface is slowly getting better with more default settings and some pointless things being hidden away or given more sensible labels. It's still rough and some things, like creating a new Player is hardly intuitive yet (although it does work, it requires you to create three separate models (User-Player-Character) explicitly at this point). I'm also seeing a bunch of formatting errors under django1.3, not sure if this is media-related or something fishy with my setups, not everyone seems to see this (see issue 197 if you want to help test). ==FULL_PERSISTENCE setting== ... is no more. FULL_PERSISTENCE=True is now always in effect. The feature to activate this setting was added at a time when the typeclass system's caching mechanism was, to say the least, wasteful. This meant that many problems with FULL_PERSISTENCE=False were hidden (it "just worked" and so was an easy feature to add). This is no longer the case. It's not worth the effort to support the False setting in parallel. Like before you can still assign non-persistent data by use of the ndb operator. ==Typeclass handling== Typeclasses are handled and managed and cached in a better way. Object.typeclass now actually returns the full instantiated typeclass object, not its class like before (you had to manually initiate it like dbobj.typeclass(dbobj)). The main reason for this change is that the system now allows very efficient calls to hook methods. The at_init() hook will now be called whenever any object is inititated - and it's very efficient; initiation will only happen whenever an entity is actually used in some ways and thus being cached (so an object in a seldomly-visited room might never be initiated, just as it should be). ==Support for out-of-band communication== Nothing is done in the server with this yet, but I plan to have a generalized way to implementing out-of-band protocols to communicate with custom clients, via e.g. GMCP or MCP or similar. There are some efforts towards defining at least one of those protocols behind the scenes, but time will tell what comes of it. = Devel branch as of September 2011 = _This update concerned the creation of the Server/Portal structure._ _This update has been merged into main. The text is copied from the mailing list post._ * Evennia was split into two processes: Server and Portal. The Server is the core game driver, as before. The Portal is a stand-alone program that handles incoming connections to the MUD. The two communicate through an AMP connection. * Due to the new Portal/Server split, the old reload mechanism is no more. Reloading is now done much more efficiently - by rebooting the Server part. Since Players are connected to the Portal side, they will not be disconnected. When Server comes back up, the two will sync their sessions automatically. @reload has been fixed to handle the new system. * The controller script evennia.py has been considerably revamped to control the Portal and Server processes. Tested also on WinXP. Windows process control works, but stopping from command line requires python2.7. Restarting from command line is not supported on Windows (use @restart from in-game). * Courtesy of user raydeejay, the server now supports internationalization (i18n) so messages can be translated to any language. So far we don't have any languages translated, but the possibility is there. * @reload will not kill "persistent" scripts and will call _at_server_reload()_ hooks. New @reset command will work like an old server shutdown except it automatically restarts. @shutdown will kill both Server and Portal (no auto-restart) * Lots of fixes and cleanup related to fixing these systems. Also the tutorial_world has seen some bugs fixed that became more obvious with the new reload system. * Wiki was updated to further explain the new features. = Update as of May 2011 = _This update marks the creation of the 'contrib' folder and some first contribs. The text is copied from the original mailing list post._ r1507 Adds the "evennia/contrib" folder, a repository of code snippets that are useful for the coder, but optional since they might not be suitable or needed for all types of games. Think of them as building blocks one could use or expand on or have as inspiration for one's own designs. For me, these primarily help me to test and debug Evennia's API features. So far, I've added the following optional modules in evennia/contrib: * Evennia ``\ MenuSystem\ `` - A base set of classes and cmdsets for creating in-game multiple-choice menus in Evennia. The menu tree can be of any depth. Menu options can be numbered or given custom keys, and each option can execute code. Also contains a yes/no question generator function. This is intended to be used by commands and presents a y/n question to the user for accepting an action. Includes a simple new command 'menu' for testing and debugging. * Evennia Lineeditor - A powerful line-by-line editor for editing text in-game. Mimics the command names of the famous VI text editor. Supports undo/redo, search/replace, regex-searches, buffer formatting, indenting etc. It comes with its own help system. (Makes minute use of the ``\ MenuSystem\ `` module to show a y/n question if quitting without having saved). Includes a basic command '@edit' for activating the editor. * Talking_NPC - An example of a simple NPC object with which you can strike a menu-driven conversation. Uses the ``\ MenuSystem\ `` to allow conversation options. The npc object defines a command 'talk' for starting the (brief) conversation. Creating these, I was happy to see that one can really create quite powerful system without any hacking of the server at all - this could all be implemented rather elegantly using normal commands, cmdsets and typeclasses. I fixed a bunch of bugs and outstanding refactorings. For example, as part of testing out the line-editor, I went back and refurbished the cmdparser - it is now much more straight forward (less bug prone) and supports a much bigger variation of command syntaxes. It's so flexible I even removed the possibility to change its module from settings - it's much easier to simply use command.parse() if you want to customize parsing later down the line. The parser is now also considerably more effective. This is due to an optimization resulting from our use of cmdsets - rather than going through X number of possible command words and store all combinations for later matching, we now do it the other way around - we merge all cmdsets first, then parse the input looking only for those command names/aliases that we know we have available. This makes for much easier and more effective code. It also means that you can identify commands also if they are missing following whitespace (as long as the match is unique). So the parser would now both understand "look me" as well as "lookme", for example. = Update as of April 2011 = _This update adds the ability to disconnect from one's puppet and go OOC._ r1484 implements some conceptual changes to the Evennia structure. If you use South, you need to run "manage.py migrate", otherwise you probably have to reset the databases from scratch. As previously desribed, Evennia impments a strict separation between Player objects (OOC, Out-of-character) objects and Characers (IC In-Character) objects. Players have no existence in the game world, they are abstract representations of connected player sessions. Characters (and all other Objects) have a game-world representation - they can be looked at, they have a location etc. They also used to be the only entities to be able to host cmdsets. This is all well and good as long as you only act as one character - the one that is automatically created for you when you first connect to Evennia. But what if you want to control _another_ character (puppet)? This is where the problems start. Imagine you are an Admin and decide on puppeting a random object. Nothing stops you from doing so, assuming you have the permissions to do so. It's also very easy to change which object you control in Evennia - just switch which object the Player's "character" property points to, and vice-versa for the Objects "player" property (there are safe helper methods for this too). So now you have become the new object. But this object has no commandset defined on it! Not only is now your Admin permissions gone, you can't even get back out, since this object doesn't have a @puppet (or equivalent) command defined for you to use! On the other hand, it's not a bad idea to be able to switch to an object with "limited" capabilities. If nothing else, this will allow Admins to play the game as a "non-privileged" character if they want - as well as log into objects that have unique commands only suitable for that object (become the huge robot and suddenly have access to the "fire cannon" command sounds sweet, doesn't it?) Having pondered how to resolve this in a flexible way, Player objects now also has a cmdsethandler and can store cmdsets, the same way as Objects can. Players have a default set of commands defined by settings.CMDSET_OOC. These are applied with a low priority, so same-named commands in the puppeted object will override the ooc command. The most important bit is that commands @ic (same as @puppet) as well as @ooc are now in the OOC command set and always available should you "become" an Object without a cmdset of its own. @ooc will leave your currently controlled character and put you in an "OOC" state where you can't do much more than chat on channels and read help files. @ic will put you back in control of your character again. Admins can @ic to any object on which they pass the "puppet" access lock restriction. You still need to go IC for most of your non-comm administrative tasks, that's the point. For your own game, the ooc state would be a great place for a Character selection/creation screen, for example. =Update as of March 2011 = _This update introduced the new lock/permission system, replacing an old one where lock and permission where used interchangeably (most confusing). Text was copied from the original mailing list post._ r1346 Adds several revisions to Evennia. Here are a few highlights: == A revised lock/permission system == The previous system combined permissions with locks into one single string called "permissions". While potentially powerful it muddled up what was an access restriction and what was a key. Having a unit "permission" that both dealt with access and limiting also made it very difficult to let anyone but superusers access to change it. The old system also defaulted to giving access, which made for hard-to-detect security holes. Having pondered this for a while the final straw was when I found that I myself didn't fully understand the system I myself wrote - that can't be a good sign. ^_^; So, the new system has several changes in philosophy: * All Evennia entities (commands, objects, scripts, channels etc) have multiple "locks" defined on them. A lock is an "access rule" that limits a certain type of access. There might be one access rule (lock) for "delete", another for "examine" or "edit" but any sort of lock is possible, such as "owner" or "get". No more mix-up between permissions and locks. Permissions should now be read as "keys" and are just one way of many to authenticate. * Locks are handled by the "locks" handler, such as locks.add(), locks.remove() etc. There is also a convenience function access() that takes the place of the old has_perm() (which is not a fitting name anymore since permissions doesn't work the way they did). * A lock is defined by a call to a set of lock functions. These are normal python functions that take the involved objects as arguments and establishes if access should be granted or not. * A system is locked by default. Access is only obtained if a suitable lock grants it. * All entities now receive a basic set of locks at creation time (otherwise noone besides superuser would have any access) In practice it works like this: You try to delete myobject by calling @delete myobject. @delete calls myobject.access(caller, 'delete'). The lockhandler looks up a lock with the access type "delete" and returns a True of False. == Permissions == Only Objects and Players have a "permissions" property anymore, and this is now only used for key strings. A permission has no special standing now - a lock can use any attribute or property to establish access. Permissions do have some nice extra security features out of the box though. * controlled from @perm, which can be a high-permission command now that locks are separate. * settings.PERMISSION_HIERARCHY is a tuple of permission strings such as ("Players", "Builders", "Wizards"). The perm() lock function will make sure that higher permissions automatically grants the permissions of those below. == General fixes == As part of testing and debugging the new lock system I fixed a few other issues: * @reload now asynchonously updates all the objects in the database. This means that you can do nifty things like updating cmdsets on the fly without a server reload! * Some 30 new unittest cases for commands and locks. Command unittests were refined a lot. This also meant finding plenty of minor bugs in those commands. * Some inconsistencies in the server/session system had been lingering behind. Fixed now. * Lots of small fixes. The wiki is almost fully updated (including the auto-updating command list!), but there might still be text around referring to the old way of doing things. Fix it if you see it. And as usual, report bugs to the issue tracker. =Devel branch as of September 2010= _This update added the twisted webserver and webclient. It also moved the default cmdset to src/._ _This has been merged into main. The text is copied from the original mailing list post._ Starting with r1245, the underlying server structure of Evennia has changed a bit. The details of protocol implementation should probably mostly be of interest for Evennia developers, but the additions of new web features should be of interest to all. Maybe the most immediate change you'll notice is that Evennia now defaults to opening two ports, one for telnet and another for a webserver. Yep, Evennia now runs and serves its web presence with its very own Twisted webserver. The webserver, which makes use of Twisted's wsgi features to seamlessly integrate with Django's template system, is found in src/server/webserver.py. The Twisted webserver should be good for most needs. You can of course still use Apache if you really want, but there is now at least no need to use Django's "test server" at all, it all runs by default. All new protocols should now inherit from src.server.session.Session, a generic class that incoorporate the hooks Evennia use to communicate with all player sessions, such as at_connect(), at_disconnect(), at_data_in(), at_data_out() etc. The all-important msg() function still handles communication from your game to the session, this now also takes an optional keyword 'data' to carry eventual extra parameters that certain protocols might have need for (data is intentionally very vaguely specified, but could for example be instructions from your code for updating a graphical client in some way). Two protocols are currently written using this new scheme - the standard telnet protocol (now found separately as server/telnet.py) and a web mud client protocol in server/webclient.py. The web mud client (which requires the web server to be running too) allows for a player to connect to your game through a web browser. You can test it from your newly started game's website. Technically it uses an ajax long polling scheme (sometimes known as 'comet'). The client part running in the browser is a javascript program I wrote using the jQuery javascript library (included in src/web/, although any client and library could be used). The django integration allows for an interesting hybrid, where the Django templating system can be used both for the game website and the client, while the twisted asynchronous reactor handles the real time updating of the client. Please note that the default javascript web client is currently very rough - both it and the underlying protocol still needs work. But it should serve as a hint as to what kind of stuff is possible. The wiki will be updated as the details stabilize. Unrelated to the new web stuff (but noticeable for game devs) is that the default command set was moved from game/gamesrc/commands/default to src/commands/default since some time. The reason for this change was to make it clearer that these commands are part of the default distribution (i.e. might be updated when you update Evennia) and should thus not be edited by admins - like all things in src/. All this did was to make what was always the best-practice more explicit: To extend the default set, make your own modules in game/gamesrc/commands, or copy them from the default command set. The basecmd.py and basecmdset.py have been updated to clearer explain how to extend things. = Devel branch as of August 2010= _This update was a major rewrite of the orginal Evennia, introducing Typeclasses and Scripts as well as Commands, CmdSets and many other features._ _Note: The devel branch merged with trunk as of r970 (aug2010). So if you are new to Evennia, this page is of no real interest to you._ == Introduction == The Evennia that has been growing in trunk for the last few years is a wonderful piece of software, with which you can do very nice coding work. It has however grown 'organically', adding features here and there by different coders at different times, and some features (such as my State system) were bolted onto an underlying structure for which it was never originally intended. Meanwhile Evennia is still in an alpha stage and not yet largely used. If one needs to do a cleanup/refactoring and homogenization of the code, now is the time time to do it. So I set out to do just that. The "devel-branch" of Evennia is a clean rework of Evennia based on trunk. I should point out that the main goal has been to make system names consistent, to add all features in a fully integrated way, and to give all subsystems a more common API for the admin to work against. This means that in the choice between a cleaner implementation and backwards-compatability with trunk, the latter has had to stand back. However, you'll hopefully find that converting old codes shouldn't be too hard. Another goal is to further push Evennia as a full-fledged barebones system for _any_ type of mud, not just MUX. So you'll find far more are now user-configurability now than ever before (MUX remains the default though). Devel is now almost ready for merging with the main trunk, but it needs some more eyes to look at it first. If you are brave and want to help report bugs, you can get it from the _griatch_ branch with {{{svn checkout http://evennia.googlecode.com/svn/branches/griatch evennia-devel}}} ==Concepts changed from trunk to devel== ===Script parent -> Typeclasses === The biggest change is probably that script parents have been replaced by _typeclasses_. Both handle the abstraction of in-game objects without having to create a separate database model for each (i.e. it allows objects to be anything from players to apples, rooms and swords all with the same django database model). A script parent in trunk was a class stored in a separate module together with a 'factory' function that the engine called. The admin had to always remember if they were calling a function on the database model or if it in fact sat on the script parent (the call was made through something called the "scriptlink"). By contrast, a typeclass is a normal python class that inherits from the _!TypeClass_ parent. There are no other required functions to define. This class uses __getattribute__ and __setattr__ transparently behind the scenes to store data onto the persistent django object. Also the django model is aware of the typeclass in the reverse direction. The admin don't really have to worry about this connection, they can usually consider the two objects (typeclass and django model) to be one. So if you have your 'apple' typeclass, accessing, say the 'location', which is stored as a persistent field on the django model, you can now just do ``\ loc + = + apple.location\ `` without caring where it is stored. The main drawback with any typeclass/parent system is that it adds an overhead to all calls, and this overhead might be slightly larger with typeclasses than with trunk's script parents although I've not done any testing. You also need to use Evennia's supplied ``\ create\ `` methods to create the objects rather than to create objects with plain Django by instantiating the model class; this so that the rather complex relationships can be instantiated safely behind the scenes. == Command functions + !StateCommands-> Command classes + !CmdSets == In trunk, there was one default group of commands in a list GLOBAL_CMD_TABLE. Every player in game used this. There was a second dictionary GLOBAL_STATE_TABLE that held commands valid only for certain _states_ the player might end up in - like entering a dark room, a text editor, or whatever. The problem with this state system, was that it was limited in its use - every player could ever only be in one state at a time for example, never two at the same time. The way the system was set up also explicitly made states something unique to players - an object could not offer different commands dependent on its state, for example. In devel, _every_ command definition is grouped in what's called a _!CmdSet_ (this is, like most things in Devel, defined as a class). A command can exist in any number of cmdsets at the same time. Also the 'default' group of commands belong to a cmdset. These command sets are no longer stored globally, but instead locally on each object capable of launching commands. You can add and new cmdsets to an object in a stack-like way. The cmdsets support set operations (Union, Difference etc) and will merge together into one cmdset with a unique set of commands. Removing a cmdset will re-calculate those available commands. This allows you to do things like the following (impossible in trunk): A player is walking down a corridor. The 'default' cmdset is in play. Now he meets an enemy. The 'combat' cmdset is merged onto (and maybe replacing part of) the default cmdset, giving him new combat-related commands only available during combat. The enemy hits him over the head, dazing him. The "Dazed" cmdset is now added on top of the previous ones - maybe he now can't use certain commands, or might even get a garbled message if trying to use 'look'. After a few moments the dazed state is over, and the 'Dazed' cmdset is removed, returning us to the combat mode we were in before. And so on. Command definitions used to be functions, but are now classes. Instead of relying on input arguments, all relevant variables are stored directly on the command object at run-time. Also parsing and function execution have been split into two methods that are very suitable for subclassing (an example is all the commands in the default set which inherits from the !MuxCommand class - that's the one knowing about MUX's special syntax with /switches, '=' and so on, Evennia's core don't deal with this at all!). Example of new command definition: {{{ class CmdTest(Command): def func(self): self.caller.msg("This is the test!") }}} == Events + States -> Scripts == The Event system of Evennia used to be a non-persistent affair; python objects that needed to be explicitly called from code when starting. States allowed for mapping different groups of commands to a certain situations (see !CmdSets above for how commands are now always grouped). _Scripts_ (warning: Not to be confused with the old _script parents_!) are persistent database objects now and are only deleted on a server restart if explicitly marked as non-persistent. A script can have a time-component, like Events used to have, but it can also work like an 'Action' or a 'State' since a script constantly checks if it is still 'valid' and if not will delete itself. A script handles everything that changes with time in Evennia. For example, all players have a script attached to them that assigns them the default cmdset when logging in. Oh, and Scripts have typeclasses too, just like Objects, and carries all the same flexibility of the Typeclass system. ==User + player -> User + Player + character == In trunk there is no clear separation between the User (which is the django model representing the player connecting to the mud) and the player object. They are both forced to the same dbref and are essentially the same for most purposes. This has its advantages, but the problem is configurability for different game types - the in-game player object becomes the place to store also OOC info, and allowing a player to have many characters is a hassle (although doable, I have coded such a system for trunk privately). Devel-branch instead separate a "player character" into three tiers: * The User (Django object) * The PlayerDB (User profile + Player typeclass) * The ObjectDB (+ Character typeclass) User is not something we can get out of without changing Django; this is a permission/password sensitive object through which all Django users connect. It is not configurable to any great extent except through it's _profile_, a django feature that allows you to have a separate model that configures the User. We call this profile 'PlayerDB', and for almost all situations we deal with this rather than User. PlayerDB can hold attributes and is typeclassed just like Objects and Scripts (normally with a typeclass named simply _Player_) allowing very big configurability options (although you can probably get away with just the default setup and use attributes for all but the most exotic designs). The Player is an OOC entity, it is what chats on channels but is not visible in a room. The last stage is the in-game ObjectDB model, typeclassed with a class called 'Character' by default. This is the in-game object that the player controls. The neat thing with this separation is that the Player object can easily switch its Character object if desired - the two are just linking to each other through attributes. This makes implementing multi-character game types much easier and less contrived than in the old system. == Help database -> command help + help database == Trunk stores all help entries in the database, including those created dynamically from the command's doc strings. This forced a system where the auto-help creation could be turned off so as to not overwrite later changes made by hand. There was also a mini-language that allowed for creating multiple help entries from the ``\ \_\_doc\_\_\ `` string. Devel-branch is simpler in this regard. All commands are _always_ using ``\ \_\_doc\_\_\ `` on the fly at run time without hitting the database (this makes use of cmdsets to only show help for commands actually available to you). The help database is stand-alone and you can add entries to it as you like, the help command will look through both sources of help entries to match your query. ==django-perms + locks -> permission/locks== Trunk relies on Django's user-permissions. These are powerful but have the disadvantage of being 'app-centric' in a way that makes sense for a web app, not so much for a mud. The devel-branch thus implements a completely stand-alone permission system that incoorperate both permissions and locks into one go - the system uses a mini-language that has a permission string work as a keystring in one situation and as a complex lock (calling python lock functions you can define yourself) in another. The permission system is working on a fundamental level, but the default setup probably needs some refinements still. ==Mux-like comms -> Generic comms == The trunk comm system is decidedly MUX-like. This is fine, but the problem is that much of that mux-likeness is hard-coded in the engine. Devel just defines three objects, Channel and Msg and an object to track connections between players and channels (this is needed to easily delete/break connections). How they interact with each other is up to the commands that use them, making the system completely configurable by the admin. All ooc messages - to channels or to players or both at the same time, are sent through use of the Msg object. This means a full log of all communications become possible to keep. Other uses could be an e-mail like in/out box for every player. The default setup is still mux-like though. ==Hard-coded parsing -> user customized parsing== Essentially all parts of parsing a command from the command line can be customized. The main parser can be replaced, as well as error messages for multiple-search matches. There is also a considerable difference in handling exits and channels - they are handled as commands with their separate cmdsets and searched with the same mechanisms as any command (almost any, anyway). ==Aliases -> Nicks== Aliases (that is, you choosing to for yourself rename something without actually changing the object itself) used to be a separate database table. It is now a dictionary 'nicks' on the Character object - that replace input commands, object names and channel names on the fly. And due to the separation between Player and Character, it means each character can have its own aliases (making this a suitable start for a recog system too, coincidentally). ==Attributes -> properties == To store data persistently in trunk requires you to call the methods ``\ get\_attribute\_value(attr)\ `` and ``\ set\_attribute(attr, + value)\ ``. This is available for in-game Objects only (which is really the only data type that makes sense anyway in Trunk). Devel allows attribute storage on both Objects, Scripts and Player objects. The attribute system works the same but now offers the option of using the ``\ db\ `` (for database) directly. So in devel you could now just do: {{{ obj.db.attr = value value = obj.db.attr }}} And for storing something non-persistently (stored only until the server reboots) you can just do {{{ obj.attr = value value = obj.attr }}} The last example may sound trivial, but it's actually impossible to do in trunk since django objects are not guaranteed to remain the same between calls (only stuff stored to the database is guaranteed to remain). Devel makes use of the third-party ``\ idmapper\ `` functionality to offer this functionality. This used to be a very confusing thing to new Evennia admins. _All_ database fields in Devel are now accessed through properties that handle in/out data storage. There is no need to save() explicitly anymore; indeed you should ideally not need to know the actual Field names. ==Always full persistence -> Semi/Full persistence == In Evennia trunk, everything has to be saved back/from the database at all times, also if you just need a temporary storage that you'll use only once, one second from now. This enforced full persistency is a good thing for most cases - especially for web-integration, where you want the world to be consistent regardless of from where you are accessing it. Devel offer the ability to yourself decide this; since semi-persistent variables can be stored on objects (see previous section). What actually happens is that such variables are stored on a normal python object called ``\ ndb\ `` (non-database), which is transparently accessed. This does not touch the database at all. Evennia-devel offers a setting ``\ FULL\_PERSISTENCE\ `` that switches how the server operates. With this off, you have to explicitly assign attributes to database storage with e.g. ``\ obj.db.attr + = value\ ``, whereas normal assignment (``\ obj.attr = + value\ ``) will be stored non-persistent. With ``\ FULL\_PERSISTENT\ `` on however, the roles are reversed. Doing ``\ obj.attr + = + value\ `` will now actually be saving to database, and you have to explicitly do ``\ obj.ndb.attr + = + value\ `` if you want non-persistence. In the end it's a matter of taste and of what kind of game/features you are implementing. Default is to use full persistence (but all of the engine explicitly put out ``\ db\ `` and ``\ ndb\ `` making it work the same with both). ==Commonly used functions/concept that changed names== There used to be that sending data to a player object used a method ``\ emit\_to()\ ``, whereas sending data to a session used a method ``\ msg()\ ``. Both are now called ``\ msg()\ ``. Since there are situations where it might be unclear if you receive a session or a player object (especially during login/logout), you can now use simply use ``\ msg()\ `` without having to check (however, you _can_ still use ``\ emit\_to\ `` for legacy code, it's an alias to msg() now). Same is true with emit_to_contents() -> msg_to_contents(). ``\ source\_object\ `` in default commands are now consistently named _caller_ instead. ``\ obj.get\_attribute\_value(attr)\ `` is now just ``\ obj.get\_attribute(attr)\ `` (but see the section on Attributes above, you should just use ``\ obj.db.attr\ `` to access your attribute). ==How hard is it to convert from trunk to devel?== It depends. Any game logic game modules you have written (AI codes, whatever) should ideally not do much more than take input/output from evennia. These can usually be used straight off. Commands and Script parents take more work but translate over quite cleanly since the idea is the same. For commands, you need to make the function into a class and add the parse(self) and func(self) methods (parse should be moved into a parent class so you don't have to use as much double code), as well as learn what variable names is made available (see the commands in ``\ gamesrc/commands/default\ `` for guidance). You can make States into !CmdSets very easy - just listing the commands needed for the state in a new !CmdSet. Script parents are made into Typeclasses by deleting the factory function and making them inherit from a !TypeClassed object (such as Object or Player) like the ones in ``\ gamesrc/typeclasses/basetypes.py\ ``, and then removing all code explicitly dealing with script parents. Converting to the new Scripts (again, don't confuse with the old _script parents_!) is probably the trickiest, since they are a more powerful incarnation of what used to be two separate things; States and Events. See the examples in the ``\ gamesrc/scripts/\ ```` + for some ideas.** + + Better docs on all of this will be forthcoming. + + Things not working/not implemented in devel (Aug 2010) + ------------------------------------------------------ + + All features planned to go into Devel are finished. There are a few + features available in Trunk that is not going to work in Devel until + after it merges with Trunk: + + \* IMC2/IRC support is not implemented.\* Attribute-level + permissions are not formalized in the default cmdset.\* Some of + the more esoteric commands are not converted. + + Please play with it and report bugs to our bug tracker! -Introduction -============ -The Evennia that has been growing in trunk for the last few years is a -wonderful piece of software, with which you can do very nice coding -work. It has however grown 'organically', adding features here and there -by different coders at different times, and some features (such as my -State system) were bolted onto an underlying structure for which it was -never originally intended. Meanwhile Evennia is still in an alpha stage -and not yet largely used. If one needs to do a cleanup/refactoring and -homogenization of the code, now is the time time to do it. So I set out -to do just that. - -The "devel-branch" of Evennia is a clean rework of Evennia based on -trunk. I should point out that the main goal has been to make system -names consistent, to add all features in a fully integrated way, and to -give all subsystems a more common API for the admin to work against. -This means that in the choice between a cleaner implementation and -backwards-compatability with trunk, the latter has had to stand back. -However, you'll hopefully find that converting old codes shouldn't be -too hard. Another goal is to further push Evennia as a full-fledged -barebones system for *any* type of mud, not just MUX. So you'll find far -more are now user-configurability now than ever before (MUX remains the -default though). - -Devel is now almost ready for merging with the main trunk, but it needs -some more eyes to look at it first. If you are brave and want to help -report bugs, you can get it from the *griatch* branch with - -``svn checkout http://evennia.googlecode.com/svn/branches/griatch evennia-devel`` - -Concepts changed from trunk to devel -==================================== - -Script parent -> Typeclasses ----------------------------- - -The biggest change is probably that script parents have been replaced by -*typeclasses*. Both handle the abstraction of in-game objects without -having to create a separate database model for each (i.e. it allows -objects to be anything from players to apples, rooms and swords all with -the same django database model). A script parent in trunk was a class -stored in a separate module together with a 'factory' function that the -engine called. The admin had to always remember if they were calling a -function on the database model or if it in fact sat on the script parent -(the call was made through something called the "scriptlink"). - -By contrast, a typeclass is a normal python class that inherits from the -*TypeClass* parent. There are no other required functions to define. -This class uses *\_getattribute\_* and *\_setattr\_* transparently -behind the scenes to store data onto the persistent django object. Also -the django model is aware of the typeclass in the reverse direction. The -admin don't really have to worry about this connection, they can usually -consider the two objects (typeclass and django model) to be one. - -So if you have your 'apple' typeclass, accessing, say the 'location', -which is stored as a persistent field on the django model, you can now -just do ``loc = apple.location`` without caring where it is stored. - -The main drawback with any typeclass/parent system is that it adds an -overhead to all calls, and this overhead might be slightly larger with -typeclasses than with trunk's script parents although I've not done any -testing. You also need to use Evennia's supplied ``create`` methods to -create the objects rather than to create objects with plain Django by -instantiating the model class; this so that the rather complex -relationships can be instantiated safely behind the scenes. - -Command functions + !StateCommands-> Command classes + !CmdSets ---------------------------------------------------------------- - -In trunk, there was one default group of commands in a list -GLOBAL\_CMD\_TABLE. Every player in game used this. There was a second -dictionary GLOBAL\_STATE\_TABLE that held commands valid only for -certain *states* the player might end up in - like entering a dark room, -a text editor, or whatever. The problem with this state system, was that -it was limited in its use - every player could ever only be in one state -at a time for example, never two at the same time. The way the system -was set up also explicitly made states something unique to players - an -object could not offer different commands dependent on its state, for -example. - -In devel, *every* command definition is grouped in what's called a -*CmdSet* (this is, like most things in Devel, defined as a class). A -command can exist in any number of cmdsets at the same time. Also the -'default' group of commands belong to a cmdset. These command sets are -no longer stored globally, but instead locally on each object capable of -launching commands. You can add and new cmdsets to an object in a -stack-like way. The cmdsets support set operations (Union, Difference -etc) and will merge together into one cmdset with a unique set of -commands. Removing a cmdset will re-calculate those available commands. -This allows you to do things like the following (impossible in trunk): A -player is walking down a corridor. The 'default' cmdset is in play. Now -he meets an enemy. The 'combat' cmdset is merged onto (and maybe -replacing part of) the default cmdset, giving him new combat-related -commands only available during combat. The enemy hits him over the head, -dazing him. The "Dazed" cmdset is now added on top of the previous ones -- maybe he now can't use certain commands, or might even get a garbled -message if trying to use 'look'. After a few moments the dazed state is -over, and the 'Dazed' cmdset is removed, returning us to the combat mode -we were in before. And so on. - -Command definitions used to be functions, but are now classes. Instead -of relying on input arguments, all relevant variables are stored -directly on the command object at run-time. Also parsing and function -execution have been split into two methods that are very suitable for -subclassing (an example is all the commands in the default set which -inherits from the MuxCommand class - that's the one knowing about MUX's -special syntax with /switches, '=' and so on, Evennia's core don't deal -with this at all!). - -Example of new command definition: - -:: - - class CmdTest(Command): - def func(self): - self.caller.msg("This is the test!") - -Events + States -> Scripts --------------------------- - -The Event system of Evennia used to be a non-persistent affair; python -objects that needed to be explicitly called from code when starting. -States allowed for mapping different groups of commands to a certain -situations (see CmdSets above for how commands are now always grouped). - -*Scripts* (warning: Not to be confused with the old *script parents*!) -are persistent database objects now and are only deleted on a server -restart if explicitly marked as non-persistent. - -A script can have a time-component, like Events used to have, but it can -also work like an 'Action' or a 'State' since a script constantly checks -if it is still 'valid' and if not will delete itself. A script handles -everything that changes with time in Evennia. For example, all players -have a script attached to them that assigns them the default cmdset when -logging in. - -Oh, and Scripts have typeclasses too, just like Objects, and carries all -the same flexibility of the Typeclass system. - -User + player -> User + Player + character ------------------------------------------- - -In trunk there is no clear separation between the User (which is the -django model representing the player connecting to the mud) and the -player object. They are both forced to the same dbref and are -essentially the same for most purposes. This has its advantages, but the -problem is configurability for different game types - the in-game player -object becomes the place to store also OOC info, and allowing a player -to have many characters is a hassle (although doable, I have coded such -a system for trunk privately). Devel-branch instead separate a "player -character" into three tiers: - -- The User (Django object) -- The PlayerDB (User profile + Player typeclass) -- The ObjectDB (+ Character typeclass) - -User is not something we can get out of without changing Django; this is -a permission/password sensitive object through which all Django users -connect. It is not configurable to any great extent except through it's -*profile*, a django feature that allows you to have a separate model -that configures the User. We call this profile 'PlayerDB', and for -almost all situations we deal with this rather than User. PlayerDB can -hold attributes and is typeclassed just like Objects and Scripts -(normally with a typeclass named simply *Player*) allowing very big -configurability options (although you can probably get away with just -the default setup and use attributes for all but the most exotic -designs). The Player is an OOC entity, it is what chats on channels but -is not visible in a room. The last stage is the in-game ObjectDB model, -typeclassed with a class called 'Character' by default. This is the -in-game object that the player controls. - -The neat thing with this separation is that the Player object can easily -switch its Character object if desired - the two are just linking to -each other through attributes. This makes implementing multi-character -game types much easier and less contrived than in the old system. - -Help database -> command help + help database ---------------------------------------------- - -Trunk stores all help entries in the database, including those created -dynamically from the command's doc strings. This forced a system where -the auto-help creation could be turned off so as to not overwrite later -changes made by hand. There was also a mini-language that allowed for -creating multiple help entries from the ``__doc__`` string. - -Devel-branch is simpler in this regard. All commands are *always* using -``__doc__`` on the fly at run time without hitting the database (this -makes use of cmdsets to only show help for commands actually available -to you). The help database is stand-alone and you can add entries to it -as you like, the help command will look through both sources of help -entries to match your query. - -django-perms + locks -> permission/locks ----------------------------------------- - -Trunk relies on Django's user-permissions. These are powerful but have -the disadvantage of being 'app-centric' in a way that makes sense for a -web app, not so much for a mud. The devel-branch thus implements a -completely stand-alone permission system that incoorperate both -permissions and locks into one go - the system uses a mini-language that -has a permission string work as a keystring in one situation and as a -complex lock (calling python lock functions you can define yourself) in -another. - -The permission system is working on a fundamental level, but the default -setup probably needs some refinements still. - -Mux-like comms -> Generic comms -------------------------------- - -The trunk comm system is decidedly MUX-like. This is fine, but the -problem is that much of that mux-likeness is hard-coded in the engine. - -Devel just defines three objects, Channel and Msg and an object to track -connections between players and channels (this is needed to easily -delete/break connections). How they interact with each other is up to -the commands that use them, making the system completely configurable by -the admin. - -All ooc messages - to channels or to players or both at the same time, -are sent through use of the Msg object. This means a full log of all -communications become possible to keep. Other uses could be an e-mail -like in/out box for every player. The default setup is still mux-like -though. - -Hard-coded parsing -> user customized parsing ---------------------------------------------- - -Essentially all parts of parsing a command from the command line can be -customized. The main parser can be replaced, as well as error messages -for multiple-search matches. There is also a considerable difference in -handling exits and channels - they are handled as commands with their -separate cmdsets and searched with the same mechanisms as any command -(almost any, anyway). - -Aliases -> Nicks ----------------- - -Aliases (that is, you choosing to for yourself rename something without -actually changing the object itself) used to be a separate database -table. It is now a dictionary 'nicks' on the Character object - that -replace input commands, object names and channel names on the fly. And -due to the separation between Player and Character, it means each -character can have its own aliases (making this a suitable start for a -recog system too, coincidentally). - -Attributes -> properties ------------------------- - -To store data persistently in trunk requires you to call the methods -``get_attribute_value(attr)`` and ``set_attribute(attr, value)``. This -is available for in-game Objects only (which is really the only data -type that makes sense anyway in Trunk). - -Devel allows attribute storage on both Objects, Scripts and Player -objects. The attribute system works the same but now offers the option -of using the ``db`` (for database) directly. So in devel you could now -just do: - -:: - - obj.db.attr = value - value = obj.db.attr - -And for storing something non-persistently (stored only until the server -reboots) you can just do - -:: - - obj.attr = value - value = obj.attr - -The last example may sound trivial, but it's actually impossible to do -in trunk since django objects are not guaranteed to remain the same -between calls (only stuff stored to the database is guaranteed to -remain). Devel makes use of the third-party ``idmapper`` functionality -to offer this functionality. This used to be a very confusing thing to -new Evennia admins. - -*All* database fields in Devel are now accessed through properties that -handle in/out data storage. There is no need to save() explicitly -anymore; indeed you should ideally not need to know the actual Field -names. - -Always full persistence -> Semi/Full persistence ------------------------------------------------- - -In Evennia trunk, everything has to be saved back/from the database at -all times, also if you just need a temporary storage that you'll use -only once, one second from now. This enforced full persistency is a good -thing for most cases - especially for web-integration, where you want -the world to be consistent regardless of from where you are accessing -it. Devel offer the ability to yourself decide this; since -semi-persistent variables can be stored on objects (see previous -section). What actually happens is that such variables are stored on a -normal python object called ``ndb`` (non-database), which is -transparently accessed. This does not touch the database at all. - -Evennia-devel offers a setting ``FULL_PERSISTENCE`` that switches how -the server operates. With this off, you have to explicitly assign -attributes to database storage with e.g. ``obj.db.attr = value``, -whereas normal assignment (``obj.attr = value``) will be stored -non-persistent. With ``FULL_PERSISTENT`` on however, the roles are -reversed. Doing ``obj.attr = value`` will now actually be saving to -database, and you have to explicitly do ``obj.ndb.attr = value`` if you -want non-persistence. In the end it's a matter of taste and of what kind -of game/features you are implementing. Default is to use full -persistence (but all of the engine explicitly put out ``db`` and ``ndb`` -making it work the same with both). - -Commonly used functions/concept that changed names -================================================== - -There used to be that sending data to a player object used a method -``emit_to()``, whereas sending data to a session used a method -``msg()``. Both are now called ``msg()``. Since there are situations -where it might be unclear if you receive a session or a player object -(especially during login/logout), you can now use simply use ``msg()`` -without having to check (however, you *can* still use ``emit_to`` for -legacy code, it's an alias to msg() now). Same is true with -emit\_to\_contents() -> msg\_to\_contents(). - -``source_object`` in default commands are now consistently named -*caller* instead. - -``obj.get_attribute_value(attr)`` is now just -``obj.get_attribute(attr)`` (but see the section on Attributes above, -you should just use ``obj.db.attr`` to access your attribute). - -How hard is it to convert from trunk to devel? -============================================== - -It depends. Any game logic game modules you have written (AI codes, -whatever) should ideally not do much more than take input/output from -evennia. These can usually be used straight off. - -Commands and Script parents take more work but translate over quite -cleanly since the idea is the same. For commands, you need to make the -function into a class and add the parse(self) and func(self) methods -(parse should be moved into a parent class so you don't have to use as -much double code), as well as learn what variable names is made -available (see the commands in ``gamesrc/commands/default`` for -guidance). You can make States into CmdSets very easy - just listing the -commands needed for the state in a new CmdSet. - -Script parents are made into Typeclasses by deleting the factory -function and making them inherit from a TypeClassed object (such as -Object or Player) like the ones in ``gamesrc/typeclasses/basetypes.py``, -and then removing all code explicitly dealing with script parents. - -Converting to the new Scripts (again, don't confuse with the old *script -parents*!) is probably the trickiest, since they are a more powerful -incarnation of what used to be two separate things; States and Events. -See the examples in the ``gamesrc/scripts/`` for some ideas. - -Better docs on all of this will be forthcoming. - -Things not working/not implemented in devel (Aug 2010) -====================================================== - -All features planned to go into Devel are finished. There are a few -features available in Trunk that is not going to work in Devel until -after it merges with Trunk: - -- IMC2/IRC support is not implemented. -- Attribute-level permissions are not formalized in the default cmdset. -- Some of the more esoteric commands are not converted. - -Please play with it and report bugs to our bug tracker! diff --git a/docs/sphinx/source/wiki/GettingStarted.rst b/docs/sphinx/source/wiki/GettingStarted.rst index 56a77fe296..f5c86ae992 100644 --- a/docs/sphinx/source/wiki/GettingStarted.rst +++ b/docs/sphinx/source/wiki/GettingStarted.rst @@ -17,14 +17,15 @@ Quick start For you who are extremely impatient, here's the gist of getting a vanilla Evennia install running. -#. *Get the pre-requisites (Python, Django, Twisted and Mercurial)*. +#. *Get the pre-requisites (Python, Django, Twisted, South and + Mercurial)*. #. *Start a command terminal/dos prompt and change directory to where you want to have your 'evennia' folder appear*. #. ``hg clone https://code.google.com/p/evennia/ evennia`` #. *Change directory to evennia/game*. #. ``python manage.py`` #. ``python manage.py syncdb`` -#. ``python manage.py migrate`` (only if using South) +#. ``python manage.py migrate`` #. ``python evennia.py -i start`` Evennia should now be running and you can connect to it by pointing a @@ -40,7 +41,7 @@ As far as operating systems go, any system with Python support should work. - Linux/Unix -- Windows (2000, XP, Vista, Win7) +- Windows (2000, XP, Vista, Win7, Win8) - Mac OSX (>=10.5 recommended) If you run into problems, or have success running Evennia on another @@ -55,19 +56,19 @@ Evennia: `ActivePython `_ instead. -- **`Twisted `_** (v10.0+) +- **`Twisted `_** (v11.0+) - `ZopeInterface `_ (v3.0+) - usually included in Twisted packages - Windows users might also need `pywin32 `_. -- **`Django `_** (v1.4+) +- **`Django `_** (v1.5+) - `PIL `_ (Python Image Library) - often distributed with Django. -- **`South `_** (v0.7+) +- **`South `_** (v0.8+) - South is used to track and apply changes to the database's structure. @@ -145,22 +146,14 @@ Optional: 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. - -**Windows** users should first and foremost recognize that the Evennia -server is run from the command line, something which some might not be -familiar with (based on the questions we have received). In the Windows -launch menu, just start *All Programs -> Accessories -> command prompt* -and you will get the Windows command line interface. There are plenty of -online tutorials on using the Windows command line, one example is found -`here `_. + **Windows** users should first and foremost recognize that the + Evennia server is run from the command line, something which some + might not be familiar with (based on the questions we have + received). In the Windows launch menu, just start \_All Programs + -> Accessories -> command prompt and you will get the Windows + command line interface. There are plenty of online tutorials on + using the Windows command line, one example is found + `here `_. Windows users may want to install `ActivePython `_ @@ -169,11 +162,12 @@ 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 - that platform defaults to missing many of -the sane developer tools that Linux users take for granted. +getting started on Windows - this platform defaults to lacking sane +developer tools and package management. After installing ActivePython you may need to restart the terminal/DOS -window to make the pypm command available on the command line: +window to make the pypm command available on the command line. Then +write: :: @@ -260,24 +254,19 @@ with the standard tables and values: python manage.py syncdb -You should be asked for a superuser username, email, and password. Make -**sure** you create a superuser here when asked, this becomes your login -name for the superuser account ``#1`` in game. After this you will see a -lot of spammy install messages. If all goes well, you're ready to -continue to the next step. If not, look at the error messages and -double-check your ``settings.py`` file. +You will see a lot of spammy install messages. If all goes well, you're +ready to continue to the next step. If not, look at the error messages +and double-check your ``settings.py`` file. -If you installed ``South`` for database schema migrations, you will then -need to do this: +Next you migrate the database to the current revision: :: python manage.py migrate -This will migrate the server to the latest version. If you don't use -``South``, migrations will not be used and your server will already be -at the latest version (but your existing database might have to be -manually edited to match eventual future schema changes that we do). +This can take a while. When we make changes to the database schema in +the future (we announce this on the homepage) you just need to re-run +this command to have your existing database converted for you. Step 3: Starting and Stopping the Server ---------------------------------------- @@ -289,10 +278,18 @@ and execute ``evennia.py`` like this: python evennia.py -i start -This starts the server and portal. The ``-i`` flag means that the server -starts in *interactive mode*, as a foreground process. You will see -debug/log messages directly in the terminal window instead of logging -them to a file. +(The ``-i`` flag means that the server starts in *interactive mode*, as +a foreground process. You will see debug/log messages directly in the +terminal window instead of logging them to a file.) + +You should be asked to create a superuser. Make **sure** you create a +superuser here when asked, this becomes your login name for the +superuser (owner) account in game. It will ask for email address and +password. The email address does not have to be an existing one. + +After entering the superuser information, the server and portal will +start for the first time. Evennia will quickly run some first-time +configurations, restart once and then be running. To stop Evennia, do: diff --git a/docs/sphinx/source/wiki/IRC.rst b/docs/sphinx/source/wiki/IRC.rst index 2830a583b2..25c85ef716 100644 --- a/docs/sphinx/source/wiki/IRC.rst +++ b/docs/sphinx/source/wiki/IRC.rst @@ -55,10 +55,15 @@ For testing, we choose the *Freenode* network, ``irc.freenode.net``. We will connect to a test channel, let's call it *#myevennia-test* (an IRC channel always begins with ``#``). It's best if you pick an obscure channel name that didn't exist previously - if it didn't exist it will -be created for you. *Don't* connect to ``#evennia``, that is Evennia's -official chat channel! +be created for you. *Don't* connect to ``#evennia`` for testing and +debugging, that is Evennia's official chat channel! -A *port* needed depends on the network. For Freenode this is ``6667``. +(By the way, you *are* welcome to connect your game to ``#evennia`` once +you have everything working - it can be a good way to get help and +ideas. But if you do, please do so with an in-game channel open only to +your game admins and developers). + +The *port* needed depends on the network. For Freenode this is ``6667``. What will happen is that your Evennia server will connect to this IRC channel as a normal user. This "user" (or "bot") needs a name, which you diff --git a/docs/sphinx/source/wiki/Links.rst b/docs/sphinx/source/wiki/Links.rst index 970f61cb10..7899f1b0d3 100644 --- a/docs/sphinx/source/wiki/Links.rst +++ b/docs/sphinx/source/wiki/Links.rst @@ -40,6 +40,10 @@ Third-party Evennia links - `Latitude `_ (MUCK under development, using Evennia) +- `notimetoplay + post `_ + about Evennia + General mud/game development ideas and discussions -------------------------------------------------- @@ -77,7 +81,26 @@ General mud/game development ideas and discussions discussion about rule systems and game balance that could be applicable also for MUDs. -x +Litterature +----------- + +- Richard Bartle *Designing Virtual Worlds* (`amazon + page `_) + - Essential reading for the design of any persistent game world, + written by the co-creator of the original game *MUD*. Discusses + basically everything you need to think about and more. +- Richard Cantillon *An Essay on Economic Theory* (`free + pdf `_) + - A very good English translation of *Essai sur la Nature du Commerce + en Général*, one of the foundations of modern economic theory. + Written in 1730 but the translation is annotated and is very easy to + follow for a modern reader. Required reading if you think of + implementing a sane game economic system. +- David M. Beazley *Python Essential Reference (4th ed)* (`amazon + page `_) + - Our recommended book on Python; it not only efficiently summarizes + the language but is also an excellent reference to the standard + library for more experienced Python coders. Frameworks ---------- diff --git a/docs/sphinx/source/wiki/Locks.rst b/docs/sphinx/source/wiki/Locks.rst index 52ec6af998..cd42c2ec1a 100644 --- a/docs/sphinx/source/wiki/Locks.rst +++ b/docs/sphinx/source/wiki/Locks.rst @@ -221,6 +221,25 @@ Some useful default lockfuncs (see ``src/locks/lockfuncs.py`` for more): ``id/dbref`` but always looks for permissions and dbrefs of *Players*, not on Characters. +Checking simple strings +----------------------- + +Sometimes you don't really need to look up a certain lock, you just want +to check a lockstring. A common use is inside Commands, in order to +check if a user has a certain permission. The lockhandler has a method +``check_lockstring(accessing_obj, lockstring, bypass_superuser=False)`` +that allows this. + +:: + + # inside command definition + if not self.caller.locks.check_lockstring(self.caller, "dummy:perm(Wizards)"): + self.caller.msg("You must be Wizard or higher to do this!" + return + +Note here that the ``access_type`` can be left to a dummy value since +this method does not actually do a Lock lookup. + Default locks ------------- diff --git a/docs/sphinx/source/wiki/Nicks.rst b/docs/sphinx/source/wiki/Nicks.rst index 37731ca1eb..b934f2d59c 100644 --- a/docs/sphinx/source/wiki/Nicks.rst +++ b/docs/sphinx/source/wiki/Nicks.rst @@ -78,8 +78,8 @@ Coding with nicks Nicks are are stored as the ``Nick`` database model and are referred from the normal Evennia `object `_ through the ``nicks`` -property. `` nicks`` is a special handler that offers effective error -checking, searches and conversion. +property - this is known as the NickHandler. The NickHandler offers +effective error checking, searches and conversion. :: @@ -87,16 +87,16 @@ checking, searches and conversion. object.nicks.add("greetjack", "tell Jack = Hello pal!") # An object nick: - object.nicks.add("rose", "The red flower", nick_type="object") + obj.nicks.add("rose", "The red flower", nick_type="object") # An player nick: - object.nicks("tom", "Tommy Hill", nick_type="player") + obj.nicks.add("tom", "Tommy Hill", nick_type="player") # My own custom nick type (handled by my own game code somehow): - object.nicks.add("hood", "The hooded man", nick_type="my_identsystem") + obj.nicks.add("hood", "The hooded man", nick_type="my_identsystem") # get back the translated nick: - full_name = object.nicks.get("rose", nick_type="object") + full_name = obj.nicks.get("rose", nick_type="object") # delete a previous set nick object.nicks.del("rose", nick_type="object") diff --git a/docs/sphinx/source/wiki/Objects.rst b/docs/sphinx/source/wiki/Objects.rst index b39616d210..99434579d9 100644 --- a/docs/sphinx/source/wiki/Objects.rst +++ b/docs/sphinx/source/wiki/Objects.rst @@ -27,7 +27,7 @@ Here's how to define a new Object typeclass in code: """ def at_object_creation(self): "this is called only once, when object is first created" - # add a persistent attribute 'desc' to object. + # add a persistent attribute 'desc' to object (this is pretty silly). self.db.desc = "This is a pretty rose with thorns." Save your class to a module under ``game/gamesrc/objects``, say @@ -38,8 +38,8 @@ Save your class to a module under ``game/gamesrc/objects``, say > @create/drop MyRose:flowers.Rose -To create a new object in code, use the method ``ev.create_object`` (a -shortcut to -src.utils.create.create\_object()\ ``): {{{ from ev import create_object new_rose = create_object("game.gamesrc.objects.flowers.Rose", key="MyRose") }}} (You have to give the full path to the class in this case - ``\ create.create\_object\ `` is a powerful function that should be used for all coded creating, for example if you create your own command that creates some objects as it runs. Check out the ``\ ev.create\_\ **`` functions. This particular Rose class doesn't really do much, all it does it make sure the attribute ``\ desc\ ``(which is what the ``\ look\ `` command looks for) is pre-set, which is pretty pointless since you will usually want to change this at build time (using the ``\ @describe\ `` command). The ``\ Object\ `` typeclass offers many more hooks that is available to use though - see next section. If you define a new Object class (inheriting from the base one), and wants the default create command (``\ @create\ ``) to default to that instead, set ``\ BASE\_OBJECT\_TYPECLASS\ `` in ``\ settings.py\ `` to point to your new class. == Properties and functions on Objects == Beyond those properties assigned to all [Typeclasses typeclassed] objects, the Object also has the following custom properties: * ``\ aliases\ `` - a list of alternative names for the object. Aliases are stored in the database and can be searched for very fast. The ``\ aliases\ `` property receives and returns lists - so assign to it like normal, e.g. ``\ obj.aliases=['flower', +What the ``@create`` command actually *does* is to use +``ev.create_object`` (a shortcut to +src.utils.create.create\_object()\ ``): {{{ from ev import create_object new_rose = create_object("game.gamesrc.objects.flowers.Rose", key="MyRose") }}} (The ``\ @create\ `` command will auto-append the most likely path to your typeclass, if you enter the call manually you have to give the full path to the class. The ``\ create.create\_object\ `` function is powerful and should be used for all coded object creating (so this is what you use when definig your own building commands). Check out the ``\ ev.create\_\ **`` functions for how to build other entities like [Scripts]). This particular Rose class doesn't really do much, all it does it make sure the attribute ``\ desc\ ``(which is what the ``\ look\ `` command looks for) is pre-set, which is pretty pointless since you will usually want to change this at build time (using the ``\ @desc\ `` command). The ``\ Object\ `` typeclass offers many more hooks that is available to use though - see next section. If you define a new Object class (inheriting from the base one), and wants the default create command (``\ @create\ ``) to default to that instead, set ``\ BASE\_OBJECT\_TYPECLASS\ `` in ``\ settings.py\ `` to point to your new class. == Properties and functions on Objects == Beyond those properties assigned to all [Typeclasses typeclassed] objects, the Object also has the following custom properties: * ``\ aliases\ `` - a list of alternative names for the object. Aliases are stored in the database and can be searched for very fast. The ``\ aliases\ `` property receives and returns lists - so assign to it like normal, e.g. ``\ obj.aliases=['flower', 'red blossom']\ `` * ``\ location\ `` - a reference to the object currently containing this object. * ``\ home\ `` is a backup location. The main motivation is to have a safe place to move the object to if its ``\ location\ `` is destroyed. All objects should usually have a home location for safety. * ``\ destination\ `` - this holds a reference to another object this object links to in some way. Its main use is for [Objects#Exits Exits], it's otherwise usually unset. * ``\ nicks\ `` - as opposed to aliases, a [Nicks Nick] holds a convenient nickname replacement for a real name, word or sequence, only valid for this object. This mainly makes sense if the Object is used as a game character - it can then store briefer shorts, example so as to quickly reference game commands or other characters. Nicks are stored in the database and are a bit more complex than aliases since they have a _type_ that defines where Evennia tries to do the substituion. In code, use nicks.get(alias, type) to get a nick, or nicks.add(alias, realname) to add a new one. * ``\ player\ `` - this holds a reference to a connected [Players Player] controlling this object (if any). Note that this is set also if the controlling player is _not_ currently online - to test if a player is online, use the ``\ has\_player\ `` property instead. * ``\ sessions\ `` - if ``\ player\ `` field is set _and the player is online_, this is a list of all active sessions (server connections) to contact them through (it may be more than one if multiple connections are allowed in settings). * ``\ permissions\ `` - a list of [Locks permission strings] for defining access rights for this Object. * ``\ has\_player\ `` - a shorthand for checking if an _online_ player is currently connected to this object. * ``\ contents\ `` - this returns a list referencing all objects 'inside' this object (i,e. which has this object set as their ``\ location\ ``). * ``\ exits\ `` - this returns all objects inside this object that are _Exits_, that is, has the ``\ destination\ `` property set. The last two properties are special: * ``\ cmdset\ `` - this is a handler that stores all [Commands#Command_Sets command sets] defined on the object (if any). * ``\ scripts\ `` - this is a handler that manages [Scripts scripts] attached to the object (if any). The Object also has a host of useful utility functions. See the function headers in ``\ src/objects/objects.py\ `` for their arguments and more details. * ``\ msg()\ `` - this function is used to send messages from the server to a player connected to this object. * ``\ msg\_contents()\ `` - calls ``\ msg\ `` on all objects inside this object. * ``\ search()\ `` - this is a convenient shorthand to search for a specific object, at a given location or globally. It's mainly useful when defining commands (in which case the object executing the command is named ``\ caller\ `` and one can do ``\ caller.search()\ `` to find objects in the room to operate on). * ``\ execute\_cmd()\ `` - Lets the object execute the given string as if it was given on the command line. * ``\ move\_to\ `` - perform a full move of this object to a new location. This is the main move method and will call all relevant hooks, do all checks etc. * ``\ clear\_exits()\ `` - will delete all [Objects#Exits Exits] to _and_ from this object. * ``\ clear\_contents()\ `` - this will not delete anything, but rather move all contents (except Exits) to their designated ``\ Home\ `` locations. * ``\ delete()\ `` - deletes this object, first calling ``\ clear\_exits()\ `` and ``\ clear\_contents()\ ``. The Object Typeclass defines many more _hook methods_ beyond ``\ at\_object\_creation\ ``. Evennia calls these hooks at various points. When implementing your custom objects, you will inherit from the base parent and overload these hooks with your own custom code. See ``\ src.objects.objects\ `` for an updated list of all the available hooks. == Subclasses of _Object_ == There are three special subclasses of _Object_ in default Evennia - _Characters_, _Rooms_ and _Exits_. The reason they are separated is because these particular object types are fundamental, something you will always need and in some cases requires some extra attention in order to be recognized by the game engine (there is nothing stopping you from redefining them though). In practice they are all pretty similar to the base Object. === Characters === Characters are objects controlled by [Players]. When a new Player logs in to Evennia for the first time, a new ``\ Character\ `` object is created and the Player object is assigned to the ``\ player\ `` attribute. A ``\ Character\ `` object must have a [Commands#Command_Sets Default Commandset] set on itself at creation, or the player will not be able to issue any commands! If you just inherit your own class from ``\ ev.Character\ `` and make sure the parent methods are not stopped from running you should not have to worry about this. You can change the default typeclass assigned to new Players in your settings with ``\ BASE\_CHARACTER\_TYPECLASS\ ``. === Rooms === _Rooms_ are the root containers of all other objects. The only thing really separating a room from any other object is that they have no ``\ location\ `` of their own and that default commands like ``\ @dig\ `` creates objects of this class - so if you want to expand your rooms with more functionality, just inherit from ``\ ev.Room\ ``. Change the default used by ``\ @dig\ `` with ``\ BASE\_ROOM\_TYPECLASS\ ``. === Exits === _Exits_ are objects connecting other objects (usually _Rooms_) together. An object named _North_ or _in_ might be an exit, as well as _door_, _portal_ or _jump out the window_. An exit has two things that separate them from other objects. Firstly, their _destination_ property is set and points to a valid object. This fact makes it easy and fast to locate exits in the database. Secondly, exits define a special [Commands Transit Command] on themselves when they are created. This command is named the same as the exit object and will, when called, handle the practicalities of moving the character to the Exits's _destination_ - this allows you to just enter the name of the exit on its own to move around, just as you would expect. The exit functionality is all defined on the Exit typeclass, so you could in principle completely change how exits work in your game (it's not recommended though, unless you really know what you are doing). Exits are [Locks locked] using an access_type called _traverse_ and also make use of a few hook methods for giving feedback if the traversal fails. See ``\ ev.Exit\ `` for more info, that is also what you should inherit from to make custom exit types. Change the default class used by e.g. ``\ @dig\ `` and ``\ @open\ `` by editing ``\ BASE\_EXIT\_TYPECLASS\ `` in your settings. == Further notes == For a more advanced example of a customized object class, see ``\ game/gamesrc/objects/examples/red\_button.py\ ````.** diff --git a/docs/sphinx/source/wiki/Players.rst b/docs/sphinx/source/wiki/Players.rst index 389d2b7b51..055ec17106 100644 --- a/docs/sphinx/source/wiki/Players.rst +++ b/docs/sphinx/source/wiki/Players.rst @@ -1,11 +1,11 @@ Players ======= -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, 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). +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, 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 @@ -25,7 +25,7 @@ or some of the other protocols Evennia supports. 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 +- In mode 2 (right) each 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. @@ -39,19 +39,18 @@ characters as well as configuration options. Players are `CmdSet `_ defaulting to the set defined by ``settings.CMDSET_PLAYER``. -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. +Logged 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 attempt to puppet a Character. 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. +Normally you should put your permissions on the Player level - this will +overrule permissions set on the Character level (unless ``@quell``-ing +is used). How to create your own Player types ----------------------------------- diff --git a/docs/sphinx/source/wiki/Quirks.rst b/docs/sphinx/source/wiki/Quirks.rst index f0341f882b..d2a31f3e47 100644 --- a/docs/sphinx/source/wiki/Quirks.rst +++ b/docs/sphinx/source/wiki/Quirks.rst @@ -4,7 +4,7 @@ Evennia Quirks This is a list of various problems or stumbling blocks that people often ask about or report when using (or trying to use) Evennia. These are common stumbling blocks, non-intuitive behaviour and common newbie -mistakes when working with Evennia. They are not bugs. +mistakes when working with Evennia. They are not Evennia bugs. Actual Evennia bugs should be reported in `Issues `_. @@ -48,9 +48,10 @@ Web admin to create new Player ------------------------------ If you use the default login system and is trying to use the Web admin -to create a new Player account, you need to thread carefully. The -default login system *requires* the Player object to already have a -connected Character object - there is no character creation screen by +to create a new Player account, you need to consider which +MULTIPLAYER\_MODE you are in. If you are in MULTIPLAYER\_MODE 0 or 1, +the login system expects each Player to also have a Character object +named the same as the Player - there is no character creation screen by default. If using the normal mud login screen, a Character with the same name is automatically created and connected to your Player. From the web interface you must do this manually. @@ -62,13 +63,6 @@ property on the Character and the "character" property on the Player to point to each other. You must also set the lockstring of the Character to allow the Player to "puppet" this particular character. -The default login system is very simple and intended for you to easily -get into the game. The more advanced login system in ``contrib/`` along -with the example character-creation system does not require such an -initial coupling (i.e. you can create the coupling in-game). For your -initial experiments, it's easist to create your characters from the -normal MUD connection screen instead. - Mutable attributes and their connection to the database ------------------------------------------------------- @@ -117,22 +111,4 @@ appreciate getting more input and help. Known upstream bugs =================== -These are known bugs in in the libraries Evennia uses, i.e. things out -of our control. - -Error during manage.py syncdb ------------------------------ - -This error can be seen using Django 1.4 without a *locale* set. It -causes a traceback during the ``manage.py syncdb`` phase, just when -trying to create the superuser. - -:: - - TypeError: decode() argument 1 must be string, not None - -This opaque error means no locale could be found. Not properly handling -this is a bug in Django 1.4 reported -`here `_. You resolve it by -setting your locale (this is a good thing to have in any case). See the -comments to that bug report for how to do this. +There are no known upstream bugs at this time. diff --git a/docs/sphinx/source/wiki/Scripts.rst b/docs/sphinx/source/wiki/Scripts.rst index 6252b81580..c23f285a77 100644 --- a/docs/sphinx/source/wiki/Scripts.rst +++ b/docs/sphinx/source/wiki/Scripts.rst @@ -143,10 +143,10 @@ find longer descriptions of these in ``src/scripts/scripts.py``. 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_start()`` - this is called when the script starts or is + unpaused. 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 @@ -173,10 +173,10 @@ possible to also invoke manually) 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. +- ``unpause()`` - resumes a previously paused script. The at\_start() + hook will be called to allow it to reclaim its internal state. Timers + etc are restored to what they were before pause. The server unpauses + all paused scripts after a server reload. - ``time_until_next_repeat()`` - for timed scripts, this returns the time in seconds until it next fires. Returns ``None`` if ``interval==0``. diff --git a/docs/sphinx/source/wiki/ServerConf.rst b/docs/sphinx/source/wiki/ServerConf.rst index 259c7b38fd..57a69d168c 100644 --- a/docs/sphinx/source/wiki/ServerConf.rst +++ b/docs/sphinx/source/wiki/ServerConf.rst @@ -43,11 +43,11 @@ code. The only way to change an Evennia setting is to edit \`game/gamesrc/conf\` directory ------------------------------- -The ``game/gamesrc/conf/`` directory contains module templates for -customizing Evennia. Common for all these is that you should *copy* the -template up one level (to ``game/gamesrc/conf/``) and edit the copy, not -the original. You then need to change your settings file to point the -right variable at your new module. Each template header describes +The ``game/gamesrc/conf/examples/`` directory contains module templates +for customizing Evennia. Common for all these is that you should *copy* +the template up one level (to ``game/gamesrc/conf/``) and edit the copy, +not the original. You then need to change your settings file to point +the right variable at your new module. Each template header describes exactly how to use it and which settings variable needs to be changed for Evennia to be able to locate it. diff --git a/docs/sphinx/source/wiki/SoftCode.rst b/docs/sphinx/source/wiki/SoftCode.rst index 59905ba29f..fc2d646e20 100644 --- a/docs/sphinx/source/wiki/SoftCode.rst +++ b/docs/sphinx/source/wiki/SoftCode.rst @@ -81,6 +81,72 @@ MUSH parsers have jumped light years ahead of where they were even seven or eight years ago, they can still stutter under the weight of the more complex systems if not designed properly. +To further illustrate the lack of readability for building larger +systems in softcode, here is another example, PennMush softcode this +time, for implementing an "+info" command (it allows you to store pages +of extra character info that is later confirmed by admins and can be +viewed by other players): + +:: + + &INC`SET u(ifo)=@include u(ifo)/INC`TARGET;@include \ + u(ifo)/INC`FILENAME;@assert strlen(%q)=@nspemit \ + %#=announce(INFO)%BERROR: Info file name empty.;@switch/inline \ + gt(strlen(setr(attr,u(u(ifo)/FUN`FINDFILE,%q,%q))),0)=1,{@assert \ + or(isadmin(%#),strmatch(%q,%#))=@nspemit \ + %#=announce(INFO)%BERROR: You may not change another's Info \ + files.;@switch/inline \ + or(getstat(%q/%q`FLAGS,Hidden),getstat(%q/%q`FLAGS,Approved))=1,{@assert \ + isadmin(%#)=@nspemit %#=announce(INFO)%BERROR: That Info File may not \ + be changed by you.}},0,{@break gt(strlen(%q),18)=@nspemit \ + %#=ERROR: Info names are limited to 18 characters or less.;@break \ + regmatchi(%q,\\|)=@nspemit %#=ERROR: Pipe symbols are not \ + allowed in info names.;@break regmatchi(%q,\/)=@nspemit \ + %#=ERROR: Slashes symbols are not allowed in info names.;@assert \ + strlen(%1)=ERROR: Text field empty. To delete an +info file, use \ + +info/delete.};&[strfirstof(%q,setr(attr,D`INFOFILE`[nextslot(%q,D`INFOFILE)]))] \ + %q=%q;&%q`CONTENTS %q=%1;th \ + setstat(%q/%q`FLAGS,SetBy,%#);th \ + setstat(%q/%q`FLAGS,SetOn,secs());@switch/inline \ + strmatch(%#,%q)=1,{@nspemit %#=announce(INFO)%BYou set your \ + %q Info File},{@nspemit %#=announce(INFO)%BYou set \ + [name(%q)]'s %q Info File!;@nspemit \ + %q=announce(INFO)%B%n set your %q Info File!} + +(Note that the softcode is actually all one line, it was split to be +viewable on this wiki). Below is the rough Evennia equivalent +functionality as an Evennia command method, written by the same softcode +author after a week of learning Evennia: + +:: + + def switch_set(self,target,files,rhs,isadmin): + if self.caller is not target and not isadmin: + self.caller.msg("ERROR: You may not set that person's files.") + return + if not self.rhs: + self.caller.msg("ERROR: No info file contents entered to set.") + return + for info in files: + if not re.match('^[\w-]+$', info.lower().strip()): + self.caller.msg("ERROR: File '" + info + \ + "' could not be set: may only use alphanumeric characters, -, and spaces in info names.") + elif self.files.get(info.lower().strip(),{}).get("approved",None) is True: + self.caller.msg("ERROR: File '" + info.strip() + "' could not be set: file is approved.") + else: + self.files[info.lower().strip()] = {"contents":rhs, "setby":self.caller, + "seton":"timestamp", "displayname":info.strip()} + if target is self.caller: + self.caller.msg("Info File '" + info.strip() + "' set!") + else: + self.caller.msg("Info File '" + info.strip() + "' set!") + target.caller.msg(self.caller.key + " set your '" + info.strip() + "' info file!") + target.db.infofiles = dict(self.files) + return + +The details of the implementation are unimportant, the difference in +readability is the main point here. + Changing Times -------------- diff --git a/docs/sphinx/source/wiki/Tutorials.rst b/docs/sphinx/source/wiki/Tutorials.rst index 71a4755a4a..5fe320b910 100644 --- a/docs/sphinx/source/wiki/Tutorials.rst +++ b/docs/sphinx/source/wiki/Tutorials.rst @@ -18,6 +18,9 @@ Coding basics More details about coding with Evennia is found in the `Developer Central `_. +- `First Steps Coding with Evennia `_ - this is + partly duplicated in the following two tutorials using different + words. - `Tutorial: Adding a new default command `_ - `Tutorial: Adding new Object typeclasses and diff --git a/docs/sphinx/source/wiki/Typeclasses.rst b/docs/sphinx/source/wiki/Typeclasses.rst index 978d4a4c94..34799d4f01 100644 --- a/docs/sphinx/source/wiki/Typeclasses.rst +++ b/docs/sphinx/source/wiki/Typeclasses.rst @@ -15,13 +15,13 @@ Evennia instead uses very generic and simple database models and functionality. Using Python classes means you get all the flexibility of Python object management for free. -There are three main game 'entities' in Evennia that are what we call +There are four main game 'entities' in Evennia that are what we call *typeclassed*. They are `Players `_, -`Objects `_ and `Scripts `_. This means that -they are *almost* normal Python classes - they behave and can be -inherited from etc just like normal Python classes. But whenever they -store data they are infact transparently storing this data into the -database. +`Objects `_, `Scripts `_ and +[Communications#Channels Channels]. This means that they are *almost* +normal Python classes - they behave and can be inherited from etc just +like normal Python classes. But whenever they store data they are infact +transparently storing this data into the database. |image0| @@ -45,8 +45,8 @@ from one of the base Typeclasses: class Furniture(Object): # this defines what 'furniture' is -Properties available to all typeclassed entities (Players, Objects, Scripts) ----------------------------------------------------------------------------- +Properties available to all typeclassed entities (Players, Objects, Scripts, Channels) +-------------------------------------------------------------------------------------- All typeclassed entities share a few very useful properties and methods. @@ -57,6 +57,17 @@ All typeclassed entities share a few very useful properties and methods. restrictsions. Use locks.add(), locks.get() etc. - ``dbref`` - the database id (database ref) of the object. This is a unique integer. You can usually also use ``id``. +- ``is_typeclass(typeclass, exact=False)`` - Checks if this object has + a typeclass mathing the one given. If exact is False, it will accept + parents matching as well. +- ``delete()`` - Deletes the object +- ``swap_typeclass(new_typeclass, clean_attributes=False, no_default=True)`` + - this will swap the object to another typeclass. You may choose to + clean out all attributes (this may be necessary if the new typeclass + is very different from the old one). The ``no_default`` dictates what + happens if the swap fails - if set it will revert back to the + pre-swap typeclass, otherwise it will fall back to the default class + as defined in your settings file. There are three further properties that warrant special mention: @@ -87,11 +98,210 @@ a few things that you need to remember however: - Create new instances of typeclasses using ``ev.create_*`` instead of 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. 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 - = - 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. + 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 + `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 +`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. + +|image1| + +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 `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 = 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: + +|image2| + +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 +.. |image1| image:: https://lh4.googleusercontent.com/-HNUhh6xCYpY/UZISHoSucxI/AAAAAAAAB4I/2ThUbuAbosg/w865-h634-no/typeclasses2a.png +.. |image2| image:: https://lh5.googleusercontent.com/-oCqy1f1ZFRA/UZIWeg0ll8I/AAAAAAAAB4g/-ewUvQ439y4/w681-h634-no/typeclasses2.png diff --git a/docs/sphinx/source/wiki/UsingMUXAsAStandard.rst b/docs/sphinx/source/wiki/UsingMUXAsAStandard.rst index b98ba576a0..461287accb 100644 --- a/docs/sphinx/source/wiki/UsingMUXAsAStandard.rst +++ b/docs/sphinx/source/wiki/UsingMUXAsAStandard.rst @@ -27,23 +27,76 @@ unlimited power to extend the game leveraging the full power of a mature high level programming language. You can find a more elaborate discussion about our take on MUX SoftCode `here `_. +Documentation policy +-------------------- + +All the commands in the default command sets have their doc-strings +formatted on a similar form: + +:: + + """ + Short header + + Usage: + key[/switches, if any] [] + + Switches: + switch1 - description + switch2 - description + + Examples: + usage example and output + + Longer documentation detailing the command. + + """ + +The ``Switches`` and ``Examples`` headers can be skipped if not needed. +Here is the ``nick`` command as an example: + +:: + + """ + Define a personal alias/nick + + Usage: + nick[/switches] = [] + alias '' + + Switches: + object - alias an object + player - alias a player + clearall - clear all your aliases + list - show all defined aliases (also "nicks" works) + + Examples: + nick hi = say Hello, I'm Sarah! + nick/object tom = the tall man + + A 'nick' is a personal shortcut you create for your own use [...] + + """ + +For commands that *require arguments*, the policy is for it to return a +``Usage`` string if the command is entered without any arguments. So for +such commands, the Command body should contain something to the effect +of + +:: + + if not self.args: + self.caller.msg("Usage: nick[/switches] = []") + return + WWMD - What Would MUX Do? ------------------------- -Our policy for implementing the default commands is as follows - we tend -to look at MUX2's implementation before contriving one of our own. This -comes with a caveat though - there are many cases where this is -impossible without sacrificing the usability and utility of the -codebase. In those cases, differences in implementation as well as -command syntax is to be expected. Evennia is *not* MUX - we handle all -underlying systems very differently and don't use -`SoftCode `_. The WWMD policy is only applied to the -default commands, not to any other programming paradigms in the -codebase. - -If you are an Evennia codebase developer, consider activating -``IMPORT_MUX_HELP`` in your ``settings.py`` file. This will import a -copy of the MUX2 help database and might come in handy when it comes to -adding/implementing new default commands. If you must deviate from -MUX2's implementation of something, make sure to document it extensively -in the command's docstring. +Our original policy for implementing the default commands was to look at +MUX2's implementation and base our command syntax on that. This means +that many default commands have roughly similar syntax and switches as +MUX commands. There are however many differences between the systems and +readability and usability has taken priority (frankly, the MUX syntax is +outright arcane in places). So the default command sets can be +considered to implement a "MUX-like" dialect - whereas the overall feel +is familiar, the details may differ considerably. diff --git a/docs/sphinx/source/wiki/Zones.rst b/docs/sphinx/source/wiki/Zones.rst index 9f6a2f690d..5a9c0a9096 100644 --- a/docs/sphinx/source/wiki/Zones.rst +++ b/docs/sphinx/source/wiki/Zones.rst @@ -30,8 +30,36 @@ should be easy to retrieve. Many MUD codebases hardcode zones as part of the engine and database. Evennia does no such distinction due to the fact that rooms themselves -are meant to be customized to any level anyway. Below is just *one* -example of how one could add zone-like functionality to a game. +are meant to be customized to any level anyway. Below are two +suggestions for how zones could be implemented. + +Zones using Tags +~~~~~~~~~~~~~~~~ + +*OBS: Placeholder - this section is NOT fully supported by the code at +the moment!* + +All objects in Evennia can hold any number of `tags `_. Tags +are short labels that you attach to objects. They make it very easy to +retrieve groups of objects. An object can have any number of different +tags. So let's attach the relevant tag to our forest: + +:: + + forestobj.tags.add("magicalforest", category="zone") + +You could add this manually, or automatically during creation somehow +(you'd need to modify your @dig command for this, most likely). + +Henceforth you can then easily retrieve only objects with a given tag: + +:: + + import ev + rooms = ev.managers.Tags.get_objs_with_tag("magicalforest", category="zone") # (error here) + +Zones using Aliases +~~~~~~~~~~~~~~~~~~~ All objects have a *key* property, stored in the database. This is the primary name of the object. But it can also have any number of *Aliases* @@ -41,8 +69,8 @@ alias "door". Aliases are actually separate database entities and are as such very fast to search for in the database, about as fast as searching for the object's primary key in fact. -This makes Aliases prime candiates for implementing zones. All you need -to do is to come up with a consistent aliasing scheme. Here's one +This makes Aliases another candidate for implementing zones. All you +need to do is to come up with a consistent aliasing scheme. Here's one suggestion: :: @@ -130,12 +158,12 @@ zone in the typeclass rather than enforce the ``@dig`` command to do it: class MagicalForestRoom(Room) def at_object_creation(self): ... - self.aliases = "#magicforest|%s" % self.key + self.aliases.add("#magicforest|%s" % self.key) ... class NormalForestRoom(Room) def at_object_creation(self): ... - self.aliases = "#normalforest|%s" % self.key + self.aliases.add("#normalforest|%s" % self.key) ... Of course, an alternative way to implement zones themselves is to have