Evennia Devel

This page serves as a changelog of the various bigger updates of Evennia over time.

Batchprocessor and misc updates Aug 2016

Copied from the `original mailing list post`_

Changes to the batchcode processor usage

There are some changes to the Batchcode processor:

  • The file path loading is a lot less finicky. A common error with using either of the batch processors was that people could not find their files by entering the python-style path to it. The reason this behaves differently is that Evennia converts this to an actual OS file path so it can be opened as a file rather than a Python module. In the past Evennia was trying to be a little too smart behind the scenes, by prepending the paths from settings.BATCHCODE_PATHS in front of whatever you used. This it will still do, but it will also be more accepting of people using full paths from the mygame or evennia folder’s roots and some other tricks. In short, it just behaves more intuitively. This also modifies evennia.utils.utils.pypath_to_realpath, so if you are using this utility function you should check it out; it now also accepts a list of file prefix paths to try.

  • The batchcode parser was completely overhauled. This closes several old bugs.

  • The use of the batchcode processor to load a bog standard Python file is now considered standard, in the processor and in the documentation. The use of #HEADER, #CODE and #INCLUDE are considered optional extras for testing the code or stepping through it interactively.

  • The #CODE block syntax has changed. The old #CODE name (varname1, varname2,,) syntax is gone and is just #CODE. If you want to run a script over and over and don’t want it to create a pile of same-named objects when doing so, you should check for the global DEBUG variable made available to all batchcode scripts at execution (this made available regardless of #CODE or not):

    # in batchcode script file
    
    obj1 = create_object("key", ...)
    
    if DEBUG: # only true if batchcode processor runs in /debug mode
        obj.delete()
    

Apart from the debug running, all old batchcode files will work the same (the #CODE string is just a comment to Python after all.

  • The docs have been fully updated to reflect these changes.

New utils.justify

Added a new text utility, evennia.utils.utils.justify. This allows justifying and indenting full blocks of text. It also honors new paragraphs.

There was also a change to utils.wrap to make sure it indents on all lines, not skipping the very first line.

New @chardelete command

Players can now use the @chardelete command to themselves delete characters they have previously created with @charcreate. Deleting a character via some other means will also now no longer show a login traceback for users of MULTISESSION_MODEs > 1.

Evennia game index: List your Evennia game!

The Evennia game index (games.evennia.com) is considered stable rather than experimental now. It is however still pretty empty, mostly because people don’t know about it yet. So don’t be shy to register even your unfinished game with it - it’s just a setting in your setting file. Your game does not need to be ready for prime time to register - you can choose to set it to pre-alpha status.

Spawner updates

  • The Spawner was updated to now also accept the exec key - this allows devs to provide an executable snippet of code and is necessary for calling custom handlers on the spawned object etc. Use of exec is restricted to people able to write prototype files: Builders cannot supply exec arguments when creating protoype dicts from the command line with @spawn (this would be a security risk).
  • All the prototype keys sent to the spawner can now also point to a callable taking no arguments. This allows for dynamic allocation of any property or attribute on the object, like random names, descriptions, stats etc etc.

Many bug fixes

A lot of bugs and issues have been closed lately. Including, but not exclusive to:

  • Webclient handles ANSI backgrounds correctly now.
  • RPsystem contrib has had a lot of fixing up as part of being used in Ainneve. It now adds a new say command and uses sdescs in more places.
  • Scripts saw some fixes related to restarting
  • Client-related compatibility fixes
  • EvForm fixes (allows multi-character markup now)

Evennia 0.6 release May 2016 - OOB/webclient overhaul

Copied from `original mailing list announcement`_.

  • msg() now has a different callsign: msg(text=None, **kwargs). All data being sent out is now considered an OOB (Out of band) instruction, including the default text (even though some protocols like telnet send it in a special way, it’s still handled as an OOB instruction). These “sendcommands” are given as keyword arguments to the msg method and can be called either with a string or with a list of arguments or a tuple (args, kwargs) to be passed on to the protocol.
  • msg() accepts one special keyword and that isoptions`. This is a dictionary of options that will only affect the protocol sending. This is where you pass that you don’t want to parse ansi colors etc. So where you before would write msg(string, raw=True), you now need to do msg(string, options={“raw”:True}). The prompt is a separate OOB command, so you use msg(prompt=prompttext) as before.
  • Inputfuncs - these are customizable functions you can now easily supply to manage any incoming command from the client. They are added in the same ways other plugins like lockfuncs or inlinefuncs are handled in Evennia - by simply defining them in a module Evennia then imports and uses. The default input is the text inputcommand, for which Evennia has a default handler (which launches the cmdhandler), but you could add support for any client-side instruction this way.
  • OOB instructions for GMCP and MSDP have been included, as well as for Evennia’s JSON-based webclient instructions. These are just functions in a file that Evennia reads at runtime.
  • Handles mudlet’s GMCP handshake (which is not standard except for IRE games)
  • Added default commands for MSDP and bridges to GMCP.
  • Protocol input/output signatures have changed a little. This is mainly of interest if you want to implement your own protocol though.
  • The MonitorHandler in evennia/scripts/monitorhandler is a convenient mechanism for monitoring property- or Attribute-changes on any object. You assign a callback to the change which will trigger when anything changes. This is very useful to use with OOB instructions (like reacting and updating a health bar whenever the health attribute changes, for example).
  • The TickerHandler has seen a lot of updates to make it more generally useful. You can now assign any function to it, to be ticked, not only methods on objects. This means that the callsign has changed quite a bit and users of the old TickerHandler will need to look into reworking their calls.
  • The OOBHandler is no more since the OOB system is now integrated completely with the message sending system rather than being tacked on like before. All the functionality of the old OOBHandler is now handled between the MonitorHandler and the revamped TickerHandler.
  • Added settings.IN_GAME_ERRORS. This is False by default. When active, it will echo Python errors not only to the log but also show the traceback in-game. This is useful for debugging but not good for a production game (where the user instead can report the log time stamp).
  • The webclient has been completely reworked. It now consists of the evennia.js javascript library that handles all communication with the server. It also falls back to AJAX/comet and has a much improved handling of timeouts and various errors. This is meant to be used by whatever gui library is used.It handles the new OOB mechanism natively, allowing you to send and receive custom OOB instructions easily (e.g. from a custom GUI). The second component is evennia_gui.js which is the front-end of the client. This, together with the webclient.html and webclient.css files, implements the “telnet-like” default client. The idea is that his could be easily swapped out if someone wants to use another gui library for the front end. -The Webclient is a lot more stable and standards-compliant than before, and now also supports xterm256 colors like the other protocols.
  • The evennia/web/ layout has been completely reworked so that there are now two easily understandable website/ and webclient/ folders rather than having all files spread around in the root of that folder. The “prosimii” template is still used but is now renamed to “website” (credit remains) to make it more obvious to remember and overload.
  • Added the ability to overload the default Command parent from your game, for making sweeping changes to commands
  • Updated the ChannelHandler:
  • Added the ability to customize the Channel command.
  • Also added an automatic log (to a log file) of all channels, along with a /history switch to the channel that allows for viewing past channel posts.
  • Only the nested inlinefuncs on the form $func() are now available. The already deprecated {func … {/func style inline funcs have been removed.
  • The @options command now allows a player to manually set a wide range of options related to their connection. This helps for cases when Evennia cannot accurately identify your client, or you want to use some other setting than is default for that client.
  • The EvMenu can now also be set to be persistent across server reboots.
  • The menu_login contrib now uses EvMenu.
  • Added evennia –initsettings for adding a new, empty settings file to a game directory. This is useful when sharing game dirs between multiple developers.
  • A lot of other bug fixes and small improvements all over.

New Library layout and typelcasses March 2015

*Copied from the original announcement here

Devel branch merged March 1, 2015

Changing typeclasses to use proxy models

To understand this change you need to be a little familiar with how typeclasses work in master. A typeclass is a Python class whose setattr and getattributes methods are overridden in such a way that whenever you store something on it, it actually stores that data on its connected database model instance. That database model instance is in turn cached and held in memory by Evennia. It stores a text string holding the python path to the typeclass associated with it. For example, when you @create a “Rock” object of the typeclass “src.objects.objects.Object”, what happens is that a new database object (model ObjectDB) is created to represent this new line in the database table. This model then initialize an instance of its typeclass (src.objects.objects.Object in this example) and ties to it using its .typeclass property. Vice-versa, this new typeclass ties back to the database model instance via its .dbobj property. By simply storing different typeclass-paths in the database one can this way represent any number of different game entities using very few database models.

A drawback with the typeclass system in the master branch is that it introduces some custom limitations on its classes. Notably you need to use the create_object, create_script etc functions to create typeclassed objects (you cannot overload init) since the database model and typeclass must be tied together and initialized correctly at creation. Furthermore there is a constant exchange between the two objects with lookups on the typeclass often leads to the lookup on its dbobj and also the other way around - this introduces a small but noticeable performance hit over time. Furthermore, since django knows nothing about this typeclass business querying objects in the database deals with database models and not typeclasses (this is a small limitation since we implement our own managers and there are also suggested other means of modifying queries to address this particular issue). When it comes to understanding the inheritance of typeclasses this is also a bit cumbersome since the typeclass inheritance tree is not actually stored in the database and can thus not be searched easily through it (such as when wanting to find all objects of a given typeclass and its children).

Enter proxy models. Proxy models has been around in django for a while but they were not there when I first started with Evennia and they had slipped under my radar until user Volund made me aware of them in chat. At the time I was working on another typeclass revamp using the existing system - I threw that aaway after looking into proxy models. A proxy model is basically described in the django docs as a way to expand a django model with alternative Python code without needing to change the database schema - in short a proxy class using the database model for storage. Sounds familiar? As it turns out django’s proxy models suit our typeclass needs very well, without the need of our custom overlying implementation. So during Christmas I have converted Evennia’s typeclass system to use proxy models. Below are the advantages and features of this new typeclass implementation:

  • typeclasses now inherit directly from their database models. All such models are automatically treated as a proxy (this is implemented using some (if I may say so) pretty sophisticated metaclass magic, so creating a new typeclass requires no extra boiler plate except inheriting from the right parent.
  • There is no longer any difference between the database model and the typeclass (we still call the proxy children of the model “typeclasses” though). This means that the .dbobj and .typeclass properties does not make sense any more and were removed. All methods were moved from the database model to the typeclass and are now available directly through inheritance, so you should have much less need to go to the database model than you had. You can however still reach a typeclass’ parent model at any time using typeclass.dbclass, which is the new standard along with the built-in class property. - - You can now query typeclasses directly, instead of only the main model. For example, if you have a typeclass “Rock” you can do Rock.objects.all() to get all the rocks. Conversely, if you do ObjectDB.objects.all() (or Rock.dbclass.objects.all()) you will (same as before) get all ObjectDB-derived instances, independent of typeclass. There are now also the new all_family, filter_family and get_family manager methods that allow you to query the database directly for an typeclass and all its subclasses, such as Rock.objects.all_family(). -
  • You can now create new instances of typeclasses using normal initialization. So rock = Rock() will now get you a correct typeclass (you need to do rock.save() to actually store it, same as any django model). Through internal signalling saving this will still trigger the correct startup hooks. It should be noted that the create_* functions still offers more functionality, since they can accept more arguments and add things like permissions at creation time (using the plain construction you’d need to add such things manually). - - Interestingly, the new system requires no changes to the database schema, so the actual change needed in your code is not so big as one might think. -
  • The new typeclass system is a lot easier to explain and should also be more efficient. Furthermore, it should be easier to cache using one of the many cache solutions available to Django (such as memcached) or using threading for supported database (more testing is needed of this though).
  • There are two caveats of the new typeclass system: - -
  • Django’s proxies does not quite fit our needs. I have modified the query system to return typeclasses rather than database models. I have also introduced a django patch to allow proxies multiple inheritance as long as they all stem from the same model. This patch is included in Evennia and introduced transparently, but it should hopefully soon be a part of future django versions so we don’t need this hack. -
  • There can only exist one proxy model of a given name for a given base model. This means that we can no longer do stuff like “from src.objects.objects import Object as BaseObject” and then create a class Object(BaseObject) (as was done in the gamesrc/ example files). Django interprets these as two proxy models based off ObjectDB, both named “Object”, something which is not allowed. For this reason the default typeclasses are now called DefaultObject, DefaultRoom, DefaultExit etc, to allow end users the possibility to use the shorter Object, Exit, Room etc. This is one reason (apart from legacyy) that the classes are still called “typeclasses”.
  • So what changes does the new typeclass system require from you, the end user. Surprisingly little. The main thing is expected to be to remove your use of .dbobj and .typeclass and to change eventual imports of the default Object, Exit etc to instead be named DefaultObject, DefaultExit e

… But I’m not done yet. what WILL require some more changes is the next new change. Read on … - - -

Evennia becomes a library - -

Once I did the typeclass revamp I though I could just as well continue and add the other big change that has been discussed for a long time - converting Evennia to a proper package/library structure. The library change is also operational in devel now. - - The package change means that Evennia itself takes on the role of a library with an “evennia” executable to do operations. The “game” directory is no longer shipped with the system but is created on-the-fly for each new game using the evennia launcher. This allows the evennia library to exist completely separately from the game implementation in the same way as django does. The freshly created game folder has empty starting modules for the common game entities, log files and configs and a dynamically created settings.py file that links to those files. So no more “copy example/cmdset.py up one level, then change your setting file to point to it …” as we use in master. Since the game directory is created on the fly it is not a part of Evennia’s version control which means that you can change it and restructure it as you please without being afraid of running into merge conflicts down the line. You can also easily create multiple games in different folders (as long as you change their ports to avoid collisions).

Here is an example of creating a new game with evennia once the library has been installed:

evennia init mygame
cd mygame -
evennia migrate        # Creates the database based on your (possibly tweaked) settings.  -
evennia -i start       # start the server - - -

Main differences when coding using the new Evennia library: -

  • src/ is no more. The src/ folder has been renamed evennia/ and the library is expected to be imported simply as “evennia” in your code. -
  • ev.py is no more. All of the flat API has been included in evennia.init, which means that you can get to most common things directly via just the evennia import (such as evennia.DefaultObject). -
  • game/ is no more, obviously. This is now the dynamically-created folder. The old examples, such as the red button has been moved to a new contrib/tutorial_examples/ folder. -
  • game/manage.py was merged with game/evennia.py into the new bin/evennia executable. For now, call it explicitly with python path-to-evennialib/evennia. This will need to be made automatically available on $PATH down the line and linux users can do so manually if they want.
  • the default typeclass paths have changed to be located in the new game dir rather than in the evennia source tree. Migrations for this are not yet finished so use a fresh database to test. - - - So why this change? The main advantage (and goal) of the package restructure is that this makes it easier to distribute Evennia in a more accessible form. Once we have worked out the kinks, it means that we can distribute Evennia in pypi and that those of you who are not interested in git will be able to do something like “pip install evennia” without much fuzz. Also getting evennia into other package systems (like debian) should be easier. It will also lead to Evennia adopting a more formal release schedule with version numbers (more on this in the future). Cool cats will of course still be able to follow and help using the bleeding edge git version as before. -

Memory optimizations of June 2014

Text from original announcement. See also the Devblog post.

To understand what was done, here is a little background. Python keeps tracks of all objects (from variables to classes and everything in between) via a memory reference. When other objects reference that object it tracks that too. Some objects don’t need to be in memory (because noone can access them any more), so Python’s garbage collector goes through them and cleans such objects up so memory can be used for other things. The garbage collector will not do so however if some other object (which will not be garbage-collected) still has a reference to the object. This is what you want - you don’t want existing objects to stop working because an object they rely on is suddenly not there.

Evennia uses something called the idmapper. This is a cache mechanism that allows objects to only be loaded from database once and be reused when later accessed. The speedup achieved from this is significant, but it is also a critical part of the typeclass system - if the memory representation changed all the time we could not store things like non-persistent attributes and would have to re-initialize all cmdhandlers, attributehandlers, lockhandlers and what have you every time you accessed an object.

The tradeoff of speed and utility is memory usage. Since the idmapper keeps those references, memory usage of Evennia could rise rapidly with an increasing number of objects.

Whereas some objects (with temporary attributes) should indeed not be garbage collected, in a working game there is likely to be objects without such volatile data that are not used some of the time - simply because players or the game don’t go there for the moment. For such objects it may be okay to re-load them on demand rather than keep them in memory when not needed.

When looking into this I found that simply flushing the idmapper did not clean up all objects from memory. The reason for this has to do with Evennia holding other references.

So I went through a rather prolonged spree of cleanups where I gradually (and carefully) cleaned up Evennia’s object referencing to a point where the only external reference to most objects were the idmapper cache reference. Removing that will now make the object possible to garbage-collect.

This is how the reference map used to look for an ObjectDB object before. Note the several references into the ObjectDB and the cyclic references for all handlers.
This is how the reference map looks now. The instance cache is the idmapper reference. There are also no more cyclic references for handlers (the diplay don’t even pick up on them for this depth of display). Just removing that single link will now garbage-collect ObjectDB and its typeclass (ignore the g reference, that is just the variable holding the object ipython). We also see that the dbobj.typeclass <-> typeclass.dbobj references keep each other alive and when one goes the other one does too.

What will generally not be cleaned currently are objects with cmdsets on them. This is a forest of references that I might look into straightening at some point, but many such objects, like Characters and Players, should never be garbage collected anyway. Assigning a non-persistent Attribute via the ndb handler will make sure this object will not be cleaned out.

There are two ways to flush the idmapper cache in the latest push: manually or automatically. Manually it can be done via @server/flushmem. There is now a new global Script that will check the memory usage every 5 minutes and flush the cache if it exceeds a given value. The flush limit for this is defined by settings.IDMAPPER_CACHE_MAXSIZE. The value you need to set for this depends very much on the size of your game and the kind of usage you expect (notably how many players and how often objects need to be in memory). src/settings_defaul.py has a table listing the suggested size for holding various numbers of objects in cache. You probably won’t need to mess with this until you run a production server.

Status as of May 2014

We are in a stretch of fixing bugs and optimizing. There is work ongoing of limiting the memory footprint of Evennia, this has not yet merged with master.

ANSIString/Evtable/Evform push of Feb-April 2014

This saw a series of work by contributor Kelketek on implementing a subclass of strings that can handle ANSI markers in a transparent way. Once this merged, the EvTable and EvForm modules became more generally useful and is now slowly replacing the old third-party PrettyTable (although prettytable will likely remain as a backup).

Github move in January 2014

*Text copied from original mailing list announcement from Jan 26. See also The Devblog post for a detailed account of the move.

As of today, Evennia’s code, documentation and issue handling has officially moved over to GitHub, to http://github/evennia/evennia.

All links and feeds on the main evennia.com page has been changed to point to the new location. Mailing list and blog are not affected but the Commit mailing list is currently not working, if you are finding the Commit mailing list indispensable, reply here if I should put work into coercing github to send to it.

Practically, this means that Google Code’s mercurial repository and wiki will no longer be updated. So to get updates you need to use the new github host. See our new GettingStarted page for updated info on how to get Evennia.

There were some cleanup of the Mercurial repository to make it convert cleanly over to GIT. So using conversion tools on your own repos may be a painful experience. If you followed guidelines and only made your local changes in game/gamesrc, the fastest and cleanest way for you to get going is to do make a new fresh clone of Evennia from github (or even better, fork it on Github), then just manually copy & paste your gamesrc changes (as well as game/settings.py and the database file game/evennia.db3 if you use SQLite3). Things should work normally from there. See our new Version Control wiki page for more info on using GIT and contributing to Evennia.

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!