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
# ignore, defined on glob format.
# This specifies file types that mercurial should
# ignore, defined on glob format.
syntax: glob

View file

@ -1,59 +1,63 @@
Evennia Code Style
------------------
All code submitted or committed to the Evennia project needs to follow the
guidelines outlined in Python PEP 8, which may be found at:
All code submitted or committed to the Evennia project needs to follow
the guidelines outlined in Python PEP 8, which may be found at:
http://www.python.org/dev/peps/pep-0008/
A quick list of code style points
---------------------------------
* 4-space indendation, NO TABS!
* Unix line endings.
* CamelCase is only used for classes, nothing else.
* All non-global variable names and all function names are to be lowercase,
words separated by underscores. Variable names should always be more than
two letters long.
* CamelCase is only used for classes, nothing else.
* All non-global variable names and all function names are to be
lowercase, words separated by underscores. Variable names should
always be more than two letters long.
* Module-level global variables (only) are to be in CAPITAL letters.
* Imports are to be done in this order:
- Python modules (builtins and modules otherwise unrelated to Evennia)
- Twisted
- Django
* (Evennia-specific): Imports should normally be done in this order:
- Python modules (builtins and standard library)
- Twisted modules
- Django modules
- Evennia src/ modules
- Evennia game/ modules
- Evennia 'ev' API imports
Documentation
-------------
Remember that Evennia's source code is intended to be read - and will be read - by
game admins trying to implement various features. Evennia prides itself with being
extensively documented. Modules, functions, classes and class methods should all
start with at least one line of docstring summing up the function's purpose. Ideally
also explain eventual arguments and caveats. Add comments where appropriate.
Remember that Evennia's source code is intended to be read - and will
be read - by game admins trying to implement their game. Evennia
prides itself with being extensively documented. Modules, functions,
classes and class methods should all start with at least one line of
docstring summing up the function's purpose. Ideally also explain
eventual arguments and caveats. Add comments where appropriate.
Pylint
------
The program 'pylint' (http://www.logilab.org/857) is a useful tool for checking
your Python code for errors. It will also check how well your code adheres to
the PEP 8 guidelines (such as lack of docstrings) and tells you what can be improved.
The program 'pylint' (http://www.logilab.org/857) is a useful tool for
checking your Python code for errors. It will also check how well your
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
elsewhere in Evennia, one needs to reduce some checks to avoid false errors and
warnings. For best results, run pylint like this:
Since pylint cannot catch dynamically created variables used in
commands and elsewhere in Evennia, one needs to reduce some checks to
avoid false errors and warnings. For best results, run pylint like
this:
> pylint --disable=E1101,E0102,F0401,W0232,R0903 filename.py
To avoid entering the options every time, you can auto-create a pylintrc file by
using the option --generate-rcfile. You need to dump this output into a
file .pylintrc, for example like this (linux):
To avoid entering the options every time, you can auto-create a
pylintrc file by using the option --generate-rcfile. You need to dump
this output into a file .pylintrc, for example like this (linux):
> 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
Ask Questions!
--------------
If any of the rules outlined in PEP 8 or in the sections above doesn't make sense, please
don't hesitate to ask on the Evennia mailing list at http://evennia.com.
Keeping our code style uniform makes this project much easier for a wider group
of people to participate in.
If any of the rules outlined in PEP 8 or in the sections above doesn't
make sense, please don't hesitate to ask on the Evennia mailing list
at http://evennia.com. Keeping our code style uniform makes this
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
@ -12,45 +12,46 @@ Installation
* Make sure you have/install the prerequsites with minimum versions
listed on http://code.google.com/p/evennia/wiki/GettingStarted:
- python
- python
- django
- twisted + PIL
- mercurial
- django-south (optional)
- mercurial
- 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/
* Get a copy of the Evennia source:
$ hg clone https://code.google.com/p/evennia/ evennia
* Change to the evennia/game directory and run the setup scripts.
$ cd evennia/game
$ python manage.py
* Edit the new game/settings.py if needed, then run
$ cd evennia/game
$ python manage.py
* Edit the new game/settings.py if needed, then run
(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
$ python manage.py migrate
Starting Evennia
Starting Evennia
----------------
$ python evennia.py -i start
or
$ python evennia.py
for a menu of launch options.
* Start the server with
$ python evennia.py -i start
or run without arguments for a menu of launch options.
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.
@ -59,8 +60,14 @@ Starting Evennia
* Alternatively, you can find the web interface and webclient by
pointing your web browser to http://localhost:8000.
* Login with the email address and password you provided to the syncdb script.
Welcome to Evennia!
* Login with the email address and password you provided when setting up the server.
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
modifications in the Standard Version of the Package.
2. make other distribution arrangements with the Copyright Holder.
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:
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
promote products derived from this software without specific prior
written permission.
10. Credits and attributions in the headers of all source and
documentation files must be left intact.

136
README
View file

@ -1,6 +1,6 @@
-----------------------------------
Evennia README
Evennia README
(http://evennia.com)
Beta hg (mercurial) version
-----------------------------------
@ -9,39 +9,37 @@
About Evennia
-------------
Evennia is a MUD/MUX/MU* server that aims to provide a functional
bare-bones base for developers. Some of our main features are:
Evennia is a MUD/MUX/MU* development system and server that aims to
provide a functional bare-bones codebase for developers. Some of our main
features are:
* Coded and extended using normal Python modules.
* Extensive web integration due to our use of Django.
* Runs its own Twisted webserver. Comes with game website and ajax web-browser mud client.
* Extensive current and potential connectivity and protocol-support through Twisted.
* Extremely easy-to-manipulate SQL database back-end via Django
(djangoproject.com)
* Powerful an extremely extendable bare-bones base system
* Coded and extended using normal Python modules.
* Reload code without players logging off
* Database handling and network connectivity are abstracted away
* Extensive web integration due to our use of Django.
* Server runs game website and ajax web-browser mud client out of the box.
* Supports a slew of different connection protocols with Twisted.
* Extremely extendable to almost any sort of text-based multiplayer game
The Django framework has database abstraction abilities that give us
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.
See the INSTALL file for help on setting up and running Evennia.
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
contrib and privately) as well testing lots of things to make sure the
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
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
maturing. All commands from the pre-Aug version, including IRC/IMC2
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
Apache or django-testserver). Contrib-folder added.
including moving Evennia to be its own webserver (no more need for
Apache or django-testserver). Contrib-folder added.
Aug 2010:
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.
May 2010:
Evennia is currently being heavily revised and cleaned from
the years of gradual piecemeal development. It is thus in a very
Evennia is currently being heavily revised and cleaned from
the years of gradual piecemeal development. It is thus in a very
'Alpha' stage at the moment. This means that old code snippets
will not be backwards compatabile. Changes touch almost all
parts of Evennia's innards, from the way Objects are handled
to Events, Commands and Permissions.
parts of Evennia's innards, from the way Objects are handled
to Events, Commands and Permissions.
April 2010:
April 2010:
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)
@ -98,67 +96,67 @@ appreciate all help! Visit either of the following resources:
* Evennia Webpage
http://evennia.com
* Evennia manual (wiki)
http://code.google.com/p/evennia/wiki/Index
* Evennia Code Page (See INSTALL text for installation)
http://code.google.com/p/evennia/source/checkout
* Bug tracker
* Bug tracker
http://code.google.com/p/evennia/issues/list
* IRC channel
* IRC channel
visit channel #evennia on the Freenode IRC network
Directory structure
-------------------
evennia
|
|_______src
| |___(engine-related dirs)
|
|_______game (start the server)
|
| ev.py
|_______game (start the server, settings)
| |___gamesrc
| |___(game-related dirs)
|
|_______contrib
|_______src
| |___(engine-related dirs)
| |
|_______contrib
|
|_______docs
|_______docs
|
|_______locales
The two main directories you will spend most of your time in
are src/ and game/ (probably mostly game/).
ev.py is the API file. It contains easy shortcuts to most
of Evennia's functionality. Import ev into a python interpreter
(like ipython) and explore what's available.
Basically src/ contains everything related to
running the gritty stuff behind the scenes. Unless you are an
Evennia developer you should normally make sure never to edit
things in src/, since this is where we push new revisions that
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.
The game/ folder is where you develop your game. The root
of this directory contains the settings file and the executables
to start the server. Under game/gamesrc you will create the
modules that will define your game.
If src/ is the Evennia developer's domain, the game/ directory
on the other hand contains YOUR game. This is where you will
define and extend the commands, objects and systems of Evennia
to make your dream game. game/ contains the main server settings
and the actual evennia executable to start things. game/gamesrc/
holds all the templates for creating objects in your virtual world.
src/ contains the Evennia library. As a normal user you should
not edit anything in this folder - you will run into mercurial
conflicts as we update things from our end. If you see code
you like (such as that of a default command), copy&paste it
into a new module in game/gamesrc/ instead. If you find that
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
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
and use them.
docs/ contain offline versions of the documentation, you can use
python-sphinx to convert the raw data to nice-looking output for
printing etc. The online wiki is otherwise first to be updated.
docs/ contain offline versions of the documentation, you can use
python-sphinx to convert the raw data to nice-looking output for
printing etc. The online wiki is however the most updated version
of the documentation.
locales/ holds translations of the server strings to other languages
than English.
With this little first orientation, you should head into the online
Evennia wiki documentation to get going with the codebase.
Enjoy!

64
ev.py
View file

@ -2,42 +2,42 @@
Central API for the Evennia MUD/MUX/MU* creation system.
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
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
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
dot-notation to import deeper. Hence, to access a default command, you can do the
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
following:
import ev
ev.default_cmds.CmdLook
or
or
from ev import default_cmds
default_cmds.CmdLook
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.
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.
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
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)
return database objects. You can convert between the two easily via dbobj.typeclass and
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
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
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
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
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
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
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
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.
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
the modules in src/ directly. You can always do this anyway, if you do not want to go through
this API.
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
the modules in src/ directly. You can always do this anyway, if you do not want to go through
this API.
"""
@ -53,7 +53,7 @@ if __name__ == "__main__":
| not be run on its own, but be imported and accessed as described
| 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.
"""
print info
@ -86,11 +86,11 @@ del sys, os
README = __doc__
# help entries
# help entries
from src.help.models import HelpEntry
db_helpentries = HelpEntry.objects
# players
# players
from src.players.player import Player
from src.players.models import PlayerDB, PlayerAttribute, PlayerNick
db_players = PlayerDB.objects
@ -106,8 +106,8 @@ from src.commands import default as default_cmds
class SystemCmds(object):
"""
Creating commands with keys set to these constants will make
them system commands called as a replacement by the parser when
special situations occur. If not defined, the hard-coded
them system commands called as a replacement by the parser when
special situations occur. If not defined, the hard-coded
responses in the server are used.
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_LOGINSTART - this command will be called as the very
first command when a player connects to
the server.
the server.
"""
from src.commands import cmdhandler
@ -125,13 +125,13 @@ class SystemCmds(object):
CMD_MULTIMATCH = cmdhandler.CMD_MULTIMATCH
CMD_CHANNEL = cmdhandler.CMD_CHANNEL
CMD_LOGINSTART = cmdhandler.CMD_LOGINSTART
del cmdhandler
del cmdhandler
syscmdkeys = SystemCmds()
# locks
from src.locks import lockfuncs
# scripts
# scripts
from src.scripts.scripts import Script
from src.scripts.models import ScriptDB, ScriptAttribute
db_scripts = ScriptDB.objects
@ -154,7 +154,7 @@ db_objects = ObjectDB.objects
#db_objattrs = ObjAttribute.objects
del ObjAttribute, Alias, ObjectNick, ObjectDB
# server
# server
from src.server.models import ServerConfig
db_serverconfigs = ServerConfig.objects
del ServerConfig

View file

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

View file

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

View file

@ -2,35 +2,35 @@
Command handler
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.
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
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
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
for giving the channel name and have the following immediately
sent to the channel. The sending is performed by the CMD_CHANNEL
system command.
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.
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.
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.
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
7) The raw input string is parsed using the parser defined by settings.COMMAND_PARSER. It
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
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
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),
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
will be available to the command coder at run-time.
12) We have a unique cmdobject, primed for use. Call all hooks:
at_pre_cmd(), cmdobj.parse(), cmdobj.func() and finally at_post_cmd().
will be available to the command coder at run-time.
12) We have a unique cmdobject, primed for use. Call all hooks:
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 src.comms.channelhandler import CHANNELHANDLER
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
#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))
# There are a few system-hardcoded command names. These
# allow for custom behaviour when the command handler hits
# 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
# to remember the actual string constants.
# to remember the actual string constants.
CMD_NOINPUT = "__noinput_command"
CMD_NOMATCH = "__nomatch_command"
@ -61,12 +61,12 @@ CMD_MULTIMATCH = "__multimatch_command"
CMD_CHANNEL = "__send_to_channel_command"
# this is the name of the command the engine calls when the player
# connects. It is expected to show the login screen.
CMD_LOGINSTART = "__unloggedin_look_command"
CMD_LOGINSTART = "__unloggedin_look_command"
class NoCmdSets(Exception):
"No cmdsets found. Critical error."
pass
pass
class ExecSystemCommand(Exception):
"Run a system command"
def __init__(self, syscmd, sysarg):
@ -77,10 +77,10 @@ class ExecSystemCommand(Exception):
@inlineCallbacks
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.
"""
# The calling object's cmdset
# The calling object's cmdset
try:
yield caller.at_cmdset_get()
except Exception:
@ -89,17 +89,17 @@ def get_and_merge_cmdsets(caller):
caller_cmdset = caller.cmdset.current
except AttributeError:
caller_cmdset = None
# Create cmdset for all player's available channels
channel_cmdset = None
if not caller_cmdset.no_channels:
channel_cmdset = yield CHANNELHANDLER.get_cmdset(caller)
# Gather cmdsets from location, objects in location or carried
local_objects_cmdsets = [None]
# Gather cmdsets from location, objects in location or carried
local_objects_cmdsets = [None]
location = None
if hasattr(caller, "location"):
location = caller.location
location = caller.location
if location and not caller_cmdset.no_objs:
# Gather all cmdsets stored on objects in the room and
# 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
if (obj.cmdset.current and obj.locks.check(caller, 'call', no_superuser_bypass=True))]
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.
cset.old_duplicates = cset.duplicates
cset.duplicates = True
# Player object's commandsets
# Player object's commandsets
try:
player_cmdset = caller.player.cmdset.current
except AttributeError:
player_cmdset = None
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]
# sort cmdsets after reverse priority (highest prio are merged in last)
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
cmdset = cmdsets.pop(0)
for merging_cmdset in cmdsets:
#print "<%s(%s,%s)> onto <%s(%s,%s)>" % (merging_cmdset.key, merging_cmdset.priority, merging_cmdset.mergetype,
# cmdset.key, cmdset.priority, cmdset.mergetype)
cmdset = yield merging_cmdset + cmdset
#print "<%s(%s,%s)> onto <%s(%s,%s)>" % (merging_cmdset.key, merging_cmdset.priority, merging_cmdset.mergetype,
# cmdset.key, cmdset.priority, cmdset.mergetype)
cmdset = yield merging_cmdset + cmdset
else:
cmdset = None
@ -146,26 +146,26 @@ def get_and_merge_cmdsets(caller):
returnValue(cmdset)
# Main command-handler function
# Main command-handler function
@inlineCallbacks
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
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.
Note that this function returns a deferred!
"""
Note that this function returns a deferred!
"""
try: # catch bugs in cmdhandler itself
try: # catch special-type commands
cmdset = yield get_and_merge_cmdsets(caller)
if not cmdset:
# this is bad and shouldn't happen.
# this is bad and shouldn't happen.
raise NoCmdSets
raw_string = raw_string.strip()
@ -182,7 +182,7 @@ def cmdhandler(caller, raw_string, testing=False):
if not matches:
# No commands match our entered command
syscmd = yield cmdset.get(CMD_NOMATCH)
if syscmd:
if syscmd:
sysarg = raw_string
else:
sysarg = "Huh? (Type \"help\" for help)"
@ -197,43 +197,43 @@ def cmdhandler(caller, raw_string, testing=False):
else:
sysarg = yield at_multimatch_cmd(caller, matches)
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]
cmdname, args, cmd = match[0], match[1], match[2]
# Check if this is a Channel match.
if hasattr(cmd, 'is_channel') and cmd.is_channel:
# even if a user-defined syscmd is not defined, the
# found cmd is already a system command in its own right.
syscmd = yield cmdset.get(CMD_CHANNEL)
# even if a user-defined syscmd is not defined, the
# found cmd is already a system command in its own right.
syscmd = yield cmdset.get(CMD_CHANNEL)
if syscmd:
# replace system command with custom version
cmd = syscmd
cmd = syscmd
sysarg = "%s:%s" % (cmdname, args)
raise ExecSystemCommand(cmd, sysarg)
# A normal command.
# Assign useful variables to the instance
cmd.caller = caller
cmd.caller = caller
cmd.cmdstring = cmdname
cmd.args = args
cmd.cmdset = cmdset
if hasattr(cmd, 'obj') and hasattr(cmd.obj, 'scripts'):
# 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()
if testing:
# only return the command instance
returnValue(cmd)
# pre-command hook
yield cmd.at_pre_cmd()
# Parse and execute
# Parse and execute
yield cmd.parse()
# (return value is normally None)
ret = yield cmd.func()
@ -246,15 +246,15 @@ def cmdhandler(caller, raw_string, testing=False):
# accessible by the next command.
caller.ndb.last_cmd = yield copy(cmd)
else:
caller.ndb.last_cmd = None
caller.ndb.last_cmd = None
# Done! This returns a deferred. By default, Evennia does
# not use this at all.
returnValue(ret)
except ExecSystemCommand, exc:
except ExecSystemCommand, exc:
# 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
sysarg = exc.sysarg
if syscmd:
@ -265,9 +265,9 @@ def cmdhandler(caller, raw_string, testing=False):
if hasattr(syscmd, 'obj') and hasattr(syscmd.obj, 'scripts'):
# 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()
if testing:
# only return the command instance
returnValue(syscmd)
@ -282,21 +282,21 @@ def cmdhandler(caller, raw_string, testing=False):
except NoCmdSets:
# Critical error.
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 += "contact the server admin through some other means "
string += "for assistance."
caller.msg(string)
logger.log_errmsg("No cmdsets found: %s" % caller)
except Exception:
# 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."
logger.log_trace(string)
caller.msg(string % format_exc())
except Exception:
except Exception:
# This catches exceptions in cmdhandler exceptions themselves
string = "%s\nAbove traceback is from a Command handler bug."
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):
"""
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
is the unparsed text entered by the caller.
is the unparsed text entered by the caller.
The cmdparser understand the following command combinations (where
[] marks optional parts.
@ -19,20 +19,20 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
[cmdname[ cmdname2 cmdname3 ...] [the rest]
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
parser return a list of matches. Each match is a tuple with its
first three elements being the parsed cmdname (lower case),
the remaining arguments, and the matched cmdobject from the cmdset.
first three elements being the parsed cmdname (lower case),
the remaining arguments, and the matched cmdobject from the cmdset.
"""
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
a ratio from 0-1 describing how much cmdname matches string.
We return a tuple (cmdname, count, ratio, args, cmdobj).
a ratio from 0-1 describing how much cmdname matches string.
We return a tuple (cmdname, count, ratio, args, cmdobj).
"""
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)
if not raw_string:
return None
return None
matches = []
# match everything that begins with a matching cmdname.
l_raw_string = raw_string.lower()
for cmd in cmdset:
for cmd in cmdset:
try:
matches.extend([create_match(cmdname, raw_string, cmd)
for cmdname in [cmd.key] + cmd.aliases
@ -58,13 +58,13 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
log_trace()
if not matches:
# no matches found.
# no matches found.
if '-' in raw_string:
# This could be due to the user trying to identify the
# command with a #num-<command> style syntax.
mindex, new_raw_string = raw_string.split("-", 1)
if mindex.isdigit():
mindex = int(mindex) - 1
mindex = int(mindex) - 1
# feed result back to parser iteratively
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:
# still multiple matches. Fall back to ratio-based quality.
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]
matches = matches[-quality.count(quality[-1]):]
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
# 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.
return matches
return matches
#------------------------------------------------------------
# Search parsers and support methods
# Search parsers and support methods
#------------------------------------------------------------
#
# 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
#
# SEARCH_AT_RESULTERROR_HANDLER
# SEARCH_MULTIMATCH_PARSER
# SEARCH_MULTIMATCH_PARSER
#
# The the replacing modules must have the same inputs and outputs as
# 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):
"""
Called by search methods after a result of any type has been found.
Takes a search result (a list) and
formats eventual errors.
msg_obj - object to receive feedback.
ostring - original search string
msg_obj - object to receive feedback.
ostring - original search string
results - list of found matches (0, 1 or more)
global_search - if this was a global_search or not
(if it is, there might be an idea of supplying
dbrefs instead of only numbers)
Multiple matches are returned to the searching object
as
as
1-object
2-object
3-object
2-object
3-object
etc
"""
string = ""
if not results:
# no results.
if not results:
# no results.
string = "Could not find '%s'." % ostring
results = None
results = None
elif len(results) > 1:
# 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
show_dbref = global_search
string += "More than one match for '%s'" % ostring
string += " (please narrow target):"
string += " (please narrow target):"
for num, result in enumerate(results):
invtext = ""
invtext = ""
dbreftext = ""
if hasattr(result, "location") and result.location == msg_obj:
invtext = " (carried)"
invtext = " (carried)"
if show_dbref:
dbreftext = "(#%i)" % result.id
string += "\n %i-%s%s%s" % (num+1, result.name,
dbreftext, invtext)
results = None
dbreftext = "(#%i)" % result.id
string += "\n %i-%s%s%s" % (num+1, result.name,
dbreftext, invtext)
results = None
else:
# we have exactly one match.
results = results[0]
if string:
if string:
msg_obj.msg(string.strip())
return results
return results
def at_multimatch_input(ostring):
"""
@ -186,9 +186,9 @@ def at_multimatch_input(ostring):
the lowest number, rather than 0 as in Python).
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
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:
> look
You see: ball, ball, ball and ball.
You see: ball, ball, ball and ball.
> get ball
There where multiple matches for ball:
1-ball
@ -205,7 +205,7 @@ def at_multimatch_input(ostring):
3-ball
4-ball
> get 3-ball
You get the ball.
You get the ball.
"""
@ -213,7 +213,7 @@ def at_multimatch_input(ostring):
return (None, ostring)
if not '-' in ostring:
return (None, ostring)
try:
try:
index = ostring.find('-')
number = int(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.
"""
string = "There where multiple matches:"
for num, match in enumerate(matches):
for num, match in enumerate(matches):
# each match is a tuple (candidate, cmd)
cmdname, arg, cmd, dum, dum = match
@ -237,7 +237,7 @@ def at_multimatch_cmd(caller, matches):
if is_channel:
is_channel = " (channel)"
else:
is_channel = ""
is_channel = ""
if cmd.is_exit and cmd.destination:
is_exit = " (exit to %s)" % cmd.destination
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
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.
"""
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):
"""
@ -29,14 +29,14 @@ class CmdSetMeta(type):
Fixes some things in the cmdclass
"""
# 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:
mcs.key = mcs.__name__
mcs.path = "%s.%s" % (mcs.__module__, mcs.__name__)
if not type(mcs.key_mergetypes) == dict:
mcs.key_mergetypes = {}
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"
cmdset_c = cmdset_a.copy_this()
# 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:
cmdset_c.commands.extend(cmdset_b.commands)
else:
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):
"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))
else:
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):
"C = A + B where the result is A."
@ -80,24 +80,24 @@ def instantiate(cmd):
"""
checks so that object is an instantiated command
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:
return cmd()
except TypeError:
return cmd
return cmd
class CmdSet(object):
"""
This class describes a unique cmdset that understands priorities. CmdSets
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.
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
commands as possible of each cmdset ends up in the
@ -125,13 +125,13 @@ class CmdSet(object):
excempt from all merge operations - they are
ALWAYS included across mergers and only affected
if same-named system commands replace them.
priority- All cmdsets are always merged in pairs of two so that
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
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
priority merge. Default has the first of them in the
merger (i.e. A above) automatically taking
@ -146,7 +146,7 @@ class CmdSet(object):
select which ball to kick ... Allowing duplicates
only makes sense for Union and Intersect, the setting
is ignored for the other mergetypes.
key_mergetype (dict) - allows the cmdset to define a unique
mergetype for particular cmdsets. Format is
{CmdSetkeystring:mergetype}. Priorities still apply.
@ -155,14 +155,14 @@ class CmdSet(object):
Myevilcmdset no matter what overall mergetype this set
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
no_exits - ignore the names of exits when matching against
commands
no_channels - ignore the name of channels when matching against
commands (WARNING- this is dangerous since the
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_objs = False
no_channels = False
permanent = False
permanent = False
def __init__(self, cmdsetobj=None, key=None):
"""
"""
Creates a new CmdSet instance.
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.
"""
if key:
@ -194,12 +194,12 @@ class CmdSet(object):
# initialize system
self.at_cmdset_creation()
def at_cmdset_creation(self):
def at_cmdset_creation(self):
"""
Hook method - this should be overloaded in the inheriting
class, and should take care of populating the cmdset
by use of self.add().
"""
"""
pass
def add(self, cmd):
@ -207,20 +207,20 @@ class CmdSet(object):
Add a command, a list of commands or a cmdset to this cmdset.
Note that if cmd already exists in set,
it will replace the old one (no priority checking etc
at this point; this is often used to overload
default commands).
it will replace the old one (no priority checking etc
at this point; this is often used to overload
default 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 the original cmdset definition. No merging or priority checks
are made, rather later added commands will simply replace
existing ones to make a unique set.
are made, rather later added commands will simply replace
existing ones to make a unique set.
"""
if inherits_from(cmd, "src.commands.cmdset.CmdSet"):
# 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)
try:
cmd = instantiate(cmd)
@ -234,46 +234,46 @@ class CmdSet(object):
else:
cmds = [instantiate(cmd)]
for cmd in cmds:
# add all commands
# add all commands
if not hasattr(cmd, 'obj'):
cmd.obj = self.cmdsetobj
try:
ic = self.commands.index(cmd)
self.commands[ic] = cmd # replace
self.commands[ic] = cmd # replace
except ValueError:
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))
#print "In cmdset.add(cmd):", self.key, cmd
def remove(self, cmd):
"""
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)
self.commands = [oldcmd for oldcmd in self.commands if oldcmd != cmd]
def get(self, cmd):
"""
Return the command in this cmdset that matches the
given command. cmd may be either a command instance or
a key string.
a key string.
"""
cmd = instantiate(cmd)
for thiscmd in self.commands:
if thiscmd == cmd:
return thiscmd
return thiscmd
def count(self):
"Return number of commands in set"
return len(self.commands)
def get_system_cmds(self):
"""
Return system commands in the cmdset, defined as
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('__')]
@ -293,11 +293,11 @@ class CmdSet(object):
cmdset.duplicates = self.duplicates
cmdset.key_mergetypes = self.key_mergetypes.copy() #copy.deepcopy(self.key_mergetypes)
return cmdset
def make_unique(self, caller):
"""
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
obj-based cmdsets always are added double. Doublets will
be weeded out with preference to commands defined on caller,
@ -313,14 +313,14 @@ class CmdSet(object):
else:
unique[cmd.key] = cmd
self.commands = unique.values()
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)])
def __iter__(self):
"""
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
by command name and aliases). This allows for things like 'if cmd in cmdset'
"""
"""
return any(cmd == othercmd for cmd in self.commands)
def __add__(self, cmdset_b):
"""
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
C'. The actual merge operation used in the 'addition' depends
@ -356,9 +356,9 @@ class CmdSet(object):
# preserve system __commands
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
mergetype = self.key_mergetypes.get(cmdset_b.key, self.mergetype)
mergetype = self.key_mergetypes.get(cmdset_b.key, self.mergetype)
if mergetype == "Intersect":
cmdset_c = intersect(self, cmdset_b, cmdset_b.duplicates)
elif mergetype == "Replace":
@ -369,7 +369,7 @@ class CmdSet(object):
cmdset_c = union(self, cmdset_b, cmdset_b.duplicates)
else:
# 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":
cmdset_c = intersect(cmdset_b, self, self.duplicates)
elif mergetype == "Replace":
@ -382,9 +382,9 @@ class CmdSet(object):
# we store actual_mergetype since key_mergetypes
# might be different from the main mergetype.
# This is used for diagnosis.
cmdset_c.actual_mergetype = mergetype
cmdset_c.actual_mergetype = mergetype
# return the system commands to the cmdset
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
the 'Fishing' set. Fishing from a boat? No problem!
"""
import traceback
import traceback
from src.utils import logger, utils
from src.commands.cmdset import CmdSet
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
instance from a python module, given a python_path. It's usually accessed
through the cmdsethandler's add() and add_default() methods.
python_path - This is the full path to the cmdset object.
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
through the cmdsethandler's add() and add_default() methods.
python_path - This is the full path to the cmdset object.
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
always be such an object)
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
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.
"""
"""
try:
try:
try:
#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)
errstring = ""
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.
modulepath, classname = python_path.rsplit('.', 1)
module = __import__(modulepath, fromlist=[True])
cmdsetclass = module.__dict__[classname]
CACHED_CMDSETS[wanted_cache_key] = cmdsetclass
cmdsetclass = module.__dict__[classname]
CACHED_CMDSETS[wanted_cache_key] = cmdsetclass
#instantiate the cmdset (and catch its errors)
if callable(cmdsetclass):
cmdsetclass = cmdsetclass(cmdsetobj)
cmdsetclass = cmdsetclass(cmdsetobj)
return cmdsetclass
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 = errstring % (classname, modulepath)
raise
except Exception:
except Exception:
errstring = "\n%s\nCompile/Run error when loading cmdset '%s'."
errstring = errstring % (traceback.format_exc(), python_path)
raise
except Exception:
except Exception:
if errstring and not no_logging:
print errstring
logger.log_trace()
print errstring
logger.log_trace()
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)
#cannot raise - it kills the server if no base cmdset exists!
# classes
# classes
class CmdSetHandler(object):
"""
@ -134,35 +134,35 @@ class CmdSetHandler(object):
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
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):
"""
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
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
# this holds the "merged" current command set
# this holds the "merged" current command set
self.current = None
# this holds a history of CommandSets
self.cmdset_stack = [CmdSet(cmdsetobj=self.obj, key="Empty")]
# 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
self.permanent_paths = [""]
#self.update(init_mode=True) is then called from the object __init__.
def __str__(self):
"Display current commands"
"Display current commands"
string = ""
mergelist = []
if len(self.cmdset_stack) > 1:
@ -176,18 +176,18 @@ class CmdSetHandler(object):
if cmdset.permanent:
permstring = "perm"
if mergetype != cmdset.mergetype:
mergetype = "%s^" % (mergetype)
mergetype = "%s^" % (mergetype)
string += "\n %i: <%s (%s, prio %i, %s)>: %s" % \
(snum, cmdset.key, mergetype,
cmdset.priority, permstring, cmdset)
cmdset.priority, permstring, cmdset)
mergelist.append(str(snum))
string += "\n"
# 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:
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:
string += " <Merged %s (%s, prio %i)>: %s" % ("+".join(mergelist), mergetype, self.current.priority, self.current)
else:
@ -196,74 +196,74 @@ class CmdSetHandler(object):
permstring = "perm"
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)))
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
current set.
init_mode is used right after this handler was
created; it imports all permanent cmdsets from db.
current set.
init_mode is used right after this handler was
created; it imports all permanent cmdsets from db.
"""
if init_mode:
# reimport all permanent cmdsets
storage = self.obj.cmdset_storage
#print "cmdset_storage:", self.obj.cmdset_storage
#print "cmdset_storage:", self.obj.cmdset_storage
if storage:
self.cmdset_stack = []
self.cmdset_stack = []
for pos, path in enumerate(storage):
if pos == 0 and not path:
self.cmdset_stack = [CmdSet(cmdsetobj=self.obj, key="Empty")]
elif path:
cmdset = self.import_cmdset(path)
cmdset = self.import_cmdset(path)
if cmdset:
cmdset.permanent = True
self.cmdset_stack.append(cmdset)
# merge the stack into a new merged cmdset
new_current = None
new_current = None
self.mergetype_stack = []
for cmdset in self.cmdset_stack:
for cmdset in self.cmdset_stack:
try:
# for cmdset's '+' operator, order matters.
new_current = cmdset + new_current
# for cmdset's '+' operator, order matters.
new_current = cmdset + new_current
except TypeError:
continue
self.mergetype_stack.append(new_current.actual_mergetype)
self.current = new_current
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.
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
"""
if not emit_to_obj:
emit_to_obj = self.obj
return import_cmdset(cmdset_path, self.obj, emit_to_obj)
def add(self, cmdset, emit_to_obj=None, permanent=False):
"""
Add a cmdset to the handler, on top of the old ones.
Default is to not make this permanent, i.e. the set
will not survive a server reset.
Default is to not make this permanent, i.e. the set
will not survive a server reset.
cmdset - can be a cmdset object or the python path to
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
Note: An interesting feature of this method is if you were to
send it an *already instantiated cmdset* (i.e. not a class),
the current cmdsethandler's obj attribute will then *not* be
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
launch command sets tied to a *different* object than the
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 not utils.inherits_from(cmdset, CmdSet):
@ -278,13 +278,13 @@ class CmdSetHandler(object):
cmdset.permanent = True
storage = self.obj.cmdset_storage
if not storage:
storage = ["", cmdset.path]
storage = ["", cmdset.path]
else:
storage.append(cmdset.path)
self.obj.cmdset_storage = storage
else:
cmdset.permanent = False
self.cmdset_stack.append(cmdset)
cmdset.permanent = False
self.cmdset_stack.append(cmdset)
self.update()
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,
it is replaced. If permanent is set, the set will survive a reboot.
cmdset - can be a cmdset object or the python path to
an instance of such an object.
emit_to_obj - an object to receive error messages.
an instance of such an object.
emit_to_obj - an object to receive error messages.
permanent - save cmdset across reboots
See also the notes for self.add(), which applies here too.
"""
"""
if callable(cmdset):
if not utils.inherits_from(cmdset, CmdSet):
raise Exception("Only CmdSets can be added to the cmdsethandler!")
@ -311,9 +311,9 @@ class CmdSetHandler(object):
else:
self.cmdset_stack = [cmdset]
self.mergetype_stack = [cmdset.mergetype]
if permanent:
cmdset.permanent = True
cmdset.permanent = True
storage = self.obj.cmdset_storage
if storage:
storage[0] = cmdset.path
@ -321,12 +321,12 @@ class CmdSetHandler(object):
storage = [cmdset.path]
self.obj.cmdset_storage = storage
else:
cmdset.permanent = False
self.update()
cmdset.permanent = False
self.update()
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,
an instance of the CmdSet or a python path
@ -338,17 +338,17 @@ class CmdSetHandler(object):
"""
if len(self.cmdset_stack) < 2:
# don't allow deleting default cmdsets here.
# don't allow deleting default cmdsets here.
return
if not cmdset:
# remove the last one in the stack
# remove the last one in the stack
cmdset = self.cmdset_stack.pop()
if cmdset.permanent:
storage = self.obj.cmdset_storage
storage.pop()
self.obj.cmdset_storage = storage
else:
else:
# try it as a callable
if callable(cmdset) and hasattr(cmdset, 'path'):
delcmdsets = [cset for cset in self.cmdset_stack[1:] if cset.path == cmdset.path]
@ -358,21 +358,21 @@ class CmdSetHandler(object):
storage = []
if any(cset.permanent for cset in delcmdsets):
# only hit database if there's need to
storage = self.obj.cmdset_storage
for cset in delcmdsets:
# only hit database if there's need to
storage = self.obj.cmdset_storage
for cset in delcmdsets:
if cset.permanent:
try:
storage.remove(cset.path)
storage.remove(cset.path)
except ValueError:
pass
for cset in delcmdsets:
# clean the in-memory stack
# clean the in-memory stack
try:
self.cmdset_stack.remove(cset)
except ValueError:
pass
# re-sync the cmdsethandler.
pass
# re-sync the cmdsethandler.
self.update()
def delete_default(self):
@ -385,9 +385,9 @@ class CmdSetHandler(object):
storage[0] = ""
else:
storage = [""]
self.cmdset_storage = storage
self.cmdset_storage = storage
self.cmdset_stack[0] = CmdSet(cmdsetobj=self.obj, key="Empty")
else:
else:
self.cmdset_stack = [CmdSet(cmdsetobj=self.obj, key="Empty")]
self.update()
@ -405,21 +405,21 @@ class CmdSetHandler(object):
self.cmdset_stack = [self.cmdset_stack[0]]
self.mergetype_stack = [self.cmdset_stack[0].mergetype]
storage = self.obj.cmdset_storage
if storage:
if storage:
storage = storage[0]
self.obj.cmdset_storage = storage
self.obj.cmdset_storage = storage
self.update()
def has_cmdset(self, cmdset_key, must_be_default=False):
"""
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:
return self.cmdset_stack and self.cmdset_stack[0].key == cmdset_key
else:
return any([cmdset.key == cmdset_key for cmdset in self.cmdset_stack])
def all(self):
"""
@ -429,16 +429,16 @@ class CmdSetHandler(object):
def reset(self):
"""
Force reload of all cmdsets in handler. This should be called
after CACHED_CMDSETS have been cleared (normally by @reload).
Force reload of all cmdsets in handler. This should be called
after CACHED_CMDSETS have been cleared (normally by @reload).
"""
new_cmdset_stack = []
new_mergetype_stack = []
new_mergetype_stack = []
for cmdset in self.cmdset_stack:
if cmdset.key == "Empty":
new_cmdset_stack.append(cmdset)
new_mergetype_stack.append("Union")
else:
else:
new_cmdset_stack.append(self.import_cmdset(cmdset.path))
new_mergetype_stack.append(cmdset.mergetype)
self.cmdset_stack = new_cmdset_stack

View file

@ -1,7 +1,7 @@
"""
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):
"""
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):
"""
Simply make sure all data are stored as lowercase and
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()
if mcs.aliases and not is_iter(mcs.aliases):
try:
mcs.aliases = mcs.aliases.split(',')
except Exception:
except Exception:
mcs.aliases = []
mcs.aliases = [str(alias).strip() for alias in mcs.aliases]
if not hasattr(mcs, "save_for_next"):
@ -61,19 +61,19 @@ class CommandMeta(type):
# define their own parser method to handle the input. The
# advantage of this is inheritage; commands that have similar
# structure can parse the input string the same way, minimizing
# parsing errors.
# parsing errors.
class Command(object):
"""
Base command
Usage:
command [args]
This is the base command class. Inherit from this
to create new commands.
The cmdhandler makes the following variables available to the
to create new commands.
The cmdhandler makes the following variables available to the
command methods (so you can always assume them to be there):
self.caller - the game object calling the command
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
help entries and lists)
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
entry for the command, so it's a good idea to format it similar to this one)
The following class properties can/should be defined on your child class:
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
__metaclass__ = CommandMeta
@ -103,20 +113,20 @@ class Command(object):
# this normally does not need to be changed. It allows to turn off
# auto-help entry creation for individual commands.
auto_help = True
# There is also the property 'obj'. This gets set by the system
auto_help = True
# 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.
# self.obj should NOT be defined here since it will not be overwritten
# if it already exists.
# self.obj should NOT be defined here since it will not be overwritten
# if it already exists.
def __init__(self):
self.lockhandler = LockHandler(self)
def __str__(self):
"Print the command"
return self.key
def __eq__(self, cmd):
"""
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
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.
"""
@ -156,11 +166,11 @@ class Command(object):
This hook is called by the cmdhandler to determine if srcobj
is allowed to execute this command. It should return a boolean
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)
# Common Command hooks
# Common Command hooks
def at_pre_cmd(self):
"""
@ -170,7 +180,7 @@ class Command(object):
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()).
"""
pass
@ -181,31 +191,31 @@ class Command(object):
want, this function is run. If many of your commands have
a similar syntax (for example 'cmd arg1 = arg2') you should simply
define this once and just let other commands of the same form
inherit from this. See the docstring of this module for
which object properties are available to use
inherit from this. See the docstring of this module for
which object properties are available to use
(notably self.args).
"""
"""
pass
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
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
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 += "\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 perms (self.permissions): {w%s{n\n" % self.permissions
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 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
string += utils.fill("current cmdset (self.cmdset): {w%s{n\n" % self.cmdset)
self.caller.msg(string)

View file

@ -1,13 +1,13 @@
#
# This is Evennia's default connection screen. It is imported
# and run from game/gamesrc/world/connection_screens.py.
# This is Evennia's default connection screen. It is imported
# and run from game/gamesrc/world/connection_screens.py.
#
from src.utils import utils
from src.utils import utils
DEFAULT_SCREEN = \
"""{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:
{wconnect <email> <password>{n

View file

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

View file

@ -6,10 +6,10 @@ from src.utils import logger, utils
class HelpEntryManager(models.Manager):
"""
This HelpEntryManager implements methods for searching
This HelpEntryManager implements methods for searching
and manipulating HelpEntries directly from the database.
These methods will all return database objects
These methods will all return database objects
(or QuerySets) directly.
Evennia-specific:
@ -24,33 +24,33 @@ class HelpEntryManager(models.Manager):
def find_topicmatch(self, topicstr, exact=False):
"""
Searches for matching topics based on player's input.
"""
"""
if utils.dbref(topicstr):
return self.filter(id=utils.dbref(topicstr))
topics = self.filter(db_key__iexact=topicstr)
if not topics and not exact:
topics = self.filter(db_key__istartswith=topicstr)
if not topics and not exact:
topics = self.filter(db_key__istartswith=topicstr)
if not topics:
topics = self.filter(db_key__icontains=topicstr)
topics = self.filter(db_key__icontains=topicstr)
return topics
def find_apropos(self, topicstr):
"""
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)
def find_topicsuggestions(self, topicstr):
"""
Do a fuzzy match, preferably within the category of the
current topic.
"""
topics = self.find_apropos(topicstr)
"""
topics = self.find_apropos(topicstr)
# we need to clean away the given help entry.
return [topic for topic in topics
if topic.key.lower() != topicstr.lower()]
def find_topics_with_category(self, help_category):
"""
Search topics having a particular category
@ -76,7 +76,7 @@ class HelpEntryManager(models.Manager):
Shifts all help entries in database to default_category.
This action cannot be reverted. It is used primarily by
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()
for topic in topics:
@ -92,7 +92,7 @@ class HelpEntryManager(models.Manager):
ostring - the help topic to look for
category - limit the search to a particular help topic
"""
ostring = ostring.strip().lower()
ostring = ostring.strip().lower()
help_entries = self.filter(db_topicstr=ostring)
if 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,
# 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')
# help category
db_help_category = models.CharField("help category", max_length=255, default="General",
# help category
db_help_category = models.CharField("help category", max_length=255, default="General",
help_text='organizes help entries in lists')
# the actual help entry text, in any formatting.
db_entrytext = models.TextField('help entry', blank=True, help_text='the main body of help text')
# the actual help entry text, in any formatting.
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.
db_permissions = models.CharField('permissions', max_length=255, blank=True)
db_permissions = models.CharField('permissions', max_length=255, blank=True)
# lock string storage
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)).
# TODO: remove this when not needed anymore.
db_staff_only = models.BooleanField(default=False)
# TODO: remove this when not needed anymore.
db_staff_only = models.BooleanField(default=False)
# Database manager
objects = HelpEntryManager()
@ -62,7 +62,7 @@ class HelpEntry(SharedMemoryModel):
def __init__(self, *args, **kwargs):
SharedMemoryModel.__init__(self, *args, **kwargs)
self.locks = LockHandler(self)
class Meta:
"Define Django meta options"
verbose_name = "Help Entry"
@ -72,10 +72,10 @@ class HelpEntry(SharedMemoryModel):
# @property decorators that allows to access these fields using
# normal python operations (without having to remember to save()
# etc). So e.g. a property 'attr' has a get/set/del decorator
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
# is the object in question).
# key property (wraps db_key)
#@property
def key_get(self):
@ -137,7 +137,7 @@ class HelpEntry(SharedMemoryModel):
if is_iter(value):
value = ",".join([str(val).strip().lower() for val in value])
self.db_permissions = value
self.save()
self.save()
#@permissions.deleter
def permissions_del(self):
"Deleter. Allows for del self.permissions"
@ -146,7 +146,7 @@ class HelpEntry(SharedMemoryModel):
permissions = property(permissions_get, permissions_set, permissions_del)
# lock_storage property (wraps db_lock_storage)
#@property
#@property
def lock_storage_get(self):
"Getter. Allows for value = self.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)
lock_storage = property(lock_storage_get, lock_storage_set, lock_storage_del)
#
#
# HelpEntry main class methods
#
#
def __str__(self):
return self.key
@ -180,5 +180,5 @@ class HelpEntry(SharedMemoryModel):
accessing_obj - object trying to access this one
access_type - type of access sought
default - what to return if no lock of access_type was found
"""
"""
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
# in the web admin interface.
# This sets up how models are displayed
# in the web admin interface.
#
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),
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.")
db_permissions = forms.CharField(label="Permissions",
initial=settings.PERMISSION_PLAYER_DEFAULT,
db_permissions = forms.CharField(label="Permissions",
initial=settings.PERMISSION_PLAYER_DEFAULT,
required=False,
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.")
db_cmdset_storage = forms.CharField(label="CmdSet",
db_cmdset_storage = forms.CharField(label="CmdSet",
initial=settings.CMDSET_DEFAULT,
required=False,
widget=forms.TextInput(attrs={'size':'78'}),
help_text="Most non-character objects don't need a cmdset and can leave this field blank.")
class ObjectEditForm(ObjectCreateForm):
"Form used for editing. Extends the create one with more fields"
db_lock_storage = forms.CharField(label="Locks",
required=False,
db_lock_storage = forms.CharField(label="Locks",
required=False,
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);...")
class ObjectDBAdmin(admin.ModelAdmin):
class ObjectDBAdmin(admin.ModelAdmin):
list_display = ('id', 'db_key', 'db_location', 'db_player', 'db_typeclass_path')
list_display_links = ('id', 'db_key')
ordering = ['db_player', 'db_typeclass_path', 'id']
search_fields = ['^db_key', 'db_typeclass_path']
save_as = True
save_as = True
save_on_top = True
list_select_related = True
list_select_related = True
list_filter = ('db_permissions', 'db_location', 'db_typeclass_path')
# editing fields setup
@ -74,7 +74,7 @@ class ObjectDBAdmin(admin.ModelAdmin):
form = ObjectEditForm
fieldsets = (
(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'
)}),
)
@ -88,7 +88,7 @@ class ObjectDBAdmin(admin.ModelAdmin):
add_form = ObjectCreateForm
add_fieldsets = (
(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'
)}),
)
@ -116,8 +116,8 @@ class ObjectDBAdmin(admin.ModelAdmin):
obj = obj.typeclass
obj.basetype_setup()
obj.basetype_posthook_setup()
obj.at_object_creation()
obj.at_object_creation()
obj.at_init()
admin.site.register(ObjectDB, ObjectDBAdmin)

View file

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

View file

@ -1,6 +1,6 @@
"""
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
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.
These are intrinsic properties of the object. The split
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_obj = models.ForeignKey("ObjectDB", verbose_name='object')
class Meta:
"Define Django meta options"
verbose_name = "Object alias"
@ -79,8 +79,8 @@ class Alias(SharedMemoryModel):
return u"%s" % self.db_key
def __str__(self):
return str(self.db_key)
#------------------------------------------------------------
#
@ -90,11 +90,11 @@ class Alias(SharedMemoryModel):
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
player - match against player searches
obj - match against object searches
obj - match against object searches
channel - used to store own names for channels
"""
db_obj = models.ForeignKey("ObjectDB", verbose_name='object')
@ -108,7 +108,7 @@ class ObjectNick(TypeNick):
class ObjectNickHandler(TypeNickHandler):
"""
Handles nick access and setting. Accessed through ObjectDB.nicks
"""
"""
NickClass = ObjectNick
@ -126,7 +126,7 @@ class ObjectDB(TypedObject):
Note that the base objectdb is very simple, with
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:
key - main name
@ -134,11 +134,11 @@ class ObjectDB(TypedObject):
typeclass_path - the path to the decorating typeclass
typeclass - auto-linked typeclass
date_created - time stamp of object creation
permissions - perm strings
permissions - perm strings
locks - lock definitions (handler)
dbref - #id of object
dbref - #id of object
db - persistent attribute storage
ndb - non-persistent attribute storage
ndb - non-persistent attribute storage
The ObjectDB adds the following properties:
player - optional connected player
@ -148,7 +148,7 @@ class ObjectDB(TypedObject):
scripts - scripts assigned to object (handler from typeclass)
cmdset - active cmdset on object (handler from typeclass)
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)
has_player - bool if an active player is currently connected
contents - other objects having this object as location
@ -160,7 +160,7 @@ class ObjectDB(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
#
# These databse fields (including the inherited ones) are all set
@ -168,11 +168,11 @@ class ObjectDB(TypedObject):
# but withtout the db_* prefix.
# If this is a character object, the player is connected here.
db_player = models.ForeignKey("players.PlayerDB", blank=True, null=True, verbose_name='player',
help_text='a Player connected to this object, if any.')
db_player = models.ForeignKey("players.PlayerDB", blank=True, null=True, verbose_name='player',
help_text='a Player connected to this object, if any.')
# The location in the game world. Since this one is likely
# 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,
blank=True, null=True, verbose_name='game location')
# a safety location, this usually don't change much.
@ -193,24 +193,24 @@ class ObjectDB(TypedObject):
def __init__(self, *args, **kwargs):
"Parent must be initialized first."
TypedObject.__init__(self, *args, **kwargs)
# handlers
TypedObject.__init__(self, *args, **kwargs)
# handlers
self.cmdset = CmdSetHandler(self)
self.cmdset.update(init_mode=True)
self.scripts = ScriptHandler(self)
self.nicks = ObjectNickHandler(self)
self.nicks = ObjectNickHandler(self)
# store the attribute class
# Wrapper properties to easily set database fields. These are
# @property decorators that allows to access these fields using
# normal python operations (without having to remember to save()
# etc). So e.g. a property 'attr' has a get/set/del decorator
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
# is the object in question).
# aliases property (wraps (db_aliases)
#@property
#@property
def aliases_get(self):
"Getter. Allows for value = self.aliases"
try:
@ -218,10 +218,10 @@ class ObjectDB(TypedObject):
except AttributeError:
aliases = list(Alias.objects.filter(db_obj=self).values_list("db_key", flat=True))
SA(self, "_cached_aliases", aliases)
return aliases
return aliases
#@aliases.setter
def aliases_set(self, aliases):
"Setter. Allows for self.aliases = value"
"Setter. Allows for self.aliases = value"
for alias in make_iter(aliases):
new_alias = Alias(db_key=alias, db_obj=self)
new_alias.save()
@ -258,121 +258,121 @@ class ObjectDB(TypedObject):
player = property(player_get, player_set, player_del)
# location property (wraps db_location)
#@property
#@property
def location_get(self):
"Getter. Allows for value = self.location."
loc = get_cache(self, "location")
if loc:
return loc.typeclass
return None
return None
#@location.setter
def location_set(self, location):
"Setter. Allows for self.location = location"
try:
if location == None or type(location) == ObjectDB:
# location is None or a valid object
loc = location
loc = location
elif ObjectDB.objects.dbref(location):
# location is a dbref; search
loc = ObjectDB.objects.dbref_search(location)
if loc and hasattr(loc,'dbobj'):
loc = loc.dbobj
else:
loc = location.dbobj
else:
loc = location.dbobj
loc = location.dbobj
else:
loc = location.dbobj
set_cache(self, "location", loc)
except Exception:
string = "Cannot set location: "
string += "%s is not a valid location."
string += "%s is not a valid location."
self.msg(string % location)
logger.log_trace(string)
raise
raise
#@location.deleter
def location_del(self):
"Deleter. Allows for del self.location"
self.db_location = None
self.db_location = None
self.save()
del_cache()
location = property(location_get, location_set, location_del)
# home property (wraps db_home)
#@property
#@property
def home_get(self):
"Getter. Allows for value = self.home"
home = get_cache(self, "home")
if home:
return home.typeclass
return None
return None
#@home.setter
def home_set(self, home):
"Setter. Allows for self.home = value"
try:
if home == None or type(home) == ObjectDB:
hom = home
hom = home
elif ObjectDB.objects.dbref(home):
hom = ObjectDB.objects.dbref_search(home)
if hom and hasattr(hom,'dbobj'):
hom = hom.dbobj
else:
hom = home.dbobj
else:
hom = home.dbobj
hom = home.dbobj
else:
hom = home.dbobj
set_cache(self, "home", hom)
except Exception:
string = "Cannot set home: "
string += "%s is not a valid home."
string += "%s is not a valid home."
self.msg(string % home)
logger.log_trace(string)
#raise
#raise
#@home.deleter
def home_del(self):
"Deleter. Allows for del self.home."
self.db_home = None
self.db_home = None
self.save()
del_cache(self, "home")
home = property(home_get, home_set, home_del)
# destination property (wraps db_destination)
#@property
#@property
def destination_get(self):
"Getter. Allows for value = self.destination."
dest = get_cache(self, "destination")
if dest:
return dest.typeclass
return None
return None
#@destination.setter
def destination_set(self, destination):
"Setter. Allows for self.destination = destination"
try:
if destination == None or type(destination) == ObjectDB:
# destination is None or a valid object
dest = destination
dest = destination
elif ObjectDB.objects.dbref(destination):
# destination is a dbref; search
dest = ObjectDB.objects.dbref_search(destination)
if dest and hasattr(dest,'dbobj'):
dest = dest.dbobj
else:
dest = destination.dbobj
else:
dest = destination.dbobj
dest = destination.dbobj
else:
dest = destination.dbobj
set_cache(self, "destination", dest)
except Exception:
string = "Cannot set destination: "
string += "%s is not a valid destination."
string += "%s is not a valid destination."
self.msg(string % destination)
logger.log_trace(string)
raise
raise
#@destination.deleter
def destination_del(self):
"Deleter. Allows for del self.destination"
self.db_destination = None
self.db_destination = None
self.save()
del_cache(self, "destination")
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
#@property
def cmdset_storage_get(self):
@ -385,7 +385,7 @@ class ObjectDB(TypedObject):
"Setter. Allows for self.name = value. Stores as a comma-separated string."
value = ",".join(str(val).strip() for val in make_iter(value))
self.db_cmdset_storage = value
self.save()
self.save()
#@cmdset_storage.deleter
def cmdset_storage_del(self):
"Deleter. Allows for del self.name"
@ -400,12 +400,12 @@ class ObjectDB(TypedObject):
#
# ObjectDB class access methods/properties
#
#
# this is required to properly handle attributes and typeclass loading.
#attribute_model_path = "src.objects.models"
#attribute_model_name = "ObjAttribute"
typeclass_paths = settings.OBJECT_TYPECLASS_PATHS
typeclass_paths = settings.OBJECT_TYPECLASS_PATHS
attribute_class = ObjAttribute
db_model_name = "objectdb" # used by attributes to safely store objects
@ -420,13 +420,13 @@ class ObjectDB(TypedObject):
"""
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:
return self.player.sessions
return []
sessions = property(sessions_get)
#@property
#@property
def has_player_get(self):
"""
Convenience function for checking if an active player is
@ -436,13 +436,13 @@ class ObjectDB(TypedObject):
has_player = property(has_player_get)
is_player = property(has_player_get)
#@property
#@property
def is_superuser_get(self):
"Check if user has a player, and if so, if it is a superuser."
return any(self.sessions) and self.player.is_superuser
is_superuser = property(is_superuser_get)
#@property
#@property
def contents_get(self, exclude=None):
"""
Returns the contents of this object, i.e. all
@ -461,11 +461,11 @@ class ObjectDB(TypedObject):
if exi.destination]
exits = property(exits_get)
#
# Main Search method
#
def search(self, ostring,
global_search=False,
attribute_name=None,
@ -474,49 +474,49 @@ class ObjectDB(TypedObject):
"""
Perform a standard object search in the database, handling
multiple results and lack thereof gracefully.
ostring: (str) The string to match object names against.
Obs - To find a player, append * to the
start of ostring.
start of ostring.
global_search: Search all objects, not just the current
location/inventory
attribute_name: (string) Which attribute to match
(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
ignore_errors : Don't display any error messages even
if there are none/multiple matches -
just return the result as a list.
player : Don't search for an Object but a Player.
if there are none/multiple matches -
just return the result as a list.
player : Don't search for an Object but a Player.
This will also find players that don't
currently have a character.
Returns - a unique Object/Player match or None. All error
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
player. Note that the object controlled by the player will be
returned, not the player object itself. This also means that
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
linked to the key in order to separate the matches from
each other without showing the dbref explicitly. Default
syntax for this is 'N-searchword'. So for example, if there
are three objects in the room all named 'ball', you could
address the individual ball as '1-ball', '2-ball', '3-ball'
etc.
etc.
"""
if use_nicks:
if ostring.startswith('*') or player:
# player nick replace
# player nick replace
ostring = self.nicks.get(ostring.lstrip('*'), nick_type="player")
if not player:
ostring = "*%s" % ostring
else:
# object nick replace
# object nick replace
ostring = self.nicks.get(ostring, nick_type="object")
if player:
@ -525,11 +525,11 @@ class ObjectDB(TypedObject):
else:
results = PlayerDB.objects.player_search(ostring.lstrip('*'))
else:
results = ObjectDB.objects.object_search(ostring, caller=self,
results = ObjectDB.objects.object_search(ostring, caller=self,
global_search=global_search,
attribute_name=attribute_name,
location=location)
if ignore_errors:
return results
# this import is cache after the first call.
@ -538,50 +538,50 @@ class ObjectDB(TypedObject):
#
# Execution/action methods
#
def execute_cmd(self, raw_string):
"""
Do something as this object. This command transparently
lets its typeclass execute the command. Evennia also calls
this method whenever the player sends a command on the command line.
Argument:
Argument:
raw_string (string) - raw command input
Returns Deferred - this is an asynchronous Twisted object that will
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
execution.
execution.
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.
# do text encoding conversion
# do text encoding conversion
raw_string = to_unicode(raw_string)
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]]
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:
raw_string = raw_string.replace(nick.db_nick, nick.db_real, 1)
break
raw_string = raw_string.replace(nick.db_nick, nick.db_real, 1)
break
return cmdhandler.cmdhandler(self.typeclass, raw_string)
def msg(self, message, from_obj=None, data=None):
"""
Emits something to any sessions attached to the object.
message (str): The message to send
from_obj (obj): object that is sending.
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.
if object.__getattribute__(self, 'player'):
object.__getattribute__(self, 'player').msg(message, from_obj=from_obj, data=data)
@ -589,7 +589,7 @@ class ObjectDB(TypedObject):
"Deprecated. Alias for msg"
logger.log_depmsg("emit_to() is deprecated. Use msg() instead.")
self.msg(message, from_obj, data)
def msg_contents(self, message, exclude=None, from_obj=None, data=None):
"""
Emits something to all objects inside an object.
@ -608,7 +608,7 @@ class ObjectDB(TypedObject):
"Deprecated. Alias for msg_contents"
logger.log_depmsg("emit_to_contents() is deprecated. Use msg_contents() instead.")
self.msg_contents(message, exclude=exclude, from_obj=from_obj, data=data)
def move_to(self, destination, quiet=False,
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
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,
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
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.
emit_to_obj: (Object) object to receive error messages
use_destination (bool): Default is for objects to use the "destination" property
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
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=""):
trc = traceback.format_exc()
@ -644,7 +644,7 @@ class ObjectDB(TypedObject):
if not destination:
emit_to_obj.msg("The destination doesn't exist.")
return
return
if destination.destination:
# traverse exits
destination = destination.destination
@ -658,8 +658,8 @@ class ObjectDB(TypedObject):
#emit_to_obj.msg(errtxt % "at_before_move()")
#logger.log_trace()
return False
# Save the old location
# Save the old location
source_location = self.location
if not source_location:
# there was some error in placing this room.
@ -678,16 +678,16 @@ class ObjectDB(TypedObject):
#emit_to_obj.msg(errtxt % "at_object_leave()")
#logger.log_trace()
return False
if not quiet:
#tell the old room we are leaving
try:
self.announce_move_from(destination)
self.announce_move_from(destination)
except Exception:
logerr(errtxt % "at_announce_move()")
logerr(errtxt % "at_announce_move()")
#emit_to_obj.msg(errtxt % "at_announce_move()" )
#logger.log_trace()
return False
return False
# Perform move
try:
@ -695,18 +695,18 @@ class ObjectDB(TypedObject):
except Exception:
emit_to_obj.msg(errtxt % "location change")
logger.log_trace()
return False
return False
if not quiet:
# Tell the new room we are there.
# Tell the new room we are there.
try:
self.announce_move_to(source_location)
except Exception:
logerr(errtxt % "announce_move_to()")
#emit_to_obj.msg(errtxt % "announce_move_to()")
#logger.log_trace()
return False
return False
# Perform eventual extra commands on the receiving location
# (the object has already arrived at this point)
try:
@ -715,7 +715,7 @@ class ObjectDB(TypedObject):
logerr(errtxt % "at_object_receive()")
#emit_to_obj.msg(errtxt % "at_object_receive()")
#logger.log_trace()
return False
return False
# Execute eventual extra commands on this object after moving it
# (usually calling 'look')
@ -725,13 +725,13 @@ class ObjectDB(TypedObject):
logerr(errtxt % "at_after_move")
#emit_to_obj.msg(errtxt % "at_after_move()")
#logger.log_trace()
return False
return True
return False
return True
#
# Object Swap, Delete and Cleanup methods
#
# Object Swap, Delete and Cleanup methods
#
def clear_exits(self):
"""
Destroys all of the exits and any exits pointing to this
@ -745,7 +745,7 @@ class ObjectDB(TypedObject):
def clear_contents(self):
"""
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.
objs = ObjectDB.objects.filter(db_location=self)
@ -754,29 +754,29 @@ class ObjectDB(TypedObject):
default_home = ObjectDB.objects.get(id=default_home_id)
if default_home.id == self.id:
# we are deleting default home!
default_home = None
default_home = None
except Exception:
string = "Could not find default home '(#%d)'."
logger.log_errmsg(string % default_home_id)
default_home = None
default_home = None
for obj in objs:
home = obj.home
for obj in objs:
home = obj.home
# Obviously, we can't send it back to here.
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 not obj.home:
string = "Missing default home, '%s(#%d)' "
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.")
logger.log_errmsg(string % (obj.name, obj.id))
return
return
if obj.has_player:
if home:
if home:
string = "Your current location has ceased to exist,"
string += " moving you to %s(#%d)."
obj.msg(string % (home.name, home.id))
@ -787,22 +787,22 @@ class ObjectDB(TypedObject):
obj.move_to(home)
def copy(self, new_key=None):
"""
"""
Makes an identical copy of this object. If you want to customize the copy by
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
<old_key>_copy by default.
Returns: Object (copy of this one)
<old_key>_copy by default.
Returns: Object (copy of this one)
"""
if not new_key:
new_key = "%s_copy" % self.key
return ObjectDB.objects.copy_object(self, new_key=new_key)
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
objects to their respective home locations, as well as clean
up all exits to/from the object.
@ -815,7 +815,7 @@ class ObjectDB(TypedObject):
if not self.at_object_delete():
# this is an extra pre-check
# run before deletion mechanism
# is kicked into gear.
# is kicked into gear.
self.delete_iter == 0
return False
@ -825,17 +825,17 @@ class ObjectDB(TypedObject):
for session in self.sessions:
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!)
if object.__getattribute__(self, 'player') and self.player:
self.player.character = None
self.player = None
self.player = None
for script in self.scripts.all():
script.stop()
# if self.player:
# self.player.user.is_active = False
# self.player.user.is_active = False
# self.player.user.save(
# Destroy any exits to and from this room, if any
@ -844,4 +844,4 @@ class ObjectDB(TypedObject):
self.clear_contents()
# Perform the deletion of the object
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
database-connection tied to itself through a property.
@ -19,15 +19,15 @@ from src.typeclasses.typeclass import TypeClass
from src.commands import cmdset, command
#
# Base class to inherit from.
# Base class to inherit from.
#
class Object(TypeClass):
"""
This is the base class for all in-game objects.
Inherit from this to create different types of
objects in the game.
"""
objects in the game.
"""
def __init__(self, dbobj):
"""
@ -39,18 +39,18 @@ class Object(TypeClass):
seen in src.object.objects).
Object Typeclass API:
Object Typeclass API:
* Available properties (only available on initiated typeclass objects)
key (string) - name of object
key (string) - name of object
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.
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
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
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)
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
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
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
* Handlers available
* Handlers available
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
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()
cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object
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)
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_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
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_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_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_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_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_server_reload() - called before server is reloaded
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_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_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.
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
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_drop(dropper) - called when this object has been dropped.
at_say(speaker, message) - by default, called if an object inside this object speaks
"""
super(Object, self).__init__(dbobj)
@ -132,84 +132,84 @@ class Object(TypeClass):
def search(self, ostring,
global_search=False,
attribute_name=None,
use_nicks=False,
use_nicks=False,
location=None,
ignore_errors=False,
ignore_errors=False,
player=False):
"""
Perform a standard object search in the database, handling
multiple results and lack thereof gracefully.
ostring: (str) The string to match object names against.
Obs - To find a player, append * to the
start of ostring.
start of ostring.
global_search(bool): Search all objects, not just the current
location/inventory
attribute_name (string) Which attribute to match
(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
ignore_errors (bool): Don't display any error messages even
if there are none/multiple matches -
just return the result as a list.
player (Objectt): Don't search for an Object but a Player.
if there are none/multiple matches -
just return the result as a list.
player (Objectt): Don't search for an Object but a Player.
This will also find players that don't
currently have a character.
Returns - a unique Object/Player match or None. All error
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
player. Note that the object controlled by the player will be
returned, not the player object itself. This also means that
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
linked to the key in order to separate the matches from
each other without showing the dbref explicitly. Default
syntax for this is 'N-searchword'. So for example, if there
are three objects in the room all named 'ball', you could
address the individual ball as '1-ball', '2-ball', '3-ball'
etc.
etc.
"""
return self.dbobj.search(ostring,
global_search=global_search,
return self.dbobj.search(ostring,
global_search=global_search,
attribute_name=attribute_name,
use_nicks=use_nicks,
location=location,
ignore_errors=ignore_errors,
ignore_errors=ignore_errors,
player=player)
def execute_cmd(self, raw_string):
"""
Do something as this object. This command transparently
lets its typeclass execute the command. Evennia also calls
this method whenever the player sends a command on the command line.
Argument:
Argument:
raw_string (string) - raw command input
Returns Deferred - this is an asynchronous Twisted object that will
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
execution.
execution.
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)
def msg(self, message, from_obj=None, data=None):
"""
Emits something to any sessions attached to the object.
message (str): The message to send
from_obj (obj): object that is sending.
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)
@ -228,44 +228,44 @@ class Object(TypeClass):
exit object (i.e. it has "destination"!=None), the move_to will
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,
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
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.
emit_to_obj: (Object) object to receive error messages
use_destination (bool): Default is for objects to use the "destination" property
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
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,
emit_to_obj=emit_to_obj, use_destination=use_destination)
def copy(self, new_key=None):
"""
"""
Makes an identical copy of this object. If you want to customize the copy by
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
<old_key>_copy by default.
Returns: Object (copy of this one)
<old_key>_copy by default.
Returns: Object (copy of this one)
"""
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
objects to their respective home locations, as well as clean
up all exits to/from the object.
Returns: boolean True if deletion succeded, False if there
were errors during deletion or deletion otherwise
failed.
failed.
"""
return self.dbobj.delete()
@ -277,16 +277,16 @@ class Object(TypeClass):
Returns true if this object has this type
OR has a typeclass which is an subclass of
the given typeclass.
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
type is exactly this typeclass, ignoring
parents.
Returns: Boolean
"""
Returns: Boolean
"""
return self.dbobj.is_typeclass(typeclass, exact=exact)
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
that in-game, this object will suddenly be something else.
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().
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
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
to create a new object and just swap the player over to
that one instead.
that one instead.
Arguments:
new_typeclass (path/classobj) - type to switch to
Arguments:
new_typeclass (path/classobj) - type to switch to
clean_attributes (bool/list) - will delete all attributes
stored on this object (but not any
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
swapping to a default typeclass in case the given
one fails for some reason. Instead the old one
will be preserved.
Returns:
will be preserved.
Returns:
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)
@ -332,14 +332,14 @@ class Object(TypeClass):
accessing_obj (Object)- object trying to access this one
access_type (string) - type of access sought
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)
def check_permstring(self, permstring):
"""
This explicitly checks the given string against this object's
'permissions' property without involving any locks.
permstring (string) - permission string that need to match a permission on the object.
(example: 'Builders')
"""
@ -355,7 +355,7 @@ class Object(TypeClass):
"""
result = self.id == other
if not result and hasattr(other, "id"):
result = self.id == other.id
result = self.id == other.id
if not result:
try:
result = other and self.user.id == other.user.id
@ -366,14 +366,14 @@ class Object(TypeClass):
## hooks called by the game engine
def basetype_setup(self):
"""
This sets up the default properties of an Object,
just before the more general at_object_creation.
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
# object. Overload in child for a custom setup. Also creation
@ -383,13 +383,13 @@ class Object(TypeClass):
dbref = self.dbobj.dbref
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("edit:perm(Wizards)") # edit properties/attributes
self.locks.add("delete:perm(Wizards)") # delete object
self.locks.add("edit:perm(Wizards)") # edit properties/attributes
self.locks.add("delete:perm(Wizards)") # delete object
self.locks.add("get:all()") # pick up 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):
"""
@ -403,27 +403,27 @@ class Object(TypeClass):
def at_object_creation(self):
"""
Called once, when this object is first created.
"""
"""
pass
def at_object_delete(self):
"""
Called just before the database object is
permanently delete()d from the database. If
this method returns False, deletion is aborted.
this method returns False, deletion is aborted.
"""
return True
def at_init(self):
"""
"""
This is always called whenever this object is initiated --
that is, whenever it its typeclass is cached from memory. This
happens on-demand first time the object is used or activated
in some way after being created but also after each server
restart or reload.
"""
pass
pass
def at_cmdset_get(self):
"""
@ -450,7 +450,7 @@ class Object(TypeClass):
"""
Called at the end of the login
process, just before letting
them loose.
them loose.
"""
pass
@ -463,16 +463,16 @@ class Object(TypeClass):
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,
this is the place to do it.
this is the place to do it.
"""
pass
def at_server_shutdown(self):
"""
This hook is called whenever the server is shutting down fully (i.e. not for
a restart).
This hook is called whenever the server is shutting down fully (i.e. not for
a restart).
"""
pass
@ -482,63 +482,63 @@ class Object(TypeClass):
def at_before_move(self, destination):
"""
Called just before starting to move
this object to destination.
this object to destination.
destination - the object we are moving to
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 True
return True
def announce_move_from(self, destination):
"""
Called if the move is to be announced. This is
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:
return
name = self.name
return
name = self.name
loc_name = ""
loc_name = self.location.name
loc_name = self.location.name
dest_name = destination.name
string = "%s is leaving %s, heading for %s."
self.location.msg_contents(string % (name, loc_name, dest_name), exclude=self)
def announce_move_to(self, source_location):
"""
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:
# This was created from nowhere and added to a player's
# inventory; it's probably the result of a create command.
string = "You now have %s in your possession." % name
self.location.msg(string)
return
return
src_name = "nowhere"
loc_name = self.location.name
if source_location:
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)
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.
source_location - where we came from
source_location - where we came from
"""
pass
@ -554,92 +554,92 @@ class Object(TypeClass):
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
source_location - where moved_object came from.
source_location - where moved_object came from.
"""
pass
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)
The target location should normally be available as self.destination.
"""
pass
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)
The target location should normally be available as self.destination.
"""
pass
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,
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):
"""
This hook is called whenever someone
This hook is called whenever someone
sends a message to this object.
Note that from_obj may be None if the sender did
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
msg is passed on to the user sesssion. If this
method returns False, the msg will not be
msg is passed on to the user sesssion. If this
method returns False, the msg will not be
passed on.
msg = the message received
from_obj = the one sending the message
"""
return True
return True
def at_msg_send(self, msg, to_obj=None, data=None):
"""
This is a hook that is called when /this/ object
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
passed along with the msg() function (i.e. using
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
# hooks called by the default cmdset.
# hooks called by the default cmdset.
def return_appearance(self, pobject):
"""
This is a convenient hook for a 'look'
command to call.
command to call.
"""
if not pobject:
return
return
string = "{c%s{n" % self.name
desc = self.attr("desc")
if desc:
string += "\n %s" % desc
exits = []
exits = []
users = []
things = []
for content in [con for con in self.contents if con.access(pobject, 'view')]:
if content == pobject:
continue
continue
name = content.name
if content.destination:
exits.append(name)
@ -651,17 +651,17 @@ class Object(TypeClass):
string += "\n{wExits:{n " + ", ".join(exits)
if users or things:
string += "\n{wYou see: {n"
if users:
if users:
string += "{c" + ", ".join(users) + "{n "
if things:
string += ", ".join(things)
if things:
string += ", ".join(things)
return string
def at_desc(self, looker=None):
"""
This is called whenever someone looks
at this object. Looker is the looking
object.
object.
"""
pass
@ -685,8 +685,8 @@ class Object(TypeClass):
def at_say(self, speaker, message):
"""
Called on this object if an object inside this object speaks.
The string returned from this method is the final form
of the speech. Obs - you don't have to add things like
The string returned from this method is the final form
of the speech. Obs - you don't have to add things like
'you say: ' or similar, that is handled by the say command.
speaker - the object speaking
@ -695,7 +695,7 @@ class Object(TypeClass):
return message
#
# Base Player object
# Base Player object
#
class Character(Object):
@ -703,24 +703,24 @@ class Character(Object):
This is just like the Object except it implements its own
version of the at_object_creation to set up the script
that adds the default cmdset to the object.
"""
"""
def basetype_setup(self):
"""
Setup character-specific security
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()
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
from settings import CMDSET_DEFAULT
from settings import CMDSET_DEFAULT
self.cmdset.add_default(CMDSET_DEFAULT, permanent=True)
# no other character should be able to call commands on the Character.
self.cmdset.outside_access = False
# no other character should be able to call commands on the Character.
self.cmdset.outside_access = False
def at_object_creation(self):
"""
@ -728,8 +728,8 @@ class Character(Object):
the script is permanently stored to this object (the permanent
keyword creates a script to do this), we should never need to
do this again for as long as this object exists.
"""
pass
"""
pass
def at_after_move(self, source_location):
"Default is to look around after a move."
@ -737,34 +737,34 @@ class Character(Object):
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).
"""
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.db.prelogout_location = self.location
self.location = None
self.location = None
def at_post_login(self):
"""
This recovers the character again after having been "stoved away" at disconnect.
"""
if self.db.prelogout_location:
# try to recover
self.location = self.db.prelogout_location
# try to recover
self.location = self.db.prelogout_location
if self.location == None:
# make sure location is never None (home should always exist)
self.location = self.home
# save location again to be sure
# save location again to be sure
self.db.prelogout_location = self.location
self.location.msg_contents("%s has entered the game." % self.name, exclude=[self])
self.location.at_object_receive(self, self.location)
#
# Base Room object
# Base Room object
#
class Room(Object):
@ -778,16 +778,16 @@ class Room(Object):
(since default is None anyway)
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()
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):
@ -795,16 +795,16 @@ class Exit(Object):
This is the base exit object - it connects a location to
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
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
command (which has a high priority) will thus allow us to traverse exits
simply by giving the exit-object's name on its own.
"""
"""
# Helper classes and methods to implement the Exit. These need not
# 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):
"""
@ -812,8 +812,8 @@ class 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,
triggering the movement between rooms.
triggering the movement between rooms.
Note that exitdbobj is an ObjectDB instance. This is necessary
for handling reloads and avoid tracebacks if this is called while
the typeclass system is rebooting.
@ -821,9 +821,9 @@ class Exit(Object):
class ExitCommand(command.Command):
"""
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
arg_regex=r"\s.*?|$"
@ -831,15 +831,15 @@ class Exit(Object):
"Default exit traverse if no syscommand is defined."
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"):
old_location = self.caller.location
old_location = self.caller.location
# call pre/post hooks and move object.
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)
else:
@ -853,7 +853,7 @@ class Exit(Object):
# create an exit command.
cmd = ExitCommand()
cmd.key = exidbobj.db_key.strip().lower()
cmd.obj = exidbobj
cmd.obj = exidbobj
cmd.aliases = exidbobj.aliases
cmd.locks = str(exidbobj.locks)
cmd.destination = exidbobj.db_destination
@ -861,18 +861,18 @@ class Exit(Object):
exit_cmdset = cmdset.CmdSet(None)
exit_cmdset.key = '_exitset'
exit_cmdset.priority = 9
exit_cmdset.duplicates = True
# add command to cmdset
exit_cmdset.add(cmd)
exit_cmdset.duplicates = True
# add command to cmdset
exit_cmdset.add(cmd)
return exit_cmdset
# Command hooks
# Command hooks
def basetype_setup(self):
"""
Setup exit-security
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()
@ -880,34 +880,34 @@ class Exit(Object):
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("get:false()") # noone can pick up the exit
# an exit should have a destination (this is replaced at creation time)
if self.dbobj.location:
self.destination = self.dbobj.location
self.destination = self.dbobj.location
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.
"""
"""
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.
self.cmdset.add_default(self.create_exit_cmdset(self.dbobj), permanent=False)
self.ndb.exit_reset = False
self.cmdset.add_default(self.create_exit_cmdset(self.dbobj), permanent=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):
"Called once, when object is first created (after basetype_setup)."
pass
pass
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,
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.")

View file

@ -19,7 +19,7 @@ try:
from django.utils.unittest import TestCase
except ImportError:
from django.test import TestCase
try:
try:
from django.utils import unittest
except ImportError:
import unittest
@ -47,10 +47,10 @@ class TestObjAttrs(TestCase):
self.obj1.db.testattr = self.obj2
self.assertEqual(self.obj2 ,self.obj1.db.testattr)
self.assertEqual(self.obj2.location, self.obj1.db.testattr.location)
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.
"""
tsuite = unittest.TestSuite()

View file

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

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 get/setters defined below. There are also a few properties
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
@ -45,12 +45,9 @@ class MetaTypeClass(type):
mcs.typename = mcs.__name__
mcs.path = "%s.%s" % (mcs.__module__, mcs.__name__)
def __str__(cls):
return "%s" % cls.__name__
class TypeClass(object):
"""
This class implements a 'typeclass' object. This is connected
@ -58,14 +55,14 @@ class TypeClass(object):
the TypeClass allows for all customization.
Most of the time this means that the admin never has to
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
database object transparently.
database object transparently.
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
@ -73,8 +70,8 @@ class TypeClass(object):
"""
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 = 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
# 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))
# store the reference to the database model instance
SA(self, 'dbobj', dbobj)
SA(self, 'dbobj', dbobj)
def __getattribute__(self, propname):
"""
@ -93,7 +90,7 @@ class TypeClass(object):
self.dbobj. Note that dbobj properties have
priority, so if you define a same-named
property on the class, it will NOT be
accessible through getattr.
accessible through getattr.
"""
if propname == 'dbobj':
return GA(self, 'dbobj')
@ -101,7 +98,7 @@ class TypeClass(object):
# python specials are parsed as-is (otherwise things like
# isinstance() fail to identify the typeclass)
return GA(self, propname)
#print "get %s (dbobj:%s)" % (propname, type(dbobj))
#print "get %s (dbobj:%s)" % (propname, type(dbobj))
try:
return GA(self, propname)
except AttributeError:
@ -109,7 +106,7 @@ class TypeClass(object):
dbobj = GA(self, 'dbobj')
except AttributeError:
log_trace("Typeclass CRITICAL ERROR! dbobj not found for Typeclass %s!" % self)
raise
raise
try:
return GA(dbobj, propname)
except AttributeError:
@ -118,31 +115,31 @@ class TypeClass(object):
except AttributeError:
string = "Object: '%s' not found on %s(%s), nor on its typeclass %s."
raise AttributeError(string % (propname, dbobj, dbobj.dbref, dbobj.typeclass_path))
def __setattr__(self, propname, value):
"""
Transparently save data to the dbobj object in
all situations. Note that this does not
necessarily mean storing it to the database
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)
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))
log_errmsg(string % (self.name, propname))
return
return
try:
dbobj = GA(self, 'dbobj')
except AttributeError:
dbobj = None
log_trace("This is probably due to an unsafe reload.")
if dbobj:
dbobj = None
log_trace("This is probably due to an unsafe reload.")
if dbobj:
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.
GA(dbobj, propname)
SA(dbobj, propname, value)
@ -154,25 +151,25 @@ class TypeClass(object):
def __eq__(self, other):
"""
dbobj-recognized comparison
"""
"""
try:
return other == self or other == GA(self, dbobj) or other == GA(self, dbobj).user
except AttributeError:
# if self.dbobj.user fails it means the two previous comparisons failed already
return False
def __delattr__(self, propname):
"""
Transparently deletes data from the typeclass or dbobj by first searching on the typeclass,
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:
string = "%s: '%s' is a protected attribute name."
string = "%s: '%s' is a protected attribute name."
string += " (protected: [%s])" % (", ".join(PROTECTED))
log_errmsg(string % (self.name, propname))
return
return
try:
DA(self, propname)
@ -181,10 +178,10 @@ class TypeClass(object):
try:
dbobj = GA(self, 'dbobj')
except AttributeError:
log_trace("This is probably due to an unsafe reload.")
return # ignore delete
log_trace("This is probably due to an unsafe reload.")
return # ignore delete
try:
dbobj.del_attribute_raise(propname)
dbobj.del_attribute_raise(propname)
except AttributeError:
string = "Object: '%s' not found on %s(%s), nor on its typeclass %s."
raise AttributeError(string % (propname, dbobj,

View file

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

View file

@ -2,7 +2,7 @@
General helper functions that don't fit neatly under any given category.
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):
"Makes sure that the object is always iterable."
if not hasattr(obj, '__iter__'): return [obj]
return obj
return obj
def fill(text, width=78, indent=0):
"""
@ -53,8 +53,8 @@ def crop(text, width=78, suffix="[...]"):
ltext = len(to_str(text))
if ltext <= width:
return text
else:
lsuffix = len(suffix)
else:
lsuffix = len(suffix)
return "%s%s" % (text[:width-lsuffix], suffix)
def dedent(text):
@ -63,7 +63,7 @@ def dedent(text):
of a paragraph. This is useful for preserving
triple-quoted string indentation while still
shifting it all to be next to the left edge of
the display.
the display.
"""
if not text:
return ""
@ -92,11 +92,11 @@ def wildcard_to_regexp(instring):
regexp_string += "$"
return regexp_string
def time_format(seconds, style=0):
"""
Function to return a 'prettified' version of a value in seconds.
Style 0: 1d 08:30
Style 1: 1d
Style 2: 1 day, 8 hours, 30 minutes, 10 seconds
@ -105,15 +105,15 @@ def time_format(seconds, style=0):
seconds = 0
else:
# We'll just use integer math, no need for decimal precision.
seconds = int(seconds)
seconds = int(seconds)
days = seconds / 86400
seconds -= days * 86400
hours = seconds / 3600
seconds -= hours * 3600
minutes = seconds / 60
seconds -= minutes * 60
if style is 0:
"""
Standard colon-style output.
@ -122,7 +122,7 @@ def time_format(seconds, style=0):
retval = '%id %02i:%02i' % (days, hours, minutes,)
else:
retval = '%02i:%02i' % (hours, minutes,)
return retval
elif style is 1:
"""
@ -155,8 +155,8 @@ def time_format(seconds, style=0):
if minutes == 1:
minutes_str = '%i minute ' % minutes
else:
minutes_str = '%i minutes ' % minutes
retval = '%s%s%s' % (days_str, hours_str, minutes_str)
minutes_str = '%i minutes ' % minutes
retval = '%s%s%s' % (days_str, hours_str, minutes_str)
elif style is 3:
"""
Full-detailed, long-winded format. Includes seconds.
@ -176,20 +176,20 @@ def time_format(seconds, style=0):
if minutes == 1:
minutes_str = '%i minute ' % minutes
else:
minutes_str = '%i minutes ' % minutes
if minutes or seconds > 0:
minutes_str = '%i minutes ' % minutes
if minutes or seconds > 0:
if seconds == 1:
seconds_str = '%i second ' % seconds
else:
seconds_str = '%i seconds ' % seconds
retval = '%s%s%s%s' % (days_str, hours_str, minutes_str, seconds_str)
return retval
return retval
def datetime_format(dtobj):
"""
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
@ -197,7 +197,7 @@ def datetime_format(dtobj):
now = datetime.datetime.now()
if year < now.year:
# another year
# another year
timestring = str(dtobj.date())
elif dtobj.date() < now.date():
# another date, same year
@ -205,9 +205,9 @@ def datetime_format(dtobj):
elif hour < now.hour - 1:
# same day, more than 1 hour ago
timestring = "%02i:%02i" % (hour, minute)
else:
else:
# same day, less than 1 hour ago
timestring = "%02i:%02i:%02i" % (hour, minute, second)
timestring = "%02i:%02i:%02i" % (hour, minute, second)
return timestring
def host_os_is(osname):
@ -223,12 +223,12 @@ def get_evennia_version():
Check for the evennia version info.
"""
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
return
except IOError:
return "Unknown version"
def pypath_to_realpath(python_path, file_ending='.py'):
"""
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('.')
if not pathsplit:
return python_path
path = settings.BASE_PATH
path = settings.BASE_PATH
for directory in pathsplit:
path = os.path.join(path, directory)
if file_ending:
return "%s%s" % (path, file_ending)
return path
return "%s%s" % (path, file_ending)
return path
def dbref(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.
"""
if isinstance(dbref, basestring):
@ -256,11 +256,11 @@ def dbref(dbref):
try:
dbref = int(dbref)
if dbref < 1:
return None
return None
except Exception:
return None
return dbref
return None
return None
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
printing. Note that non-string objects are let through without
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):
@ -279,28 +279,28 @@ def to_unicode(obj, encoding='utf-8', force_string=False):
elif hasattr(obj, '__unicode__'):
obj = obj.__unicode__()
else:
# last resort
# last resort
obj = str(obj)
if isinstance(obj, basestring) and not isinstance(obj, unicode):
try:
obj = unicode(obj, encoding)
return obj
return obj
except UnicodeDecodeError:
for alt_encoding in ENCODINGS:
for alt_encoding in ENCODINGS:
try:
obj = unicode(obj, alt_encoding)
return obj
except UnicodeDecodeError:
pass
pass
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):
"""
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
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
conversion of objects to strings.
"""
@ -313,7 +313,7 @@ def to_str(obj, encoding='utf-8', force_string=False):
elif hasattr(obj, '__unicode__'):
obj = obj.__unicode__()
else:
# last resort
# last resort
obj = str(obj)
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.
(This snippet was adapted from
(This snippet was adapted from
http://commandline.org.uk/python/email-syntax-check.)
"""
emailaddress = r"%s" % emailaddress
domains = ("aero", "asia", "biz", "cat", "com", "coop",
"edu", "gov", "info", "int", "jobs", "mil", "mobi", "museum",
domains = ("aero", "asia", "biz", "cat", "com", "coop",
"edu", "gov", "info", "int", "jobs", "mil", "mobi", "museum",
"name", "net", "org", "pro", "tel", "travel")
# Email address must be more than 7 characters in total.
@ -372,11 +372,11 @@ def validate_email_address(emailaddress):
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()
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
from the evennia root directory).
from the evennia root directory).
"""
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()]
else:
obj_paths = ["%s.%s" % (mod.__module__, mod.__name__) for mod in obj.__class__.mro()]
if isinstance(parent, basestring):
# a given string path, for direct matching
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,...], ...]
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
empty though).
collumns must have the same number of rows (some positions may be
empty though).
The function formats the columns to be as wide as the widest member
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):
if ir == 0:
# make first row white
if ir == 0:
# make first row white
string += "\n{w" + ""join(row) + "{n"
else:
string += "\n" + "".join(row)
print string
print string
"""
if not table:
return [[]]
max_widths = [max([len(str(val)) for val in col]) for col in table]
ftable = []
for irow in range(len(table[0])):
ftable.append([str(col[irow]).ljust(max_widths[icol]) + " " * extra_space
ftable = []
for irow in range(len(table[0])):
ftable.append([str(col[irow]).ljust(max_widths[icol]) + " " * extra_space
for icol, col in enumerate(table)])
return ftable
def run_async(async_func, at_return=None, at_err=None):
"""
This wrapper will use Twisted's asynchronous features to run a slow
function using a separate reactor thread. In effect this means that
the server will not be blocked while the slow process finish.
function using a separate reactor thread. In effect this means that
the server will not be blocked while the slow process finish.
Use this function with restrain and only for features/commands
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
it has finished).
async_func() - function that should be run asynchroneously
at_return(r) - if given, this function will be called when async_func returns
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)
"""
# create deferred object
# create deferred object
deferred = threads.deferToThread(async_func)
if 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
def default_errback(e):
from src.utils import logger
logger.log_trace(e)
logger.log_trace(e)
deferred.addErrback(default_errback)
@ -491,7 +491,7 @@ def check_evennia_dependencies():
import twisted
tversion = twisted.version.short()
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:
errstring += "\n ERROR: Twisted does not seem to be installed."
no_error = False
@ -508,12 +508,12 @@ def check_evennia_dependencies():
# South
try:
import south
sversion = south.__version__
sversion = south.__version__
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:
pass
# IRC support
# IRC support
if settings.IRC_ENABLED:
try:
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 Linux Debian/Ubuntu users should install package 'python-twisted-words', others"
errstring += "\n can get it from http://twistedmatrix.com/trac/wiki/TwistedWords."
no_error = False
no_error = False
errstring = errstring.strip()
if errstring:
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__))
except (TypeError, AttributeError):
# this can occur if we tried to store a class object, not an
# instance. Not sure if one should defend against this.
return False
# instance. Not sure if one should defend against this.
return False
def mod_import(mod_path, propname=None):
"""
Takes filename of a module (a python path or a full pathname)
and imports it. If property is given, return the named
property from this module instead of the module itself.
and imports it. If property is given, return the named
property from this module instead of the module itself.
"""
def log_trace(errmsg=None):
"""
Log a traceback to the log. This should be called
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 twisted.python import log
@ -557,7 +557,7 @@ def mod_import(mod_path, propname=None):
tracestring = format_exc()
if tracestring:
for line in tracestring.splitlines():
log.msg('[::] %s' % line)
log.msg('[::] %s' % line)
if errmsg:
try:
errmsg = to_str(errmsg)
@ -569,10 +569,10 @@ def mod_import(mod_path, propname=None):
if not mod_path:
return None
# first try to import as a python path
try:
try:
mod = __import__(mod_path, fromlist=["None"])
except ImportError:
# try absolute path import instead
if not os.path.isabs(mod_path):
@ -583,13 +583,13 @@ def mod_import(mod_path, propname=None):
try:
result = imp.find_module(modname, [path])
except ImportError:
log_trace("Could not find module '%s' (%s.py) at path '%s'" % (modname, modname, path))
return
log_trace("Could not find module '%s' (%s.py) at path '%s'" % (modname, modname, path))
return
try:
mod = imp.load_module(modname, *result)
except ImportError:
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
result[0].close()
@ -598,16 +598,16 @@ def mod_import(mod_path, propname=None):
try:
mod_prop = mod.__dict__[to_str(propname)]
except KeyError:
log_trace("Could not import property '%s' from module %s." % (propname, mod_path))
return None
log_trace("Could not import property '%s' from module %s." % (propname, mod_path))
return None
return mod_prop
return mod
return mod
def variable_from_module(modpath, variable, default=None):
"""
Retrieve a given variable from a module. The variable must be
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
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
specific variable name will also retrieve non-strings.
The variable must be global within that module - that is, defined
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
@ -640,8 +640,8 @@ def string_from_module(modpath, variable=None, default=None):
if variable:
return mod.__dict__.get(variable, default)
else:
mvars = [val for key, val in mod.__dict__.items()
if not key.startswith('_') and isinstance(val, basestring)]
mvars = [val for key, val in mod.__dict__.items()
if not key.startswith('_') and isinstance(val, basestring)]
if not mvars:
return default
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
player (and potentially their character, if it exists already)
"""
# the FIRST_LOGIN flags are necessary for the system to call
# the relevant first-login hooks.
# the FIRST_LOGIN flags are necessary for the system to call
# the relevant first-login hooks.
if player.character:
player.character.db.FIRST_LOGIN = True
player.db.FIRST_LOGIN = True
player.character.db.FIRST_LOGIN = True
player.db.FIRST_LOGIN = True