Cleaning some unnecessary whitespace, overall cleanup of various source codes.

This commit is contained in:
Griatch 2012-03-30 23:47:22 +02:00
parent d4c97d7df8
commit c0322c9eae
27 changed files with 1342 additions and 1318 deletions

View file

@ -1,5 +1,5 @@
# This specifies file types that mercurial should # This specifies file types that mercurial should
# ignore, defined on glob format. # ignore, defined on glob format.
syntax: glob syntax: glob

View file

@ -1,59 +1,63 @@
Evennia Code Style Evennia Code Style
------------------ ------------------
All code submitted or committed to the Evennia project needs to follow the All code submitted or committed to the Evennia project needs to follow
guidelines outlined in Python PEP 8, which may be found at: the guidelines outlined in Python PEP 8, which may be found at:
http://www.python.org/dev/peps/pep-0008/ http://www.python.org/dev/peps/pep-0008/
A quick list of code style points A quick list of code style points
--------------------------------- ---------------------------------
* 4-space indendation, NO TABS! * 4-space indendation, NO TABS!
* Unix line endings. * Unix line endings.
* CamelCase is only used for classes, nothing else. * CamelCase is only used for classes, nothing else.
* All non-global variable names and all function names are to be lowercase, * All non-global variable names and all function names are to be
words separated by underscores. Variable names should always be more than lowercase, words separated by underscores. Variable names should
two letters long. always be more than two letters long.
* Module-level global variables (only) are to be in CAPITAL letters. * Module-level global variables (only) are to be in CAPITAL letters.
* Imports are to be done in this order: * (Evennia-specific): Imports should normally be done in this order:
- Python modules (builtins and modules otherwise unrelated to Evennia) - Python modules (builtins and standard library)
- Twisted - Twisted modules
- Django - Django modules
- Evennia src/ modules - Evennia src/ modules
- Evennia game/ modules - Evennia game/ modules
- Evennia 'ev' API imports
Documentation Documentation
------------- -------------
Remember that Evennia's source code is intended to be read - and will be read - by Remember that Evennia's source code is intended to be read - and will
game admins trying to implement various features. Evennia prides itself with being be read - by game admins trying to implement their game. Evennia
extensively documented. Modules, functions, classes and class methods should all prides itself with being extensively documented. Modules, functions,
start with at least one line of docstring summing up the function's purpose. Ideally classes and class methods should all start with at least one line of
also explain eventual arguments and caveats. Add comments where appropriate. docstring summing up the function's purpose. Ideally also explain
eventual arguments and caveats. Add comments where appropriate.
Pylint Pylint
------ ------
The program 'pylint' (http://www.logilab.org/857) is a useful tool for checking The program 'pylint' (http://www.logilab.org/857) is a useful tool for
your Python code for errors. It will also check how well your code adheres to checking your Python code for errors. It will also check how well your
the PEP 8 guidelines (such as lack of docstrings) and tells you what can be improved. code adheres to the PEP 8 guidelines (such as lack of docstrings) and
tells you what can be improved.
Since pylint cannot catch dynamically created variables used in commands and Since pylint cannot catch dynamically created variables used in
elsewhere in Evennia, one needs to reduce some checks to avoid false errors and commands and elsewhere in Evennia, one needs to reduce some checks to
warnings. For best results, run pylint like this: avoid false errors and warnings. For best results, run pylint like
this:
> pylint --disable=E1101,E0102,F0401,W0232,R0903 filename.py > pylint --disable=E1101,E0102,F0401,W0232,R0903 filename.py
To avoid entering the options every time, you can auto-create a pylintrc file by To avoid entering the options every time, you can auto-create a
using the option --generate-rcfile. You need to dump this output into a pylintrc file by using the option --generate-rcfile. You need to dump
file .pylintrc, for example like this (linux): this output into a file .pylintrc, for example like this (linux):
> pylint --disable=E1101,E0102,F0401,W0232,R0903 --generate-rcfile > ~/.pylintrc > pylint --disable=E1101,E0102,F0401,W0232,R0903 --generate-rcfile > ~/.pylintrc
From now on you can then just run From now on you can then just run
> pylint filename.py > pylint filename.py
Ask Questions! Ask Questions!
-------------- --------------
If any of the rules outlined in PEP 8 or in the sections above doesn't make sense, please If any of the rules outlined in PEP 8 or in the sections above doesn't
don't hesitate to ask on the Evennia mailing list at http://evennia.com. make sense, please don't hesitate to ask on the Evennia mailing list
Keeping our code style uniform makes this project much easier for a wider group at http://evennia.com. Keeping our code style uniform makes this
of people to participate in. project much easier for a wider group of people to participate in.

57
INSTALL
View file

@ -1,9 +1,9 @@
------------- -------------
Evennia Setup Evennia Setup
------------- -------------
You can find the updated and more detailed version of this page on You can find the updated and more detailed version of this page on
http://code.google.com/p/evennia/wiki/GettingStarted http://code.google.com/p/evennia/wiki/GettingStarted
@ -12,45 +12,46 @@ Installation
* Make sure you have/install the prerequsites with minimum versions * Make sure you have/install the prerequsites with minimum versions
listed on http://code.google.com/p/evennia/wiki/GettingStarted: listed on http://code.google.com/p/evennia/wiki/GettingStarted:
- python
- python
- django - django
- twisted + PIL - twisted + PIL
- mercurial - mercurial
- django-south (optional) - django-south (optional, but highly recommended)
* Go to a directory on your harddrive where you want the 'evennia'
directory to be created, for example mud/.
* Go to a directory on your harddrive where you want the "evennia"
directory to be created.
$ cd mud/ $ cd mud/
* Get a copy of the Evennia source: * Get a copy of the Evennia source:
$ hg clone https://code.google.com/p/evennia/ evennia $ hg clone https://code.google.com/p/evennia/ evennia
* Change to the evennia/game directory and run the setup scripts. * Change to the evennia/game directory and run the setup scripts.
$ cd evennia/game $ cd evennia/game
$ python manage.py $ python manage.py
* Edit the new game/settings.py if needed, then run * Edit the new game/settings.py if needed, then run
(make sure to create an admin account when asked): (make sure to create an admin account when asked):
$ python manage.py syncdb $ python manage.py syncdb
* If you use django-south you need to also run * If you use django-south you need to also run
$ python manage.py migrate $ python manage.py migrate
Starting Evennia Starting Evennia
---------------- ----------------
$ python evennia.py -i start * Start the server with
or
$ python evennia.py $ python evennia.py -i start
for a menu of launch options. or run without arguments for a menu of launch options.
See http://code.google.com/p/evennia/wiki/StartStopReload for more info. See http://code.google.com/p/evennia/wiki/StartStopReload for more info.
* Start up your MUD client of choice and point it to your server and port 4000. * Start up your MUD client of choice and point it to your server and port 4000.
@ -59,8 +60,14 @@ Starting Evennia
* Alternatively, you can find the web interface and webclient by * Alternatively, you can find the web interface and webclient by
pointing your web browser to http://localhost:8000. pointing your web browser to http://localhost:8000.
* Login with the email address and password you provided to the syncdb script. * Login with the email address and password you provided when setting up the server.
Welcome to Evennia!
See also "Getting Started" on www.evennia.com for more verbose instructions and
the documentation wiki for further help. Welcome to Evennia!
-------------------
* See www.evennia.com for more information and help with how to
proceed from here.
* For questions, see the discussion group or the chat. Report bugs or
request features via the Issue Tracker.

View file

@ -57,7 +57,7 @@ Licence Regulations
them, or by allowing the Copyright Holder to include your them, or by allowing the Copyright Holder to include your
modifications in the Standard Version of the Package. modifications in the Standard Version of the Package.
2. make other distribution arrangements with the Copyright Holder. 2. make other distribution arrangements with the Copyright Holder.
4. You may distribute the programs of this Package in object code or 4. You may distribute the programs of this Package in object code or
executable form, provided that you do at least ONE of the following: executable form, provided that you do at least ONE of the following:
1. distribute a Standard Version of the executables and library 1. distribute a Standard Version of the executables and library
@ -117,7 +117,7 @@ Licence Regulations
9. The name of the Copyright Holder may not be used to endorse or 9. The name of the Copyright Holder may not be used to endorse or
promote products derived from this software without specific prior promote products derived from this software without specific prior
written permission. written permission.
10. Credits and attributions in the headers of all source and 10. Credits and attributions in the headers of all source and
documentation files must be left intact. documentation files must be left intact.

136
README
View file

@ -1,6 +1,6 @@
----------------------------------- -----------------------------------
Evennia README Evennia README
(http://evennia.com) (http://evennia.com)
Beta hg (mercurial) version Beta hg (mercurial) version
----------------------------------- -----------------------------------
@ -9,39 +9,37 @@
About Evennia About Evennia
------------- -------------
Evennia is a MUD/MUX/MU* server that aims to provide a functional Evennia is a MUD/MUX/MU* development system and server that aims to
bare-bones base for developers. Some of our main features are: provide a functional bare-bones codebase for developers. Some of our main
features are:
* Coded and extended using normal Python modules. * Coded and extended using normal Python modules.
* Extensive web integration due to our use of Django. * Reload code without players logging off
* Runs its own Twisted webserver. Comes with game website and ajax web-browser mud client. * Database handling and network connectivity are abstracted away
* Extensive current and potential connectivity and protocol-support through Twisted. * Extensive web integration due to our use of Django.
* Extremely easy-to-manipulate SQL database back-end via Django * Server runs game website and ajax web-browser mud client out of the box.
(djangoproject.com) * Supports a slew of different connection protocols with Twisted.
* Powerful an extremely extendable bare-bones base system * Extremely extendable to almost any sort of text-based multiplayer game
The Django framework has database abstraction abilities that give us See the INSTALL file for help on setting up and running Evennia.
many features free, such as:
* The codebase will run transparently on MySQL, SQLite, or Postgres
* At the time of this document's writing, our SQL-backed application here
contains 0 lines of SQL. Django's database abstraction layer is absolutely
simple yet very powerful.
* For any model we outline for the server's use, we have the ability to
more or less automatically generate a web-based admin interface for it with
two lines of code. This lets you Create, Update, or Delete entries, as well
limit permissions for those abilities.
* On the web-based side of things, features such as automatic form validation,
abstraction of sessions and cookies, and access to whatever game data you
desire are all attractive.
See the INSTALL file for help on setting up and running Evennia.
Current Status Current Status
-------------- --------------
Nov 2011: March 2012:
Evennia's API has changed and simplified slightly in that the
base-modules where removed from game/gamesrc. Instead admins are
encouraged to explicitly create new modules under game/gamesrc/ when
they want to implement their game - gamesrc/ is empty by default
except for the example folders that contain template files to use for
this purpose. We also added the ev.py file, implementing a new, flat
API. Work is ongoing to add support for mud-specific telnet
extensions, notably the MSDP and GMCP out-of-band extensions. On the
community side, evennia's dev blog was started and linked on planet
Mud-dev aggregator.
Nov 2011:
After creating several different proof-of-concept game systems (in After creating several different proof-of-concept game systems (in
contrib and privately) as well testing lots of things to make sure the contrib and privately) as well testing lots of things to make sure the
implementation is basically sound, we are declaring Evennia out of implementation is basically sound, we are declaring Evennia out of
@ -59,12 +57,12 @@ hackish, flakey and unstable code. With the Portal-Server split, the
Server can simply be rebooted while players connected to the Portal Server can simply be rebooted while players connected to the Portal
remain connected. The two communicates over twisted's AMP protocol. remain connected. The two communicates over twisted's AMP protocol.
May 2011: May 2011:
The new version of Evennia, originally hitting trunk in Aug2010, is The new version of Evennia, originally hitting trunk in Aug2010, is
maturing. All commands from the pre-Aug version, including IRC/IMC2 maturing. All commands from the pre-Aug version, including IRC/IMC2
support works again. An ajax web-client was added earlier in the year, support works again. An ajax web-client was added earlier in the year,
including moving Evennia to be its own webserver (no more need for including moving Evennia to be its own webserver (no more need for
Apache or django-testserver). Contrib-folder added. Apache or django-testserver). Contrib-folder added.
Aug 2010: Aug 2010:
Evennia-griatch-branch is ready for merging with trunk. This marks a Evennia-griatch-branch is ready for merging with trunk. This marks a
@ -74,16 +72,16 @@ ScriptParents and Events) but should hopefully bring everything
together into one consistent package as code development continues. together into one consistent package as code development continues.
May 2010: May 2010:
Evennia is currently being heavily revised and cleaned from Evennia is currently being heavily revised and cleaned from
the years of gradual piecemeal development. It is thus in a very the years of gradual piecemeal development. It is thus in a very
'Alpha' stage at the moment. This means that old code snippets 'Alpha' stage at the moment. This means that old code snippets
will not be backwards compatabile. Changes touch almost all will not be backwards compatabile. Changes touch almost all
parts of Evennia's innards, from the way Objects are handled parts of Evennia's innards, from the way Objects are handled
to Events, Commands and Permissions. to Events, Commands and Permissions.
April 2010: April 2010:
Griatch takes over Maintainership of the Evennia project from Griatch takes over Maintainership of the Evennia project from
the original creator Greg Taylor. the original creator Greg Taylor.
(Earlier revisions, with previous maintainer, go back to 2005) (Earlier revisions, with previous maintainer, go back to 2005)
@ -98,67 +96,67 @@ appreciate all help! Visit either of the following resources:
* Evennia Webpage * Evennia Webpage
http://evennia.com http://evennia.com
* Evennia manual (wiki) * Evennia manual (wiki)
http://code.google.com/p/evennia/wiki/Index http://code.google.com/p/evennia/wiki/Index
* Evennia Code Page (See INSTALL text for installation) * Evennia Code Page (See INSTALL text for installation)
http://code.google.com/p/evennia/source/checkout http://code.google.com/p/evennia/source/checkout
* Bug tracker * Bug tracker
http://code.google.com/p/evennia/issues/list http://code.google.com/p/evennia/issues/list
* IRC channel * IRC channel
visit channel #evennia on the Freenode IRC network visit channel #evennia on the Freenode IRC network
Directory structure Directory structure
------------------- -------------------
evennia evennia
| |
|_______src | ev.py
| |___(engine-related dirs) |_______game (start the server, settings)
|
|_______game (start the server)
| |___gamesrc | |___gamesrc
| |___(game-related dirs) | |___(game-related dirs)
| |_______src
|_______contrib | |___(engine-related dirs)
| |
|_______contrib
| |
|_______docs |_______docs
| |
|_______locales |_______locales
The two main directories you will spend most of your time in ev.py is the API file. It contains easy shortcuts to most
are src/ and game/ (probably mostly game/). of Evennia's functionality. Import ev into a python interpreter
(like ipython) and explore what's available.
Basically src/ contains everything related to The game/ folder is where you develop your game. The root
running the gritty stuff behind the scenes. Unless you are an of this directory contains the settings file and the executables
Evennia developer you should normally make sure never to edit to start the server. Under game/gamesrc you will create the
things in src/, since this is where we push new revisions that modules that will define your game.
may overwrite your changes when you update. You will however
need to have a good feeling for the resources supplied by
the functions in src, since accessing them correctly is the key
to making your dream game come true.
If src/ is the Evennia developer's domain, the game/ directory src/ contains the Evennia library. As a normal user you should
on the other hand contains YOUR game. This is where you will not edit anything in this folder - you will run into mercurial
define and extend the commands, objects and systems of Evennia conflicts as we update things from our end. If you see code
to make your dream game. game/ contains the main server settings you like (such as that of a default command), copy&paste it
and the actual evennia executable to start things. game/gamesrc/ into a new module in game/gamesrc/ instead. If you find that
holds all the templates for creating objects in your virtual world. src/ doesn't support a functionality you need, issue a Feature
request or a bug report appropriately.
If you do add functionality or fix bugs in src yourself, please
consider contributing it to Evennia main to help us improve!
contrib/ contains optional code snippets. These are potentially useful contrib/ contains optional code snippets. These are potentially useful
but deemed to be too game-specific to be part of the server itself. but are deemed to be too game-specific to be part of the server itself.
Modules in contrib are not used unless you yourself decide to import Modules in contrib are not used unless you yourself decide to import
and use them. and use them.
docs/ contain offline versions of the documentation, you can use docs/ contain offline versions of the documentation, you can use
python-sphinx to convert the raw data to nice-looking output for python-sphinx to convert the raw data to nice-looking output for
printing etc. The online wiki is otherwise first to be updated. printing etc. The online wiki is however the most updated version
of the documentation.
locales/ holds translations of the server strings to other languages locales/ holds translations of the server strings to other languages
than English. than English.
With this little first orientation, you should head into the online Enjoy!
Evennia wiki documentation to get going with the codebase.

64
ev.py
View file

@ -2,42 +2,42 @@
Central API for the Evennia MUD/MUX/MU* creation system. Central API for the Evennia MUD/MUX/MU* creation system.
This basically a set of shortcuts to the main modules in src/. Import this This basically a set of shortcuts to the main modules in src/. Import this
from your code or explore it interactively from ./manage.py shell (or a normal from your code or explore it interactively from ./manage.py shell (or a normal
python shell if you set DJANGO_SETTINGS_MODULE manually). python shell if you set DJANGO_SETTINGS_MODULE manually).
Notes: Notes:
1) You should import things explicitly from the root of this module - you can not use 1) You should import things explicitly from the root of this module - you can not use
dot-notation to import deeper. Hence, to access a default command, you can do the dot-notation to import deeper. Hence, to access a default command, you can do the
following: following:
import ev import ev
ev.default_cmds.CmdLook ev.default_cmds.CmdLook
or or
from ev import default_cmds from ev import default_cmds
default_cmds.CmdLook default_cmds.CmdLook
But trying to import CmdLook directly with "from ev.default_cmds import CmdLook" will But trying to import CmdLook directly with "from ev.default_cmds import CmdLook" will
not work since default_cmds is a property on the "ev" module, not a module of its own. not work since default_cmds is a property on the "ev" module, not a module of its own.
2) db_* are shortcuts to initiated versions of Evennia's django database managers (e.g. 2) db_* are shortcuts to initiated versions of Evennia's django database managers (e.g.
db_objects is an alias for ObjectDB.objects). These allows for exploring the database in db_objects is an alias for ObjectDB.objects). These allows for exploring the database in
various ways. Please note that the evennia-specific methods in the managers return various ways. Please note that the evennia-specific methods in the managers return
typeclasses (or lists of typeclasses), whereas the default django ones (filter etc) typeclasses (or lists of typeclasses), whereas the default django ones (filter etc)
return database objects. You can convert between the two easily via dbobj.typeclass and return database objects. You can convert between the two easily via dbobj.typeclass and
typeclass.dbobj, but it's worth to remember this difference. typeclass.dbobj, but it's worth to remember this difference.
3) You -have- to use the create_* functions (shortcuts to src.utils.create) to create new 3) You -have- to use the create_* functions (shortcuts to src.utils.create) to create new
Typeclassed game entities (Objects, Scripts or Players). Just initializing e.g. the Player class will Typeclassed game entities (Objects, Scripts or Players). Just initializing e.g. the Player class will
-not- set up Typeclasses correctly and will lead to errors. Other types of database objects -not- set up Typeclasses correctly and will lead to errors. Other types of database objects
can be created normally, but there are conveniant create_* functions for those too, making can be created normally, but there are conveniant create_* functions for those too, making
some more error checking. some more error checking.
4) "settings" links to Evennia's game/settings file. "settings_full" shows all of django's available 4) "settings" links to Evennia's game/settings file. "settings_full" shows all of django's available
settings. Note that you cannot change settings from here in a meaningful way, you need to update settings. Note that you cannot change settings from here in a meaningful way, you need to update
game/settings.py and restart the server. game/settings.py and restart the server.
5) The API accesses all relevant and most-neeeded functions/classes from src/, but might not 5) The API accesses all relevant and most-neeeded functions/classes from src/, but might not
always include all helper-functions referenced from each such entity. To get to those, access always include all helper-functions referenced from each such entity. To get to those, access
the modules in src/ directly. You can always do this anyway, if you do not want to go through the modules in src/ directly. You can always do this anyway, if you do not want to go through
this API. this API.
""" """
@ -53,7 +53,7 @@ if __name__ == "__main__":
| not be run on its own, but be imported and accessed as described | not be run on its own, but be imported and accessed as described
| above. | above.
| |
| To start the Evennia server, see game/manage.py and game/evennia.py. | To start the Evennia server, see game/manage.py and game/evennia.py.
| More help can be found at http://www.evennia.com. | More help can be found at http://www.evennia.com.
""" """
print info print info
@ -86,11 +86,11 @@ del sys, os
README = __doc__ README = __doc__
# help entries # help entries
from src.help.models import HelpEntry from src.help.models import HelpEntry
db_helpentries = HelpEntry.objects db_helpentries = HelpEntry.objects
# players # players
from src.players.player import Player from src.players.player import Player
from src.players.models import PlayerDB, PlayerAttribute, PlayerNick from src.players.models import PlayerDB, PlayerAttribute, PlayerNick
db_players = PlayerDB.objects db_players = PlayerDB.objects
@ -106,8 +106,8 @@ from src.commands import default as default_cmds
class SystemCmds(object): class SystemCmds(object):
""" """
Creating commands with keys set to these constants will make Creating commands with keys set to these constants will make
them system commands called as a replacement by the parser when them system commands called as a replacement by the parser when
special situations occur. If not defined, the hard-coded special situations occur. If not defined, the hard-coded
responses in the server are used. responses in the server are used.
CMD_NOINPUT - no input was given on command line CMD_NOINPUT - no input was given on command line
@ -116,7 +116,7 @@ class SystemCmds(object):
CMD_CHANNEL - the command name is a channel name CMD_CHANNEL - the command name is a channel name
CMD_LOGINSTART - this command will be called as the very CMD_LOGINSTART - this command will be called as the very
first command when a player connects to first command when a player connects to
the server. the server.
""" """
from src.commands import cmdhandler from src.commands import cmdhandler
@ -125,13 +125,13 @@ class SystemCmds(object):
CMD_MULTIMATCH = cmdhandler.CMD_MULTIMATCH CMD_MULTIMATCH = cmdhandler.CMD_MULTIMATCH
CMD_CHANNEL = cmdhandler.CMD_CHANNEL CMD_CHANNEL = cmdhandler.CMD_CHANNEL
CMD_LOGINSTART = cmdhandler.CMD_LOGINSTART CMD_LOGINSTART = cmdhandler.CMD_LOGINSTART
del cmdhandler del cmdhandler
syscmdkeys = SystemCmds() syscmdkeys = SystemCmds()
# locks # locks
from src.locks import lockfuncs from src.locks import lockfuncs
# scripts # scripts
from src.scripts.scripts import Script from src.scripts.scripts import Script
from src.scripts.models import ScriptDB, ScriptAttribute from src.scripts.models import ScriptDB, ScriptAttribute
db_scripts = ScriptDB.objects db_scripts = ScriptDB.objects
@ -154,7 +154,7 @@ db_objects = ObjectDB.objects
#db_objattrs = ObjAttribute.objects #db_objattrs = ObjAttribute.objects
del ObjAttribute, Alias, ObjectNick, ObjectDB del ObjAttribute, Alias, ObjectNick, ObjectDB
# server # server
from src.server.models import ServerConfig from src.server.models import ServerConfig
db_serverconfigs = ServerConfig.objects db_serverconfigs = ServerConfig.objects
del ServerConfig del ServerConfig

View file

@ -4,5 +4,5 @@
django >= 1.2 django >= 1.2
twisted >= 10.0 twisted >= 10.0
pil pil
south >= 0.7 south >= 0.7

View file

@ -1,6 +1,6 @@
""" """
This special Python config file sets the default encoding for This special Python config file sets the default encoding for
the codebase to UTF-8 instead of ascii. This allows for just the codebase to UTF-8 instead of ascii. This allows for just
about any language to be used in-game. about any language to be used in-game.
It is not advisable to change the value set below, as It is not advisable to change the value set below, as

View file

@ -2,35 +2,35 @@
Command handler Command handler
This module contains the infrastructure for accepting commands on the This module contains the infrastructure for accepting commands on the
command line. The process is as follows: command line. The process is as follows:
1) The calling object (caller) inputs a string and triggers the command parsing system. 1) The calling object (caller) inputs a string and triggers the command parsing system.
2) The system checks the state of the caller - loggedin or not 2) The system checks the state of the caller - loggedin or not
3) If no command string was supplied, we search the merged cmdset for system command CMD_NOINPUT 3) If no command string was supplied, we search the merged cmdset for system command CMD_NOINPUT
and branches to execute that. --> Finished and branches to execute that. --> Finished
4) Cmdsets are gathered from different sources (in order of dropping priority): 4) Cmdsets are gathered from different sources (in order of dropping priority):
channels - all available channel names are auto-created into a cmdset, to allow channels - all available channel names are auto-created into a cmdset, to allow
for giving the channel name and have the following immediately for giving the channel name and have the following immediately
sent to the channel. The sending is performed by the CMD_CHANNEL sent to the channel. The sending is performed by the CMD_CHANNEL
system command. system command.
object cmdsets - all objects at caller's location are scanned for non-empty object cmdsets - all objects at caller's location are scanned for non-empty
cmdsets. This includes cmdsets on exits. cmdsets. This includes cmdsets on exits.
caller - the caller is searched for its own currently active cmdset. caller - the caller is searched for its own currently active cmdset.
player - lastly the cmdsets defined on caller.player are added. player - lastly the cmdsets defined on caller.player are added.
5) All the gathered cmdsets (if more than one) are merged into one using the cmdset priority rules. 5) All the gathered cmdsets (if more than one) are merged into one using the cmdset priority rules.
6) If merged cmdset is empty, raise NoCmdSet exception (this should not happen, at least the 6) If merged cmdset is empty, raise NoCmdSet exception (this should not happen, at least the
player should have a default cmdset available at all times). --> Finished player should have a default cmdset available at all times). --> Finished
7) The raw input string is parsed using the parser defined by settings.COMMAND_PARSER. It 7) The raw input string is parsed using the parser defined by settings.COMMAND_PARSER. It
uses the available commands from the merged cmdset to know which commands to look for and uses the available commands from the merged cmdset to know which commands to look for and
returns one or many matches. returns one or many matches.
8) If match list is empty, branch to system command CMD_NOMATCH --> Finished 8) If match list is empty, branch to system command CMD_NOMATCH --> Finished
9) If match list has more than one element, branch to system command CMD_MULTIMATCH --> Finished 9) If match list has more than one element, branch to system command CMD_MULTIMATCH --> Finished
10) A single match was found. If this is a channel-command (i.e. the command name is that of a channel), 10) A single match was found. If this is a channel-command (i.e. the command name is that of a channel),
branch to CMD_CHANNEL --> Finished branch to CMD_CHANNEL --> Finished
11) At this point we have found a normal command. We assign useful variables to it that 11) At this point we have found a normal command. We assign useful variables to it that
will be available to the command coder at run-time. will be available to the command coder at run-time.
12) We have a unique cmdobject, primed for use. Call all hooks: 12) We have a unique cmdobject, primed for use. Call all hooks:
at_pre_cmd(), cmdobj.parse(), cmdobj.func() and finally at_post_cmd(). at_pre_cmd(), cmdobj.parse(), cmdobj.func() and finally at_post_cmd().
""" """
@ -41,19 +41,19 @@ from twisted.internet.defer import inlineCallbacks, returnValue
from django.conf import settings from django.conf import settings
from src.comms.channelhandler import CHANNELHANDLER from src.comms.channelhandler import CHANNELHANDLER
from src.commands.cmdsethandler import import_cmdset from src.commands.cmdsethandler import import_cmdset
from src.utils import logger, utils from src.utils import logger, utils
from src.commands.cmdparser import at_multimatch_cmd from src.commands.cmdparser import at_multimatch_cmd
#This switches the command parser to a user-defined one. #This switches the command parser to a user-defined one.
# You have to restart the server for this to take effect. # You have to restart the server for this to take effect.
COMMAND_PARSER = utils.mod_import(*settings.COMMAND_PARSER.rsplit('.', 1)) COMMAND_PARSER = utils.mod_import(*settings.COMMAND_PARSER.rsplit('.', 1))
# There are a few system-hardcoded command names. These # There are a few system-hardcoded command names. These
# allow for custom behaviour when the command handler hits # allow for custom behaviour when the command handler hits
# special situations -- it then calls a normal Command # special situations -- it then calls a normal Command
# that you can customize! # that you can customize!
# Import these variables and use them rather than trying # Import these variables and use them rather than trying
# to remember the actual string constants. # to remember the actual string constants.
CMD_NOINPUT = "__noinput_command" CMD_NOINPUT = "__noinput_command"
CMD_NOMATCH = "__nomatch_command" CMD_NOMATCH = "__nomatch_command"
@ -61,12 +61,12 @@ CMD_MULTIMATCH = "__multimatch_command"
CMD_CHANNEL = "__send_to_channel_command" CMD_CHANNEL = "__send_to_channel_command"
# this is the name of the command the engine calls when the player # this is the name of the command the engine calls when the player
# connects. It is expected to show the login screen. # connects. It is expected to show the login screen.
CMD_LOGINSTART = "__unloggedin_look_command" CMD_LOGINSTART = "__unloggedin_look_command"
class NoCmdSets(Exception): class NoCmdSets(Exception):
"No cmdsets found. Critical error." "No cmdsets found. Critical error."
pass pass
class ExecSystemCommand(Exception): class ExecSystemCommand(Exception):
"Run a system command" "Run a system command"
def __init__(self, syscmd, sysarg): def __init__(self, syscmd, sysarg):
@ -77,10 +77,10 @@ class ExecSystemCommand(Exception):
@inlineCallbacks @inlineCallbacks
def get_and_merge_cmdsets(caller): def get_and_merge_cmdsets(caller):
""" """
Gather all relevant cmdsets and merge them. Note Gather all relevant cmdsets and merge them. Note
that this is only relevant for logged-in callers. that this is only relevant for logged-in callers.
""" """
# The calling object's cmdset # The calling object's cmdset
try: try:
yield caller.at_cmdset_get() yield caller.at_cmdset_get()
except Exception: except Exception:
@ -89,17 +89,17 @@ def get_and_merge_cmdsets(caller):
caller_cmdset = caller.cmdset.current caller_cmdset = caller.cmdset.current
except AttributeError: except AttributeError:
caller_cmdset = None caller_cmdset = None
# Create cmdset for all player's available channels # Create cmdset for all player's available channels
channel_cmdset = None channel_cmdset = None
if not caller_cmdset.no_channels: if not caller_cmdset.no_channels:
channel_cmdset = yield CHANNELHANDLER.get_cmdset(caller) channel_cmdset = yield CHANNELHANDLER.get_cmdset(caller)
# Gather cmdsets from location, objects in location or carried # Gather cmdsets from location, objects in location or carried
local_objects_cmdsets = [None] local_objects_cmdsets = [None]
location = None location = None
if hasattr(caller, "location"): if hasattr(caller, "location"):
location = caller.location location = caller.location
if location and not caller_cmdset.no_objs: if location and not caller_cmdset.no_objs:
# Gather all cmdsets stored on objects in the room and # Gather all cmdsets stored on objects in the room and
# also in the caller's inventory and the location itself # also in the caller's inventory and the location itself
@ -113,19 +113,19 @@ def get_and_merge_cmdsets(caller):
local_objects_cmdsets = yield [obj.cmdset.current for obj in local_objlist local_objects_cmdsets = yield [obj.cmdset.current for obj in local_objlist
if (obj.cmdset.current and obj.locks.check(caller, 'call', no_superuser_bypass=True))] if (obj.cmdset.current and obj.locks.check(caller, 'call', no_superuser_bypass=True))]
for cset in local_objects_cmdsets: for cset in local_objects_cmdsets:
#This is necessary for object sets, or we won't be able to separate #This is necessary for object sets, or we won't be able to separate
#the command sets from each other in a busy room. #the command sets from each other in a busy room.
cset.old_duplicates = cset.duplicates cset.old_duplicates = cset.duplicates
cset.duplicates = True cset.duplicates = True
# Player object's commandsets # Player object's commandsets
try: try:
player_cmdset = caller.player.cmdset.current player_cmdset = caller.player.cmdset.current
except AttributeError: except AttributeError:
player_cmdset = None player_cmdset = None
cmdsets = [caller_cmdset] + [player_cmdset] + [channel_cmdset] + local_objects_cmdsets cmdsets = [caller_cmdset] + [player_cmdset] + [channel_cmdset] + local_objects_cmdsets
# weed out all non-found sets # weed out all non-found sets
cmdsets = yield [cmdset for cmdset in cmdsets if cmdset] cmdsets = yield [cmdset for cmdset in cmdsets if cmdset]
# sort cmdsets after reverse priority (highest prio are merged in last) # sort cmdsets after reverse priority (highest prio are merged in last)
cmdsets = yield sorted(cmdsets, key=lambda x: x.priority) cmdsets = yield sorted(cmdsets, key=lambda x: x.priority)
@ -134,9 +134,9 @@ def get_and_merge_cmdsets(caller):
# Merge all command sets into one, beginning with the lowest-prio one # Merge all command sets into one, beginning with the lowest-prio one
cmdset = cmdsets.pop(0) cmdset = cmdsets.pop(0)
for merging_cmdset in cmdsets: for merging_cmdset in cmdsets:
#print "<%s(%s,%s)> onto <%s(%s,%s)>" % (merging_cmdset.key, merging_cmdset.priority, merging_cmdset.mergetype, #print "<%s(%s,%s)> onto <%s(%s,%s)>" % (merging_cmdset.key, merging_cmdset.priority, merging_cmdset.mergetype,
# cmdset.key, cmdset.priority, cmdset.mergetype) # cmdset.key, cmdset.priority, cmdset.mergetype)
cmdset = yield merging_cmdset + cmdset cmdset = yield merging_cmdset + cmdset
else: else:
cmdset = None cmdset = None
@ -146,26 +146,26 @@ def get_and_merge_cmdsets(caller):
returnValue(cmdset) returnValue(cmdset)
# Main command-handler function # Main command-handler function
@inlineCallbacks @inlineCallbacks
def cmdhandler(caller, raw_string, testing=False): def cmdhandler(caller, raw_string, testing=False):
""" """
This is the main function to handle any string sent to the engine. This is the main function to handle any string sent to the engine.
caller - calling object caller - calling object
raw_string - the command string given on the command line raw_string - the command string given on the command line
testing - if we should actually execute the command or not. testing - if we should actually execute the command or not.
if True, the command instance will be returned instead. if True, the command instance will be returned instead.
Note that this function returns a deferred! Note that this function returns a deferred!
""" """
try: # catch bugs in cmdhandler itself try: # catch bugs in cmdhandler itself
try: # catch special-type commands try: # catch special-type commands
cmdset = yield get_and_merge_cmdsets(caller) cmdset = yield get_and_merge_cmdsets(caller)
if not cmdset: if not cmdset:
# this is bad and shouldn't happen. # this is bad and shouldn't happen.
raise NoCmdSets raise NoCmdSets
raw_string = raw_string.strip() raw_string = raw_string.strip()
@ -182,7 +182,7 @@ def cmdhandler(caller, raw_string, testing=False):
if not matches: if not matches:
# No commands match our entered command # No commands match our entered command
syscmd = yield cmdset.get(CMD_NOMATCH) syscmd = yield cmdset.get(CMD_NOMATCH)
if syscmd: if syscmd:
sysarg = raw_string sysarg = raw_string
else: else:
sysarg = "Huh? (Type \"help\" for help)" sysarg = "Huh? (Type \"help\" for help)"
@ -197,43 +197,43 @@ def cmdhandler(caller, raw_string, testing=False):
else: else:
sysarg = yield at_multimatch_cmd(caller, matches) sysarg = yield at_multimatch_cmd(caller, matches)
raise ExecSystemCommand(syscmd, sysarg) raise ExecSystemCommand(syscmd, sysarg)
# At this point, we have a unique command match. # At this point, we have a unique command match.
match = matches[0] match = matches[0]
cmdname, args, cmd = match[0], match[1], match[2] cmdname, args, cmd = match[0], match[1], match[2]
# Check if this is a Channel match. # Check if this is a Channel match.
if hasattr(cmd, 'is_channel') and cmd.is_channel: if hasattr(cmd, 'is_channel') and cmd.is_channel:
# even if a user-defined syscmd is not defined, the # even if a user-defined syscmd is not defined, the
# found cmd is already a system command in its own right. # found cmd is already a system command in its own right.
syscmd = yield cmdset.get(CMD_CHANNEL) syscmd = yield cmdset.get(CMD_CHANNEL)
if syscmd: if syscmd:
# replace system command with custom version # replace system command with custom version
cmd = syscmd cmd = syscmd
sysarg = "%s:%s" % (cmdname, args) sysarg = "%s:%s" % (cmdname, args)
raise ExecSystemCommand(cmd, sysarg) raise ExecSystemCommand(cmd, sysarg)
# A normal command. # A normal command.
# Assign useful variables to the instance # Assign useful variables to the instance
cmd.caller = caller cmd.caller = caller
cmd.cmdstring = cmdname cmd.cmdstring = cmdname
cmd.args = args cmd.args = args
cmd.cmdset = cmdset cmd.cmdset = cmdset
if hasattr(cmd, 'obj') and hasattr(cmd.obj, 'scripts'): if hasattr(cmd, 'obj') and hasattr(cmd.obj, 'scripts'):
# cmd.obj are automatically made available. # cmd.obj are automatically made available.
# we make sure to validate its scripts. # we make sure to validate its scripts.
yield cmd.obj.scripts.validate() yield cmd.obj.scripts.validate()
if testing: if testing:
# only return the command instance # only return the command instance
returnValue(cmd) returnValue(cmd)
# pre-command hook # pre-command hook
yield cmd.at_pre_cmd() yield cmd.at_pre_cmd()
# Parse and execute # Parse and execute
yield cmd.parse() yield cmd.parse()
# (return value is normally None) # (return value is normally None)
ret = yield cmd.func() ret = yield cmd.func()
@ -246,15 +246,15 @@ def cmdhandler(caller, raw_string, testing=False):
# accessible by the next command. # accessible by the next command.
caller.ndb.last_cmd = yield copy(cmd) caller.ndb.last_cmd = yield copy(cmd)
else: else:
caller.ndb.last_cmd = None caller.ndb.last_cmd = None
# Done! This returns a deferred. By default, Evennia does # Done! This returns a deferred. By default, Evennia does
# not use this at all. # not use this at all.
returnValue(ret) returnValue(ret)
except ExecSystemCommand, exc: except ExecSystemCommand, exc:
# Not a normal command: run a system command, if available, # Not a normal command: run a system command, if available,
# or fall back to a return string. # or fall back to a return string.
syscmd = exc.syscmd syscmd = exc.syscmd
sysarg = exc.sysarg sysarg = exc.sysarg
if syscmd: if syscmd:
@ -265,9 +265,9 @@ def cmdhandler(caller, raw_string, testing=False):
if hasattr(syscmd, 'obj') and hasattr(syscmd.obj, 'scripts'): if hasattr(syscmd, 'obj') and hasattr(syscmd.obj, 'scripts'):
# cmd.obj is automatically made available. # cmd.obj is automatically made available.
# we make sure to validate its scripts. # we make sure to validate its scripts.
yield syscmd.obj.scripts.validate() yield syscmd.obj.scripts.validate()
if testing: if testing:
# only return the command instance # only return the command instance
returnValue(syscmd) returnValue(syscmd)
@ -282,21 +282,21 @@ def cmdhandler(caller, raw_string, testing=False):
except NoCmdSets: except NoCmdSets:
# Critical error. # Critical error.
string = "No command sets found! This is a sign of a critical bug.\n" string = "No command sets found! This is a sign of a critical bug.\n"
string += "The error was logged.\n" string += "The error was logged.\n"
string += "If logging out/in doesn't solve the problem, try to " string += "If logging out/in doesn't solve the problem, try to "
string += "contact the server admin through some other means " string += "contact the server admin through some other means "
string += "for assistance." string += "for assistance."
caller.msg(string) caller.msg(string)
logger.log_errmsg("No cmdsets found: %s" % caller) logger.log_errmsg("No cmdsets found: %s" % caller)
except Exception: except Exception:
# We should not end up here. If we do, it's a programming bug. # We should not end up here. If we do, it's a programming bug.
string = "%s\nAbove traceback is from an untrapped error." string = "%s\nAbove traceback is from an untrapped error."
string += " Please file a bug report." string += " Please file a bug report."
logger.log_trace(string) logger.log_trace(string)
caller.msg(string % format_exc()) caller.msg(string % format_exc())
except Exception: except Exception:
# This catches exceptions in cmdhandler exceptions themselves # This catches exceptions in cmdhandler exceptions themselves
string = "%s\nAbove traceback is from a Command handler bug." string = "%s\nAbove traceback is from a Command handler bug."
string += " Please contact an admin and/or file a bug report." string += " Please contact an admin and/or file a bug report."

View file

@ -9,9 +9,9 @@ from src.utils.logger import log_trace
def cmdparser(raw_string, cmdset, caller, match_index=None): def cmdparser(raw_string, cmdset, caller, match_index=None):
""" """
This function is called by the cmdhandler once it has This function is called by the cmdhandler once it has
gathered all valid cmdsets for the calling player. raw_string gathered all valid cmdsets for the calling player. raw_string
is the unparsed text entered by the caller. is the unparsed text entered by the caller.
The cmdparser understand the following command combinations (where The cmdparser understand the following command combinations (where
[] marks optional parts. [] marks optional parts.
@ -19,20 +19,20 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
[cmdname[ cmdname2 cmdname3 ...] [the rest] [cmdname[ cmdname2 cmdname3 ...] [the rest]
A command may consist of any number of space-separated words of any A command may consist of any number of space-separated words of any
length, and contain any character. length, and contain any character.
The parser makes use of the cmdset to find command candidates. The The parser makes use of the cmdset to find command candidates. The
parser return a list of matches. Each match is a tuple with its parser return a list of matches. Each match is a tuple with its
first three elements being the parsed cmdname (lower case), first three elements being the parsed cmdname (lower case),
the remaining arguments, and the matched cmdobject from the cmdset. the remaining arguments, and the matched cmdobject from the cmdset.
""" """
def create_match(cmdname, string, cmdobj): def create_match(cmdname, string, cmdobj):
""" """
Evaluates the quality of a match by counting how many chars of cmdname Evaluates the quality of a match by counting how many chars of cmdname
matches string (counting from beginning of string). We also calculate matches string (counting from beginning of string). We also calculate
a ratio from 0-1 describing how much cmdname matches string. a ratio from 0-1 describing how much cmdname matches string.
We return a tuple (cmdname, count, ratio, args, cmdobj). We return a tuple (cmdname, count, ratio, args, cmdobj).
""" """
cmdlen, strlen = len(cmdname), len(string) cmdlen, strlen = len(cmdname), len(string)
@ -41,13 +41,13 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
return (cmdname, args, cmdobj, cmdlen, mratio) return (cmdname, args, cmdobj, cmdlen, mratio)
if not raw_string: if not raw_string:
return None return None
matches = [] matches = []
# match everything that begins with a matching cmdname. # match everything that begins with a matching cmdname.
l_raw_string = raw_string.lower() l_raw_string = raw_string.lower()
for cmd in cmdset: for cmd in cmdset:
try: try:
matches.extend([create_match(cmdname, raw_string, cmd) matches.extend([create_match(cmdname, raw_string, cmd)
for cmdname in [cmd.key] + cmd.aliases for cmdname in [cmd.key] + cmd.aliases
@ -58,13 +58,13 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
log_trace() log_trace()
if not matches: if not matches:
# no matches found. # no matches found.
if '-' in raw_string: if '-' in raw_string:
# This could be due to the user trying to identify the # This could be due to the user trying to identify the
# command with a #num-<command> style syntax. # command with a #num-<command> style syntax.
mindex, new_raw_string = raw_string.split("-", 1) mindex, new_raw_string = raw_string.split("-", 1)
if mindex.isdigit(): if mindex.isdigit():
mindex = int(mindex) - 1 mindex = int(mindex) - 1
# feed result back to parser iteratively # feed result back to parser iteratively
return cmdparser(new_raw_string, cmdset, caller, match_index=mindex) return cmdparser(new_raw_string, cmdset, caller, match_index=mindex)
@ -85,22 +85,22 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
if len(matches) > 1: if len(matches) > 1:
# still multiple matches. Fall back to ratio-based quality. # still multiple matches. Fall back to ratio-based quality.
matches = sorted(matches, key=lambda m: m[4]) matches = sorted(matches, key=lambda m: m[4])
# only pick the highest rated ratio match # only pick the highest rated ratio match
quality = [mat[4] for mat in matches] quality = [mat[4] for mat in matches]
matches = matches[-quality.count(quality[-1]):] matches = matches[-quality.count(quality[-1]):]
if len(matches) > 1 and match_index != None and 0 <= match_index < len(matches): if len(matches) > 1 and match_index != None and 0 <= match_index < len(matches):
# We couldn't separate match by quality, but we have an index argument to # We couldn't separate match by quality, but we have an index argument to
# tell us which match to use. # tell us which match to use.
matches = [matches[match_index]] matches = [matches[match_index]]
# no matter what we have at this point, we have to return it. # no matter what we have at this point, we have to return it.
return matches return matches
#------------------------------------------------------------ #------------------------------------------------------------
# Search parsers and support methods # Search parsers and support methods
#------------------------------------------------------------ #------------------------------------------------------------
# #
# Default functions for formatting and processing searches. # Default functions for formatting and processing searches.
@ -109,7 +109,7 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
# replace from the settings file by setting the variables # replace from the settings file by setting the variables
# #
# SEARCH_AT_RESULTERROR_HANDLER # SEARCH_AT_RESULTERROR_HANDLER
# SEARCH_MULTIMATCH_PARSER # SEARCH_MULTIMATCH_PARSER
# #
# The the replacing modules must have the same inputs and outputs as # The the replacing modules must have the same inputs and outputs as
# those in this module. # those in this module.
@ -118,57 +118,57 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
def at_search_result(msg_obj, ostring, results, global_search=False): def at_search_result(msg_obj, ostring, results, global_search=False):
""" """
Called by search methods after a result of any type has been found. Called by search methods after a result of any type has been found.
Takes a search result (a list) and Takes a search result (a list) and
formats eventual errors. formats eventual errors.
msg_obj - object to receive feedback. msg_obj - object to receive feedback.
ostring - original search string ostring - original search string
results - list of found matches (0, 1 or more) results - list of found matches (0, 1 or more)
global_search - if this was a global_search or not global_search - if this was a global_search or not
(if it is, there might be an idea of supplying (if it is, there might be an idea of supplying
dbrefs instead of only numbers) dbrefs instead of only numbers)
Multiple matches are returned to the searching object Multiple matches are returned to the searching object
as as
1-object 1-object
2-object 2-object
3-object 3-object
etc etc
""" """
string = "" string = ""
if not results: if not results:
# no results. # no results.
string = "Could not find '%s'." % ostring string = "Could not find '%s'." % ostring
results = None results = None
elif len(results) > 1: elif len(results) > 1:
# we have more than one match. We will display a # we have more than one match. We will display a
# list of the form 1-objname, 2-objname etc. # list of the form 1-objname, 2-objname etc.
# check if the msg_object may se dbrefs # check if the msg_object may se dbrefs
show_dbref = global_search show_dbref = global_search
string += "More than one match for '%s'" % ostring string += "More than one match for '%s'" % ostring
string += " (please narrow target):" string += " (please narrow target):"
for num, result in enumerate(results): for num, result in enumerate(results):
invtext = "" invtext = ""
dbreftext = "" dbreftext = ""
if hasattr(result, "location") and result.location == msg_obj: if hasattr(result, "location") and result.location == msg_obj:
invtext = " (carried)" invtext = " (carried)"
if show_dbref: if show_dbref:
dbreftext = "(#%i)" % result.id dbreftext = "(#%i)" % result.id
string += "\n %i-%s%s%s" % (num+1, result.name, string += "\n %i-%s%s%s" % (num+1, result.name,
dbreftext, invtext) dbreftext, invtext)
results = None results = None
else: else:
# we have exactly one match. # we have exactly one match.
results = results[0] results = results[0]
if string: if string:
msg_obj.msg(string.strip()) msg_obj.msg(string.strip())
return results return results
def at_multimatch_input(ostring): def at_multimatch_input(ostring):
""" """
@ -186,9 +186,9 @@ def at_multimatch_input(ostring):
the lowest number, rather than 0 as in Python). the lowest number, rather than 0 as in Python).
This parser version will identify search strings on the following This parser version will identify search strings on the following
forms forms
2-object 2-object
This will be parsed to (2, "object") and, if applicable, will tell This will be parsed to (2, "object") and, if applicable, will tell
the engine to pick the second from a list of same-named matches of the engine to pick the second from a list of same-named matches of
@ -197,7 +197,7 @@ def at_multimatch_input(ostring):
Ex for use in a game session: Ex for use in a game session:
> look > look
You see: ball, ball, ball and ball. You see: ball, ball, ball and ball.
> get ball > get ball
There where multiple matches for ball: There where multiple matches for ball:
1-ball 1-ball
@ -205,7 +205,7 @@ def at_multimatch_input(ostring):
3-ball 3-ball
4-ball 4-ball
> get 3-ball > get 3-ball
You get the ball. You get the ball.
""" """
@ -213,7 +213,7 @@ def at_multimatch_input(ostring):
return (None, ostring) return (None, ostring)
if not '-' in ostring: if not '-' in ostring:
return (None, ostring) return (None, ostring)
try: try:
index = ostring.find('-') index = ostring.find('-')
number = int(ostring[:index])-1 number = int(ostring[:index])-1
return (number, ostring[index+1:]) return (number, ostring[index+1:])
@ -229,7 +229,7 @@ def at_multimatch_cmd(caller, matches):
Format multiple command matches to a useful error. Format multiple command matches to a useful error.
""" """
string = "There where multiple matches:" string = "There where multiple matches:"
for num, match in enumerate(matches): for num, match in enumerate(matches):
# each match is a tuple (candidate, cmd) # each match is a tuple (candidate, cmd)
cmdname, arg, cmd, dum, dum = match cmdname, arg, cmd, dum, dum = match
@ -237,7 +237,7 @@ def at_multimatch_cmd(caller, matches):
if is_channel: if is_channel:
is_channel = " (channel)" is_channel = " (channel)"
else: else:
is_channel = "" is_channel = ""
if cmd.is_exit and cmd.destination: if cmd.is_exit and cmd.destination:
is_exit = " (exit to %s)" % cmd.destination is_exit = " (exit to %s)" % cmd.destination
else: else:

View file

@ -10,14 +10,14 @@ on-the-fly CmdSet that is some combination of the
previous ones. Their function are borrowed to a large parts from mathematical previous ones. Their function are borrowed to a large parts from mathematical
Set theory, it should not be much of a problem to understand. Set theory, it should not be much of a problem to understand.
See CmdHandler for practical examples on how to apply cmdsets See CmdHandler for practical examples on how to apply cmdsets
together to create interesting in-game effects. together to create interesting in-game effects.
""" """
import copy import copy
from src.utils.utils import inherits_from, is_iter from src.utils.utils import inherits_from, is_iter
RECURSIVE_PROTECTION = False RECURSIVE_PROTECTION = False
class CmdSetMeta(type): class CmdSetMeta(type):
""" """
@ -29,14 +29,14 @@ class CmdSetMeta(type):
Fixes some things in the cmdclass Fixes some things in the cmdclass
""" """
# by default we key the cmdset the same as the # by default we key the cmdset the same as the
# name of its class. # name of its class.
if not hasattr(mcs, 'key') or not mcs.key: if not hasattr(mcs, 'key') or not mcs.key:
mcs.key = mcs.__name__ mcs.key = mcs.__name__
mcs.path = "%s.%s" % (mcs.__module__, mcs.__name__) mcs.path = "%s.%s" % (mcs.__module__, mcs.__name__)
if not type(mcs.key_mergetypes) == dict: if not type(mcs.key_mergetypes) == dict:
mcs.key_mergetypes = {} mcs.key_mergetypes = {}
super(CmdSetMeta, mcs).__init__(*args, **kwargs) super(CmdSetMeta, mcs).__init__(*args, **kwargs)
@ -46,12 +46,12 @@ def union(cmdset_a, cmdset_b, duplicates=False):
"C = A U B. CmdSet A is assumed to have higher priority" "C = A U B. CmdSet A is assumed to have higher priority"
cmdset_c = cmdset_a.copy_this() cmdset_c = cmdset_a.copy_this()
# we make copies, not refs by use of [:] # we make copies, not refs by use of [:]
cmdset_c.commands = cmdset_a.commands[:] cmdset_c.commands = cmdset_a.commands[:]
if duplicates and cmdset_a.priority == cmdset_b.priority: if duplicates and cmdset_a.priority == cmdset_b.priority:
cmdset_c.commands.extend(cmdset_b.commands) cmdset_c.commands.extend(cmdset_b.commands)
else: else:
cmdset_c.commands.extend([cmd for cmd in cmdset_b if not cmd in cmdset_a]) cmdset_c.commands.extend([cmd for cmd in cmdset_b if not cmd in cmdset_a])
return cmdset_c return cmdset_c
def intersect(cmdset_a, cmdset_b, duplicates=False): def intersect(cmdset_a, cmdset_b, duplicates=False):
"C = A (intersect) B. A is assumed higher priority" "C = A (intersect) B. A is assumed higher priority"
@ -62,7 +62,7 @@ def intersect(cmdset_a, cmdset_b, duplicates=False):
cmdset_c.add(cmdset_b.get(cmd)) cmdset_c.add(cmdset_b.get(cmd))
else: else:
cmdset_c.commands = [cmd for cmd in cmdset_a if cmd in cmdset_b] cmdset_c.commands = [cmd for cmd in cmdset_a if cmd in cmdset_b]
return cmdset_c return cmdset_c
def replace(cmdset_a, cmdset_b, cmdset_c): def replace(cmdset_a, cmdset_b, cmdset_c):
"C = A + B where the result is A." "C = A + B where the result is A."
@ -80,24 +80,24 @@ def instantiate(cmd):
""" """
checks so that object is an instantiated command checks so that object is an instantiated command
and not, say a cmdclass. If it is, instantiate it. and not, say a cmdclass. If it is, instantiate it.
Other types, like strings, are passed through. Other types, like strings, are passed through.
""" """
try: try:
return cmd() return cmd()
except TypeError: except TypeError:
return cmd return cmd
class CmdSet(object): class CmdSet(object):
""" """
This class describes a unique cmdset that understands priorities. CmdSets This class describes a unique cmdset that understands priorities. CmdSets
can be merged and made to perform various set operations on each other. can be merged and made to perform various set operations on each other.
CmdSets have priorities that affect which of their ingoing commands gets used. CmdSets have priorities that affect which of their ingoing commands gets used.
In the examples, cmdset A always have higher priority than cmdset B.
key - the name of the cmdset. This can be used on its own for game operations In the examples, cmdset A always have higher priority than cmdset B.
mergetype (partly from Set theory): key - the name of the cmdset. This can be used on its own for game operations
mergetype (partly from Set theory):
Union - The two command sets are merged so that as many Union - The two command sets are merged so that as many
commands as possible of each cmdset ends up in the commands as possible of each cmdset ends up in the
@ -125,13 +125,13 @@ class CmdSet(object):
excempt from all merge operations - they are excempt from all merge operations - they are
ALWAYS included across mergers and only affected ALWAYS included across mergers and only affected
if same-named system commands replace them. if same-named system commands replace them.
priority- All cmdsets are always merged in pairs of two so that priority- All cmdsets are always merged in pairs of two so that
the higher set's mergetype is applied to the the higher set's mergetype is applied to the
lower-priority cmdset. Default commands have priority 0, lower-priority cmdset. Default commands have priority 0,
high-priority ones like Exits and Channels have 10 and 9. Priorities high-priority ones like Exits and Channels have 10 and 9. Priorities
can be negative as well to give default commands preference. can be negative as well to give default commands preference.
duplicates - determines what happens when two sets of equal duplicates - determines what happens when two sets of equal
priority merge. Default has the first of them in the priority merge. Default has the first of them in the
merger (i.e. A above) automatically taking merger (i.e. A above) automatically taking
@ -146,7 +146,7 @@ class CmdSet(object):
select which ball to kick ... Allowing duplicates select which ball to kick ... Allowing duplicates
only makes sense for Union and Intersect, the setting only makes sense for Union and Intersect, the setting
is ignored for the other mergetypes. is ignored for the other mergetypes.
key_mergetype (dict) - allows the cmdset to define a unique key_mergetype (dict) - allows the cmdset to define a unique
mergetype for particular cmdsets. Format is mergetype for particular cmdsets. Format is
{CmdSetkeystring:mergetype}. Priorities still apply. {CmdSetkeystring:mergetype}. Priorities still apply.
@ -155,14 +155,14 @@ class CmdSet(object):
Myevilcmdset no matter what overall mergetype this set Myevilcmdset no matter what overall mergetype this set
has. has.
no_objs - don't include any commands from nearby objects no_objs - don't include any commands from nearby objects
when searching for suitable commands when searching for suitable commands
no_exits - ignore the names of exits when matching against no_exits - ignore the names of exits when matching against
commands commands
no_channels - ignore the name of channels when matching against no_channels - ignore the name of channels when matching against
commands (WARNING- this is dangerous since the commands (WARNING- this is dangerous since the
player can then not even ask staff for help if player can then not even ask staff for help if
something goes wrong) something goes wrong)
""" """
@ -176,14 +176,14 @@ class CmdSet(object):
no_exits = False no_exits = False
no_objs = False no_objs = False
no_channels = False no_channels = False
permanent = False permanent = False
def __init__(self, cmdsetobj=None, key=None): def __init__(self, cmdsetobj=None, key=None):
""" """
Creates a new CmdSet instance. Creates a new CmdSet instance.
cmdsetobj - this is the database object to which this particular cmdsetobj - this is the database object to which this particular
instance of cmdset is related. It is often a player but may also be a instance of cmdset is related. It is often a player but may also be a
regular object. regular object.
""" """
if key: if key:
@ -194,12 +194,12 @@ class CmdSet(object):
# initialize system # initialize system
self.at_cmdset_creation() self.at_cmdset_creation()
def at_cmdset_creation(self): def at_cmdset_creation(self):
""" """
Hook method - this should be overloaded in the inheriting Hook method - this should be overloaded in the inheriting
class, and should take care of populating the cmdset class, and should take care of populating the cmdset
by use of self.add(). by use of self.add().
""" """
pass pass
def add(self, cmd): def add(self, cmd):
@ -207,20 +207,20 @@ class CmdSet(object):
Add a command, a list of commands or a cmdset to this cmdset. Add a command, a list of commands or a cmdset to this cmdset.
Note that if cmd already exists in set, Note that if cmd already exists in set,
it will replace the old one (no priority checking etc it will replace the old one (no priority checking etc
at this point; this is often used to overload at this point; this is often used to overload
default commands). default commands).
If cmd is another cmdset class or -instance, the commands If cmd is another cmdset class or -instance, the commands
of that command set is added to this one, as if they were part of that command set is added to this one, as if they were part
of the original cmdset definition. No merging or priority checks of the original cmdset definition. No merging or priority checks
are made, rather later added commands will simply replace are made, rather later added commands will simply replace
existing ones to make a unique set. existing ones to make a unique set.
""" """
if inherits_from(cmd, "src.commands.cmdset.CmdSet"): if inherits_from(cmd, "src.commands.cmdset.CmdSet"):
# cmd is a command set so merge all commands in that set # cmd is a command set so merge all commands in that set
# to this one. We raise a visible error if we created # to this one. We raise a visible error if we created
# an infinite loop (adding cmdset to itself somehow) # an infinite loop (adding cmdset to itself somehow)
try: try:
cmd = instantiate(cmd) cmd = instantiate(cmd)
@ -234,46 +234,46 @@ class CmdSet(object):
else: else:
cmds = [instantiate(cmd)] cmds = [instantiate(cmd)]
for cmd in cmds: for cmd in cmds:
# add all commands # add all commands
if not hasattr(cmd, 'obj'): if not hasattr(cmd, 'obj'):
cmd.obj = self.cmdsetobj cmd.obj = self.cmdsetobj
try: try:
ic = self.commands.index(cmd) ic = self.commands.index(cmd)
self.commands[ic] = cmd # replace self.commands[ic] = cmd # replace
except ValueError: except ValueError:
self.commands.append(cmd) self.commands.append(cmd)
# extra run to make sure to avoid doublets # extra run to make sure to avoid doublets
self.commands = list(set(self.commands)) self.commands = list(set(self.commands))
#print "In cmdset.add(cmd):", self.key, cmd #print "In cmdset.add(cmd):", self.key, cmd
def remove(self, cmd): def remove(self, cmd):
""" """
Remove a command instance from the cmdset. Remove a command instance from the cmdset.
cmd can be either a cmd instance or a key string. cmd can be either a cmd instance or a key string.
""" """
cmd = instantiate(cmd) cmd = instantiate(cmd)
self.commands = [oldcmd for oldcmd in self.commands if oldcmd != cmd] self.commands = [oldcmd for oldcmd in self.commands if oldcmd != cmd]
def get(self, cmd): def get(self, cmd):
""" """
Return the command in this cmdset that matches the Return the command in this cmdset that matches the
given command. cmd may be either a command instance or given command. cmd may be either a command instance or
a key string. a key string.
""" """
cmd = instantiate(cmd) cmd = instantiate(cmd)
for thiscmd in self.commands: for thiscmd in self.commands:
if thiscmd == cmd: if thiscmd == cmd:
return thiscmd return thiscmd
def count(self): def count(self):
"Return number of commands in set" "Return number of commands in set"
return len(self.commands) return len(self.commands)
def get_system_cmds(self): def get_system_cmds(self):
""" """
Return system commands in the cmdset, defined as Return system commands in the cmdset, defined as
commands starting with double underscore __. commands starting with double underscore __.
These are excempt from merge operations. These are excempt from merge operations.
""" """
return [cmd for cmd in self.commands if cmd.key.startswith('__')] return [cmd for cmd in self.commands if cmd.key.startswith('__')]
@ -293,11 +293,11 @@ class CmdSet(object):
cmdset.duplicates = self.duplicates cmdset.duplicates = self.duplicates
cmdset.key_mergetypes = self.key_mergetypes.copy() #copy.deepcopy(self.key_mergetypes) cmdset.key_mergetypes = self.key_mergetypes.copy() #copy.deepcopy(self.key_mergetypes)
return cmdset return cmdset
def make_unique(self, caller): def make_unique(self, caller):
""" """
This is an unsafe command meant to clean out a cmdset of This is an unsafe command meant to clean out a cmdset of
doublet commands after it has been created. It is useful doublet commands after it has been created. It is useful
for commands inheriting cmdsets from the cmdhandler where for commands inheriting cmdsets from the cmdhandler where
obj-based cmdsets always are added double. Doublets will obj-based cmdsets always are added double. Doublets will
be weeded out with preference to commands defined on caller, be weeded out with preference to commands defined on caller,
@ -313,14 +313,14 @@ class CmdSet(object):
else: else:
unique[cmd.key] = cmd unique[cmd.key] = cmd
self.commands = unique.values() self.commands = unique.values()
def __str__(self): def __str__(self):
""" """
Show all commands in cmdset when printing it. Show all commands in cmdset when printing it.
""" """
return ", ".join([str(cmd) for cmd in sorted(self.commands, key=lambda o:o.key)]) return ", ".join([str(cmd) for cmd in sorted(self.commands, key=lambda o:o.key)])
def __iter__(self): def __iter__(self):
""" """
Allows for things like 'for cmd in cmdset': Allows for things like 'for cmd in cmdset':
@ -331,14 +331,14 @@ class CmdSet(object):
""" """
Returns True if this cmdset contains the given command (as defined Returns True if this cmdset contains the given command (as defined
by command name and aliases). This allows for things like 'if cmd in cmdset' by command name and aliases). This allows for things like 'if cmd in cmdset'
""" """
return any(cmd == othercmd for cmd in self.commands) return any(cmd == othercmd for cmd in self.commands)
def __add__(self, cmdset_b): def __add__(self, cmdset_b):
""" """
Merge this cmdset (A) with another cmdset (B) using the + operator, Merge this cmdset (A) with another cmdset (B) using the + operator,
C = A + B C = A + B
Here, we (by convention) say that 'A is merged onto B to form Here, we (by convention) say that 'A is merged onto B to form
C'. The actual merge operation used in the 'addition' depends C'. The actual merge operation used in the 'addition' depends
@ -356,9 +356,9 @@ class CmdSet(object):
# preserve system __commands # preserve system __commands
sys_commands = self.get_system_cmds() + cmdset_b.get_system_cmds() sys_commands = self.get_system_cmds() + cmdset_b.get_system_cmds()
if self.priority >= cmdset_b.priority: if self.priority >= cmdset_b.priority:
# A higher or equal priority than B # A higher or equal priority than B
mergetype = self.key_mergetypes.get(cmdset_b.key, self.mergetype) mergetype = self.key_mergetypes.get(cmdset_b.key, self.mergetype)
if mergetype == "Intersect": if mergetype == "Intersect":
cmdset_c = intersect(self, cmdset_b, cmdset_b.duplicates) cmdset_c = intersect(self, cmdset_b, cmdset_b.duplicates)
elif mergetype == "Replace": elif mergetype == "Replace":
@ -369,7 +369,7 @@ class CmdSet(object):
cmdset_c = union(self, cmdset_b, cmdset_b.duplicates) cmdset_c = union(self, cmdset_b, cmdset_b.duplicates)
else: else:
# B higher priority than A # B higher priority than A
mergetype = cmdset_b.key_mergetypes.get(self.key, cmdset_b.mergetype) mergetype = cmdset_b.key_mergetypes.get(self.key, cmdset_b.mergetype)
if mergetype == "Intersect": if mergetype == "Intersect":
cmdset_c = intersect(cmdset_b, self, self.duplicates) cmdset_c = intersect(cmdset_b, self, self.duplicates)
elif mergetype == "Replace": elif mergetype == "Replace":
@ -382,9 +382,9 @@ class CmdSet(object):
# we store actual_mergetype since key_mergetypes # we store actual_mergetype since key_mergetypes
# might be different from the main mergetype. # might be different from the main mergetype.
# This is used for diagnosis. # This is used for diagnosis.
cmdset_c.actual_mergetype = mergetype cmdset_c.actual_mergetype = mergetype
# return the system commands to the cmdset # return the system commands to the cmdset
cmdset_c.add(sys_commands) cmdset_c.add(sys_commands)
return cmdset_c return cmdset_c

View file

@ -63,7 +63,7 @@ can then implement separate sets for different situations. For
example, you can have a 'On a boat' set, onto which you then tack on example, you can have a 'On a boat' set, onto which you then tack on
the 'Fishing' set. Fishing from a boat? No problem! the 'Fishing' set. Fishing from a boat? No problem!
""" """
import traceback import traceback
from src.utils import logger, utils from src.utils import logger, utils
from src.commands.cmdset import CmdSet from src.commands.cmdset import CmdSet
from src.server.models import ServerConfig from src.server.models import ServerConfig
@ -74,21 +74,21 @@ def import_cmdset(python_path, cmdsetobj, emit_to_obj=None, no_logging=False):
""" """
This helper function is used by the cmdsethandler to load a cmdset This helper function is used by the cmdsethandler to load a cmdset
instance from a python module, given a python_path. It's usually accessed instance from a python module, given a python_path. It's usually accessed
through the cmdsethandler's add() and add_default() methods. through the cmdsethandler's add() and add_default() methods.
python_path - This is the full path to the cmdset object. python_path - This is the full path to the cmdset object.
cmdsetobj - the database object/typeclass on which this cmdset is to be assigned cmdsetobj - the database object/typeclass on which this cmdset is to be assigned
(this can be also channels and exits, as well as players but there will (this can be also channels and exits, as well as players but there will
always be such an object) always be such an object)
emit_to_obj - if given, error is emitted to this object (in addition to logging) emit_to_obj - if given, error is emitted to this object (in addition to logging)
no_logging - don't log/send error messages. This can be useful if import_cmdset is just no_logging - don't log/send error messages. This can be useful if import_cmdset is just
used to check if this is a valid python path or not. used to check if this is a valid python path or not.
function returns None if an error was encountered or path not found. function returns None if an error was encountered or path not found.
""" """
try: try:
try: try:
#print "importing %s: CACHED_CMDSETS=%s" % (python_path, CACHED_CMDSETS) #print "importing %s: CACHED_CMDSETS=%s" % (python_path, CACHED_CMDSETS)
wanted_cache_key = python_path wanted_cache_key = python_path
cmdsetclass = CACHED_CMDSETS.get(wanted_cache_key, None) cmdsetclass = CACHED_CMDSETS.get(wanted_cache_key, None)
errstring = "" errstring = ""
if not cmdsetclass: if not cmdsetclass:
@ -96,11 +96,11 @@ def import_cmdset(python_path, cmdsetobj, emit_to_obj=None, no_logging=False):
# Not in cache. Reload from disk. # Not in cache. Reload from disk.
modulepath, classname = python_path.rsplit('.', 1) modulepath, classname = python_path.rsplit('.', 1)
module = __import__(modulepath, fromlist=[True]) module = __import__(modulepath, fromlist=[True])
cmdsetclass = module.__dict__[classname] cmdsetclass = module.__dict__[classname]
CACHED_CMDSETS[wanted_cache_key] = cmdsetclass CACHED_CMDSETS[wanted_cache_key] = cmdsetclass
#instantiate the cmdset (and catch its errors) #instantiate the cmdset (and catch its errors)
if callable(cmdsetclass): if callable(cmdsetclass):
cmdsetclass = cmdsetclass(cmdsetobj) cmdsetclass = cmdsetclass(cmdsetobj)
return cmdsetclass return cmdsetclass
except ImportError: except ImportError:
@ -111,20 +111,20 @@ def import_cmdset(python_path, cmdsetobj, emit_to_obj=None, no_logging=False):
errstring = "Error in loading cmdset: No cmdset class '%s' in %s." errstring = "Error in loading cmdset: No cmdset class '%s' in %s."
errstring = errstring % (classname, modulepath) errstring = errstring % (classname, modulepath)
raise raise
except Exception: except Exception:
errstring = "\n%s\nCompile/Run error when loading cmdset '%s'." errstring = "\n%s\nCompile/Run error when loading cmdset '%s'."
errstring = errstring % (traceback.format_exc(), python_path) errstring = errstring % (traceback.format_exc(), python_path)
raise raise
except Exception: except Exception:
if errstring and not no_logging: if errstring and not no_logging:
print errstring print errstring
logger.log_trace() logger.log_trace()
if emit_to_obj and not ServerConfig.objects.conf("server_starting_mode"): if emit_to_obj and not ServerConfig.objects.conf("server_starting_mode"):
object.__getattribute__(emit_to_obj, "msg")(errstring) object.__getattribute__(emit_to_obj, "msg")(errstring)
logger.log_errmsg("Error: %s" % errstring) logger.log_errmsg("Error: %s" % errstring)
#cannot raise - it kills the server if no base cmdset exists! #cannot raise - it kills the server if no base cmdset exists!
# classes # classes
class CmdSetHandler(object): class CmdSetHandler(object):
""" """
@ -134,35 +134,35 @@ class CmdSetHandler(object):
This is the set the game engine will retrieve when determining which This is the set the game engine will retrieve when determining which
commands are available to the object. The cmdset_stack holds a history of all CmdSets commands are available to the object. The cmdset_stack holds a history of all CmdSets
to allow the handler to remove/add cmdsets at will. Doing so will re-calculate to allow the handler to remove/add cmdsets at will. Doing so will re-calculate
the 'current' cmdset. the 'current' cmdset.
""" """
def __init__(self, obj): def __init__(self, obj):
""" """
This method is called whenever an object is recreated. This method is called whenever an object is recreated.
obj - this is a reference to the game object this handler obj - this is a reference to the game object this handler
belongs to. belongs to.
""" """
self.obj = obj self.obj = obj
# the id of the "merged" current cmdset for easy access. # the id of the "merged" current cmdset for easy access.
self.key = None self.key = None
# this holds the "merged" current command set # this holds the "merged" current command set
self.current = None self.current = None
# this holds a history of CommandSets # this holds a history of CommandSets
self.cmdset_stack = [CmdSet(cmdsetobj=self.obj, key="Empty")] self.cmdset_stack = [CmdSet(cmdsetobj=self.obj, key="Empty")]
# this tracks which mergetypes are actually in play in the stack # this tracks which mergetypes are actually in play in the stack
self.mergetype_stack = ["Union"] self.mergetype_stack = ["Union"]
# the subset of the cmdset_paths that are to be stored in the database # the subset of the cmdset_paths that are to be stored in the database
self.permanent_paths = [""] self.permanent_paths = [""]
#self.update(init_mode=True) is then called from the object __init__. #self.update(init_mode=True) is then called from the object __init__.
def __str__(self): def __str__(self):
"Display current commands" "Display current commands"
string = "" string = ""
mergelist = [] mergelist = []
if len(self.cmdset_stack) > 1: if len(self.cmdset_stack) > 1:
@ -176,18 +176,18 @@ class CmdSetHandler(object):
if cmdset.permanent: if cmdset.permanent:
permstring = "perm" permstring = "perm"
if mergetype != cmdset.mergetype: if mergetype != cmdset.mergetype:
mergetype = "%s^" % (mergetype) mergetype = "%s^" % (mergetype)
string += "\n %i: <%s (%s, prio %i, %s)>: %s" % \ string += "\n %i: <%s (%s, prio %i, %s)>: %s" % \
(snum, cmdset.key, mergetype, (snum, cmdset.key, mergetype,
cmdset.priority, permstring, cmdset) cmdset.priority, permstring, cmdset)
mergelist.append(str(snum)) mergelist.append(str(snum))
string += "\n" string += "\n"
# Display the currently active cmdset, limited by self.obj's permissions # Display the currently active cmdset, limited by self.obj's permissions
mergetype = self.mergetype_stack[-1] mergetype = self.mergetype_stack[-1]
if mergetype != self.current.mergetype: if mergetype != self.current.mergetype:
merged_on = self.cmdset_stack[-2].key merged_on = self.cmdset_stack[-2].key
mergetype = "custom %s on cmdset '%s'" % (mergetype, merged_on) mergetype = "custom %s on cmdset '%s'" % (mergetype, merged_on)
if mergelist: if mergelist:
string += " <Merged %s (%s, prio %i)>: %s" % ("+".join(mergelist), mergetype, self.current.priority, self.current) string += " <Merged %s (%s, prio %i)>: %s" % ("+".join(mergelist), mergetype, self.current.priority, self.current)
else: else:
@ -196,74 +196,74 @@ class CmdSetHandler(object):
permstring = "perm" permstring = "perm"
string += " <%s (%s, prio %i, %s)>: %s" % (self.current.key, mergetype, self.current.priority, permstring, string += " <%s (%s, prio %i, %s)>: %s" % (self.current.key, mergetype, self.current.priority, permstring,
", ".join(cmd.key for cmd in sorted(self.current, key=lambda o:o.key))) ", ".join(cmd.key for cmd in sorted(self.current, key=lambda o:o.key)))
return string.strip() return string.strip()
def update(self, init_mode=False): def update(self, init_mode=False):
""" """
Re-adds all sets in the handler to have an updated Re-adds all sets in the handler to have an updated
current set. current set.
init_mode is used right after this handler was init_mode is used right after this handler was
created; it imports all permanent cmdsets from db. created; it imports all permanent cmdsets from db.
""" """
if init_mode: if init_mode:
# reimport all permanent cmdsets # reimport all permanent cmdsets
storage = self.obj.cmdset_storage storage = self.obj.cmdset_storage
#print "cmdset_storage:", self.obj.cmdset_storage #print "cmdset_storage:", self.obj.cmdset_storage
if storage: if storage:
self.cmdset_stack = [] self.cmdset_stack = []
for pos, path in enumerate(storage): for pos, path in enumerate(storage):
if pos == 0 and not path: if pos == 0 and not path:
self.cmdset_stack = [CmdSet(cmdsetobj=self.obj, key="Empty")] self.cmdset_stack = [CmdSet(cmdsetobj=self.obj, key="Empty")]
elif path: elif path:
cmdset = self.import_cmdset(path) cmdset = self.import_cmdset(path)
if cmdset: if cmdset:
cmdset.permanent = True cmdset.permanent = True
self.cmdset_stack.append(cmdset) self.cmdset_stack.append(cmdset)
# merge the stack into a new merged cmdset # merge the stack into a new merged cmdset
new_current = None new_current = None
self.mergetype_stack = [] self.mergetype_stack = []
for cmdset in self.cmdset_stack: for cmdset in self.cmdset_stack:
try: try:
# for cmdset's '+' operator, order matters. # for cmdset's '+' operator, order matters.
new_current = cmdset + new_current new_current = cmdset + new_current
except TypeError: except TypeError:
continue continue
self.mergetype_stack.append(new_current.actual_mergetype) self.mergetype_stack.append(new_current.actual_mergetype)
self.current = new_current self.current = new_current
def import_cmdset(self, cmdset_path, emit_to_obj=None): def import_cmdset(self, cmdset_path, emit_to_obj=None):
""" """
Method wrapper for import_cmdset. Method wrapper for import_cmdset.
load a cmdset from a module. load a cmdset from a module.
cmdset_path - the python path to an cmdset object. cmdset_path - the python path to an cmdset object.
emit_to_obj - object to send error messages to emit_to_obj - object to send error messages to
""" """
if not emit_to_obj: if not emit_to_obj:
emit_to_obj = self.obj emit_to_obj = self.obj
return import_cmdset(cmdset_path, self.obj, emit_to_obj) return import_cmdset(cmdset_path, self.obj, emit_to_obj)
def add(self, cmdset, emit_to_obj=None, permanent=False): def add(self, cmdset, emit_to_obj=None, permanent=False):
""" """
Add a cmdset to the handler, on top of the old ones. Add a cmdset to the handler, on top of the old ones.
Default is to not make this permanent, i.e. the set Default is to not make this permanent, i.e. the set
will not survive a server reset. will not survive a server reset.
cmdset - can be a cmdset object or the python path to cmdset - can be a cmdset object or the python path to
such an object. such an object.
emit_to_obj - an object to receive error messages. emit_to_obj - an object to receive error messages.
permanent - this cmdset will remain across a server reboot permanent - this cmdset will remain across a server reboot
Note: An interesting feature of this method is if you were to Note: An interesting feature of this method is if you were to
send it an *already instantiated cmdset* (i.e. not a class), send it an *already instantiated cmdset* (i.e. not a class),
the current cmdsethandler's obj attribute will then *not* be the current cmdsethandler's obj attribute will then *not* be
transferred over to this already instantiated set (this is transferred over to this already instantiated set (this is
because it might be used elsewhere and can cause strange effects). because it might be used elsewhere and can cause strange effects).
This means you could in principle have the handler This means you could in principle have the handler
launch command sets tied to a *different* object than the launch command sets tied to a *different* object than the
handler. Not sure when this would be useful, but it's a 'quirk' handler. Not sure when this would be useful, but it's a 'quirk'
that has to be documented. that has to be documented.
""" """
if callable(cmdset): if callable(cmdset):
if not utils.inherits_from(cmdset, CmdSet): if not utils.inherits_from(cmdset, CmdSet):
@ -278,13 +278,13 @@ class CmdSetHandler(object):
cmdset.permanent = True cmdset.permanent = True
storage = self.obj.cmdset_storage storage = self.obj.cmdset_storage
if not storage: if not storage:
storage = ["", cmdset.path] storage = ["", cmdset.path]
else: else:
storage.append(cmdset.path) storage.append(cmdset.path)
self.obj.cmdset_storage = storage self.obj.cmdset_storage = storage
else: else:
cmdset.permanent = False cmdset.permanent = False
self.cmdset_stack.append(cmdset) self.cmdset_stack.append(cmdset)
self.update() self.update()
def add_default(self, cmdset, emit_to_obj=None, permanent=True): def add_default(self, cmdset, emit_to_obj=None, permanent=True):
@ -292,11 +292,11 @@ class CmdSetHandler(object):
Add a new default cmdset. If an old default existed, Add a new default cmdset. If an old default existed,
it is replaced. If permanent is set, the set will survive a reboot. it is replaced. If permanent is set, the set will survive a reboot.
cmdset - can be a cmdset object or the python path to cmdset - can be a cmdset object or the python path to
an instance of such an object. an instance of such an object.
emit_to_obj - an object to receive error messages. emit_to_obj - an object to receive error messages.
permanent - save cmdset across reboots permanent - save cmdset across reboots
See also the notes for self.add(), which applies here too. See also the notes for self.add(), which applies here too.
""" """
if callable(cmdset): if callable(cmdset):
if not utils.inherits_from(cmdset, CmdSet): if not utils.inherits_from(cmdset, CmdSet):
raise Exception("Only CmdSets can be added to the cmdsethandler!") raise Exception("Only CmdSets can be added to the cmdsethandler!")
@ -311,9 +311,9 @@ class CmdSetHandler(object):
else: else:
self.cmdset_stack = [cmdset] self.cmdset_stack = [cmdset]
self.mergetype_stack = [cmdset.mergetype] self.mergetype_stack = [cmdset.mergetype]
if permanent: if permanent:
cmdset.permanent = True cmdset.permanent = True
storage = self.obj.cmdset_storage storage = self.obj.cmdset_storage
if storage: if storage:
storage[0] = cmdset.path storage[0] = cmdset.path
@ -321,12 +321,12 @@ class CmdSetHandler(object):
storage = [cmdset.path] storage = [cmdset.path]
self.obj.cmdset_storage = storage self.obj.cmdset_storage = storage
else: else:
cmdset.permanent = False cmdset.permanent = False
self.update() self.update()
def delete(self, cmdset=None): def delete(self, cmdset=None):
""" """
Remove a cmdset from the handler. Remove a cmdset from the handler.
cmdset can be supplied either as a cmdset-key, cmdset can be supplied either as a cmdset-key,
an instance of the CmdSet or a python path an instance of the CmdSet or a python path
@ -338,17 +338,17 @@ class CmdSetHandler(object):
""" """
if len(self.cmdset_stack) < 2: if len(self.cmdset_stack) < 2:
# don't allow deleting default cmdsets here. # don't allow deleting default cmdsets here.
return return
if not cmdset: if not cmdset:
# remove the last one in the stack # remove the last one in the stack
cmdset = self.cmdset_stack.pop() cmdset = self.cmdset_stack.pop()
if cmdset.permanent: if cmdset.permanent:
storage = self.obj.cmdset_storage storage = self.obj.cmdset_storage
storage.pop() storage.pop()
self.obj.cmdset_storage = storage self.obj.cmdset_storage = storage
else: else:
# try it as a callable # try it as a callable
if callable(cmdset) and hasattr(cmdset, 'path'): if callable(cmdset) and hasattr(cmdset, 'path'):
delcmdsets = [cset for cset in self.cmdset_stack[1:] if cset.path == cmdset.path] delcmdsets = [cset for cset in self.cmdset_stack[1:] if cset.path == cmdset.path]
@ -358,21 +358,21 @@ class CmdSetHandler(object):
storage = [] storage = []
if any(cset.permanent for cset in delcmdsets): if any(cset.permanent for cset in delcmdsets):
# only hit database if there's need to # only hit database if there's need to
storage = self.obj.cmdset_storage storage = self.obj.cmdset_storage
for cset in delcmdsets: for cset in delcmdsets:
if cset.permanent: if cset.permanent:
try: try:
storage.remove(cset.path) storage.remove(cset.path)
except ValueError: except ValueError:
pass pass
for cset in delcmdsets: for cset in delcmdsets:
# clean the in-memory stack # clean the in-memory stack
try: try:
self.cmdset_stack.remove(cset) self.cmdset_stack.remove(cset)
except ValueError: except ValueError:
pass pass
# re-sync the cmdsethandler. # re-sync the cmdsethandler.
self.update() self.update()
def delete_default(self): def delete_default(self):
@ -385,9 +385,9 @@ class CmdSetHandler(object):
storage[0] = "" storage[0] = ""
else: else:
storage = [""] storage = [""]
self.cmdset_storage = storage self.cmdset_storage = storage
self.cmdset_stack[0] = CmdSet(cmdsetobj=self.obj, key="Empty") self.cmdset_stack[0] = CmdSet(cmdsetobj=self.obj, key="Empty")
else: else:
self.cmdset_stack = [CmdSet(cmdsetobj=self.obj, key="Empty")] self.cmdset_stack = [CmdSet(cmdsetobj=self.obj, key="Empty")]
self.update() self.update()
@ -405,21 +405,21 @@ class CmdSetHandler(object):
self.cmdset_stack = [self.cmdset_stack[0]] self.cmdset_stack = [self.cmdset_stack[0]]
self.mergetype_stack = [self.cmdset_stack[0].mergetype] self.mergetype_stack = [self.cmdset_stack[0].mergetype]
storage = self.obj.cmdset_storage storage = self.obj.cmdset_storage
if storage: if storage:
storage = storage[0] storage = storage[0]
self.obj.cmdset_storage = storage self.obj.cmdset_storage = storage
self.update() self.update()
def has_cmdset(self, cmdset_key, must_be_default=False): def has_cmdset(self, cmdset_key, must_be_default=False):
""" """
checks so the cmdsethandler contains a cmdset with the given key. checks so the cmdsethandler contains a cmdset with the given key.
must_be_default - only match against the default cmdset. must_be_default - only match against the default cmdset.
""" """
if must_be_default: if must_be_default:
return self.cmdset_stack and self.cmdset_stack[0].key == cmdset_key return self.cmdset_stack and self.cmdset_stack[0].key == cmdset_key
else: else:
return any([cmdset.key == cmdset_key for cmdset in self.cmdset_stack]) return any([cmdset.key == cmdset_key for cmdset in self.cmdset_stack])
def all(self): def all(self):
""" """
@ -429,16 +429,16 @@ class CmdSetHandler(object):
def reset(self): def reset(self):
""" """
Force reload of all cmdsets in handler. This should be called Force reload of all cmdsets in handler. This should be called
after CACHED_CMDSETS have been cleared (normally by @reload). after CACHED_CMDSETS have been cleared (normally by @reload).
""" """
new_cmdset_stack = [] new_cmdset_stack = []
new_mergetype_stack = [] new_mergetype_stack = []
for cmdset in self.cmdset_stack: for cmdset in self.cmdset_stack:
if cmdset.key == "Empty": if cmdset.key == "Empty":
new_cmdset_stack.append(cmdset) new_cmdset_stack.append(cmdset)
new_mergetype_stack.append("Union") new_mergetype_stack.append("Union")
else: else:
new_cmdset_stack.append(self.import_cmdset(cmdset.path)) new_cmdset_stack.append(self.import_cmdset(cmdset.path))
new_mergetype_stack.append(cmdset.mergetype) new_mergetype_stack.append(cmdset.mergetype)
self.cmdset_stack = new_cmdset_stack self.cmdset_stack = new_cmdset_stack

View file

@ -1,7 +1,7 @@
""" """
The base Command class. The base Command class.
All commands in Evennia inherit from the 'Command' class in this module. All commands in Evennia inherit from the 'Command' class in this module.
""" """
@ -12,19 +12,19 @@ from src.utils.utils import is_iter
class CommandMeta(type): class CommandMeta(type):
""" """
This metaclass makes some minor on-the-fly convenience fixes to the command This metaclass makes some minor on-the-fly convenience fixes to the command
class in case the admin forgets to put things in lowercase etc. class in case the admin forgets to put things in lowercase etc.
""" """
def __init__(mcs, *args, **kwargs): def __init__(mcs, *args, **kwargs):
""" """
Simply make sure all data are stored as lowercase and Simply make sure all data are stored as lowercase and
do checking on all properties that should be in list form. do checking on all properties that should be in list form.
Sets up locks to be more forgiving. Sets up locks to be more forgiving.
""" """
mcs.key = mcs.key.lower() mcs.key = mcs.key.lower()
if mcs.aliases and not is_iter(mcs.aliases): if mcs.aliases and not is_iter(mcs.aliases):
try: try:
mcs.aliases = mcs.aliases.split(',') mcs.aliases = mcs.aliases.split(',')
except Exception: except Exception:
mcs.aliases = [] mcs.aliases = []
mcs.aliases = [str(alias).strip() for alias in mcs.aliases] mcs.aliases = [str(alias).strip() for alias in mcs.aliases]
if not hasattr(mcs, "save_for_next"): if not hasattr(mcs, "save_for_next"):
@ -61,19 +61,19 @@ class CommandMeta(type):
# define their own parser method to handle the input. The # define their own parser method to handle the input. The
# advantage of this is inheritage; commands that have similar # advantage of this is inheritage; commands that have similar
# structure can parse the input string the same way, minimizing # structure can parse the input string the same way, minimizing
# parsing errors. # parsing errors.
class Command(object): class Command(object):
""" """
Base command Base command
Usage: Usage:
command [args] command [args]
This is the base command class. Inherit from this This is the base command class. Inherit from this
to create new commands. to create new commands.
The cmdhandler makes the following variables available to the The cmdhandler makes the following variables available to the
command methods (so you can always assume them to be there): command methods (so you can always assume them to be there):
self.caller - the game object calling the command self.caller - the game object calling the command
self.cmdstring - the command name used to trigger this command (allows self.cmdstring - the command name used to trigger this command (allows
@ -84,10 +84,20 @@ class Command(object):
seldomly, notably for help-type commands, to create dynamic seldomly, notably for help-type commands, to create dynamic
help entries and lists) help entries and lists)
cmd.obj - the object on which this command is defined. If a default command, cmd.obj - the object on which this command is defined. If a default command,
this is usually the same as caller. this is usually the same as caller.
(Note that this initial string is also used by the system to create the help The following class properties can/should be defined on your child class:
entry for the command, so it's a good idea to format it similar to this one)
key - identifier for command (e.g. "look")
aliases - (optional) list of aliases (e.g. ["l", "loo"])
locks - lock string (default is "cmd:all()")
help_category - how to organize this help entry in help system (default is "General")
auto_help - defaults to True. Allows for turning off auto-help generation
arg_regex - (optional) raw string regex defining how the argument part of the command should look
in order to match for this command (e.g. must it be a space between cmdname and arg?)
(Note that if auto_help is on, this initial string is also used by the system
to create the help entry for the command, so it's a good idea to format it similar to this one)
""" """
# Tie our metaclass, for some convenience cleanup # Tie our metaclass, for some convenience cleanup
__metaclass__ = CommandMeta __metaclass__ = CommandMeta
@ -103,20 +113,20 @@ class Command(object):
# this normally does not need to be changed. It allows to turn off # this normally does not need to be changed. It allows to turn off
# auto-help entry creation for individual commands. # auto-help entry creation for individual commands.
auto_help = True auto_help = True
# There is also the property 'obj'. This gets set by the system # There is also the property 'obj'. This gets set by the system
# on the fly to tie this particular command to a certain in-game entity. # on the fly to tie this particular command to a certain in-game entity.
# self.obj should NOT be defined here since it will not be overwritten # self.obj should NOT be defined here since it will not be overwritten
# if it already exists. # if it already exists.
def __init__(self): def __init__(self):
self.lockhandler = LockHandler(self) self.lockhandler = LockHandler(self)
def __str__(self): def __str__(self):
"Print the command" "Print the command"
return self.key return self.key
def __eq__(self, cmd): def __eq__(self, cmd):
""" """
Compare two command instances to each other by matching their Compare two command instances to each other by matching their
@ -132,7 +142,7 @@ class Command(object):
""" """
This implements searches like 'if query in cmd'. It's a fuzzy matching This implements searches like 'if query in cmd'. It's a fuzzy matching
used by the help system, returning True if query can be found used by the help system, returning True if query can be found
as a substring of the commands key or its aliases. as a substring of the commands key or its aliases.
input can be either a command object or a command name. input can be either a command object or a command name.
""" """
@ -156,11 +166,11 @@ class Command(object):
This hook is called by the cmdhandler to determine if srcobj This hook is called by the cmdhandler to determine if srcobj
is allowed to execute this command. It should return a boolean is allowed to execute this command. It should return a boolean
value and is not normally something that need to be changed since value and is not normally something that need to be changed since
it's using the Evennia permission system directly. it's using the Evennia permission system directly.
""" """
return self.lockhandler.check(srcobj, access_type, default=default) return self.lockhandler.check(srcobj, access_type, default=default)
# Common Command hooks # Common Command hooks
def at_pre_cmd(self): def at_pre_cmd(self):
""" """
@ -170,7 +180,7 @@ class Command(object):
def at_post_cmd(self): def at_post_cmd(self):
""" """
This hook is called after the command has finished executing This hook is called after the command has finished executing
(after self.func()). (after self.func()).
""" """
pass pass
@ -181,31 +191,31 @@ class Command(object):
want, this function is run. If many of your commands have want, this function is run. If many of your commands have
a similar syntax (for example 'cmd arg1 = arg2') you should simply a similar syntax (for example 'cmd arg1 = arg2') you should simply
define this once and just let other commands of the same form define this once and just let other commands of the same form
inherit from this. See the docstring of this module for inherit from this. See the docstring of this module for
which object properties are available to use which object properties are available to use
(notably self.args). (notably self.args).
""" """
pass pass
def func(self): def func(self):
""" """
This is the actual executing part of the command. This is the actual executing part of the command.
It is called directly after self.parse(). See the docstring It is called directly after self.parse(). See the docstring
of this module for which object properties are available of this module for which object properties are available
(beyond those set in self.parse()) (beyond those set in self.parse())
""" """
# a simple test command to show the available properties # a simple test command to show the available properties
string = "-" * 50 string = "-" * 50
string += "\n{w%s{n - Command variables from evennia:\n" % self.key string += "\n{w%s{n - Command variables from evennia:\n" % self.key
string += "-" * 50 string += "-" * 50
string += "\nname of cmd (self.key): {w%s{n\n" % self.key string += "\nname of cmd (self.key): {w%s{n\n" % self.key
string += "cmd aliases (self.aliases): {w%s{n\n" % self.aliases string += "cmd aliases (self.aliases): {w%s{n\n" % self.aliases
string += "cmd perms (self.permissions): {w%s{n\n" % self.permissions string += "cmd perms (self.permissions): {w%s{n\n" % self.permissions
string += "help category (self.help_category): {w%s{n\n" % self.help_category string += "help category (self.help_category): {w%s{n\n" % self.help_category
string += "object calling (self.caller): {w%s{n\n" % self.caller string += "object calling (self.caller): {w%s{n\n" % self.caller
string += "object storing cmdset (self.obj): {w%s{n\n" % self.obj string += "object storing cmdset (self.obj): {w%s{n\n" % self.obj
string += "command string given (self.cmdstring): {w%s{n\n" % self.cmdstring string += "command string given (self.cmdstring): {w%s{n\n" % self.cmdstring
# show cmdset.key instead of cmdset to shorten output # show cmdset.key instead of cmdset to shorten output
string += utils.fill("current cmdset (self.cmdset): {w%s{n\n" % self.cmdset) string += utils.fill("current cmdset (self.cmdset): {w%s{n\n" % self.cmdset)
self.caller.msg(string) self.caller.msg(string)

View file

@ -1,13 +1,13 @@
# #
# This is Evennia's default connection screen. It is imported # This is Evennia's default connection screen. It is imported
# and run from game/gamesrc/world/connection_screens.py. # and run from game/gamesrc/world/connection_screens.py.
# #
from src.utils import utils from src.utils import utils
DEFAULT_SCREEN = \ DEFAULT_SCREEN = \
"""{b=============================================================={n """{b=============================================================={n
Welcome to {gEvennia{n, version %s! Welcome to {gEvennia{n, version %s!
If you have an existing account, connect to it by typing: If you have an existing account, connect to it by typing:
{wconnect <email> <password>{n {wconnect <email> <password>{n

View file

@ -23,9 +23,9 @@ class HelpEntryAdmin(admin.ModelAdmin):
list_display_links = ('id', 'db_key') list_display_links = ('id', 'db_key')
search_fields = ['^db_key', 'db_entrytext'] search_fields = ['^db_key', 'db_entrytext']
ordering = ['db_help_category', 'db_key'] ordering = ['db_help_category', 'db_key']
save_as = True save_as = True
save_on_top = True save_on_top = True
list_select_related = True list_select_related = True
form = HelpEntryForm form = HelpEntryForm
fieldsets = ( fieldsets = (

View file

@ -6,10 +6,10 @@ from src.utils import logger, utils
class HelpEntryManager(models.Manager): class HelpEntryManager(models.Manager):
""" """
This HelpEntryManager implements methods for searching This HelpEntryManager implements methods for searching
and manipulating HelpEntries directly from the database. and manipulating HelpEntries directly from the database.
These methods will all return database objects These methods will all return database objects
(or QuerySets) directly. (or QuerySets) directly.
Evennia-specific: Evennia-specific:
@ -24,33 +24,33 @@ class HelpEntryManager(models.Manager):
def find_topicmatch(self, topicstr, exact=False): def find_topicmatch(self, topicstr, exact=False):
""" """
Searches for matching topics based on player's input. Searches for matching topics based on player's input.
""" """
if utils.dbref(topicstr): if utils.dbref(topicstr):
return self.filter(id=utils.dbref(topicstr)) return self.filter(id=utils.dbref(topicstr))
topics = self.filter(db_key__iexact=topicstr) topics = self.filter(db_key__iexact=topicstr)
if not topics and not exact: if not topics and not exact:
topics = self.filter(db_key__istartswith=topicstr) topics = self.filter(db_key__istartswith=topicstr)
if not topics: if not topics:
topics = self.filter(db_key__icontains=topicstr) topics = self.filter(db_key__icontains=topicstr)
return topics return topics
def find_apropos(self, topicstr): def find_apropos(self, topicstr):
""" """
Do a very loose search, returning all help entries containing Do a very loose search, returning all help entries containing
the search criterion in their titles. the search criterion in their titles.
""" """
return self.filter(db_key__icontains=topicstr) return self.filter(db_key__icontains=topicstr)
def find_topicsuggestions(self, topicstr): def find_topicsuggestions(self, topicstr):
""" """
Do a fuzzy match, preferably within the category of the Do a fuzzy match, preferably within the category of the
current topic. current topic.
""" """
topics = self.find_apropos(topicstr) topics = self.find_apropos(topicstr)
# we need to clean away the given help entry. # we need to clean away the given help entry.
return [topic for topic in topics return [topic for topic in topics
if topic.key.lower() != topicstr.lower()] if topic.key.lower() != topicstr.lower()]
def find_topics_with_category(self, help_category): def find_topics_with_category(self, help_category):
""" """
Search topics having a particular category Search topics having a particular category
@ -76,7 +76,7 @@ class HelpEntryManager(models.Manager):
Shifts all help entries in database to default_category. Shifts all help entries in database to default_category.
This action cannot be reverted. It is used primarily by This action cannot be reverted. It is used primarily by
the engine when importing a default help database, making the engine when importing a default help database, making
sure this ends up in one easily separated category. sure this ends up in one easily separated category.
""" """
topics = self.all() topics = self.all()
for topic in topics: for topic in topics:
@ -92,7 +92,7 @@ class HelpEntryManager(models.Manager):
ostring - the help topic to look for ostring - the help topic to look for
category - limit the search to a particular help topic category - limit the search to a particular help topic
""" """
ostring = ostring.strip().lower() ostring = ostring.strip().lower()
help_entries = self.filter(db_topicstr=ostring) help_entries = self.filter(db_topicstr=ostring)
if help_category: if help_category:
help_entries.filter(db_help_category=help_category) help_entries.filter(db_help_category=help_category)

View file

@ -41,20 +41,20 @@ class HelpEntry(SharedMemoryModel):
# These database fields are all set using their corresponding properties, # These database fields are all set using their corresponding properties,
# named same as the field, but withtout the db_* prefix. # named same as the field, but withtout the db_* prefix.
# title of the help # title of the help
db_key = models.CharField('help key', max_length=255, unique=True, help_text='key to search for') db_key = models.CharField('help key', max_length=255, unique=True, help_text='key to search for')
# help category # help category
db_help_category = models.CharField("help category", max_length=255, default="General", db_help_category = models.CharField("help category", max_length=255, default="General",
help_text='organizes help entries in lists') help_text='organizes help entries in lists')
# the actual help entry text, in any formatting. # the actual help entry text, in any formatting.
db_entrytext = models.TextField('help entry', blank=True, help_text='the main body of help text') db_entrytext = models.TextField('help entry', blank=True, help_text='the main body of help text')
# a string of permissionstrings, separated by commas. Not used by help entries. # a string of permissionstrings, separated by commas. Not used by help entries.
db_permissions = models.CharField('permissions', max_length=255, blank=True) db_permissions = models.CharField('permissions', max_length=255, blank=True)
# lock string storage # lock string storage
db_lock_storage = models.CharField('locks', max_length=512, blank=True, help_text='normally view:all().') db_lock_storage = models.CharField('locks', max_length=512, blank=True, help_text='normally view:all().')
# (deprecated, only here to allow MUX helpfile load (don't use otherwise)). # (deprecated, only here to allow MUX helpfile load (don't use otherwise)).
# TODO: remove this when not needed anymore. # TODO: remove this when not needed anymore.
db_staff_only = models.BooleanField(default=False) db_staff_only = models.BooleanField(default=False)
# Database manager # Database manager
objects = HelpEntryManager() objects = HelpEntryManager()
@ -62,7 +62,7 @@ class HelpEntry(SharedMemoryModel):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
SharedMemoryModel.__init__(self, *args, **kwargs) SharedMemoryModel.__init__(self, *args, **kwargs)
self.locks = LockHandler(self) self.locks = LockHandler(self)
class Meta: class Meta:
"Define Django meta options" "Define Django meta options"
verbose_name = "Help Entry" verbose_name = "Help Entry"
@ -72,10 +72,10 @@ class HelpEntry(SharedMemoryModel):
# @property decorators that allows to access these fields using # @property decorators that allows to access these fields using
# normal python operations (without having to remember to save() # normal python operations (without having to remember to save()
# etc). So e.g. a property 'attr' has a get/set/del decorator # etc). So e.g. a property 'attr' has a get/set/del decorator
# defined that allows the user to do self.attr = value, # defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self # value = self.attr and del self.attr respectively (where self
# is the object in question). # is the object in question).
# key property (wraps db_key) # key property (wraps db_key)
#@property #@property
def key_get(self): def key_get(self):
@ -137,7 +137,7 @@ class HelpEntry(SharedMemoryModel):
if is_iter(value): if is_iter(value):
value = ",".join([str(val).strip().lower() for val in value]) value = ",".join([str(val).strip().lower() for val in value])
self.db_permissions = value self.db_permissions = value
self.save() self.save()
#@permissions.deleter #@permissions.deleter
def permissions_del(self): def permissions_del(self):
"Deleter. Allows for del self.permissions" "Deleter. Allows for del self.permissions"
@ -146,7 +146,7 @@ class HelpEntry(SharedMemoryModel):
permissions = property(permissions_get, permissions_set, permissions_del) permissions = property(permissions_get, permissions_set, permissions_del)
# lock_storage property (wraps db_lock_storage) # lock_storage property (wraps db_lock_storage)
#@property #@property
def lock_storage_get(self): def lock_storage_get(self):
"Getter. Allows for value = self.lock_storage" "Getter. Allows for value = self.lock_storage"
return self.db_lock_storage return self.db_lock_storage
@ -161,13 +161,13 @@ class HelpEntry(SharedMemoryModel):
logger.log_errmsg("Lock_Storage (on %s) cannot be deleted. Use obj.lock.delete() instead." % self) logger.log_errmsg("Lock_Storage (on %s) cannot be deleted. Use obj.lock.delete() instead." % self)
lock_storage = property(lock_storage_get, lock_storage_set, lock_storage_del) lock_storage = property(lock_storage_get, lock_storage_set, lock_storage_del)
# #
# #
# HelpEntry main class methods # HelpEntry main class methods
# #
# #
def __str__(self): def __str__(self):
return self.key return self.key
@ -180,5 +180,5 @@ class HelpEntry(SharedMemoryModel):
accessing_obj - object trying to access this one accessing_obj - object trying to access this one
access_type - type of access sought access_type - type of access sought
default - what to return if no lock of access_type was found default - what to return if no lock of access_type was found
""" """
return self.locks.check(accessing_obj, access_type=access_type, default=default) return self.locks.check(accessing_obj, access_type=access_type, default=default)

View file

@ -1,6 +1,6 @@
# #
# This sets up how models are displayed # This sets up how models are displayed
# in the web admin interface. # in the web admin interface.
# #
from django import forms from django import forms
@ -35,38 +35,38 @@ class ObjectCreateForm(forms.ModelForm):
db_typeclass_path = forms.CharField(label="Typeclass",initial="Change to (for example) %s or %s." % (settings.BASE_OBJECT_TYPECLASS, settings.BASE_CHARACTER_TYPECLASS), db_typeclass_path = forms.CharField(label="Typeclass",initial="Change to (for example) %s or %s." % (settings.BASE_OBJECT_TYPECLASS, settings.BASE_CHARACTER_TYPECLASS),
widget=forms.TextInput(attrs={'size':'78'}), widget=forms.TextInput(attrs={'size':'78'}),
help_text="This defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass. If you are creating a Character you should use the typeclass defined by settings.BASE_CHARACTER_TYPECLASS or one derived from that.") help_text="This defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass. If you are creating a Character you should use the typeclass defined by settings.BASE_CHARACTER_TYPECLASS or one derived from that.")
db_permissions = forms.CharField(label="Permissions", db_permissions = forms.CharField(label="Permissions",
initial=settings.PERMISSION_PLAYER_DEFAULT, initial=settings.PERMISSION_PLAYER_DEFAULT,
required=False, required=False,
widget=forms.TextInput(attrs={'size':'78'}), widget=forms.TextInput(attrs={'size':'78'}),
help_text="a comma-separated list of text strings checked by certain locks. They are mainly of use for Character objects. Character permissions overload permissions defined on a controlling Player. Most objects normally don't have any permissions defined.") help_text="a comma-separated list of text strings checked by certain locks. They are mainly of use for Character objects. Character permissions overload permissions defined on a controlling Player. Most objects normally don't have any permissions defined.")
db_cmdset_storage = forms.CharField(label="CmdSet", db_cmdset_storage = forms.CharField(label="CmdSet",
initial=settings.CMDSET_DEFAULT, initial=settings.CMDSET_DEFAULT,
required=False, required=False,
widget=forms.TextInput(attrs={'size':'78'}), widget=forms.TextInput(attrs={'size':'78'}),
help_text="Most non-character objects don't need a cmdset and can leave this field blank.") help_text="Most non-character objects don't need a cmdset and can leave this field blank.")
class ObjectEditForm(ObjectCreateForm): class ObjectEditForm(ObjectCreateForm):
"Form used for editing. Extends the create one with more fields" "Form used for editing. Extends the create one with more fields"
db_lock_storage = forms.CharField(label="Locks", db_lock_storage = forms.CharField(label="Locks",
required=False, required=False,
widget=forms.Textarea(attrs={'cols':'100', 'rows':'2'}), widget=forms.Textarea(attrs={'cols':'100', 'rows':'2'}),
help_text="In-game lock definition string. If not given, defaults will be used. This string should be on the form <i>type:lockfunction(args);type2:lockfunction2(args);...") help_text="In-game lock definition string. If not given, defaults will be used. This string should be on the form <i>type:lockfunction(args);type2:lockfunction2(args);...")
class ObjectDBAdmin(admin.ModelAdmin): class ObjectDBAdmin(admin.ModelAdmin):
list_display = ('id', 'db_key', 'db_location', 'db_player', 'db_typeclass_path') list_display = ('id', 'db_key', 'db_location', 'db_player', 'db_typeclass_path')
list_display_links = ('id', 'db_key') list_display_links = ('id', 'db_key')
ordering = ['db_player', 'db_typeclass_path', 'id'] ordering = ['db_player', 'db_typeclass_path', 'id']
search_fields = ['^db_key', 'db_typeclass_path'] search_fields = ['^db_key', 'db_typeclass_path']
save_as = True save_as = True
save_on_top = True save_on_top = True
list_select_related = True list_select_related = True
list_filter = ('db_permissions', 'db_location', 'db_typeclass_path') list_filter = ('db_permissions', 'db_location', 'db_typeclass_path')
# editing fields setup # editing fields setup
@ -74,7 +74,7 @@ class ObjectDBAdmin(admin.ModelAdmin):
form = ObjectEditForm form = ObjectEditForm
fieldsets = ( fieldsets = (
(None, { (None, {
'fields': (('db_key','db_typeclass_path'), ('db_permissions', 'db_lock_storage'), 'fields': (('db_key','db_typeclass_path'), ('db_permissions', 'db_lock_storage'),
('db_location', 'db_home'), 'db_destination','db_cmdset_storage' ('db_location', 'db_home'), 'db_destination','db_cmdset_storage'
)}), )}),
) )
@ -88,7 +88,7 @@ class ObjectDBAdmin(admin.ModelAdmin):
add_form = ObjectCreateForm add_form = ObjectCreateForm
add_fieldsets = ( add_fieldsets = (
(None, { (None, {
'fields': (('db_key','db_typeclass_path'), 'db_permissions', 'fields': (('db_key','db_typeclass_path'), 'db_permissions',
('db_location', 'db_home'), 'db_destination','db_cmdset_storage' ('db_location', 'db_home'), 'db_destination','db_cmdset_storage'
)}), )}),
) )
@ -116,8 +116,8 @@ class ObjectDBAdmin(admin.ModelAdmin):
obj = obj.typeclass obj = obj.typeclass
obj.basetype_setup() obj.basetype_setup()
obj.basetype_posthook_setup() obj.basetype_posthook_setup()
obj.at_object_creation() obj.at_object_creation()
obj.at_init() obj.at_init()
admin.site.register(ObjectDB, ObjectDBAdmin) admin.site.register(ObjectDB, ObjectDBAdmin)

View file

@ -3,7 +3,7 @@ Custom manager for Objects.
""" """
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models.fields import exceptions from django.db.models.fields import exceptions
from src.typeclasses.managers import TypedObjectManager from src.typeclasses.managers import TypedObjectManager
from src.typeclasses.managers import returns_typeclass, returns_typeclass_list from src.typeclasses.managers import returns_typeclass, returns_typeclass_list
from src.utils import utils from src.utils import utils
@ -17,11 +17,11 @@ AT_MULTIMATCH_INPUT = utils.mod_import(*settings.SEARCH_AT_MULTIMATCH_INPUT.rspl
class ObjectManager(TypedObjectManager): class ObjectManager(TypedObjectManager):
""" """
This ObjectManager implementes methods for searching This ObjectManager implementes methods for searching
and manipulating Objects directly from the database. and manipulating Objects directly from the database.
Evennia-specific search methods (will return Typeclasses or Evennia-specific search methods (will return Typeclasses or
lists of Typeclasses, whereas Django-general methods will return lists of Typeclasses, whereas Django-general methods will return
Querysets or database objects). Querysets or database objects).
dbref (converter) dbref (converter)
dbref_search dbref_search
@ -41,19 +41,19 @@ class ObjectManager(TypedObjectManager):
copy_object copy_object
""" """
# #
# ObjectManager Get methods # ObjectManager Get methods
# #
# user/player related # user/player related
@returns_typeclass @returns_typeclass
def get_object_with_user(self, user): def get_object_with_user(self, user):
""" """
Matches objects with obj.player.user matching the argument. Matches objects with obj.player.user matching the argument.
A player<->user is a one-to-relationship, so this always A player<->user is a one-to-relationship, so this always
returns just one result or None. returns just one result or None.
user - may be a user object or user id. user - may be a user object or user id.
""" """
@ -61,26 +61,26 @@ class ObjectManager(TypedObjectManager):
uid = int(user) uid = int(user)
except TypeError: except TypeError:
try: try:
uid = user.id uid = user.id
except: except:
return None return None
try: try:
return self.get(db_player__user__id=uid) return self.get(db_player__user__id=uid)
except Exception: except Exception:
return None return None
# This returns typeclass since get_object_with_user and get_dbref does. # This returns typeclass since get_object_with_user and get_dbref does.
def get_object_with_player(self, search_string): def get_object_with_player(self, search_string):
""" """
Search for an object based on its player's name or dbref. Search for an object based on its player's name or dbref.
This search This search
is sometimes initiated by appending a * to the beginning of is sometimes initiated by appending a * to the beginning of
the search criterion (e.g. in local_and_global_search). the search criterion (e.g. in local_and_global_search).
search_string: (string) The name or dbref to search for. search_string: (string) The name or dbref to search for.
""" """
search_string = to_unicode(search_string).lstrip('*') search_string = to_unicode(search_string).lstrip('*')
dbref = self.dbref(search_string) dbref = self.dbref(search_string)
if not dbref: if not dbref:
# not a dbref. Search by name. # not a dbref. Search by name.
player_matches = User.objects.filter(username__iexact=search_string) player_matches = User.objects.filter(username__iexact=search_string)
if player_matches: if player_matches:
@ -105,17 +105,17 @@ class ObjectManager(TypedObjectManager):
from src.objects.models import ObjAttribute from src.objects.models import ObjAttribute
lstring = "" lstring = ""
if location: if location:
lstring = ", db_obj__db_location=location" lstring = ", db_obj__db_location=location"
attrs = eval("ObjAttribute.objects.filter(db_key=attribute_name%s)" % lstring) attrs = eval("ObjAttribute.objects.filter(db_key=attribute_name%s)" % lstring)
return [attr.obj for attr in attrs] return [attr.obj for attr in attrs]
@returns_typeclass_list @returns_typeclass_list
def get_objs_with_attr_match(self, attribute_name, attribute_value, location=None, exact=False): def get_objs_with_attr_match(self, attribute_name, attribute_value, location=None, exact=False):
""" """
Returns all objects having the valid Returns all objects having the valid
attrname set to the given value. Note that no conversion is made attrname set to the given value. Note that no conversion is made
to attribute_value, and so it can accept also non-strings. to attribute_value, and so it can accept also non-strings.
""" """
from src.objects.models import ObjAttribute from src.objects.models import ObjAttribute
lstring = "" lstring = ""
if location: if location:
@ -123,27 +123,27 @@ class ObjectManager(TypedObjectManager):
attrs = eval("ObjAttribute.objects.filter(db_key=attribute_name%s)" % lstring) attrs = eval("ObjAttribute.objects.filter(db_key=attribute_name%s)" % lstring)
# since attribute values are pickled in database, we cannot search directly, but # since attribute values are pickled in database, we cannot search directly, but
# must loop through the results. . # must loop through the results. .
if exact: if exact:
return [attr.obj for attr in attrs if attribute_value == attr.value] return [attr.obj for attr in attrs if attribute_value == attr.value]
else: else:
return [attr.obj for attr in attrs if to_unicode(attribute_value) in str(attr.value)] return [attr.obj for attr in attrs if to_unicode(attribute_value) in str(attr.value)]
@returns_typeclass_list @returns_typeclass_list
def get_objs_with_db_property(self, property_name, location=None): def get_objs_with_db_property(self, property_name, location=None):
""" """
Returns all objects having a given db field property. Returns all objects having a given db field property.
property_name = search string property_name = search string
location - actual location object to restrict to location - actual location object to restrict to
""" """
lstring = "" lstring = ""
if location: if location:
lstring = ".filter(db_location=location)" lstring = ".filter(db_location=location)"
try: try:
return eval("self.exclude(db_%s=None)%s" % (property_name, lstring)) return eval("self.exclude(db_%s=None)%s" % (property_name, lstring))
except exceptions.FieldError: except exceptions.FieldError:
return [] return []
@returns_typeclass_list @returns_typeclass_list
def get_objs_with_db_property_match(self, property_name, property_value, location, exact=False): def get_objs_with_db_property_match(self, property_name, property_value, location, exact=False):
""" """
@ -165,7 +165,7 @@ class ObjectManager(TypedObjectManager):
def get_objs_with_key_or_alias(self, ostring, location, exact=False): def get_objs_with_key_or_alias(self, ostring, location, exact=False):
""" """
Returns objects based on key or alias match Returns objects based on key or alias match
""" """
lstring_key, lstring_alias, estring = "", "", "icontains" lstring_key, lstring_alias, estring = "", "", "icontains"
if location: if location:
lstring_key = ", db_location=location" lstring_key = ", db_location=location"
@ -181,7 +181,7 @@ class ObjectManager(TypedObjectManager):
return matches return matches
# main search methods and helper functions # main search methods and helper functions
@returns_typeclass_list @returns_typeclass_list
def get_contents(self, location, excludeobj=None): def get_contents(self, location, excludeobj=None):
""" """
@ -192,31 +192,40 @@ class ObjectManager(TypedObjectManager):
if excludeobj: if excludeobj:
estring = ".exclude(db_key=excludeobj)" estring = ".exclude(db_key=excludeobj)"
return eval("self.filter(db_location__id=location.id)%s" % estring) return eval("self.filter(db_location__id=location.id)%s" % estring)
@returns_typeclass_list @returns_typeclass_list
def object_search(self, ostring, caller=None, def object_search(self, ostring, caller=None,
global_search=False, global_search=False,
attribute_name=None, location=None): attribute_name=None, location=None):
""" """
Search as an object and return results. The result is always an Object. Search as an object and return results. The result is always an Object.
If * is appended (player search, a Character controlled by this Player If * is appended (player search, a Character controlled by this Player
is looked for. The Character is returned, not the Player. Use player_search is looked for. The Character is returned, not the Player. Use player_search
to find Player objects. Always returns a list. to find Player objects. Always returns a list.
Arguments:
ostring: (string) The string to compare names against. ostring: (string) The string to compare names against.
Can be a dbref. If name is appended by *, a player is searched for. Can be a dbref. If name is appended by *, a player is searched for.
caller: (Object) The object performing the search. caller: (Object) The optional object performing the search.
global_search: Search all objects, not just the current location/inventory global_search (bool). Defaults to False. If a caller is defined, search will
attribute_name: (string) Which attribute to search in each object. be restricted to the contents of caller.location unless global_search
If None, the default 'key' attribute is used. is True. If no caller is given (or the caller has no location), a
location: If None, character.location will be used. global search is assumed automatically.
attribute_name: (string) Which object attribute to match ostring against. If not
set, the "key" and "aliases" properties are searched in order.
location (Object): If set, this location's contents will be used to limit the search instead
of the callers. global_search will override this argument
Returns:
A list of matching objects (or a list with one unique match)
""" """
ostring = to_unicode(ostring, force_string=True) ostring = to_unicode(ostring, force_string=True)
if not ostring: if not ostring:
return [] return []
# Easiest case - dbref matching (always exact) # Easiest case - dbref matching (always exact)
dbref = self.dbref(ostring) dbref = self.dbref(ostring)
if dbref: if dbref:
dbref_match = self.dbref_search(dbref) dbref_match = self.dbref_search(dbref)
@ -229,12 +238,12 @@ class ObjectManager(TypedObjectManager):
# Test some common self-references # Test some common self-references
if location and ostring == 'here': if location and ostring == 'here':
return [location] return [location]
if caller and ostring in ('me', 'self'): if caller and ostring in ('me', 'self'):
return [caller] return [caller]
if caller and ostring in ('*me', '*self'): if caller and ostring in ('*me', '*self'):
return [caller] return [caller]
# Test if we are looking for an object controlled by a # Test if we are looking for an object controlled by a
# specific player # specific player
@ -244,24 +253,24 @@ class ObjectManager(TypedObjectManager):
player_match = self.get_object_with_player(ostring) player_match = self.get_object_with_player(ostring)
if player_match is not None: if player_match is not None:
return [player_match] return [player_match]
# Search for keys, aliases or other attributes # Search for keys, aliases or other attributes
search_locations = [None] # this means a global search search_locations = [None] # this means a global search
if not global_search and location: if not global_search and location:
# Test if we are referring to the current room # Test if we are referring to the current room
if location and (ostring.lower() == location.key.lower() if location and (ostring.lower() == location.key.lower()
or ostring.lower() in [alias.lower() for alias in location.aliases]): or ostring.lower() in [alias.lower() for alias in location.aliases]):
return [location] return [location]
# otherwise, setup the locations to search in # otherwise, setup the locations to search in
search_locations = [location] search_locations = [location]
if caller: if caller:
search_locations.append(caller) search_locations.append(caller)
def local_and_global_search(ostring, exact=False): def local_and_global_search(ostring, exact=False):
"Helper method for searching objects" "Helper method for searching objects"
matches = [] matches = []
for location in search_locations: for location in search_locations:
if attribute_name: if attribute_name:
# Attribute/property search. First, search for db_<attrname> matches on the model # Attribute/property search. First, search for db_<attrname> matches on the model
matches.extend(self.get_objs_with_db_property_match(attribute_name, ostring, location, exact)) matches.extend(self.get_objs_with_db_property_match(attribute_name, ostring, location, exact))
@ -269,14 +278,14 @@ class ObjectManager(TypedObjectManager):
# Next, try Attribute matches # Next, try Attribute matches
matches.extend(self.get_objs_with_attr_match(attribute_name, ostring, location, exact)) matches.extend(self.get_objs_with_attr_match(attribute_name, ostring, location, exact))
else: else:
# No attribute/property named. Do a normal key/alias-search # No attribute/property named. Do a normal key/alias-search
matches.extend(self.get_objs_with_key_or_alias(ostring, location, exact)) matches.extend(self.get_objs_with_key_or_alias(ostring, location, exact))
return matches return matches
# Search through all possibilities. # Search through all possibilities.
match_number = None match_number = None
matches = local_and_global_search(ostring, exact=True) matches = local_and_global_search(ostring, exact=True)
if not matches: if not matches:
# if we have no match, check if we are dealing with an "N-keyword" query - if so, strip it. # if we have no match, check if we are dealing with an "N-keyword" query - if so, strip it.
match_number, ostring = AT_MULTIMATCH_INPUT(ostring) match_number, ostring = AT_MULTIMATCH_INPUT(ostring)
@ -289,7 +298,7 @@ class ObjectManager(TypedObjectManager):
elif len(matches) > 1: elif len(matches) > 1:
# multiple matches already. Run a fuzzy search. This catches partial matches (suggestions) # multiple matches already. Run a fuzzy search. This catches partial matches (suggestions)
matches = local_and_global_search(ostring, exact=False) matches = local_and_global_search(ostring, exact=False)
# deal with the result # deal with the result
if len(matches) > 1 and match_number != None: if len(matches) > 1 and match_number != None:
# We have multiple matches, but a N-type match number is available to separate them. # We have multiple matches, but a N-type match number is available to separate them.
@ -299,21 +308,21 @@ class ObjectManager(TypedObjectManager):
pass pass
# This is always a list. # This is always a list.
return matches return matches
# #
# ObjectManager Copy method # ObjectManager Copy method
# #
def copy_object(self, original_object, new_key=None, def copy_object(self, original_object, new_key=None,
new_location=None, new_player=None, new_home=None, new_location=None, new_player=None, new_home=None,
new_permissions=None, new_locks=None, new_aliases=None, new_destination=None): new_permissions=None, new_locks=None, new_aliases=None, new_destination=None):
""" """
Create and return a new object as a copy of the original object. All will Create and return a new object as a copy of the original object. All will
be identical to the original except for the arguments given specifically be identical to the original except for the arguments given specifically
to this method. to this method.
original_object (obj) - the object to make a copy from original_object (obj) - the object to make a copy from
new_key (str) - name the copy differently from the original. new_key (str) - name the copy differently from the original.
new_location (obj) - if not None, change the location new_location (obj) - if not None, change the location
new_home (obj) - if not None, change the Home new_home (obj) - if not None, change the Home
new_aliases (list of strings) - if not None, change object aliases. new_aliases (list of strings) - if not None, change object aliases.
@ -322,7 +331,7 @@ class ObjectManager(TypedObjectManager):
# get all the object's stats # get all the object's stats
typeclass_path = original_object.typeclass_path typeclass_path = original_object.typeclass_path
if not new_key: if not new_key:
new_key = original_object.key new_key = original_object.key
if not new_location: if not new_location:
new_location = original_object.location new_location = original_object.location
@ -331,36 +340,36 @@ class ObjectManager(TypedObjectManager):
if not new_player: if not new_player:
new_player = original_object.player new_player = original_object.player
if not new_aliases: if not new_aliases:
new_aliases = original_object.aliases new_aliases = original_object.aliases
if not new_locks: if not new_locks:
new_locks = original_object.db_lock_storage new_locks = original_object.db_lock_storage
if not new_permissions: if not new_permissions:
new_permissions = original_object.permissions new_permissions = original_object.permissions
if not new_destination: if not new_destination:
new_destination = original_object.destination new_destination = original_object.destination
# create new object # create new object
from src.utils import create from src.utils import create
from src.scripts.models import ScriptDB from src.scripts.models import ScriptDB
new_object = create.create_object(typeclass_path, key=new_key, location=new_location, new_object = create.create_object(typeclass_path, key=new_key, location=new_location,
home=new_home, player=new_player, permissions=new_permissions, home=new_home, player=new_player, permissions=new_permissions,
locks=new_locks, aliases=new_aliases, destination=new_destination) locks=new_locks, aliases=new_aliases, destination=new_destination)
if not new_object: if not new_object:
return None return None
# copy over all attributes from old to new. # copy over all attributes from old to new.
for attr in original_object.get_all_attributes(): for attr in original_object.get_all_attributes():
new_object.set_attribute(attr.key, attr.value) new_object.set_attribute(attr.key, attr.value)
# copy over all cmdsets, if any # copy over all cmdsets, if any
for icmdset, cmdset in enumerate(original_object.cmdset.all()): for icmdset, cmdset in enumerate(original_object.cmdset.all()):
if icmdset == 0: if icmdset == 0:
new_object.cmdset.add_default(cmdset) new_object.cmdset.add_default(cmdset)
else: else:
new_object.cmdset.add(cmdset) new_object.cmdset.add(cmdset)
# copy over all scripts, if any # copy over all scripts, if any
for script in original_object.scripts.all(): for script in original_object.scripts.all():
ScriptDB.objects.copy_script(script, new_obj=new_object.dbobj) ScriptDB.objects.copy_script(script, new_obj=new_object.dbobj)
return new_object return new_object

View file

@ -1,6 +1,6 @@
""" """
This module defines the database models for all in-game objects, that This module defines the database models for all in-game objects, that
is, all objects that has an actual existence in-game. is, all objects that has an actual existence in-game.
Each database object is 'decorated' with a 'typeclass', a normal Each database object is 'decorated' with a 'typeclass', a normal
python class that implements all the various logics needed by the game python class that implements all the various logics needed by the game
@ -66,11 +66,11 @@ class Alias(SharedMemoryModel):
This model holds a range of alternate names for an object. This model holds a range of alternate names for an object.
These are intrinsic properties of the object. The split These are intrinsic properties of the object. The split
is so as to allow for effective global searches also by is so as to allow for effective global searches also by
alias. alias.
""" """
db_key = models.CharField('alias', max_length=255, db_index=True) db_key = models.CharField('alias', max_length=255, db_index=True)
db_obj = models.ForeignKey("ObjectDB", verbose_name='object') db_obj = models.ForeignKey("ObjectDB", verbose_name='object')
class Meta: class Meta:
"Define Django meta options" "Define Django meta options"
verbose_name = "Object alias" verbose_name = "Object alias"
@ -79,8 +79,8 @@ class Alias(SharedMemoryModel):
return u"%s" % self.db_key return u"%s" % self.db_key
def __str__(self): def __str__(self):
return str(self.db_key) return str(self.db_key)
#------------------------------------------------------------ #------------------------------------------------------------
# #
@ -90,11 +90,11 @@ class Alias(SharedMemoryModel):
class ObjectNick(TypeNick): class ObjectNick(TypeNick):
""" """
The default nick types used by Evennia are: The default nick types used by Evennia are:
inputline (default) - match against all input inputline (default) - match against all input
player - match against player searches player - match against player searches
obj - match against object searches obj - match against object searches
channel - used to store own names for channels channel - used to store own names for channels
""" """
db_obj = models.ForeignKey("ObjectDB", verbose_name='object') db_obj = models.ForeignKey("ObjectDB", verbose_name='object')
@ -108,7 +108,7 @@ class ObjectNick(TypeNick):
class ObjectNickHandler(TypeNickHandler): class ObjectNickHandler(TypeNickHandler):
""" """
Handles nick access and setting. Accessed through ObjectDB.nicks Handles nick access and setting. Accessed through ObjectDB.nicks
""" """
NickClass = ObjectNick NickClass = ObjectNick
@ -126,7 +126,7 @@ class ObjectDB(TypedObject):
Note that the base objectdb is very simple, with Note that the base objectdb is very simple, with
few defined fields. Use attributes to extend your few defined fields. Use attributes to extend your
type class with new database-stored variables. type class with new database-stored variables.
The TypedObject supplies the following (inherited) properties: The TypedObject supplies the following (inherited) properties:
key - main name key - main name
@ -134,11 +134,11 @@ class ObjectDB(TypedObject):
typeclass_path - the path to the decorating typeclass typeclass_path - the path to the decorating typeclass
typeclass - auto-linked typeclass typeclass - auto-linked typeclass
date_created - time stamp of object creation date_created - time stamp of object creation
permissions - perm strings permissions - perm strings
locks - lock definitions (handler) locks - lock definitions (handler)
dbref - #id of object dbref - #id of object
db - persistent attribute storage db - persistent attribute storage
ndb - non-persistent attribute storage ndb - non-persistent attribute storage
The ObjectDB adds the following properties: The ObjectDB adds the following properties:
player - optional connected player player - optional connected player
@ -148,7 +148,7 @@ class ObjectDB(TypedObject):
scripts - scripts assigned to object (handler from typeclass) scripts - scripts assigned to object (handler from typeclass)
cmdset - active cmdset on object (handler from typeclass) cmdset - active cmdset on object (handler from typeclass)
aliases - aliases for this object (property) aliases - aliases for this object (property)
nicks - nicknames for *other* things in Evennia (handler) nicks - nicknames for *other* things in Evennia (handler)
sessions - sessions connected to this object (see also player) sessions - sessions connected to this object (see also player)
has_player - bool if an active player is currently connected has_player - bool if an active player is currently connected
contents - other objects having this object as location contents - other objects having this object as location
@ -160,7 +160,7 @@ class ObjectDB(TypedObject):
# #
# #
# inherited fields (from TypedObject): # inherited fields (from TypedObject):
# db_key (also 'name' works), db_typeclass_path, db_date_created, # db_key (also 'name' works), db_typeclass_path, db_date_created,
# db_permissions # db_permissions
# #
# These databse fields (including the inherited ones) are all set # These databse fields (including the inherited ones) are all set
@ -168,11 +168,11 @@ class ObjectDB(TypedObject):
# but withtout the db_* prefix. # but withtout the db_* prefix.
# If this is a character object, the player is connected here. # If this is a character object, the player is connected here.
db_player = models.ForeignKey("players.PlayerDB", blank=True, null=True, verbose_name='player', db_player = models.ForeignKey("players.PlayerDB", blank=True, null=True, verbose_name='player',
help_text='a Player connected to this object, if any.') help_text='a Player connected to this object, if any.')
# The location in the game world. Since this one is likely # The location in the game world. Since this one is likely
# to change often, we set this with the 'location' property # to change often, we set this with the 'location' property
# to transparently handle Typeclassing. # to transparently handle Typeclassing.
db_location = models.ForeignKey('self', related_name="locations_set",db_index=True, db_location = models.ForeignKey('self', related_name="locations_set",db_index=True,
blank=True, null=True, verbose_name='game location') blank=True, null=True, verbose_name='game location')
# a safety location, this usually don't change much. # a safety location, this usually don't change much.
@ -193,24 +193,24 @@ class ObjectDB(TypedObject):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"Parent must be initialized first." "Parent must be initialized first."
TypedObject.__init__(self, *args, **kwargs) TypedObject.__init__(self, *args, **kwargs)
# handlers # handlers
self.cmdset = CmdSetHandler(self) self.cmdset = CmdSetHandler(self)
self.cmdset.update(init_mode=True) self.cmdset.update(init_mode=True)
self.scripts = ScriptHandler(self) self.scripts = ScriptHandler(self)
self.nicks = ObjectNickHandler(self) self.nicks = ObjectNickHandler(self)
# store the attribute class # store the attribute class
# Wrapper properties to easily set database fields. These are # Wrapper properties to easily set database fields. These are
# @property decorators that allows to access these fields using # @property decorators that allows to access these fields using
# normal python operations (without having to remember to save() # normal python operations (without having to remember to save()
# etc). So e.g. a property 'attr' has a get/set/del decorator # etc). So e.g. a property 'attr' has a get/set/del decorator
# defined that allows the user to do self.attr = value, # defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self # value = self.attr and del self.attr respectively (where self
# is the object in question). # is the object in question).
# aliases property (wraps (db_aliases) # aliases property (wraps (db_aliases)
#@property #@property
def aliases_get(self): def aliases_get(self):
"Getter. Allows for value = self.aliases" "Getter. Allows for value = self.aliases"
try: try:
@ -218,10 +218,10 @@ class ObjectDB(TypedObject):
except AttributeError: except AttributeError:
aliases = list(Alias.objects.filter(db_obj=self).values_list("db_key", flat=True)) aliases = list(Alias.objects.filter(db_obj=self).values_list("db_key", flat=True))
SA(self, "_cached_aliases", aliases) SA(self, "_cached_aliases", aliases)
return aliases return aliases
#@aliases.setter #@aliases.setter
def aliases_set(self, aliases): def aliases_set(self, aliases):
"Setter. Allows for self.aliases = value" "Setter. Allows for self.aliases = value"
for alias in make_iter(aliases): for alias in make_iter(aliases):
new_alias = Alias(db_key=alias, db_obj=self) new_alias = Alias(db_key=alias, db_obj=self)
new_alias.save() new_alias.save()
@ -258,121 +258,121 @@ class ObjectDB(TypedObject):
player = property(player_get, player_set, player_del) player = property(player_get, player_set, player_del)
# location property (wraps db_location) # location property (wraps db_location)
#@property #@property
def location_get(self): def location_get(self):
"Getter. Allows for value = self.location." "Getter. Allows for value = self.location."
loc = get_cache(self, "location") loc = get_cache(self, "location")
if loc: if loc:
return loc.typeclass return loc.typeclass
return None return None
#@location.setter #@location.setter
def location_set(self, location): def location_set(self, location):
"Setter. Allows for self.location = location" "Setter. Allows for self.location = location"
try: try:
if location == None or type(location) == ObjectDB: if location == None or type(location) == ObjectDB:
# location is None or a valid object # location is None or a valid object
loc = location loc = location
elif ObjectDB.objects.dbref(location): elif ObjectDB.objects.dbref(location):
# location is a dbref; search # location is a dbref; search
loc = ObjectDB.objects.dbref_search(location) loc = ObjectDB.objects.dbref_search(location)
if loc and hasattr(loc,'dbobj'): if loc and hasattr(loc,'dbobj'):
loc = loc.dbobj loc = loc.dbobj
else: else:
loc = location.dbobj loc = location.dbobj
else: else:
loc = location.dbobj loc = location.dbobj
set_cache(self, "location", loc) set_cache(self, "location", loc)
except Exception: except Exception:
string = "Cannot set location: " string = "Cannot set location: "
string += "%s is not a valid location." string += "%s is not a valid location."
self.msg(string % location) self.msg(string % location)
logger.log_trace(string) logger.log_trace(string)
raise raise
#@location.deleter #@location.deleter
def location_del(self): def location_del(self):
"Deleter. Allows for del self.location" "Deleter. Allows for del self.location"
self.db_location = None self.db_location = None
self.save() self.save()
del_cache() del_cache()
location = property(location_get, location_set, location_del) location = property(location_get, location_set, location_del)
# home property (wraps db_home) # home property (wraps db_home)
#@property #@property
def home_get(self): def home_get(self):
"Getter. Allows for value = self.home" "Getter. Allows for value = self.home"
home = get_cache(self, "home") home = get_cache(self, "home")
if home: if home:
return home.typeclass return home.typeclass
return None return None
#@home.setter #@home.setter
def home_set(self, home): def home_set(self, home):
"Setter. Allows for self.home = value" "Setter. Allows for self.home = value"
try: try:
if home == None or type(home) == ObjectDB: if home == None or type(home) == ObjectDB:
hom = home hom = home
elif ObjectDB.objects.dbref(home): elif ObjectDB.objects.dbref(home):
hom = ObjectDB.objects.dbref_search(home) hom = ObjectDB.objects.dbref_search(home)
if hom and hasattr(hom,'dbobj'): if hom and hasattr(hom,'dbobj'):
hom = hom.dbobj hom = hom.dbobj
else: else:
hom = home.dbobj hom = home.dbobj
else: else:
hom = home.dbobj hom = home.dbobj
set_cache(self, "home", hom) set_cache(self, "home", hom)
except Exception: except Exception:
string = "Cannot set home: " string = "Cannot set home: "
string += "%s is not a valid home." string += "%s is not a valid home."
self.msg(string % home) self.msg(string % home)
logger.log_trace(string) logger.log_trace(string)
#raise #raise
#@home.deleter #@home.deleter
def home_del(self): def home_del(self):
"Deleter. Allows for del self.home." "Deleter. Allows for del self.home."
self.db_home = None self.db_home = None
self.save() self.save()
del_cache(self, "home") del_cache(self, "home")
home = property(home_get, home_set, home_del) home = property(home_get, home_set, home_del)
# destination property (wraps db_destination) # destination property (wraps db_destination)
#@property #@property
def destination_get(self): def destination_get(self):
"Getter. Allows for value = self.destination." "Getter. Allows for value = self.destination."
dest = get_cache(self, "destination") dest = get_cache(self, "destination")
if dest: if dest:
return dest.typeclass return dest.typeclass
return None return None
#@destination.setter #@destination.setter
def destination_set(self, destination): def destination_set(self, destination):
"Setter. Allows for self.destination = destination" "Setter. Allows for self.destination = destination"
try: try:
if destination == None or type(destination) == ObjectDB: if destination == None or type(destination) == ObjectDB:
# destination is None or a valid object # destination is None or a valid object
dest = destination dest = destination
elif ObjectDB.objects.dbref(destination): elif ObjectDB.objects.dbref(destination):
# destination is a dbref; search # destination is a dbref; search
dest = ObjectDB.objects.dbref_search(destination) dest = ObjectDB.objects.dbref_search(destination)
if dest and hasattr(dest,'dbobj'): if dest and hasattr(dest,'dbobj'):
dest = dest.dbobj dest = dest.dbobj
else: else:
dest = destination.dbobj dest = destination.dbobj
else: else:
dest = destination.dbobj dest = destination.dbobj
set_cache(self, "destination", dest) set_cache(self, "destination", dest)
except Exception: except Exception:
string = "Cannot set destination: " string = "Cannot set destination: "
string += "%s is not a valid destination." string += "%s is not a valid destination."
self.msg(string % destination) self.msg(string % destination)
logger.log_trace(string) logger.log_trace(string)
raise raise
#@destination.deleter #@destination.deleter
def destination_del(self): def destination_del(self):
"Deleter. Allows for del self.destination" "Deleter. Allows for del self.destination"
self.db_destination = None self.db_destination = None
self.save() self.save()
del_cache(self, "destination") del_cache(self, "destination")
destination = property(destination_get, destination_set, destination_del) destination = property(destination_get, destination_set, destination_del)
# cmdset_storage property. # cmdset_storage property.
# This seems very sensitive to caching, so leaving it be for now. /Griatch # This seems very sensitive to caching, so leaving it be for now. /Griatch
#@property #@property
def cmdset_storage_get(self): def cmdset_storage_get(self):
@ -385,7 +385,7 @@ class ObjectDB(TypedObject):
"Setter. Allows for self.name = value. Stores as a comma-separated string." "Setter. Allows for self.name = value. Stores as a comma-separated string."
value = ",".join(str(val).strip() for val in make_iter(value)) value = ",".join(str(val).strip() for val in make_iter(value))
self.db_cmdset_storage = value self.db_cmdset_storage = value
self.save() self.save()
#@cmdset_storage.deleter #@cmdset_storage.deleter
def cmdset_storage_del(self): def cmdset_storage_del(self):
"Deleter. Allows for del self.name" "Deleter. Allows for del self.name"
@ -400,12 +400,12 @@ class ObjectDB(TypedObject):
# #
# ObjectDB class access methods/properties # ObjectDB class access methods/properties
# #
# this is required to properly handle attributes and typeclass loading. # this is required to properly handle attributes and typeclass loading.
#attribute_model_path = "src.objects.models" #attribute_model_path = "src.objects.models"
#attribute_model_name = "ObjAttribute" #attribute_model_name = "ObjAttribute"
typeclass_paths = settings.OBJECT_TYPECLASS_PATHS typeclass_paths = settings.OBJECT_TYPECLASS_PATHS
attribute_class = ObjAttribute attribute_class = ObjAttribute
db_model_name = "objectdb" # used by attributes to safely store objects db_model_name = "objectdb" # used by attributes to safely store objects
@ -420,13 +420,13 @@ class ObjectDB(TypedObject):
""" """
Retrieve sessions connected to this object. Retrieve sessions connected to this object.
""" """
# if the player is not connected, this will simply be an empty list. # if the player is not connected, this will simply be an empty list.
if self.player: if self.player:
return self.player.sessions return self.player.sessions
return [] return []
sessions = property(sessions_get) sessions = property(sessions_get)
#@property #@property
def has_player_get(self): def has_player_get(self):
""" """
Convenience function for checking if an active player is Convenience function for checking if an active player is
@ -436,13 +436,13 @@ class ObjectDB(TypedObject):
has_player = property(has_player_get) has_player = property(has_player_get)
is_player = property(has_player_get) is_player = property(has_player_get)
#@property #@property
def is_superuser_get(self): def is_superuser_get(self):
"Check if user has a player, and if so, if it is a superuser." "Check if user has a player, and if so, if it is a superuser."
return any(self.sessions) and self.player.is_superuser return any(self.sessions) and self.player.is_superuser
is_superuser = property(is_superuser_get) is_superuser = property(is_superuser_get)
#@property #@property
def contents_get(self, exclude=None): def contents_get(self, exclude=None):
""" """
Returns the contents of this object, i.e. all Returns the contents of this object, i.e. all
@ -461,11 +461,11 @@ class ObjectDB(TypedObject):
if exi.destination] if exi.destination]
exits = property(exits_get) exits = property(exits_get)
# #
# Main Search method # Main Search method
# #
def search(self, ostring, def search(self, ostring,
global_search=False, global_search=False,
attribute_name=None, attribute_name=None,
@ -474,49 +474,49 @@ class ObjectDB(TypedObject):
""" """
Perform a standard object search in the database, handling Perform a standard object search in the database, handling
multiple results and lack thereof gracefully. multiple results and lack thereof gracefully.
ostring: (str) The string to match object names against. ostring: (str) The string to match object names against.
Obs - To find a player, append * to the Obs - To find a player, append * to the
start of ostring. start of ostring.
global_search: Search all objects, not just the current global_search: Search all objects, not just the current
location/inventory location/inventory
attribute_name: (string) Which attribute to match attribute_name: (string) Which attribute to match
(if None, uses default 'name') (if None, uses default 'name')
use_nicks : Use nickname replace (off by default) use_nicks : Use nickname replace (off by default)
location : If None, use caller's current location location : If None, use caller's current location
ignore_errors : Don't display any error messages even ignore_errors : Don't display any error messages even
if there are none/multiple matches - if there are none/multiple matches -
just return the result as a list. just return the result as a list.
player : Don't search for an Object but a Player. player : Don't search for an Object but a Player.
This will also find players that don't This will also find players that don't
currently have a character. currently have a character.
Returns - a unique Object/Player match or None. All error Returns - a unique Object/Player match or None. All error
messages are handled by system-commands and the parser-handlers messages are handled by system-commands and the parser-handlers
specified in settings. specified in settings.
Use *<string> to search for objects controlled by a specific Use *<string> to search for objects controlled by a specific
player. Note that the object controlled by the player will be player. Note that the object controlled by the player will be
returned, not the player object itself. This also means that returned, not the player object itself. This also means that
this will not find Players without a character. Use the keyword this will not find Players without a character. Use the keyword
player=True to find player objects. player=True to find player objects.
Note - for multiple matches, the engine accepts a number Note - for multiple matches, the engine accepts a number
linked to the key in order to separate the matches from linked to the key in order to separate the matches from
each other without showing the dbref explicitly. Default each other without showing the dbref explicitly. Default
syntax for this is 'N-searchword'. So for example, if there syntax for this is 'N-searchword'. So for example, if there
are three objects in the room all named 'ball', you could are three objects in the room all named 'ball', you could
address the individual ball as '1-ball', '2-ball', '3-ball' address the individual ball as '1-ball', '2-ball', '3-ball'
etc. etc.
""" """
if use_nicks: if use_nicks:
if ostring.startswith('*') or player: if ostring.startswith('*') or player:
# player nick replace # player nick replace
ostring = self.nicks.get(ostring.lstrip('*'), nick_type="player") ostring = self.nicks.get(ostring.lstrip('*'), nick_type="player")
if not player: if not player:
ostring = "*%s" % ostring ostring = "*%s" % ostring
else: else:
# object nick replace # object nick replace
ostring = self.nicks.get(ostring, nick_type="object") ostring = self.nicks.get(ostring, nick_type="object")
if player: if player:
@ -525,11 +525,11 @@ class ObjectDB(TypedObject):
else: else:
results = PlayerDB.objects.player_search(ostring.lstrip('*')) results = PlayerDB.objects.player_search(ostring.lstrip('*'))
else: else:
results = ObjectDB.objects.object_search(ostring, caller=self, results = ObjectDB.objects.object_search(ostring, caller=self,
global_search=global_search, global_search=global_search,
attribute_name=attribute_name, attribute_name=attribute_name,
location=location) location=location)
if ignore_errors: if ignore_errors:
return results return results
# this import is cache after the first call. # this import is cache after the first call.
@ -538,50 +538,50 @@ class ObjectDB(TypedObject):
# #
# Execution/action methods # Execution/action methods
# #
def execute_cmd(self, raw_string): def execute_cmd(self, raw_string):
""" """
Do something as this object. This command transparently Do something as this object. This command transparently
lets its typeclass execute the command. Evennia also calls lets its typeclass execute the command. Evennia also calls
this method whenever the player sends a command on the command line. this method whenever the player sends a command on the command line.
Argument: Argument:
raw_string (string) - raw command input raw_string (string) - raw command input
Returns Deferred - this is an asynchronous Twisted object that will Returns Deferred - this is an asynchronous Twisted object that will
not fire until the command has actually finished executing. To overload not fire until the command has actually finished executing. To overload
this one needs to attach callback functions to it, with addCallback(function). this one needs to attach callback functions to it, with addCallback(function).
This function will be called with an eventual return value from the command This function will be called with an eventual return value from the command
execution. execution.
This return is not used at all by Evennia by default, but might be useful This return is not used at all by Evennia by default, but might be useful
for coders intending to implement some sort of nested command structure. for coders intending to implement some sort of nested command structure.
""" """
# nick replacement - we require full-word matching. # nick replacement - we require full-word matching.
# do text encoding conversion # do text encoding conversion
raw_string = to_unicode(raw_string) raw_string = to_unicode(raw_string)
raw_list = raw_string.split(None) raw_list = raw_string.split(None)
raw_list = [" ".join(raw_list[:i+1]) for i in range(len(raw_list)) if raw_list[:i+1]] raw_list = [" ".join(raw_list[:i+1]) for i in range(len(raw_list)) if raw_list[:i+1]]
for nick in ObjectNick.objects.filter(db_obj=self, db_type__in=("inputline","channel")): for nick in ObjectNick.objects.filter(db_obj=self, db_type__in=("inputline","channel")):
if nick.db_nick in raw_list: if nick.db_nick in raw_list:
raw_string = raw_string.replace(nick.db_nick, nick.db_real, 1) raw_string = raw_string.replace(nick.db_nick, nick.db_real, 1)
break break
return cmdhandler.cmdhandler(self.typeclass, raw_string) return cmdhandler.cmdhandler(self.typeclass, raw_string)
def msg(self, message, from_obj=None, data=None): def msg(self, message, from_obj=None, data=None):
""" """
Emits something to any sessions attached to the object. Emits something to any sessions attached to the object.
message (str): The message to send message (str): The message to send
from_obj (obj): object that is sending. from_obj (obj): object that is sending.
data (object): an optional data object that may or may not data (object): an optional data object that may or may not
be used by the protocol. be used by the protocol.
""" """
# This is an important function that must always work. # This is an important function that must always work.
# we use a different __getattribute__ to avoid recursive loops. # we use a different __getattribute__ to avoid recursive loops.
if object.__getattribute__(self, 'player'): if object.__getattribute__(self, 'player'):
object.__getattribute__(self, 'player').msg(message, from_obj=from_obj, data=data) object.__getattribute__(self, 'player').msg(message, from_obj=from_obj, data=data)
@ -589,7 +589,7 @@ class ObjectDB(TypedObject):
"Deprecated. Alias for msg" "Deprecated. Alias for msg"
logger.log_depmsg("emit_to() is deprecated. Use msg() instead.") logger.log_depmsg("emit_to() is deprecated. Use msg() instead.")
self.msg(message, from_obj, data) self.msg(message, from_obj, data)
def msg_contents(self, message, exclude=None, from_obj=None, data=None): def msg_contents(self, message, exclude=None, from_obj=None, data=None):
""" """
Emits something to all objects inside an object. Emits something to all objects inside an object.
@ -608,7 +608,7 @@ class ObjectDB(TypedObject):
"Deprecated. Alias for msg_contents" "Deprecated. Alias for msg_contents"
logger.log_depmsg("emit_to_contents() is deprecated. Use msg_contents() instead.") logger.log_depmsg("emit_to_contents() is deprecated. Use msg_contents() instead.")
self.msg_contents(message, exclude=exclude, from_obj=from_obj, data=data) self.msg_contents(message, exclude=exclude, from_obj=from_obj, data=data)
def move_to(self, destination, quiet=False, def move_to(self, destination, quiet=False,
emit_to_obj=None, use_destination=True): emit_to_obj=None, use_destination=True):
""" """
@ -618,19 +618,19 @@ class ObjectDB(TypedObject):
exit object (i.e. it has "destination"!=None), the move_to will exit object (i.e. it has "destination"!=None), the move_to will
happen to this destination and -not- into the exit object itself, unless happen to this destination and -not- into the exit object itself, unless
use_destination=False. Note that no lock checks are done by this function, use_destination=False. Note that no lock checks are done by this function,
such things are assumed to have been handled before calling move_to. such things are assumed to have been handled before calling move_to.
destination: (Object) Reference to the object to move to. This destination: (Object) Reference to the object to move to. This
can also be an exit object, in which case the destination can also be an exit object, in which case the destination
property is used as destination. property is used as destination.
quiet: (bool) If true, don't emit left/arrived messages. quiet: (bool) If true, don't emit left/arrived messages.
emit_to_obj: (Object) object to receive error messages emit_to_obj: (Object) object to receive error messages
use_destination (bool): Default is for objects to use the "destination" property use_destination (bool): Default is for objects to use the "destination" property
of destinations as the target to move to. Turning off this of destinations as the target to move to. Turning off this
keyword allows objects to move "inside" exit objects. keyword allows objects to move "inside" exit objects.
Returns True/False depending on if there were problems with the move. This method Returns True/False depending on if there were problems with the move. This method
may also return various error messages to the emit_to_obj. may also return various error messages to the emit_to_obj.
""" """
def logerr(string=""): def logerr(string=""):
trc = traceback.format_exc() trc = traceback.format_exc()
@ -644,7 +644,7 @@ class ObjectDB(TypedObject):
if not destination: if not destination:
emit_to_obj.msg("The destination doesn't exist.") emit_to_obj.msg("The destination doesn't exist.")
return return
if destination.destination: if destination.destination:
# traverse exits # traverse exits
destination = destination.destination destination = destination.destination
@ -658,8 +658,8 @@ class ObjectDB(TypedObject):
#emit_to_obj.msg(errtxt % "at_before_move()") #emit_to_obj.msg(errtxt % "at_before_move()")
#logger.log_trace() #logger.log_trace()
return False return False
# Save the old location # Save the old location
source_location = self.location source_location = self.location
if not source_location: if not source_location:
# there was some error in placing this room. # there was some error in placing this room.
@ -678,16 +678,16 @@ class ObjectDB(TypedObject):
#emit_to_obj.msg(errtxt % "at_object_leave()") #emit_to_obj.msg(errtxt % "at_object_leave()")
#logger.log_trace() #logger.log_trace()
return False return False
if not quiet: if not quiet:
#tell the old room we are leaving #tell the old room we are leaving
try: try:
self.announce_move_from(destination) self.announce_move_from(destination)
except Exception: except Exception:
logerr(errtxt % "at_announce_move()") logerr(errtxt % "at_announce_move()")
#emit_to_obj.msg(errtxt % "at_announce_move()" ) #emit_to_obj.msg(errtxt % "at_announce_move()" )
#logger.log_trace() #logger.log_trace()
return False return False
# Perform move # Perform move
try: try:
@ -695,18 +695,18 @@ class ObjectDB(TypedObject):
except Exception: except Exception:
emit_to_obj.msg(errtxt % "location change") emit_to_obj.msg(errtxt % "location change")
logger.log_trace() logger.log_trace()
return False return False
if not quiet: if not quiet:
# Tell the new room we are there. # Tell the new room we are there.
try: try:
self.announce_move_to(source_location) self.announce_move_to(source_location)
except Exception: except Exception:
logerr(errtxt % "announce_move_to()") logerr(errtxt % "announce_move_to()")
#emit_to_obj.msg(errtxt % "announce_move_to()") #emit_to_obj.msg(errtxt % "announce_move_to()")
#logger.log_trace() #logger.log_trace()
return False return False
# Perform eventual extra commands on the receiving location # Perform eventual extra commands on the receiving location
# (the object has already arrived at this point) # (the object has already arrived at this point)
try: try:
@ -715,7 +715,7 @@ class ObjectDB(TypedObject):
logerr(errtxt % "at_object_receive()") logerr(errtxt % "at_object_receive()")
#emit_to_obj.msg(errtxt % "at_object_receive()") #emit_to_obj.msg(errtxt % "at_object_receive()")
#logger.log_trace() #logger.log_trace()
return False return False
# Execute eventual extra commands on this object after moving it # Execute eventual extra commands on this object after moving it
# (usually calling 'look') # (usually calling 'look')
@ -725,13 +725,13 @@ class ObjectDB(TypedObject):
logerr(errtxt % "at_after_move") logerr(errtxt % "at_after_move")
#emit_to_obj.msg(errtxt % "at_after_move()") #emit_to_obj.msg(errtxt % "at_after_move()")
#logger.log_trace() #logger.log_trace()
return False return False
return True return True
# #
# Object Swap, Delete and Cleanup methods # Object Swap, Delete and Cleanup methods
# #
def clear_exits(self): def clear_exits(self):
""" """
Destroys all of the exits and any exits pointing to this Destroys all of the exits and any exits pointing to this
@ -745,7 +745,7 @@ class ObjectDB(TypedObject):
def clear_contents(self): def clear_contents(self):
""" """
Moves all objects (players/things) to their home Moves all objects (players/things) to their home
location or to default home. location or to default home.
""" """
# Gather up everything that thinks this is its location. # Gather up everything that thinks this is its location.
objs = ObjectDB.objects.filter(db_location=self) objs = ObjectDB.objects.filter(db_location=self)
@ -754,29 +754,29 @@ class ObjectDB(TypedObject):
default_home = ObjectDB.objects.get(id=default_home_id) default_home = ObjectDB.objects.get(id=default_home_id)
if default_home.id == self.id: if default_home.id == self.id:
# we are deleting default home! # we are deleting default home!
default_home = None default_home = None
except Exception: except Exception:
string = "Could not find default home '(#%d)'." string = "Could not find default home '(#%d)'."
logger.log_errmsg(string % default_home_id) logger.log_errmsg(string % default_home_id)
default_home = None default_home = None
for obj in objs: for obj in objs:
home = obj.home home = obj.home
# Obviously, we can't send it back to here. # Obviously, we can't send it back to here.
if not home or (home and home.id == self.id): if not home or (home and home.id == self.id):
obj.home = default_home obj.home = default_home
# If for some reason it's still None... # If for some reason it's still None...
if not obj.home: if not obj.home:
string = "Missing default home, '%s(#%d)' " string = "Missing default home, '%s(#%d)' "
string += "now has a null location." string += "now has a null location."
obj.location = None obj.location = None
obj.msg("Something went wrong! You are dumped into nowhere. Contact an admin.") obj.msg("Something went wrong! You are dumped into nowhere. Contact an admin.")
logger.log_errmsg(string % (obj.name, obj.id)) logger.log_errmsg(string % (obj.name, obj.id))
return return
if obj.has_player: if obj.has_player:
if home: if home:
string = "Your current location has ceased to exist," string = "Your current location has ceased to exist,"
string += " moving you to %s(#%d)." string += " moving you to %s(#%d)."
obj.msg(string % (home.name, home.id)) obj.msg(string % (home.name, home.id))
@ -787,22 +787,22 @@ class ObjectDB(TypedObject):
obj.move_to(home) obj.move_to(home)
def copy(self, new_key=None): def copy(self, new_key=None):
""" """
Makes an identical copy of this object. If you want to customize the copy by Makes an identical copy of this object. If you want to customize the copy by
changing some settings, use ObjectDB.object.copy_object() directly. changing some settings, use ObjectDB.object.copy_object() directly.
new_key (string) - new key/name of copied object. If new_key is not specified, the copy will be named new_key (string) - new key/name of copied object. If new_key is not specified, the copy will be named
<old_key>_copy by default. <old_key>_copy by default.
Returns: Object (copy of this one) Returns: Object (copy of this one)
""" """
if not new_key: if not new_key:
new_key = "%s_copy" % self.key new_key = "%s_copy" % self.key
return ObjectDB.objects.copy_object(self, new_key=new_key) return ObjectDB.objects.copy_object(self, new_key=new_key)
delete_iter = 0 delete_iter = 0
def delete(self): def delete(self):
""" """
Deletes this object. Deletes this object.
Before deletion, this method makes sure to move all contained Before deletion, this method makes sure to move all contained
objects to their respective home locations, as well as clean objects to their respective home locations, as well as clean
up all exits to/from the object. up all exits to/from the object.
@ -815,7 +815,7 @@ class ObjectDB(TypedObject):
if not self.at_object_delete(): if not self.at_object_delete():
# this is an extra pre-check # this is an extra pre-check
# run before deletion mechanism # run before deletion mechanism
# is kicked into gear. # is kicked into gear.
self.delete_iter == 0 self.delete_iter == 0
return False return False
@ -825,17 +825,17 @@ class ObjectDB(TypedObject):
for session in self.sessions: for session in self.sessions:
session.msg("Your character %s has been destroyed." % self.name) session.msg("Your character %s has been destroyed." % self.name)
# no need to disconnect, Player just jumps to OOC mode. # no need to disconnect, Player just jumps to OOC mode.
# sever the connection (important!) # sever the connection (important!)
if object.__getattribute__(self, 'player') and self.player: if object.__getattribute__(self, 'player') and self.player:
self.player.character = None self.player.character = None
self.player = None self.player = None
for script in self.scripts.all(): for script in self.scripts.all():
script.stop() script.stop()
# if self.player: # if self.player:
# self.player.user.is_active = False # self.player.user.is_active = False
# self.player.user.save( # self.player.user.save(
# Destroy any exits to and from this room, if any # Destroy any exits to and from this room, if any
@ -844,4 +844,4 @@ class ObjectDB(TypedObject):
self.clear_contents() self.clear_contents()
# Perform the deletion of the object # Perform the deletion of the object
super(ObjectDB, self).delete() super(ObjectDB, self).delete()
return True return True

View file

@ -1,5 +1,5 @@
""" """
This is the basis of the typeclass system. This is the basis of the typeclass system.
The idea is have the object as a normal class with the The idea is have the object as a normal class with the
database-connection tied to itself through a property. database-connection tied to itself through a property.
@ -19,15 +19,15 @@ from src.typeclasses.typeclass import TypeClass
from src.commands import cmdset, command from src.commands import cmdset, command
# #
# Base class to inherit from. # Base class to inherit from.
# #
class Object(TypeClass): class Object(TypeClass):
""" """
This is the base class for all in-game objects. This is the base class for all in-game objects.
Inherit from this to create different types of Inherit from this to create different types of
objects in the game. objects in the game.
""" """
def __init__(self, dbobj): def __init__(self, dbobj):
""" """
@ -39,18 +39,18 @@ class Object(TypeClass):
seen in src.object.objects). seen in src.object.objects).
Object Typeclass API: Object Typeclass API:
* Available properties (only available on initiated typeclass objects) * Available properties (only available on initiated typeclass objects)
key (string) - name of object key (string) - name of object
name (string)- same as key name (string)- same as key
aliases (list of strings) - aliases to the object. Will be saved to database as AliasDB entries but returned as strings. aliases (list of strings) - aliases to the object. Will be saved to database as AliasDB entries but returned as strings.
dbref (int, read-only) - unique #id-number. Also "id" can be used. dbref (int, read-only) - unique #id-number. Also "id" can be used.
dbobj (Object, read-only) - link to database model. dbobj.typeclass points back to this class dbobj (Object, read-only) - link to database model. dbobj.typeclass points back to this class
typeclass (Object, read-only) - this links back to this class as an identified only. Use self.swap_typeclass() to switch. typeclass (Object, read-only) - this links back to this class as an identified only. Use self.swap_typeclass() to switch.
date_created (string) - time stamp of object creation date_created (string) - time stamp of object creation
permissions (list of strings) - list of permission strings permissions (list of strings) - list of permission strings
player (Player) - controlling player (will also return offline player) player (Player) - controlling player (will also return offline player)
location (Object) - current location. Is None if this is a room location (Object) - current location. Is None if this is a room
@ -59,19 +59,19 @@ class Object(TypeClass):
has_player (bool, read-only)- will only return *connected* players has_player (bool, read-only)- will only return *connected* players
contents (list of Objects, read-only) - returns all objects inside this object (including exits) contents (list of Objects, read-only) - returns all objects inside this object (including exits)
exits (list of Objects, read-only) - returns all exits from this object, if any exits (list of Objects, read-only) - returns all exits from this object, if any
destination (Object) - only set if this object is an exit. destination (Object) - only set if this object is an exit.
is_superuser (bool, read-only) - True/False if this user is a superuser is_superuser (bool, read-only) - True/False if this user is a superuser
* Handlers available * Handlers available
locks - lock-handler: use locks.add() to add new lock strings locks - lock-handler: use locks.add() to add new lock strings
db - attribute-handler: store/retrieve database attributes on this self.db.myattr=val, val=self.db.myattr db - attribute-handler: store/retrieve database attributes on this self.db.myattr=val, val=self.db.myattr
ndb - non-persistent attribute handler: same as db but does not create a database entry when storing data ndb - non-persistent attribute handler: same as db but does not create a database entry when storing data
scripts - script-handler. Add new scripts to object with scripts.add() scripts - script-handler. Add new scripts to object with scripts.add()
cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object
nicks - nick-handler. New nicks with nicks.add(). nicks - nick-handler. New nicks with nicks.add().
* Helper methods (see src.objects.objects.py for full headers) * Helper methods (see src.objects.objects.py for full headers)
search(ostring, global_search=False, attribute_name=None, use_nicks=False, location=None, ignore_errors=False, player=False) search(ostring, global_search=False, attribute_name=None, use_nicks=False, location=None, ignore_errors=False, player=False)
execute_cmd(raw_string) execute_cmd(raw_string)
@ -90,15 +90,15 @@ class Object(TypeClass):
basetype_setup() - only called once, used for behind-the-scenes setup. Normally not modified. basetype_setup() - only called once, used for behind-the-scenes setup. Normally not modified.
basetype_posthook_setup() - customization in basetype, after the object has been created; Normally not modified. basetype_posthook_setup() - customization in basetype, after the object has been created; Normally not modified.
at_object_creation() - only called once, when object is first created. Object customizations go here. at_object_creation() - only called once, when object is first created. Object customizations go here.
at_object_delete() - called just before deleting an object. If returning False, deletion is aborted. Note that all objects at_object_delete() - called just before deleting an object. If returning False, deletion is aborted. Note that all objects
inside a deleted object are automatically moved to their <home>, they don't need to be removed here. inside a deleted object are automatically moved to their <home>, they don't need to be removed here.
at_init() - called whenever typeclass is cached from memory, at least once every server restart/reload at_init() - called whenever typeclass is cached from memory, at least once every server restart/reload
at_cmdset_get() - this is called just before the command handler requests a cmdset from this object at_cmdset_get() - this is called just before the command handler requests a cmdset from this object
at_first_login() - (player-controlled objects only) called once, the very first time user logs in. at_first_login() - (player-controlled objects only) called once, the very first time user logs in.
at_pre_login() - (player-controlled objects only) called every time the user connects, after they have identified, before other setup at_pre_login() - (player-controlled objects only) called every time the user connects, after they have identified, before other setup
at_post_login() - (player-controlled objects only) called at the end of login, just before setting the player loose in the world. at_post_login() - (player-controlled objects only) called at the end of login, just before setting the player loose in the world.
at_disconnect() - (player-controlled objects only) called just before the user disconnects (or goes linkless) at_disconnect() - (player-controlled objects only) called just before the user disconnects (or goes linkless)
at_server_reload() - called before server is reloaded at_server_reload() - called before server is reloaded
at_server_shutdown() - called just before server is fully shut down at_server_shutdown() - called just before server is fully shut down
@ -111,19 +111,19 @@ class Object(TypeClass):
at_object_receive(obj, source_location) - called when this object receives another object at_object_receive(obj, source_location) - called when this object receives another object
at_before_traverse(traversing_object) - (exit-objects only) called just before an object traverses this object at_before_traverse(traversing_object) - (exit-objects only) called just before an object traverses this object
at_after_traverse(traversing_object, source_location) - (exit-objects only) called just after a traversal has happened. at_after_traverse(traversing_object, source_location) - (exit-objects only) called just after a traversal has happened.
at_failed_traverse(traversing_object) - (exit-objects only) called if traversal fails and property err_traverse is not defined. at_failed_traverse(traversing_object) - (exit-objects only) called if traversal fails and property err_traverse is not defined.
at_msg_receive(self, msg, from_obj=None, data=None) - called when a message (via self.msg()) is sent to this obj. at_msg_receive(self, msg, from_obj=None, data=None) - called when a message (via self.msg()) is sent to this obj.
If returns false, aborts send. If returns false, aborts send.
at_msg_send(self, msg, to_obj=None, data=None) - called when this objects sends a message to someone via self.msg(). at_msg_send(self, msg, to_obj=None, data=None) - called when this objects sends a message to someone via self.msg().
return_appearance(looker) - describes this object. Used by "look" command by default return_appearance(looker) - describes this object. Used by "look" command by default
at_desc(looker=None) - called by 'look' whenever the appearance is requested. at_desc(looker=None) - called by 'look' whenever the appearance is requested.
at_get(getter) - called after object has been picked up. Does not stop pickup. at_get(getter) - called after object has been picked up. Does not stop pickup.
at_drop(dropper) - called when this object has been dropped. at_drop(dropper) - called when this object has been dropped.
at_say(speaker, message) - by default, called if an object inside this object speaks at_say(speaker, message) - by default, called if an object inside this object speaks
""" """
super(Object, self).__init__(dbobj) super(Object, self).__init__(dbobj)
@ -132,84 +132,84 @@ class Object(TypeClass):
def search(self, ostring, def search(self, ostring,
global_search=False, global_search=False,
attribute_name=None, attribute_name=None,
use_nicks=False, use_nicks=False,
location=None, location=None,
ignore_errors=False, ignore_errors=False,
player=False): player=False):
""" """
Perform a standard object search in the database, handling Perform a standard object search in the database, handling
multiple results and lack thereof gracefully. multiple results and lack thereof gracefully.
ostring: (str) The string to match object names against. ostring: (str) The string to match object names against.
Obs - To find a player, append * to the Obs - To find a player, append * to the
start of ostring. start of ostring.
global_search(bool): Search all objects, not just the current global_search(bool): Search all objects, not just the current
location/inventory location/inventory
attribute_name (string) Which attribute to match attribute_name (string) Which attribute to match
(if None, uses default 'name') (if None, uses default 'name')
use_nicks (bool) : Use nickname replace (off by default) use_nicks (bool) : Use nickname replace (off by default)
location (Object): If None, use caller's current location location (Object): If None, use caller's current location
ignore_errors (bool): Don't display any error messages even ignore_errors (bool): Don't display any error messages even
if there are none/multiple matches - if there are none/multiple matches -
just return the result as a list. just return the result as a list.
player (Objectt): Don't search for an Object but a Player. player (Objectt): Don't search for an Object but a Player.
This will also find players that don't This will also find players that don't
currently have a character. currently have a character.
Returns - a unique Object/Player match or None. All error Returns - a unique Object/Player match or None. All error
messages are handled by system-commands and the parser-handlers messages are handled by system-commands and the parser-handlers
specified in settings. specified in settings.
Use *<string> to search for objects controlled by a specific Use *<string> to search for objects controlled by a specific
player. Note that the object controlled by the player will be player. Note that the object controlled by the player will be
returned, not the player object itself. This also means that returned, not the player object itself. This also means that
this will not find Players without a character. Use the keyword this will not find Players without a character. Use the keyword
player=True to find player objects. player=True to find player objects.
Note - for multiple matches, the engine accepts a number Note - for multiple matches, the engine accepts a number
linked to the key in order to separate the matches from linked to the key in order to separate the matches from
each other without showing the dbref explicitly. Default each other without showing the dbref explicitly. Default
syntax for this is 'N-searchword'. So for example, if there syntax for this is 'N-searchword'. So for example, if there
are three objects in the room all named 'ball', you could are three objects in the room all named 'ball', you could
address the individual ball as '1-ball', '2-ball', '3-ball' address the individual ball as '1-ball', '2-ball', '3-ball'
etc. etc.
""" """
return self.dbobj.search(ostring, return self.dbobj.search(ostring,
global_search=global_search, global_search=global_search,
attribute_name=attribute_name, attribute_name=attribute_name,
use_nicks=use_nicks, use_nicks=use_nicks,
location=location, location=location,
ignore_errors=ignore_errors, ignore_errors=ignore_errors,
player=player) player=player)
def execute_cmd(self, raw_string): def execute_cmd(self, raw_string):
""" """
Do something as this object. This command transparently Do something as this object. This command transparently
lets its typeclass execute the command. Evennia also calls lets its typeclass execute the command. Evennia also calls
this method whenever the player sends a command on the command line. this method whenever the player sends a command on the command line.
Argument: Argument:
raw_string (string) - raw command input raw_string (string) - raw command input
Returns Deferred - this is an asynchronous Twisted object that will Returns Deferred - this is an asynchronous Twisted object that will
not fire until the command has actually finished executing. To overload not fire until the command has actually finished executing. To overload
this one needs to attach callback functions to it, with addCallback(function). this one needs to attach callback functions to it, with addCallback(function).
This function will be called with an eventual return value from the command This function will be called with an eventual return value from the command
execution. execution.
This return is not used at all by Evennia by default, but might be useful This return is not used at all by Evennia by default, but might be useful
for coders intending to implement some sort of nested command structure. for coders intending to implement some sort of nested command structure.
""" """
return self.dbobj.execute_cmd(raw_string) return self.dbobj.execute_cmd(raw_string)
def msg(self, message, from_obj=None, data=None): def msg(self, message, from_obj=None, data=None):
""" """
Emits something to any sessions attached to the object. Emits something to any sessions attached to the object.
message (str): The message to send message (str): The message to send
from_obj (obj): object that is sending. from_obj (obj): object that is sending.
data (object): an optional data object that may or may not data (object): an optional data object that may or may not
be used by the protocol. be used by the protocol.
""" """
self.dbobj.msg(message, from_obj=from_obj, data=data) self.dbobj.msg(message, from_obj=from_obj, data=data)
@ -228,44 +228,44 @@ class Object(TypeClass):
exit object (i.e. it has "destination"!=None), the move_to will exit object (i.e. it has "destination"!=None), the move_to will
happen to this destination and -not- into the exit object itself, unless happen to this destination and -not- into the exit object itself, unless
use_destination=False. Note that no lock checks are done by this function, use_destination=False. Note that no lock checks are done by this function,
such things are assumed to have been handled before calling move_to. such things are assumed to have been handled before calling move_to.
destination: (Object) Reference to the object to move to. This destination: (Object) Reference to the object to move to. This
can also be an exit object, in which case the destination can also be an exit object, in which case the destination
property is used as destination. property is used as destination.
quiet: (bool) If true, don't emit left/arrived messages. quiet: (bool) If true, don't emit left/arrived messages.
emit_to_obj: (Object) object to receive error messages emit_to_obj: (Object) object to receive error messages
use_destination (bool): Default is for objects to use the "destination" property use_destination (bool): Default is for objects to use the "destination" property
of destinations as the target to move to. Turning off this of destinations as the target to move to. Turning off this
keyword allows objects to move "inside" exit objects. keyword allows objects to move "inside" exit objects.
Returns True/False depending on if there were problems with the move. This method Returns True/False depending on if there were problems with the move. This method
may also return various error messages to the emit_to_obj. may also return various error messages to the emit_to_obj.
""" """
return self.dbobj.move_to(destination, quiet=quiet, return self.dbobj.move_to(destination, quiet=quiet,
emit_to_obj=emit_to_obj, use_destination=use_destination) emit_to_obj=emit_to_obj, use_destination=use_destination)
def copy(self, new_key=None): def copy(self, new_key=None):
""" """
Makes an identical copy of this object. If you want to customize the copy by Makes an identical copy of this object. If you want to customize the copy by
changing some settings, use ObjectDB.object.copy_object() directly. changing some settings, use ObjectDB.object.copy_object() directly.
new_key (string) - new key/name of copied object. If new_key is not specified, the copy will be named new_key (string) - new key/name of copied object. If new_key is not specified, the copy will be named
<old_key>_copy by default. <old_key>_copy by default.
Returns: Object (copy of this one) Returns: Object (copy of this one)
""" """
return self.dbobj.copy(new_key=new_key) return self.dbobj.copy(new_key=new_key)
def delete(self): def delete(self):
""" """
Deletes this object. Deletes this object.
Before deletion, this method makes sure to move all contained Before deletion, this method makes sure to move all contained
objects to their respective home locations, as well as clean objects to their respective home locations, as well as clean
up all exits to/from the object. up all exits to/from the object.
Returns: boolean True if deletion succeded, False if there Returns: boolean True if deletion succeded, False if there
were errors during deletion or deletion otherwise were errors during deletion or deletion otherwise
failed. failed.
""" """
return self.dbobj.delete() return self.dbobj.delete()
@ -277,16 +277,16 @@ class Object(TypeClass):
Returns true if this object has this type Returns true if this object has this type
OR has a typeclass which is an subclass of OR has a typeclass which is an subclass of
the given typeclass. the given typeclass.
typeclass - can be a class object or the typeclass - can be a class object or the
python path to such an object to match against. python path to such an object to match against.
exact - returns true only if the object's exact - returns true only if the object's
type is exactly this typeclass, ignoring type is exactly this typeclass, ignoring
parents. parents.
Returns: Boolean Returns: Boolean
""" """
return self.dbobj.is_typeclass(typeclass, exact=exact) return self.dbobj.is_typeclass(typeclass, exact=exact)
def swap_typeclass(self, new_typeclass, clean_attributes=False, no_default=True): def swap_typeclass(self, new_typeclass, clean_attributes=False, no_default=True):
@ -294,18 +294,18 @@ class Object(TypeClass):
This performs an in-situ swap of the typeclass. This means This performs an in-situ swap of the typeclass. This means
that in-game, this object will suddenly be something else. that in-game, this object will suddenly be something else.
Player will not be affected. To 'move' a player to a different Player will not be affected. To 'move' a player to a different
object entirely (while retaining this object's type), use object entirely (while retaining this object's type), use
self.player.swap_object(). self.player.swap_object().
Note that this might be an error prone operation if the Note that this might be an error prone operation if the
old/new typeclass was heavily customized - your code old/new typeclass was heavily customized - your code
might expect one and not the other, so be careful to might expect one and not the other, so be careful to
bug test your code if using this feature! Often its easiest bug test your code if using this feature! Often its easiest
to create a new object and just swap the player over to to create a new object and just swap the player over to
that one instead. that one instead.
Arguments: Arguments:
new_typeclass (path/classobj) - type to switch to new_typeclass (path/classobj) - type to switch to
clean_attributes (bool/list) - will delete all attributes clean_attributes (bool/list) - will delete all attributes
stored on this object (but not any stored on this object (but not any
of the database fields such as name or of the database fields such as name or
@ -317,10 +317,10 @@ class Object(TypeClass):
no_default - if this is active, the swapper will not allow for no_default - if this is active, the swapper will not allow for
swapping to a default typeclass in case the given swapping to a default typeclass in case the given
one fails for some reason. Instead the old one one fails for some reason. Instead the old one
will be preserved. will be preserved.
Returns: Returns:
boolean True/False depending on if the swap worked or not. boolean True/False depending on if the swap worked or not.
""" """
self.dbobj.swap_typeclass(new_typeclass, clean_attributes=clean_attributes, no_default=no_default) self.dbobj.swap_typeclass(new_typeclass, clean_attributes=clean_attributes, no_default=no_default)
@ -332,14 +332,14 @@ class Object(TypeClass):
accessing_obj (Object)- object trying to access this one accessing_obj (Object)- object trying to access this one
access_type (string) - type of access sought access_type (string) - type of access sought
default (bool) - what to return if no lock of access_type was found default (bool) - what to return if no lock of access_type was found
""" """
return self.dbobj.access(accessing_obj, access_type=access_type, default=default) return self.dbobj.access(accessing_obj, access_type=access_type, default=default)
def check_permstring(self, permstring): def check_permstring(self, permstring):
""" """
This explicitly checks the given string against this object's This explicitly checks the given string against this object's
'permissions' property without involving any locks. 'permissions' property without involving any locks.
permstring (string) - permission string that need to match a permission on the object. permstring (string) - permission string that need to match a permission on the object.
(example: 'Builders') (example: 'Builders')
""" """
@ -355,7 +355,7 @@ class Object(TypeClass):
""" """
result = self.id == other result = self.id == other
if not result and hasattr(other, "id"): if not result and hasattr(other, "id"):
result = self.id == other.id result = self.id == other.id
if not result: if not result:
try: try:
result = other and self.user.id == other.user.id result = other and self.user.id == other.user.id
@ -366,14 +366,14 @@ class Object(TypeClass):
## hooks called by the game engine ## hooks called by the game engine
def basetype_setup(self): def basetype_setup(self):
""" """
This sets up the default properties of an Object, This sets up the default properties of an Object,
just before the more general at_object_creation. just before the more general at_object_creation.
Don't change this, instead edit at_object_creation() to Don't change this, instead edit at_object_creation() to
overload the defaults (it is called after this one). overload the defaults (it is called after this one).
""" """
# the default security setup fallback for a generic # the default security setup fallback for a generic
# object. Overload in child for a custom setup. Also creation # object. Overload in child for a custom setup. Also creation
@ -383,13 +383,13 @@ class Object(TypeClass):
dbref = self.dbobj.dbref dbref = self.dbobj.dbref
self.locks.add("control:id(%s) or perm(Immortals)" % dbref) # edit locks/permissions, delete self.locks.add("control:id(%s) or perm(Immortals)" % dbref) # edit locks/permissions, delete
self.locks.add("examine:perm(Builders)") # examine properties self.locks.add("examine:perm(Builders)") # examine properties
self.locks.add("view:all()") # look at object (visibility) self.locks.add("view:all()") # look at object (visibility)
self.locks.add("edit:perm(Wizards)") # edit properties/attributes self.locks.add("edit:perm(Wizards)") # edit properties/attributes
self.locks.add("delete:perm(Wizards)") # delete object self.locks.add("delete:perm(Wizards)") # delete object
self.locks.add("get:all()") # pick up object self.locks.add("get:all()") # pick up object
self.locks.add("call:true()") # allow to call commands on this object self.locks.add("call:true()") # allow to call commands on this object
self.locks.add("puppet:id(%s) or perm(Immortals) or pperm(Immortals)" % dbref) # restricts puppeting of this object self.locks.add("puppet:id(%s) or perm(Immortals) or pperm(Immortals)" % dbref) # restricts puppeting of this object
def basetype_posthook_setup(self): def basetype_posthook_setup(self):
""" """
@ -403,27 +403,27 @@ class Object(TypeClass):
def at_object_creation(self): def at_object_creation(self):
""" """
Called once, when this object is first created. Called once, when this object is first created.
""" """
pass pass
def at_object_delete(self): def at_object_delete(self):
""" """
Called just before the database object is Called just before the database object is
permanently delete()d from the database. If permanently delete()d from the database. If
this method returns False, deletion is aborted. this method returns False, deletion is aborted.
""" """
return True return True
def at_init(self): def at_init(self):
""" """
This is always called whenever this object is initiated -- This is always called whenever this object is initiated --
that is, whenever it its typeclass is cached from memory. This that is, whenever it its typeclass is cached from memory. This
happens on-demand first time the object is used or activated happens on-demand first time the object is used or activated
in some way after being created but also after each server in some way after being created but also after each server
restart or reload. restart or reload.
""" """
pass pass
def at_cmdset_get(self): def at_cmdset_get(self):
""" """
@ -450,7 +450,7 @@ class Object(TypeClass):
""" """
Called at the end of the login Called at the end of the login
process, just before letting process, just before letting
them loose. them loose.
""" """
pass pass
@ -463,16 +463,16 @@ class Object(TypeClass):
def at_server_reload(self): def at_server_reload(self):
""" """
This hook is called whenever the server is shutting down for restart/reboot. This hook is called whenever the server is shutting down for restart/reboot.
If you want to, for example, save non-persistent properties across a restart, If you want to, for example, save non-persistent properties across a restart,
this is the place to do it. this is the place to do it.
""" """
pass pass
def at_server_shutdown(self): def at_server_shutdown(self):
""" """
This hook is called whenever the server is shutting down fully (i.e. not for This hook is called whenever the server is shutting down fully (i.e. not for
a restart). a restart).
""" """
pass pass
@ -482,63 +482,63 @@ class Object(TypeClass):
def at_before_move(self, destination): def at_before_move(self, destination):
""" """
Called just before starting to move Called just before starting to move
this object to destination. this object to destination.
destination - the object we are moving to destination - the object we are moving to
If this method returns False/None, the move If this method returns False/None, the move
is cancelled before it is even started. is cancelled before it is even started.
""" """
#return has_perm(self, destination, "can_move") #return has_perm(self, destination, "can_move")
return True return True
def announce_move_from(self, destination): def announce_move_from(self, destination):
""" """
Called if the move is to be announced. This is Called if the move is to be announced. This is
called while we are still standing in the old called while we are still standing in the old
location. location.
destination - the place we are going to. destination - the place we are going to.
""" """
if not self.location: if not self.location:
return return
name = self.name name = self.name
loc_name = "" loc_name = ""
loc_name = self.location.name loc_name = self.location.name
dest_name = destination.name dest_name = destination.name
string = "%s is leaving %s, heading for %s." string = "%s is leaving %s, heading for %s."
self.location.msg_contents(string % (name, loc_name, dest_name), exclude=self) self.location.msg_contents(string % (name, loc_name, dest_name), exclude=self)
def announce_move_to(self, source_location): def announce_move_to(self, source_location):
""" """
Called after the move if the move was not quiet. At this Called after the move if the move was not quiet. At this
point we are standing in the new location. point we are standing in the new location.
source_location - the place we came from source_location - the place we came from
""" """
name = self.name name = self.name
if not source_location and self.location.has_player: if not source_location and self.location.has_player:
# This was created from nowhere and added to a player's # This was created from nowhere and added to a player's
# inventory; it's probably the result of a create command. # inventory; it's probably the result of a create command.
string = "You now have %s in your possession." % name string = "You now have %s in your possession." % name
self.location.msg(string) self.location.msg(string)
return return
src_name = "nowhere" src_name = "nowhere"
loc_name = self.location.name loc_name = self.location.name
if source_location: if source_location:
src_name = source_location.name src_name = source_location.name
string = "%s arrives to %s from %s." string = "%s arrives to %s from %s."
self.location.msg_contents(string % (name, loc_name, src_name), exclude=self) self.location.msg_contents(string % (name, loc_name, src_name), exclude=self)
def at_after_move(self, source_location): def at_after_move(self, source_location):
""" """
Called after move has completed, regardless of quiet mode or not. Called after move has completed, regardless of quiet mode or not.
Allows changes to the object due to the location it is now in. Allows changes to the object due to the location it is now in.
source_location - where we came from source_location - where we came from
""" """
pass pass
@ -554,92 +554,92 @@ class Object(TypeClass):
def at_object_receive(self, moved_obj, source_location): def at_object_receive(self, moved_obj, source_location):
""" """
Called after an object has been moved into this object. Called after an object has been moved into this object.
moved_obj - the object moved into this one moved_obj - the object moved into this one
source_location - where moved_object came from. source_location - where moved_object came from.
""" """
pass pass
def at_before_traverse(self, traversing_object): def at_before_traverse(self, traversing_object):
""" """
Called just before an object uses this object to Called just before an object uses this object to
traverse to another object (i.e. this object is a type of Exit) traverse to another object (i.e. this object is a type of Exit)
The target location should normally be available as self.destination. The target location should normally be available as self.destination.
""" """
pass pass
def at_after_traverse(self, traversing_object, source_location): def at_after_traverse(self, traversing_object, source_location):
""" """
Called just after an object successfully used this object to Called just after an object successfully used this object to
traverse to another object (i.e. this object is a type of Exit) traverse to another object (i.e. this object is a type of Exit)
The target location should normally be available as self.destination. The target location should normally be available as self.destination.
""" """
pass pass
def at_failed_traverse(self, traversing_object): def at_failed_traverse(self, traversing_object):
""" """
This is called if an object fails to traverse this object for some This is called if an object fails to traverse this object for some
reason. It will not be called if the attribute err_traverse is defined, reason. It will not be called if the attribute err_traverse is defined,
that attribute will then be echoed back instead. that attribute will then be echoed back instead.
""" """
pass pass
def at_msg_receive(self, msg, from_obj=None, data=None): def at_msg_receive(self, msg, from_obj=None, data=None):
""" """
This hook is called whenever someone This hook is called whenever someone
sends a message to this object. sends a message to this object.
Note that from_obj may be None if the sender did Note that from_obj may be None if the sender did
not include itself as an argument to the obj.msg() not include itself as an argument to the obj.msg()
call - so you have to check for this. . call - so you have to check for this. .
Consider this a pre-processing method before Consider this a pre-processing method before
msg is passed on to the user sesssion. If this msg is passed on to the user sesssion. If this
method returns False, the msg will not be method returns False, the msg will not be
passed on. passed on.
msg = the message received msg = the message received
from_obj = the one sending the message from_obj = the one sending the message
""" """
return True return True
def at_msg_send(self, msg, to_obj=None, data=None): def at_msg_send(self, msg, to_obj=None, data=None):
""" """
This is a hook that is called when /this/ object This is a hook that is called when /this/ object
sends a message to another object with obj.msg() sends a message to another object with obj.msg()
while also specifying that it is the one sending. while also specifying that it is the one sending.
Note that this method is executed on the object Note that this method is executed on the object
passed along with the msg() function (i.e. using passed along with the msg() function (i.e. using
obj.msg(msg, caller) will then launch caller.at_msg()) obj.msg(msg, caller) will then launch caller.at_msg())
and if no object was passed, it will never be called. and if no object was passed, it will never be called.
""" """
pass pass
# hooks called by the default cmdset.
# hooks called by the default cmdset.
def return_appearance(self, pobject): def return_appearance(self, pobject):
""" """
This is a convenient hook for a 'look' This is a convenient hook for a 'look'
command to call. command to call.
""" """
if not pobject: if not pobject:
return return
string = "{c%s{n" % self.name string = "{c%s{n" % self.name
desc = self.attr("desc") desc = self.attr("desc")
if desc: if desc:
string += "\n %s" % desc string += "\n %s" % desc
exits = [] exits = []
users = [] users = []
things = [] things = []
for content in [con for con in self.contents if con.access(pobject, 'view')]: for content in [con for con in self.contents if con.access(pobject, 'view')]:
if content == pobject: if content == pobject:
continue continue
name = content.name name = content.name
if content.destination: if content.destination:
exits.append(name) exits.append(name)
@ -651,17 +651,17 @@ class Object(TypeClass):
string += "\n{wExits:{n " + ", ".join(exits) string += "\n{wExits:{n " + ", ".join(exits)
if users or things: if users or things:
string += "\n{wYou see: {n" string += "\n{wYou see: {n"
if users: if users:
string += "{c" + ", ".join(users) + "{n " string += "{c" + ", ".join(users) + "{n "
if things: if things:
string += ", ".join(things) string += ", ".join(things)
return string return string
def at_desc(self, looker=None): def at_desc(self, looker=None):
""" """
This is called whenever someone looks This is called whenever someone looks
at this object. Looker is the looking at this object. Looker is the looking
object. object.
""" """
pass pass
@ -685,8 +685,8 @@ class Object(TypeClass):
def at_say(self, speaker, message): def at_say(self, speaker, message):
""" """
Called on this object if an object inside this object speaks. Called on this object if an object inside this object speaks.
The string returned from this method is the final form The string returned from this method is the final form
of the speech. Obs - you don't have to add things like of the speech. Obs - you don't have to add things like
'you say: ' or similar, that is handled by the say command. 'you say: ' or similar, that is handled by the say command.
speaker - the object speaking speaker - the object speaking
@ -695,7 +695,7 @@ class Object(TypeClass):
return message return message
# #
# Base Player object # Base Player object
# #
class Character(Object): class Character(Object):
@ -703,24 +703,24 @@ class Character(Object):
This is just like the Object except it implements its own This is just like the Object except it implements its own
version of the at_object_creation to set up the script version of the at_object_creation to set up the script
that adds the default cmdset to the object. that adds the default cmdset to the object.
""" """
def basetype_setup(self): def basetype_setup(self):
""" """
Setup character-specific security Setup character-specific security
Don't change this, instead edit at_object_creation() to Don't change this, instead edit at_object_creation() to
overload the defaults (it is called after this one). overload the defaults (it is called after this one).
""" """
super(Character, self).basetype_setup() super(Character, self).basetype_setup()
self.locks.add("get:false()") # noone can pick up the character self.locks.add("get:false()") # noone can pick up the character
self.locks.add("call:false()") # no commands can be called on character from outside self.locks.add("call:false()") # no commands can be called on character from outside
# add the default cmdset # add the default cmdset
from settings import CMDSET_DEFAULT from settings import CMDSET_DEFAULT
self.cmdset.add_default(CMDSET_DEFAULT, permanent=True) self.cmdset.add_default(CMDSET_DEFAULT, permanent=True)
# no other character should be able to call commands on the Character. # no other character should be able to call commands on the Character.
self.cmdset.outside_access = False self.cmdset.outside_access = False
def at_object_creation(self): def at_object_creation(self):
""" """
@ -728,8 +728,8 @@ class Character(Object):
the script is permanently stored to this object (the permanent the script is permanently stored to this object (the permanent
keyword creates a script to do this), we should never need to keyword creates a script to do this), we should never need to
do this again for as long as this object exists. do this again for as long as this object exists.
""" """
pass pass
def at_after_move(self, source_location): def at_after_move(self, source_location):
"Default is to look around after a move." "Default is to look around after a move."
@ -737,34 +737,34 @@ class Character(Object):
def at_disconnect(self): def at_disconnect(self):
""" """
We stove away the character when logging off, otherwise the character object will We stove away the character when logging off, otherwise the character object will
remain in the room also after the player logged off ("headless", so to say). remain in the room also after the player logged off ("headless", so to say).
""" """
if self.location: # have to check, in case of multiple connections closing if self.location: # have to check, in case of multiple connections closing
self.location.msg_contents("%s has left the game." % self.name, exclude=[self]) self.location.msg_contents("%s has left the game." % self.name, exclude=[self])
self.db.prelogout_location = self.location self.db.prelogout_location = self.location
self.location = None self.location = None
def at_post_login(self): def at_post_login(self):
""" """
This recovers the character again after having been "stoved away" at disconnect. This recovers the character again after having been "stoved away" at disconnect.
""" """
if self.db.prelogout_location: if self.db.prelogout_location:
# try to recover # try to recover
self.location = self.db.prelogout_location self.location = self.db.prelogout_location
if self.location == None: if self.location == None:
# make sure location is never None (home should always exist) # make sure location is never None (home should always exist)
self.location = self.home self.location = self.home
# save location again to be sure # save location again to be sure
self.db.prelogout_location = self.location self.db.prelogout_location = self.location
self.location.msg_contents("%s has entered the game." % self.name, exclude=[self]) self.location.msg_contents("%s has entered the game." % self.name, exclude=[self])
self.location.at_object_receive(self, self.location) self.location.at_object_receive(self, self.location)
# #
# Base Room object # Base Room object
# #
class Room(Object): class Room(Object):
@ -778,16 +778,16 @@ class Room(Object):
(since default is None anyway) (since default is None anyway)
Don't change this, instead edit at_object_creation() to Don't change this, instead edit at_object_creation() to
overload the defaults (it is called after this one). overload the defaults (it is called after this one).
""" """
super(Room, self).basetype_setup() super(Room, self).basetype_setup()
self.locks.add("get:false();puppet:false()") # would be weird to puppet a room ... self.locks.add("get:false();puppet:false()") # would be weird to puppet a room ...
self.location = None self.location = None
# #
# Exits # Exits
# #
class Exit(Object): class Exit(Object):
@ -795,16 +795,16 @@ class Exit(Object):
This is the base exit object - it connects a location to This is the base exit object - it connects a location to
another. This is done by the exit assigning a "command" on itself another. This is done by the exit assigning a "command" on itself
with the same name as the exit object (to do this we need to with the same name as the exit object (to do this we need to
remember to re-create the command when the object is cached since it must be remember to re-create the command when the object is cached since it must be
created dynamically depending on what the exit is called). This created dynamically depending on what the exit is called). This
command (which has a high priority) will thus allow us to traverse exits command (which has a high priority) will thus allow us to traverse exits
simply by giving the exit-object's name on its own. simply by giving the exit-object's name on its own.
""" """
# Helper classes and methods to implement the Exit. These need not # Helper classes and methods to implement the Exit. These need not
# be overloaded unless one want to change the foundation for how # be overloaded unless one want to change the foundation for how
# Exits work. See the end of the class for hook methods to overload. # Exits work. See the end of the class for hook methods to overload.
def create_exit_cmdset(self, exidbobj): def create_exit_cmdset(self, exidbobj):
""" """
@ -812,8 +812,8 @@ class Exit(Object):
The command of this cmdset has the same name as the Exit object The command of this cmdset has the same name as the Exit object
and allows the exit to react when the player enter the exit's name, and allows the exit to react when the player enter the exit's name,
triggering the movement between rooms. triggering the movement between rooms.
Note that exitdbobj is an ObjectDB instance. This is necessary Note that exitdbobj is an ObjectDB instance. This is necessary
for handling reloads and avoid tracebacks if this is called while for handling reloads and avoid tracebacks if this is called while
the typeclass system is rebooting. the typeclass system is rebooting.
@ -821,9 +821,9 @@ class Exit(Object):
class ExitCommand(command.Command): class ExitCommand(command.Command):
""" """
This is a command that simply cause the caller This is a command that simply cause the caller
to traverse the object it is attached to. to traverse the object it is attached to.
""" """
locks = "cmd:all()" # should always be set to this. locks = "cmd:all()" # should always be set to this.
obj = None obj = None
arg_regex=r"\s.*?|$" arg_regex=r"\s.*?|$"
@ -831,15 +831,15 @@ class Exit(Object):
"Default exit traverse if no syscommand is defined." "Default exit traverse if no syscommand is defined."
if self.obj.access(self.caller, 'traverse'): if self.obj.access(self.caller, 'traverse'):
# we may traverse the exit. # we may traverse the exit.
old_location = None old_location = None
if hasattr(self.caller, "location"): if hasattr(self.caller, "location"):
old_location = self.caller.location old_location = self.caller.location
# call pre/post hooks and move object. # call pre/post hooks and move object.
self.obj.at_before_traverse(self.caller) self.obj.at_before_traverse(self.caller)
self.caller.move_to(self.obj.destination) self.caller.move_to(self.obj.destination)
self.obj.at_after_traverse(self.caller, old_location) self.obj.at_after_traverse(self.caller, old_location)
else: else:
@ -853,7 +853,7 @@ class Exit(Object):
# create an exit command. # create an exit command.
cmd = ExitCommand() cmd = ExitCommand()
cmd.key = exidbobj.db_key.strip().lower() cmd.key = exidbobj.db_key.strip().lower()
cmd.obj = exidbobj cmd.obj = exidbobj
cmd.aliases = exidbobj.aliases cmd.aliases = exidbobj.aliases
cmd.locks = str(exidbobj.locks) cmd.locks = str(exidbobj.locks)
cmd.destination = exidbobj.db_destination cmd.destination = exidbobj.db_destination
@ -861,18 +861,18 @@ class Exit(Object):
exit_cmdset = cmdset.CmdSet(None) exit_cmdset = cmdset.CmdSet(None)
exit_cmdset.key = '_exitset' exit_cmdset.key = '_exitset'
exit_cmdset.priority = 9 exit_cmdset.priority = 9
exit_cmdset.duplicates = True exit_cmdset.duplicates = True
# add command to cmdset # add command to cmdset
exit_cmdset.add(cmd) exit_cmdset.add(cmd)
return exit_cmdset return exit_cmdset
# Command hooks # Command hooks
def basetype_setup(self): def basetype_setup(self):
""" """
Setup exit-security Setup exit-security
Don't change this, instead edit at_object_creation() to Don't change this, instead edit at_object_creation() to
overload the default locks (it is called after this one). overload the default locks (it is called after this one).
""" """
super(Exit, self).basetype_setup() super(Exit, self).basetype_setup()
@ -880,34 +880,34 @@ class Exit(Object):
self.locks.add("puppet:false()") # would be weird to puppet an exit ... self.locks.add("puppet:false()") # would be weird to puppet an exit ...
self.locks.add("traverse:all()") # who can pass through exit by default self.locks.add("traverse:all()") # who can pass through exit by default
self.locks.add("get:false()") # noone can pick up the exit self.locks.add("get:false()") # noone can pick up the exit
# an exit should have a destination (this is replaced at creation time) # an exit should have a destination (this is replaced at creation time)
if self.dbobj.location: if self.dbobj.location:
self.destination = self.dbobj.location self.destination = self.dbobj.location
def at_cmdset_get(self): def at_cmdset_get(self):
""" """
Called when the cmdset is requested from this object, just before the cmdset is Called when the cmdset is requested from this object, just before the cmdset is
actually extracted. If no Exit-cmdset is cached, create it now. actually extracted. If no Exit-cmdset is cached, create it now.
""" """
if self.ndb.exit_reset or not self.cmdset.has_cmdset("_exitset", must_be_default=True): if self.ndb.exit_reset or not self.cmdset.has_cmdset("_exitset", must_be_default=True):
# we are resetting, or no exit-cmdset was set. Create one dynamically. # we are resetting, or no exit-cmdset was set. Create one dynamically.
self.cmdset.add_default(self.create_exit_cmdset(self.dbobj), permanent=False) self.cmdset.add_default(self.create_exit_cmdset(self.dbobj), permanent=False)
self.ndb.exit_reset = False self.ndb.exit_reset = False
# this and other hooks are what usually can be modified safely. # this and other hooks are what usually can be modified safely.
def at_object_creation(self): def at_object_creation(self):
"Called once, when object is first created (after basetype_setup)." "Called once, when object is first created (after basetype_setup)."
pass pass
def at_failed_traverse(self, traversing_object): def at_failed_traverse(self, traversing_object):
""" """
This is called if an object fails to traverse this object for some This is called if an object fails to traverse this object for some
reason. It will not be called if the attribute "err_traverse" is defined, reason. It will not be called if the attribute "err_traverse" is defined,
that attribute will then be echoed back instead as a convenient shortcut. that attribute will then be echoed back instead as a convenient shortcut.
(See also hooks at_before_traverse and at_after_traverse). (See also hooks at_before_traverse and at_after_traverse).
""" """
traversing_object.msg("You cannot go there.") traversing_object.msg("You cannot go there.")

View file

@ -19,7 +19,7 @@ try:
from django.utils.unittest import TestCase from django.utils.unittest import TestCase
except ImportError: except ImportError:
from django.test import TestCase from django.test import TestCase
try: try:
from django.utils import unittest from django.utils import unittest
except ImportError: except ImportError:
import unittest import unittest
@ -47,10 +47,10 @@ class TestObjAttrs(TestCase):
self.obj1.db.testattr = self.obj2 self.obj1.db.testattr = self.obj2
self.assertEqual(self.obj2 ,self.obj1.db.testattr) self.assertEqual(self.obj2 ,self.obj1.db.testattr)
self.assertEqual(self.obj2.location, self.obj1.db.testattr.location) self.assertEqual(self.obj2.location, self.obj1.db.testattr.location)
def suite(): def suite():
""" """
This function is called automatically by the django test runner. This function is called automatically by the django test runner.
This also runs the command tests defined in src/commands/default/tests.py. This also runs the command tests defined in src/commands/default/tests.py.
""" """
tsuite = unittest.TestSuite() tsuite = unittest.TestSuite()

View file

@ -1,7 +1,7 @@
""" """
This implements the common managers that are used by the This implements the common managers that are used by the
abstract models in dbobjects.py (and which are thus shared by abstract models in dbobjects.py (and which are thus shared by
all Attributes and TypedObjects). all Attributes and TypedObjects).
""" """
from functools import update_wrapper from functools import update_wrapper
from django.db import models from django.db import models
@ -9,15 +9,15 @@ from src.utils import idmapper
from src.utils.utils import make_iter from src.utils.utils import make_iter
#from src.typeclasses import idmap #from src.typeclasses import idmap
# Managers # Managers
class AttributeManager(models.Manager): class AttributeManager(models.Manager):
"Manager for handling Attributes." "Manager for handling Attributes."
def attr_namesearch(self, searchstr, obj, exact_match=True): def attr_namesearch(self, searchstr, obj, exact_match=True):
""" """
Searches the object's attributes for name matches. Searches the object's attributes for name matches.
searchstr: (str) A string to search for. searchstr: (str) A string to search for.
""" """
# Retrieve the list of attributes for this object. # Retrieve the list of attributes for this object.
@ -29,21 +29,21 @@ class AttributeManager(models.Manager):
db_key__icontains=searchstr) db_key__icontains=searchstr)
# #
# helper functions for the TypedObjectManager. # helper functions for the TypedObjectManager.
# #
def returns_typeclass_list(method): def returns_typeclass_list(method):
""" """
Decorator: Chantes return of the decorated method (which are Decorator: Chantes return of the decorated method (which are
TypeClassed objects) into object_classes(s) instead. Will always TypeClassed objects) into object_classes(s) instead. Will always
return a list (may be empty). return a list (may be empty).
""" """
def func(self, *args, **kwargs): def func(self, *args, **kwargs):
"decorator. Returns a list." "decorator. Returns a list."
self.__doc__ = method.__doc__ self.__doc__ = method.__doc__
matches = method(self, *args, **kwargs) matches = method(self, *args, **kwargs)
return [(hasattr(dbobj, "typeclass") and dbobj.typeclass) or dbobj for dbobj in make_iter(matches)] return [(hasattr(dbobj, "typeclass") and dbobj.typeclass) or dbobj for dbobj in make_iter(matches)]
return update_wrapper(func, method) return update_wrapper(func, method)
def returns_typeclass(method): def returns_typeclass(method):
""" """
@ -55,8 +55,8 @@ def returns_typeclass(method):
rfunc = returns_typeclass_list(method) rfunc = returns_typeclass_list(method)
try: try:
return rfunc(self, *args, **kwargs)[0] return rfunc(self, *args, **kwargs)[0]
except IndexError: except IndexError:
return None return None
return update_wrapper(func, method) return update_wrapper(func, method)
@ -64,20 +64,20 @@ def returns_typeclass(method):
#class TypedObjectManager(models.Manager): #class TypedObjectManager(models.Manager):
class TypedObjectManager(idmapper.manager.SharedMemoryManager): class TypedObjectManager(idmapper.manager.SharedMemoryManager):
""" """
Common ObjectManager for all dbobjects. Common ObjectManager for all dbobjects.
""" """
def dbref(self, dbref): def dbref(self, dbref):
""" """
Valid forms of dbref (database reference number) Valid forms of dbref (database reference number)
are either a string '#N' or an integer N. are either a string '#N' or an integer N.
Output is the integer part. Output is the integer part.
""" """
if isinstance(dbref, basestring): if isinstance(dbref, basestring):
dbref = dbref.lstrip('#') dbref = dbref.lstrip('#')
try: try:
if int(dbref) < 1: if int(dbref) < 1:
return None return None
except Exception: except Exception:
return None return None
return dbref return dbref
@ -92,7 +92,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
""" """
self.filter() self.filter()
@returns_typeclass @returns_typeclass
def dbref_search(self, dbref): def dbref_search(self, dbref):
""" """
@ -131,25 +131,24 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
""" """
Returns a dictionary with all the typeclasses active in-game Returns a dictionary with all the typeclasses active in-game
as well as the number of such objects defined (i.e. the number as well as the number of such objects defined (i.e. the number
of database object having that typeclass set on themselves). of database object having that typeclass set on themselves).
""" """
dbtotals = {} dbtotals = {}
typeclass_paths = set(self.values_list('db_typeclass_path', flat=True)) typeclass_paths = set(self.values_list('db_typeclass_path', flat=True))
for typeclass_path in typeclass_paths: for typeclass_path in typeclass_paths:
dbtotals[typeclass_path] = \ dbtotals[typeclass_path] = \
self.filter(db_typeclass_path=typeclass_path).count() self.filter(db_typeclass_path=typeclass_path).count()
return dbtotals return dbtotals
@returns_typeclass_list @returns_typeclass_list
def typeclass_search(self, typeclass): def typeclass_search(self, typeclass):
""" """
Searches through all objects returning those which has a certain Searches through all objects returning those which has a certain
typeclass. If location is set, limit search to objects in typeclass. If location is set, limit search to objects in
that location. that location.
""" """
if callable(typeclass): if callable(typeclass):
cls = typeclass.__class__ cls = typeclass.__class__
typeclass = "%s.%s" % (cls.__module__, cls.__name__) typeclass = "%s.%s" % (cls.__module__, cls.__name__)
o_query = self.filter(db_typeclass_path__exact=typeclass) o_query = self.filter(db_typeclass_path__exact=typeclass)
return o_query return o_query

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,7 @@ with a 'normal' Python class. The only restrictions is that
the typeclass must inherit from TypeClass and not reimplement the typeclass must inherit from TypeClass and not reimplement
the get/setters defined below. There are also a few properties the get/setters defined below. There are also a few properties
that are protected, so as to not overwrite property names that are protected, so as to not overwrite property names
used by the typesystem or django itself. used by the typesystem or django itself.
""" """
from src.utils.logger import log_trace, log_errmsg from src.utils.logger import log_trace, log_errmsg
@ -45,12 +45,9 @@ class MetaTypeClass(type):
mcs.typename = mcs.__name__ mcs.typename = mcs.__name__
mcs.path = "%s.%s" % (mcs.__module__, mcs.__name__) mcs.path = "%s.%s" % (mcs.__module__, mcs.__name__)
def __str__(cls): def __str__(cls):
return "%s" % cls.__name__ return "%s" % cls.__name__
class TypeClass(object): class TypeClass(object):
""" """
This class implements a 'typeclass' object. This is connected This class implements a 'typeclass' object. This is connected
@ -58,14 +55,14 @@ class TypeClass(object):
the TypeClass allows for all customization. the TypeClass allows for all customization.
Most of the time this means that the admin never has to Most of the time this means that the admin never has to
worry about database access but only deal with extending worry about database access but only deal with extending
TypeClasses to create diverse objects in the game. TypeClasses to create diverse objects in the game.
The ObjectType class has all functionality for wrapping a The ObjectType class has all functionality for wrapping a
database object transparently. database object transparently.
It's up to its child classes to implement eventual custom hooks It's up to its child classes to implement eventual custom hooks
and other functions called by the engine. and other functions called by the engine.
""" """
__metaclass__ = MetaTypeClass __metaclass__ = MetaTypeClass
@ -73,8 +70,8 @@ class TypeClass(object):
""" """
Initialize the object class. There are two ways to call this class. Initialize the object class. There are two ways to call this class.
o = object_class(dbobj) : this is used to initialize dbobj with the class name o = object_class(dbobj) : this is used to initialize dbobj with the class name
o = dbobj.object_class(dbobj) : this is used when dbobj.object_class is already set. o = dbobj.object_class(dbobj) : this is used when dbobj.object_class is already set.
""" """
# typecheck of dbobj - we can't allow it to be added here # typecheck of dbobj - we can't allow it to be added here
# unless it's really a TypedObject. # unless it's really a TypedObject.
@ -84,7 +81,7 @@ class TypeClass(object):
raise Exception("dbobj is not a TypedObject: %s: %s" % (dbobj_cls, dbobj_mro)) raise Exception("dbobj is not a TypedObject: %s: %s" % (dbobj_cls, dbobj_mro))
# store the reference to the database model instance # store the reference to the database model instance
SA(self, 'dbobj', dbobj) SA(self, 'dbobj', dbobj)
def __getattribute__(self, propname): def __getattribute__(self, propname):
""" """
@ -93,7 +90,7 @@ class TypeClass(object):
self.dbobj. Note that dbobj properties have self.dbobj. Note that dbobj properties have
priority, so if you define a same-named priority, so if you define a same-named
property on the class, it will NOT be property on the class, it will NOT be
accessible through getattr. accessible through getattr.
""" """
if propname == 'dbobj': if propname == 'dbobj':
return GA(self, 'dbobj') return GA(self, 'dbobj')
@ -101,7 +98,7 @@ class TypeClass(object):
# python specials are parsed as-is (otherwise things like # python specials are parsed as-is (otherwise things like
# isinstance() fail to identify the typeclass) # isinstance() fail to identify the typeclass)
return GA(self, propname) return GA(self, propname)
#print "get %s (dbobj:%s)" % (propname, type(dbobj)) #print "get %s (dbobj:%s)" % (propname, type(dbobj))
try: try:
return GA(self, propname) return GA(self, propname)
except AttributeError: except AttributeError:
@ -109,7 +106,7 @@ class TypeClass(object):
dbobj = GA(self, 'dbobj') dbobj = GA(self, 'dbobj')
except AttributeError: except AttributeError:
log_trace("Typeclass CRITICAL ERROR! dbobj not found for Typeclass %s!" % self) log_trace("Typeclass CRITICAL ERROR! dbobj not found for Typeclass %s!" % self)
raise raise
try: try:
return GA(dbobj, propname) return GA(dbobj, propname)
except AttributeError: except AttributeError:
@ -118,31 +115,31 @@ class TypeClass(object):
except AttributeError: except AttributeError:
string = "Object: '%s' not found on %s(%s), nor on its typeclass %s." string = "Object: '%s' not found on %s(%s), nor on its typeclass %s."
raise AttributeError(string % (propname, dbobj, dbobj.dbref, dbobj.typeclass_path)) raise AttributeError(string % (propname, dbobj, dbobj.dbref, dbobj.typeclass_path))
def __setattr__(self, propname, value): def __setattr__(self, propname, value):
""" """
Transparently save data to the dbobj object in Transparently save data to the dbobj object in
all situations. Note that this does not all situations. Note that this does not
necessarily mean storing it to the database necessarily mean storing it to the database
unless data is stored into a propname unless data is stored into a propname
corresponding to a field on ObjectDB model. corresponding to a field on ObjectDB model.
""" """
#print "set %s -> %s" % (propname, value) #print "set %s -> %s" % (propname, value)
if propname in PROTECTED: if propname in PROTECTED:
string = "%s: '%s' is a protected attribute name." string = "%s: '%s' is a protected attribute name."
string += " (protected: [%s])" % (", ".join(PROTECTED)) string += " (protected: [%s])" % (", ".join(PROTECTED))
log_errmsg(string % (self.name, propname)) log_errmsg(string % (self.name, propname))
return return
try: try:
dbobj = GA(self, 'dbobj') dbobj = GA(self, 'dbobj')
except AttributeError: except AttributeError:
dbobj = None dbobj = None
log_trace("This is probably due to an unsafe reload.") log_trace("This is probably due to an unsafe reload.")
if dbobj: if dbobj:
try: try:
# only set value on propname if propname already exists # only set value on propname if propname already exists
# on dbobj. __getattribute__ will raise attribute error otherwise. # on dbobj. __getattribute__ will raise attribute error otherwise.
GA(dbobj, propname) GA(dbobj, propname)
SA(dbobj, propname, value) SA(dbobj, propname, value)
@ -154,25 +151,25 @@ class TypeClass(object):
def __eq__(self, other): def __eq__(self, other):
""" """
dbobj-recognized comparison dbobj-recognized comparison
""" """
try: try:
return other == self or other == GA(self, dbobj) or other == GA(self, dbobj).user return other == self or other == GA(self, dbobj) or other == GA(self, dbobj).user
except AttributeError: except AttributeError:
# if self.dbobj.user fails it means the two previous comparisons failed already # if self.dbobj.user fails it means the two previous comparisons failed already
return False return False
def __delattr__(self, propname): def __delattr__(self, propname):
""" """
Transparently deletes data from the typeclass or dbobj by first searching on the typeclass, Transparently deletes data from the typeclass or dbobj by first searching on the typeclass,
secondly on the dbobj.db. secondly on the dbobj.db.
Will not allow deletion of properties stored directly on dbobj. Will not allow deletion of properties stored directly on dbobj.
""" """
if propname in PROTECTED: if propname in PROTECTED:
string = "%s: '%s' is a protected attribute name." string = "%s: '%s' is a protected attribute name."
string += " (protected: [%s])" % (", ".join(PROTECTED)) string += " (protected: [%s])" % (", ".join(PROTECTED))
log_errmsg(string % (self.name, propname)) log_errmsg(string % (self.name, propname))
return return
try: try:
DA(self, propname) DA(self, propname)
@ -181,10 +178,10 @@ class TypeClass(object):
try: try:
dbobj = GA(self, 'dbobj') dbobj = GA(self, 'dbobj')
except AttributeError: except AttributeError:
log_trace("This is probably due to an unsafe reload.") log_trace("This is probably due to an unsafe reload.")
return # ignore delete return # ignore delete
try: try:
dbobj.del_attribute_raise(propname) dbobj.del_attribute_raise(propname)
except AttributeError: except AttributeError:
string = "Object: '%s' not found on %s(%s), nor on its typeclass %s." string = "Object: '%s' not found on %s(%s), nor on its typeclass %s."
raise AttributeError(string % (propname, dbobj, raise AttributeError(string % (propname, dbobj,

View file

@ -7,19 +7,19 @@ a higher layer module.
""" """
from traceback import format_exc from traceback import format_exc
from twisted.python import log from twisted.python import log
from src.utils import utils from src.utils import utils
def log_trace(errmsg=None): def log_trace(errmsg=None):
""" """
Log a traceback to the log. This should be called Log a traceback to the log. This should be called
from within an exception. errmsg is optional and from within an exception. errmsg is optional and
adds an extra line with added info. adds an extra line with added info.
""" """
tracestring = format_exc() tracestring = format_exc()
try: try:
if tracestring: if tracestring:
for line in tracestring.splitlines(): for line in tracestring.splitlines():
log.msg('[::] %s' % line) log.msg('[::] %s' % line)
if errmsg: if errmsg:
try: try:
errmsg = utils.to_str(errmsg) errmsg = utils.to_str(errmsg)
@ -29,7 +29,7 @@ def log_trace(errmsg=None):
log.msg('[EE] %s' % line) log.msg('[EE] %s' % line)
except Exception: except Exception:
log.msg('[EE] %s' % errmsg ) log.msg('[EE] %s' % errmsg )
def log_errmsg(errmsg): def log_errmsg(errmsg):
""" """
Prints/logs an error message to the server log. Prints/logs an error message to the server log.
@ -37,7 +37,7 @@ def log_errmsg(errmsg):
errormsg: (string) The message to be logged. errormsg: (string) The message to be logged.
""" """
try: try:
errmsg = utils.to_str(errmsg) errmsg = utils.to_str(errmsg)
except Exception, e: except Exception, e:
errmsg = str(e) errmsg = str(e)
for line in errmsg.splitlines(): for line in errmsg.splitlines():
@ -47,7 +47,7 @@ def log_errmsg(errmsg):
def log_warnmsg(warnmsg): def log_warnmsg(warnmsg):
""" """
Prints/logs any warnings that aren't critical but should be noted. Prints/logs any warnings that aren't critical but should be noted.
warnmsg: (string) The message to be logged. warnmsg: (string) The message to be logged.
""" """
try: try:

View file

@ -2,7 +2,7 @@
General helper functions that don't fit neatly under any given category. General helper functions that don't fit neatly under any given category.
They provide some useful string and conversion methods that might They provide some useful string and conversion methods that might
be of use when designing your own game. be of use when designing your own game.
""" """
@ -27,7 +27,7 @@ def is_iter(iterable):
def make_iter(obj): def make_iter(obj):
"Makes sure that the object is always iterable." "Makes sure that the object is always iterable."
if not hasattr(obj, '__iter__'): return [obj] if not hasattr(obj, '__iter__'): return [obj]
return obj return obj
def fill(text, width=78, indent=0): def fill(text, width=78, indent=0):
""" """
@ -53,8 +53,8 @@ def crop(text, width=78, suffix="[...]"):
ltext = len(to_str(text)) ltext = len(to_str(text))
if ltext <= width: if ltext <= width:
return text return text
else: else:
lsuffix = len(suffix) lsuffix = len(suffix)
return "%s%s" % (text[:width-lsuffix], suffix) return "%s%s" % (text[:width-lsuffix], suffix)
def dedent(text): def dedent(text):
@ -63,7 +63,7 @@ def dedent(text):
of a paragraph. This is useful for preserving of a paragraph. This is useful for preserving
triple-quoted string indentation while still triple-quoted string indentation while still
shifting it all to be next to the left edge of shifting it all to be next to the left edge of
the display. the display.
""" """
if not text: if not text:
return "" return ""
@ -92,11 +92,11 @@ def wildcard_to_regexp(instring):
regexp_string += "$" regexp_string += "$"
return regexp_string return regexp_string
def time_format(seconds, style=0): def time_format(seconds, style=0):
""" """
Function to return a 'prettified' version of a value in seconds. Function to return a 'prettified' version of a value in seconds.
Style 0: 1d 08:30 Style 0: 1d 08:30
Style 1: 1d Style 1: 1d
Style 2: 1 day, 8 hours, 30 minutes, 10 seconds Style 2: 1 day, 8 hours, 30 minutes, 10 seconds
@ -105,15 +105,15 @@ def time_format(seconds, style=0):
seconds = 0 seconds = 0
else: else:
# We'll just use integer math, no need for decimal precision. # We'll just use integer math, no need for decimal precision.
seconds = int(seconds) seconds = int(seconds)
days = seconds / 86400 days = seconds / 86400
seconds -= days * 86400 seconds -= days * 86400
hours = seconds / 3600 hours = seconds / 3600
seconds -= hours * 3600 seconds -= hours * 3600
minutes = seconds / 60 minutes = seconds / 60
seconds -= minutes * 60 seconds -= minutes * 60
if style is 0: if style is 0:
""" """
Standard colon-style output. Standard colon-style output.
@ -122,7 +122,7 @@ def time_format(seconds, style=0):
retval = '%id %02i:%02i' % (days, hours, minutes,) retval = '%id %02i:%02i' % (days, hours, minutes,)
else: else:
retval = '%02i:%02i' % (hours, minutes,) retval = '%02i:%02i' % (hours, minutes,)
return retval return retval
elif style is 1: elif style is 1:
""" """
@ -155,8 +155,8 @@ def time_format(seconds, style=0):
if minutes == 1: if minutes == 1:
minutes_str = '%i minute ' % minutes minutes_str = '%i minute ' % minutes
else: else:
minutes_str = '%i minutes ' % minutes minutes_str = '%i minutes ' % minutes
retval = '%s%s%s' % (days_str, hours_str, minutes_str) retval = '%s%s%s' % (days_str, hours_str, minutes_str)
elif style is 3: elif style is 3:
""" """
Full-detailed, long-winded format. Includes seconds. Full-detailed, long-winded format. Includes seconds.
@ -176,20 +176,20 @@ def time_format(seconds, style=0):
if minutes == 1: if minutes == 1:
minutes_str = '%i minute ' % minutes minutes_str = '%i minute ' % minutes
else: else:
minutes_str = '%i minutes ' % minutes minutes_str = '%i minutes ' % minutes
if minutes or seconds > 0: if minutes or seconds > 0:
if seconds == 1: if seconds == 1:
seconds_str = '%i second ' % seconds seconds_str = '%i second ' % seconds
else: else:
seconds_str = '%i seconds ' % seconds seconds_str = '%i seconds ' % seconds
retval = '%s%s%s%s' % (days_str, hours_str, minutes_str, seconds_str) retval = '%s%s%s%s' % (days_str, hours_str, minutes_str, seconds_str)
return retval return retval
def datetime_format(dtobj): def datetime_format(dtobj):
""" """
Takes a datetime object instance (e.g. from django's DateTimeField) Takes a datetime object instance (e.g. from django's DateTimeField)
and returns a string describing how long ago that date was. and returns a string describing how long ago that date was.
""" """
year, month, day = dtobj.year, dtobj.month, dtobj.day year, month, day = dtobj.year, dtobj.month, dtobj.day
@ -197,7 +197,7 @@ def datetime_format(dtobj):
now = datetime.datetime.now() now = datetime.datetime.now()
if year < now.year: if year < now.year:
# another year # another year
timestring = str(dtobj.date()) timestring = str(dtobj.date())
elif dtobj.date() < now.date(): elif dtobj.date() < now.date():
# another date, same year # another date, same year
@ -205,9 +205,9 @@ def datetime_format(dtobj):
elif hour < now.hour - 1: elif hour < now.hour - 1:
# same day, more than 1 hour ago # same day, more than 1 hour ago
timestring = "%02i:%02i" % (hour, minute) timestring = "%02i:%02i" % (hour, minute)
else: else:
# same day, less than 1 hour ago # same day, less than 1 hour ago
timestring = "%02i:%02i:%02i" % (hour, minute, second) timestring = "%02i:%02i:%02i" % (hour, minute, second)
return timestring return timestring
def host_os_is(osname): def host_os_is(osname):
@ -223,12 +223,12 @@ def get_evennia_version():
Check for the evennia version info. Check for the evennia version info.
""" """
try: try:
with open(settings.BASE_PATH + os.sep + "VERSION") as f: with open(settings.BASE_PATH + os.sep + "VERSION") as f:
return "%s-r%s" % (f.read().strip(), os.popen("hg id -i").read().strip()) return "%s-r%s" % (f.read().strip(), os.popen("hg id -i").read().strip())
return return
except IOError: except IOError:
return "Unknown version" return "Unknown version"
def pypath_to_realpath(python_path, file_ending='.py'): def pypath_to_realpath(python_path, file_ending='.py'):
""" """
Converts a path on dot python form (e.g. 'src.objects.models') to Converts a path on dot python form (e.g. 'src.objects.models') to
@ -238,17 +238,17 @@ def pypath_to_realpath(python_path, file_ending='.py'):
pathsplit = python_path.strip().split('.') pathsplit = python_path.strip().split('.')
if not pathsplit: if not pathsplit:
return python_path return python_path
path = settings.BASE_PATH path = settings.BASE_PATH
for directory in pathsplit: for directory in pathsplit:
path = os.path.join(path, directory) path = os.path.join(path, directory)
if file_ending: if file_ending:
return "%s%s" % (path, file_ending) return "%s%s" % (path, file_ending)
return path return path
def dbref(dbref): def dbref(dbref):
""" """
Converts/checks if input is a valid dbref Valid forms of dbref Converts/checks if input is a valid dbref Valid forms of dbref
(database reference number) are either a string '#N' or (database reference number) are either a string '#N' or
an integer N. Output is the integer part. an integer N. Output is the integer part.
""" """
if isinstance(dbref, basestring): if isinstance(dbref, basestring):
@ -256,11 +256,11 @@ def dbref(dbref):
try: try:
dbref = int(dbref) dbref = int(dbref)
if dbref < 1: if dbref < 1:
return None return None
except Exception: except Exception:
return None return None
return dbref return dbref
return None return None
def to_unicode(obj, encoding='utf-8', force_string=False): def to_unicode(obj, encoding='utf-8', force_string=False):
""" """
@ -268,7 +268,7 @@ def to_unicode(obj, encoding='utf-8', force_string=False):
one needs to encode it back to utf-8 before writing to disk or one needs to encode it back to utf-8 before writing to disk or
printing. Note that non-string objects are let through without printing. Note that non-string objects are let through without
conversion - this is important for e.g. Attributes. Use conversion - this is important for e.g. Attributes. Use
force_string to enforce conversion of objects to string. . force_string to enforce conversion of objects to string. .
""" """
if force_string and not isinstance(obj, basestring): if force_string and not isinstance(obj, basestring):
@ -279,28 +279,28 @@ def to_unicode(obj, encoding='utf-8', force_string=False):
elif hasattr(obj, '__unicode__'): elif hasattr(obj, '__unicode__'):
obj = obj.__unicode__() obj = obj.__unicode__()
else: else:
# last resort # last resort
obj = str(obj) obj = str(obj)
if isinstance(obj, basestring) and not isinstance(obj, unicode): if isinstance(obj, basestring) and not isinstance(obj, unicode):
try: try:
obj = unicode(obj, encoding) obj = unicode(obj, encoding)
return obj return obj
except UnicodeDecodeError: except UnicodeDecodeError:
for alt_encoding in ENCODINGS: for alt_encoding in ENCODINGS:
try: try:
obj = unicode(obj, alt_encoding) obj = unicode(obj, alt_encoding)
return obj return obj
except UnicodeDecodeError: except UnicodeDecodeError:
pass pass
raise Exception("Error: '%s' contains invalid character(s) not in %s." % (obj, encoding)) raise Exception("Error: '%s' contains invalid character(s) not in %s." % (obj, encoding))
return obj return obj
def to_str(obj, encoding='utf-8', force_string=False): def to_str(obj, encoding='utf-8', force_string=False):
""" """
This encodes a unicode string back to byte-representation, This encodes a unicode string back to byte-representation,
for printing, writing to disk etc. Note that non-string for printing, writing to disk etc. Note that non-string
objects are let through without modification - this is objects are let through without modification - this is
required e.g. for Attributes. Use force_string to force required e.g. for Attributes. Use force_string to force
conversion of objects to strings. conversion of objects to strings.
""" """
@ -313,7 +313,7 @@ def to_str(obj, encoding='utf-8', force_string=False):
elif hasattr(obj, '__unicode__'): elif hasattr(obj, '__unicode__'):
obj = obj.__unicode__() obj = obj.__unicode__()
else: else:
# last resort # last resort
obj = str(obj) obj = str(obj)
if isinstance(obj, basestring) and isinstance(obj, unicode): if isinstance(obj, basestring) and isinstance(obj, unicode):
@ -334,14 +334,14 @@ def validate_email_address(emailaddress):
""" """
Checks if an email address is syntactically correct. Checks if an email address is syntactically correct.
(This snippet was adapted from (This snippet was adapted from
http://commandline.org.uk/python/email-syntax-check.) http://commandline.org.uk/python/email-syntax-check.)
""" """
emailaddress = r"%s" % emailaddress emailaddress = r"%s" % emailaddress
domains = ("aero", "asia", "biz", "cat", "com", "coop", domains = ("aero", "asia", "biz", "cat", "com", "coop",
"edu", "gov", "info", "int", "jobs", "mil", "mobi", "museum", "edu", "gov", "info", "int", "jobs", "mil", "mobi", "museum",
"name", "net", "org", "pro", "tel", "travel") "name", "net", "org", "pro", "tel", "travel")
# Email address must be more than 7 characters in total. # Email address must be more than 7 characters in total.
@ -372,11 +372,11 @@ def validate_email_address(emailaddress):
def inherits_from(obj, parent): def inherits_from(obj, parent):
""" """
Takes an object and tries to determine if it inherits at any distance Takes an object and tries to determine if it inherits at any distance
from parent. What differs this function from e.g. isinstance() from parent. What differs this function from e.g. isinstance()
is that obj may be both an instance and a class, and parent is that obj may be both an instance and a class, and parent
< may be an instance, a class, or the python path to a class (counting < may be an instance, a class, or the python path to a class (counting
from the evennia root directory). from the evennia root directory).
""" """
if callable(obj): if callable(obj):
@ -384,7 +384,7 @@ def inherits_from(obj, parent):
obj_paths = ["%s.%s" % (mod.__module__, mod.__name__) for mod in obj.mro()] obj_paths = ["%s.%s" % (mod.__module__, mod.__name__) for mod in obj.mro()]
else: else:
obj_paths = ["%s.%s" % (mod.__module__, mod.__name__) for mod in obj.__class__.mro()] obj_paths = ["%s.%s" % (mod.__module__, mod.__name__) for mod in obj.__class__.mro()]
if isinstance(parent, basestring): if isinstance(parent, basestring):
# a given string path, for direct matching # a given string path, for direct matching
parent_path = parent parent_path = parent
@ -400,56 +400,56 @@ def format_table(table, extra_space=1):
""" """
Takes a table of collumns: [[val,val,val,...], [val,val,val,...], ...] Takes a table of collumns: [[val,val,val,...], [val,val,val,...], ...]
where each val will be placed on a separate row in the column. All where each val will be placed on a separate row in the column. All
collumns must have the same number of rows (some positions may be collumns must have the same number of rows (some positions may be
empty though). empty though).
The function formats the columns to be as wide as the widest member The function formats the columns to be as wide as the widest member
of each column. of each column.
extra_space defines how much extra padding should minimum be left between
collumns.
print the resulting list e.g. with extra_space defines how much extra padding should minimum be left between
collumns.
print the resulting list e.g. with
for ir, row in enumarate(ftable): for ir, row in enumarate(ftable):
if ir == 0: if ir == 0:
# make first row white # make first row white
string += "\n{w" + ""join(row) + "{n" string += "\n{w" + ""join(row) + "{n"
else: else:
string += "\n" + "".join(row) string += "\n" + "".join(row)
print string print string
""" """
if not table: if not table:
return [[]] return [[]]
max_widths = [max([len(str(val)) for val in col]) for col in table] max_widths = [max([len(str(val)) for val in col]) for col in table]
ftable = [] ftable = []
for irow in range(len(table[0])): for irow in range(len(table[0])):
ftable.append([str(col[irow]).ljust(max_widths[icol]) + " " * extra_space ftable.append([str(col[irow]).ljust(max_widths[icol]) + " " * extra_space
for icol, col in enumerate(table)]) for icol, col in enumerate(table)])
return ftable return ftable
def run_async(async_func, at_return=None, at_err=None): def run_async(async_func, at_return=None, at_err=None):
""" """
This wrapper will use Twisted's asynchronous features to run a slow This wrapper will use Twisted's asynchronous features to run a slow
function using a separate reactor thread. In effect this means that function using a separate reactor thread. In effect this means that
the server will not be blocked while the slow process finish. the server will not be blocked while the slow process finish.
Use this function with restrain and only for features/commands Use this function with restrain and only for features/commands
that you know has no influence on the cause-and-effect order of your that you know has no influence on the cause-and-effect order of your
game (commands given after the async function might be executed before game (commands given after the async function might be executed before
it has finished). it has finished).
async_func() - function that should be run asynchroneously async_func() - function that should be run asynchroneously
at_return(r) - if given, this function will be called when async_func returns at_return(r) - if given, this function will be called when async_func returns
value r at the end of a successful execution value r at the end of a successful execution
at_err(e) - if given, this function is called if async_func fails with an exception e. at_err(e) - if given, this function is called if async_func fails with an exception e.
use e.trap(ExceptionType1, ExceptionType2) use e.trap(ExceptionType1, ExceptionType2)
""" """
# create deferred object # create deferred object
deferred = threads.deferToThread(async_func) deferred = threads.deferToThread(async_func)
if at_return: if at_return:
deferred.addCallback(at_return) deferred.addCallback(at_return)
@ -458,7 +458,7 @@ def run_async(async_func, at_return=None, at_err=None):
# always add a logging errback as a last catch # always add a logging errback as a last catch
def default_errback(e): def default_errback(e):
from src.utils import logger from src.utils import logger
logger.log_trace(e) logger.log_trace(e)
deferred.addErrback(default_errback) deferred.addErrback(default_errback)
@ -491,7 +491,7 @@ def check_evennia_dependencies():
import twisted import twisted
tversion = twisted.version.short() tversion = twisted.version.short()
if tversion < twisted_min: if tversion < twisted_min:
errstring += "\n WARNING: Twisted %s found. Evennia recommends version %s or higher." % (twisted.version.short(), twisted_min) errstring += "\n WARNING: Twisted %s found. Evennia recommends version %s or higher." % (twisted.version.short(), twisted_min)
except ImportError: except ImportError:
errstring += "\n ERROR: Twisted does not seem to be installed." errstring += "\n ERROR: Twisted does not seem to be installed."
no_error = False no_error = False
@ -508,12 +508,12 @@ def check_evennia_dependencies():
# South # South
try: try:
import south import south
sversion = south.__version__ sversion = south.__version__
if sversion < south_min: if sversion < south_min:
errstring += "\n WARNING: South version %s found. Evennia recommends version %s or higher." % (sversion, south_min) errstring += "\n WARNING: South version %s found. Evennia recommends version %s or higher." % (sversion, south_min)
except ImportError: except ImportError:
pass pass
# IRC support # IRC support
if settings.IRC_ENABLED: if settings.IRC_ENABLED:
try: try:
import twisted.words import twisted.words
@ -521,7 +521,7 @@ def check_evennia_dependencies():
errstring += "\n ERROR: IRC is enabled, but twisted.words is not installed. Please install it." errstring += "\n ERROR: IRC is enabled, but twisted.words is not installed. Please install it."
errstring += "\n Linux Debian/Ubuntu users should install package 'python-twisted-words', others" errstring += "\n Linux Debian/Ubuntu users should install package 'python-twisted-words', others"
errstring += "\n can get it from http://twistedmatrix.com/trac/wiki/TwistedWords." errstring += "\n can get it from http://twistedmatrix.com/trac/wiki/TwistedWords."
no_error = False no_error = False
errstring = errstring.strip() errstring = errstring.strip()
if errstring: if errstring:
print "%s\n %s\n%s" % ("-"*78, errstring, '-'*78) print "%s\n %s\n%s" % ("-"*78, errstring, '-'*78)
@ -534,21 +534,21 @@ def has_parent(basepath, obj):
if basepath == "%s.%s" % (cls.__module__, cls.__name__)) if basepath == "%s.%s" % (cls.__module__, cls.__name__))
except (TypeError, AttributeError): except (TypeError, AttributeError):
# this can occur if we tried to store a class object, not an # this can occur if we tried to store a class object, not an
# instance. Not sure if one should defend against this. # instance. Not sure if one should defend against this.
return False return False
def mod_import(mod_path, propname=None): def mod_import(mod_path, propname=None):
""" """
Takes filename of a module (a python path or a full pathname) Takes filename of a module (a python path or a full pathname)
and imports it. If property is given, return the named and imports it. If property is given, return the named
property from this module instead of the module itself. property from this module instead of the module itself.
""" """
def log_trace(errmsg=None): def log_trace(errmsg=None):
""" """
Log a traceback to the log. This should be called Log a traceback to the log. This should be called
from within an exception. errmsg is optional and from within an exception. errmsg is optional and
adds an extra line with added info. adds an extra line with added info.
""" """
from traceback import format_exc from traceback import format_exc
from twisted.python import log from twisted.python import log
@ -557,7 +557,7 @@ def mod_import(mod_path, propname=None):
tracestring = format_exc() tracestring = format_exc()
if tracestring: if tracestring:
for line in tracestring.splitlines(): for line in tracestring.splitlines():
log.msg('[::] %s' % line) log.msg('[::] %s' % line)
if errmsg: if errmsg:
try: try:
errmsg = to_str(errmsg) errmsg = to_str(errmsg)
@ -569,10 +569,10 @@ def mod_import(mod_path, propname=None):
if not mod_path: if not mod_path:
return None return None
# first try to import as a python path # first try to import as a python path
try: try:
mod = __import__(mod_path, fromlist=["None"]) mod = __import__(mod_path, fromlist=["None"])
except ImportError: except ImportError:
# try absolute path import instead # try absolute path import instead
if not os.path.isabs(mod_path): if not os.path.isabs(mod_path):
@ -583,13 +583,13 @@ def mod_import(mod_path, propname=None):
try: try:
result = imp.find_module(modname, [path]) result = imp.find_module(modname, [path])
except ImportError: except ImportError:
log_trace("Could not find module '%s' (%s.py) at path '%s'" % (modname, modname, path)) log_trace("Could not find module '%s' (%s.py) at path '%s'" % (modname, modname, path))
return return
try: try:
mod = imp.load_module(modname, *result) mod = imp.load_module(modname, *result)
except ImportError: except ImportError:
log_trace("Could not find or import module %s at path '%s'" % (modname, path)) log_trace("Could not find or import module %s at path '%s'" % (modname, path))
mod = None mod = None
# we have to close the file handle manually # we have to close the file handle manually
result[0].close() result[0].close()
@ -598,16 +598,16 @@ def mod_import(mod_path, propname=None):
try: try:
mod_prop = mod.__dict__[to_str(propname)] mod_prop = mod.__dict__[to_str(propname)]
except KeyError: except KeyError:
log_trace("Could not import property '%s' from module %s." % (propname, mod_path)) log_trace("Could not import property '%s' from module %s." % (propname, mod_path))
return None return None
return mod_prop return mod_prop
return mod return mod
def variable_from_module(modpath, variable, default=None): def variable_from_module(modpath, variable, default=None):
""" """
Retrieve a given variable from a module. The variable must be Retrieve a given variable from a module. The variable must be
defined globally in the module. This can be used to implement defined globally in the module. This can be used to implement
arbitrary plugin imports in the server. arbitrary plugin imports in the server.
If module cannot be imported or variable not found, default If module cannot be imported or variable not found, default
is returned. is returned.
@ -627,7 +627,7 @@ def string_from_module(modpath, variable=None, default=None):
This obtains a string from a given module python path. Using a This obtains a string from a given module python path. Using a
specific variable name will also retrieve non-strings. specific variable name will also retrieve non-strings.
The variable must be global within that module - that is, defined The variable must be global within that module - that is, defined
in the outermost scope of the module. The value of the variable in the outermost scope of the module. The value of the variable
will be returned. If not found, default is returned. If no variable is will be returned. If not found, default is returned. If no variable is
@ -640,8 +640,8 @@ def string_from_module(modpath, variable=None, default=None):
if variable: if variable:
return mod.__dict__.get(variable, default) return mod.__dict__.get(variable, default)
else: else:
mvars = [val for key, val in mod.__dict__.items() mvars = [val for key, val in mod.__dict__.items()
if not key.startswith('_') and isinstance(val, basestring)] if not key.startswith('_') and isinstance(val, basestring)]
if not mvars: if not mvars:
return default return default
return mvars[random.randint(0, len(mvars)-1)] return mvars[random.randint(0, len(mvars)-1)]
@ -651,8 +651,8 @@ def init_new_player(player):
Helper method to call all hooks, set flags etc on a newly created Helper method to call all hooks, set flags etc on a newly created
player (and potentially their character, if it exists already) player (and potentially their character, if it exists already)
""" """
# the FIRST_LOGIN flags are necessary for the system to call # the FIRST_LOGIN flags are necessary for the system to call
# the relevant first-login hooks. # the relevant first-login hooks.
if player.character: if player.character:
player.character.db.FIRST_LOGIN = True player.character.db.FIRST_LOGIN = True
player.db.FIRST_LOGIN = True player.db.FIRST_LOGIN = True