149 lines
52 KiB
ReStructuredText
149 lines
52 KiB
ReStructuredText
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 <https://groups.google.com/forum/#!topic/evennia/0ejMyGpw5P0>`_.
|
|
- 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!
|
|
|
|
|