diff --git a/.hgignore b/.hgignore index 0b9039b841..aea031bdb8 100644 --- a/.hgignore +++ b/.hgignore @@ -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 diff --git a/CODING_STYLE b/CODING_STYLE index 26568d5ffb..5557ab3794 100644 --- a/CODING_STYLE +++ b/CODING_STYLE @@ -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. diff --git a/INSTALL b/INSTALL index 0e0c604e40..8b2803faca 100644 --- a/INSTALL +++ b/INSTALL @@ -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. \ No newline at end of file + +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. \ No newline at end of file diff --git a/LICENSE b/LICENSE index 7f3acaca14..386443895d 100644 --- a/LICENSE +++ b/LICENSE @@ -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. diff --git a/README b/README index 7d75aa1345..01fdfa7722 100644 --- a/README +++ b/README @@ -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. \ No newline at end of file +Enjoy! \ No newline at end of file diff --git a/ev.py b/ev.py index ab6ebe9145..a94c783f90 100644 --- a/ev.py +++ b/ev.py @@ -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 diff --git a/requirements.txt b/requirements.txt index 6f339837eb..63a0746ff6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,5 +4,5 @@ django >= 1.2 twisted >= 10.0 -pil +pil south >= 0.7 diff --git a/sitecustomize.py b/sitecustomize.py index 4478f3c2eb..39b6e373a3 100644 --- a/sitecustomize.py +++ b/sitecustomize.py @@ -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 diff --git a/src/commands/cmdhandler.py b/src/commands/cmdhandler.py index 34c80bae53..a14eddbddf 100644 --- a/src/commands/cmdhandler.py +++ b/src/commands/cmdhandler.py @@ -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." diff --git a/src/commands/cmdparser.py b/src/commands/cmdparser.py index 75412b7c51..8eb1c95504 100644 --- a/src/commands/cmdparser.py +++ b/src/commands/cmdparser.py @@ -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- 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: diff --git a/src/commands/cmdset.py b/src/commands/cmdset.py index 45d3f26395..a48245d04b 100644 --- a/src/commands/cmdset.py +++ b/src/commands/cmdset.py @@ -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 diff --git a/src/commands/cmdsethandler.py b/src/commands/cmdsethandler.py index e557c185cf..18d3e73214 100644 --- a/src/commands/cmdsethandler.py +++ b/src/commands/cmdsethandler.py @@ -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 += " : %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 diff --git a/src/commands/command.py b/src/commands/command.py index c7908176d3..c66dad558e 100644 --- a/src/commands/command.py +++ b/src/commands/command.py @@ -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) diff --git a/src/commands/connection_screen.py b/src/commands/connection_screen.py index 59a96aaa75..9106f73ef4 100644 --- a/src/commands/connection_screen.py +++ b/src/commands/connection_screen.py @@ -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 {n diff --git a/src/help/admin.py b/src/help/admin.py index b82b12b0db..8194f96a6b 100644 --- a/src/help/admin.py +++ b/src/help/admin.py @@ -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 = ( diff --git a/src/help/manager.py b/src/help/manager.py index ff0306fd29..4e32f2043c 100644 --- a/src/help/manager.py +++ b/src/help/manager.py @@ -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) diff --git a/src/help/models.py b/src/help/models.py index 87cc74ac0c..524dafee8e 100644 --- a/src/help/models.py +++ b/src/help/models.py @@ -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) diff --git a/src/objects/admin.py b/src/objects/admin.py index e2eb9b832f..556ad329aa 100644 --- a/src/objects/admin.py +++ b/src/objects/admin.py @@ -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 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) diff --git a/src/objects/manager.py b/src/objects/manager.py index 3d652f6dfe..a847b7f72a 100644 --- a/src/objects/manager.py +++ b/src/objects/manager.py @@ -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_ 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 diff --git a/src/objects/models.py b/src/objects/models.py index 3188fa2501..6082049a41 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -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 * 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 - _copy by default. - Returns: Object (copy of this one) + _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 diff --git a/src/objects/objects.py b/src/objects/objects.py index c9658d4da9..1df23f37ce 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -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 , they don't need to be removed here. + inside a deleted object are automatically moved to their , 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 * 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 - _copy by default. - Returns: Object (copy of this one) + _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.") diff --git a/src/objects/tests.py b/src/objects/tests.py index 2bd76e6231..a9e43cb915 100644 --- a/src/objects/tests.py +++ b/src/objects/tests.py @@ -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() diff --git a/src/typeclasses/managers.py b/src/typeclasses/managers.py index 4a5e8e1fdb..932336ad26 100644 --- a/src/typeclasses/managers.py +++ b/src/typeclasses/managers.py @@ -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 - diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 9d7ecf5ec3..dc1e32c3a5 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -3,14 +3,14 @@ This is the *abstract* django models for many of the database objects in Evennia. A django abstract (obs, not the same as a Python metaclass!) is a model which is not actually created in the database, but which only exists for other models to inherit from, to avoid code duplication. Any model can -import and inherit from these classes. +import and inherit from these classes. Attributes are database objects stored on other objects. The implementing class needs to supply a ForeignKey field attr_object pointing to the kind of object being mapped. Attributes storing iterables actually store special types of iterables named PackedList/PackedDict respectively. These make -sure to save changes to them to database - this is criticial in order to -allow for obj.db.mylist[2] = data. Also, all dbobjects are saved as +sure to save changes to them to database - this is criticial in order to +allow for obj.db.mylist[2] = data. Also, all dbobjects are saved as dbrefs but are also aggressively cached. TypedObjects are objects 'decorated' with a typeclass - that is, the typeclass @@ -19,11 +19,11 @@ get/set attribute methods, allows for the creation of all sorts of different objects all with the same database object underneath. Usually attributes are used to permanently store things not hard-coded as field on the database object. The admin should usually not have to deal directly with the database object -layer. +layer. This module also contains the Managers for the respective models; inherit from -these to create custom managers. - +these to create custom managers. + """ import sys @@ -50,7 +50,7 @@ GA = object.__getattribute__ SA = object.__setattr__ DA = object.__delattr__ PLOADS = pickle.loads -PDUMPS = pickle.dumps +PDUMPS = pickle.dumps # Property Cache mechanism. @@ -58,10 +58,10 @@ def get_cache(obj, name): "On-model Cache handler." try: return GA(obj, "_cached_db_%s" % name) - except AttributeError: + except AttributeError: val = GA(obj, "db_%s" % name) if val: SA(obj, "_cached_db_%s" % name, val) - return val + return val def set_cache(obj, name, val): "On-model Cache setter" SA(obj, "db_%s" % name, val) @@ -73,25 +73,25 @@ def del_cache(obj, name): try: DA(obj, "_cached_db_%s" % name) except AttributeError: - pass + pass #------------------------------------------------------------ # -# Attributes +# Attributes # #------------------------------------------------------------ class PackedDBobject(object): """ Attribute helper class. - A container for storing and easily identifying database objects in + A container for storing and easily identifying database objects in the database (which doesn't suppport storing db_objects directly). """ - def __init__(self, ID, db_model, db_key): + def __init__(self, ID, db_model, db_key): self.id = ID self.db_model = db_model self.key = db_key - def __str__(self): + def __str__(self): return "%s(#%s)" % (self.key, self.id) def __unicode__(self): return u"%s(#%s)" % (self.key, self.id) @@ -99,15 +99,15 @@ class PackedDBobject(object): class PackedDict(dict): """ Attribute helper class. - A variant of dict that stores itself to the database when - updating one of its keys. This is called and handled by - Attribute.validate_data(). + A variant of dict that stores itself to the database when + updating one of its keys. This is called and handled by + Attribute.validate_data(). """ def __init__(self, db_obj, *args, **kwargs): """ Sets up the packing dict. The db_store variable is set by Attribute.validate_data() when returned in - order to allow custom updates to the dict. + order to allow custom updates to the dict. db_obj - the Attribute object storing this dict. @@ -116,7 +116,7 @@ class PackedDict(dict): when first assigning the dict. Once initialization is over, the Attribute from_attr() method will assign the parent (or None, if at the root) - + """ self.db_obj = db_obj self.parent = 'init' @@ -128,40 +128,40 @@ class PackedDict(dict): if self.parent == 'init': pass elif self.parent: - self.parent.save() + self.parent.save() else: - self.db_obj.value = self - def __setitem__(self, *args, **kwargs): + self.db_obj.value = self + def __setitem__(self, *args, **kwargs): "assign item to this dict" super(PackedDict, self).__setitem__(*args, **kwargs) self.save() - def clear(self, *args, **kwargs): + def clear(self, *args, **kwargs): "Custom clear" super(PackedDict, self).clear(*args, **kwargs) self.save() - def pop(self, *args, **kwargs): + def pop(self, *args, **kwargs): "Custom pop" super(PackedDict, self).pop(*args, **kwargs) self.save() - def popitem(self, *args, **kwargs): + def popitem(self, *args, **kwargs): "Custom popitem" super(PackedDict, self).popitem(*args, **kwargs) self.save() - def update(self, *args, **kwargs): + def update(self, *args, **kwargs): "Custom update" super(PackedDict, self).update(*args, **kwargs) self.save() - + class PackedList(list): """ Attribute helper class. - A variant of list that stores itself to the database when - updating one of its keys. This is called and handled by - Attribute.validate_data(). + A variant of list that stores itself to the database when + updating one of its keys. This is called and handled by + Attribute.validate_data(). """ def __init__(self, db_obj, *args, **kwargs): """ - Sets up the packing list. + Sets up the packing list. db_obj - the Attribute object storing this dict. The 'parent' property is set to 'init' at creation, @@ -181,12 +181,12 @@ class PackedList(list): if self.parent == 'init': pass elif self.parent: - self.parent.save() + self.parent.save() else: - self.db_obj.value = self - def __setitem__(self, *args, **kwargs): + self.db_obj.value = self + def __setitem__(self, *args, **kwargs): "Custom setitem that stores changed list to database." - super(PackedList, self).__setitem__(*args, **kwargs) + super(PackedList, self).__setitem__(*args, **kwargs) self.save() def append(self, *args, **kwargs): "Custom append" @@ -225,7 +225,7 @@ class Attribute(SharedMemoryModel): example, a drink container needs to store its fill level, whereas an exit needs to store its open/closed/locked/unlocked state. These are done via attributes, rather than making different classes for each object type and - storing them directly. The added benefit is that we can add/remove + storing them directly. The added benefit is that we can add/remove attributes on the fly as we like. The Attribute class defines the following properties: @@ -240,8 +240,8 @@ class Attribute(SharedMemoryModel): {type : nodb|dbobj|dbiter, data : } - where type is info for the loader, telling it if holds a single - dbobject (dbobj), have to do a full scan for dbrefs (dbiter) or + where type is info for the loader, telling it if holds a single + dbobject (dbobj), have to do a full scan for dbrefs (dbiter) or if it is a normal Python structure without any dbobjs inside it and can thus return it without further action (nodb). """ @@ -254,19 +254,19 @@ class Attribute(SharedMemoryModel): # named same as the field, but withtout the db_* prefix. db_key = models.CharField('key', max_length=255, db_index=True) - # access through the value property + # access through the value property db_value = models.TextField('value', blank=True, null=True) - # Lock storage - db_lock_storage = models.CharField('locks', max_length=512, blank=True) - # references the object the attribute is linked to (this is set + # Lock storage + db_lock_storage = models.CharField('locks', max_length=512, blank=True) + # references the object the attribute is linked to (this is set # by each child class to this abstact class) db_obj = None # models.ForeignKey("RefencedObject") # time stamp db_date_created = models.DateTimeField('date_created', editable=False, auto_now_add=True) - - # Database manager + + # Database manager objects = managers.AttributeManager() - + # Lock handler self.locks def __init__(self, *args, **kwargs): "Initializes the parent first -important!" @@ -274,24 +274,24 @@ class Attribute(SharedMemoryModel): self.locks = LockHandler(self) self.no_cache = True self.cached_value = None - + class Meta: "Define Django meta options" - abstract = True - verbose_name = "Evennia Attribute" + abstract = True + verbose_name = "Evennia Attribute" # 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). # key property (wraps db_key) #@property def key_get(self): - "Getter. Allows for value = self.key" + "Getter. Allows for value = self.key" return get_cache(self, "key") #@key.setter def key_set(self, value): @@ -311,14 +311,14 @@ class Attribute(SharedMemoryModel): #@obj.setter def obj_set(self, value): "Setter. Allows for self.obj = value" - set_cache(self, "obj", value) + set_cache(self, "obj", value) #@obj.deleter def obj_del(self): "Deleter. Allows for del self.obj" self.db_obj = None self.save() del_cache(self, "obj") - obj = property(obj_get, obj_set, obj_del) + obj = property(obj_get, obj_set, obj_del) # date_created property (wraps db_date_created) #@property @@ -340,15 +340,15 @@ class Attribute(SharedMemoryModel): def value_get(self): """ Getter. Allows for value = self.value. Reads from cache if possible. - """ + """ if self.no_cache: - # re-create data from database and cache it + # re-create data from database and cache it try: value = self.from_attr(PLOADS(to_str(self.db_value))) except pickle.UnpicklingError: - value = self.db_value + value = self.db_value self.cached_value = value - self.no_cache = False + self.no_cache = False return value else: # normally the memory cache holds the latest data so no db access is needed. @@ -358,7 +358,7 @@ class Attribute(SharedMemoryModel): def value_set(self, new_value): """ Setter. Allows for self.value = value. We make sure to cache everything. - """ + """ new_value = self.to_attr(new_value) self.cached_value = self.from_attr(new_value) self.no_cache = False @@ -371,7 +371,7 @@ class Attribute(SharedMemoryModel): value = property(value_get, value_set, value_del) # lock_storage property (wraps db_lock_storage) - #@property + #@property def lock_storage_get(self): "Getter. Allows for value = self.lock_storage" return get_cache(self, "lock_storage") @@ -385,7 +385,7 @@ class Attribute(SharedMemoryModel): "Deleter is disabled. Use the lockhandler.delete (self.lock.delete) instead""" 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) - + # # @@ -395,12 +395,12 @@ class Attribute(SharedMemoryModel): def __str__(self): return smart_str("%s(%s)" % (self.key, self.id)) - + def __unicode__(self): return u"%s(%s)" % (self.key, self.id) - # operators on various data - + # operators on various data + def to_attr(self, data): """ Convert data to proper attr data format before saving @@ -416,13 +416,13 @@ class Attribute(SharedMemoryModel): (and any nested combination of them) this way, all other iterables are stored and returned as lists. - data storage format: + data storage format: (simple|dbobj|iter, ) - where + where simple - a single non-db object, like a string or number dbobj - a single dbobj iter - any iterable object - will be looped over recursively - to convert dbobj->id. + to convert dbobj->id. """ @@ -433,7 +433,7 @@ class Attribute(SharedMemoryModel): """ dtype = type(item) if dtype in (basestring, int, float): # check the most common types first, for speed - return item + return item elif hasattr(item, "id") and hasattr(item, "db_model_name") and hasattr(item, "db_key"): db_model_name = item.db_model_name if db_model_name == "typeclass": @@ -459,31 +459,31 @@ class Attribute(SharedMemoryModel): if db_model_name == "typeclass": # typeclass cannot help us, we want the actual child object model name db_model_name = GA(data.dbobj, "db_model_name") - return ("dbobj", PackedDBobject(data.id, db_model_name, data.db_key)) - elif hasattr(data, "__iter__"): + return ("dbobj", PackedDBobject(data.id, db_model_name, data.db_key)) + elif hasattr(data, "__iter__"): return ("iter", iter_db2id(data)) else: return ("simple", data) - + def from_attr(self, datatuple): """ Retrieve data from a previously stored attribute. This - is always a dict with keys type and data. + is always a dict with keys type and data. - datatuple comes from the database storage and has - the following format: + datatuple comes from the database storage and has + the following format: (simple|dbobj|iter, ) where simple - a single non-db object, like a string. is returned as-is. - dbobj - a single dbobj-id. This id is retrieved back from the database. + dbobj - a single dbobj-id. This id is retrieved back from the database. iter - an iterable. This is traversed iteratively, converting all found - dbobj-ids back to objects. Also, all lists and dictionaries are - returned as their PackedList/PackedDict counterparts in order to + dbobj-ids back to objects. Also, all lists and dictionaries are + returned as their PackedList/PackedDict counterparts in order to allow in-place assignment such as obj.db.mylist[3] = val. Mylist - is then a PackedList that saves the data on the fly. + is then a PackedList that saves the data on the fly. """ - # nested functions + # nested functions def id2db(data): """ Convert db-stored dbref back to object @@ -496,21 +496,21 @@ class Attribute(SharedMemoryModel): try: return mclass.objects.get(id=data.id) except mclass.DoesNotExist: # could happen if object was deleted in the interim. - return None + return None def iter_id2db(item, parent=None): """ Recursively looping through stored iterables, replacing ids with actual objects. We return PackedDict and PackedLists instead of normal lists; this is needed in order for the user to do dynamic saving of nested in-place, such as obj.db.attrlist[2]=3. What is - stored in the database are however always normal python primitives. + stored in the database are however always normal python primitives. """ dtype = type(item) if dtype in (basestring, int, float): # check the most common types first, for speed - return item + return item elif dtype == PackedDBobject: - return id2db(item) - elif dtype == tuple: + return id2db(item) + elif dtype == tuple: return tuple([iter_id2db(val) for val in item]) elif dtype in (dict, PackedDict): pdict = PackedDict(self) @@ -523,28 +523,28 @@ class Attribute(SharedMemoryModel): plist.extend(list(iter_id2db(val, plist) for val in item)) plist.parent = parent return plist - else: - return item + else: + return item typ, data = datatuple - if typ == 'simple': + if typ == 'simple': # single non-db objects return data - elif typ == 'dbobj': - # a single stored dbobj + elif typ == 'dbobj': + # a single stored dbobj return id2db(data) - elif typ == 'iter': + elif typ == 'iter': # all types of iterables return iter_id2db(data) - + def access(self, accessing_obj, access_type='read', default=False): """ Determines if another object has permission to access. 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) @@ -556,21 +556,21 @@ class Attribute(SharedMemoryModel): class TypeNick(SharedMemoryModel): """ - This model holds whichever alternate names this object + This model holds whichever alternate names this object has for OTHER objects, but also for arbitrary strings, channels, players etc. Setting a nick does not affect - the nicknamed object at all (as opposed to Aliases above), + the nicknamed object at all (as opposed to Aliases above), and only this object will be able to refer to the nicknamed - object by the given nick. + object by the given nick. - 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_nick = models.CharField('nickname',max_length=255, db_index=True, help_text='the alias') + db_nick = models.CharField('nickname',max_length=255, db_index=True, help_text='the alias') db_real = models.TextField('realname', help_text='the original string to match and replace.') db_type = models.CharField('nick type',default="inputline", max_length=16, null=True, blank=True, help_text="the nick type describes when the engine tries to do nick-replacement. Common options are 'inputline','player','obj' and 'channel'. Inputline checks everything being inserted, whereas the other cases tries to replace in various searches or when posting to channels.") @@ -578,16 +578,16 @@ class TypeNick(SharedMemoryModel): class Meta: "Define Django meta options" - abstract = True + abstract = True verbose_name = "Nickname" unique_together = ("db_nick", "db_type", "db_obj") class TypeNickHandler(object): """ Handles nick access and setting. Accessed through ObjectDB.nicks - """ + """ - NickClass = TypeNick + NickClass = TypeNick def __init__(self, obj): """ @@ -595,11 +595,11 @@ class TypeNickHandler(object): on-the-fly replacements for various text input passing through this object (most often a Character) - 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 You can define other nicktypes by using the add() method of @@ -612,38 +612,38 @@ class TypeNickHandler(object): def add(self, nick, realname, nick_type="inputline"): """ - Assign a new nick for realname. - nick_types used by Evennia are + Assign a new nick for realname. + nick_types used by Evennia are 'inputline', 'player', 'obj' and 'channel' """ if not nick or not nick.strip(): - return - nick = nick.strip() - real = realname.strip() - query = self.NickClass.objects.filter(db_obj=self.obj, db_nick__iexact=nick, db_type__iexact=nick_type) + return + nick = nick.strip() + real = realname.strip() + query = self.NickClass.objects.filter(db_obj=self.obj, db_nick__iexact=nick, db_type__iexact=nick_type) if query.count(): old_nick = query[0] old_nick.db_real = real old_nick.save() - else: + else: new_nick = self.NickClass(db_nick=nick, db_real=real, db_type=nick_type, db_obj=self.obj) - new_nick.save() - def delete(self, nick, nick_type="inputline"): + new_nick.save() + def delete(self, nick, nick_type="inputline"): "Removes a previously stored nick" - nick = nick.strip() - query = self.NickClass.objects.filter(db_obj=self.obj, db_nick__iexact=nick, db_type__iexact=nick_type) + nick = nick.strip() + query = self.NickClass.objects.filter(db_obj=self.obj, db_nick__iexact=nick, db_type__iexact=nick_type) if query.count(): # remove the found nick(s) - query.delete() + query.delete() def get(self, nick=None, nick_type="inputline", obj=None): """Retrieves a given nick (with a specified nick_type) on an object. If no nick is given, returns a list - of all nicks on the object, or the empty list. + of all nicks on the object, or the empty list. Defaults to searching the current object.""" if not obj: # defaults to the current object obj = self.obj if nick: - query = self.NickClass.objects.filter(db_obj=obj, db_nick__iexact=nick, db_type__iexact=nick_type) + query = self.NickClass.objects.filter(db_obj=obj, db_nick__iexact=nick, db_type__iexact=nick_type) query = query.values_list("db_real", flat=True) if query.count(): return query[0] @@ -654,8 +654,8 @@ class TypeNickHandler(object): def has(self, nick, nick_type="inputline", obj=None): """ Returns true/false if this nick and nick_type is defined on the given - object or not. If no obj is given, default to the current object the - handler is defined on. + object or not. If no obj is given, default to the current object the + handler is defined on. """ if not obj: @@ -665,39 +665,39 @@ class TypeNickHandler(object): #------------------------------------------------------------ # -# Typed Objects +# Typed Objects # -#------------------------------------------------------------ +#------------------------------------------------------------ class TypedObject(SharedMemoryModel): """ Abstract Django model. - + This is the basis for a typed object. It also contains all the - mechanics for managing connected attributes. - + mechanics for managing connected attributes. + The TypedObject has the following properties: key - main name name - alias for key typeclass_path - the path to the decorating typeclass typeclass - auto-linked typeclass date_created - time stamp of object creation - permissions - perm strings - dbref - #id of object + permissions - perm strings + dbref - #id of object db - persistent attribute storage - ndb - non-persistent attribute storage + ndb - non-persistent attribute storage - """ + """ - # + # # TypedObject Database Model setup # # # These databse fields are all set using their corresponding properties, # named same as the field, but withtou the db_* prefix. - + # Main identifier of the object, for searching. Can also - # be referenced as 'name'. + # be referenced as 'name'. db_key = models.CharField('key', max_length=255, db_index=True) # This is the python path to the type class this object is tied to # (the type class is what defines what kind of Object this is) @@ -706,35 +706,35 @@ class TypedObject(SharedMemoryModel): db_date_created = models.DateTimeField('creation date', editable=False, auto_now_add=True) # Permissions (access these through the 'permissions' property) db_permissions = models.CharField('permissions', max_length=255, blank=True, help_text="a comma-separated list of text strings checked by certain locks. They are often used for hierarchies, such as letting a Player have permission 'Wizards', 'Builders' etc. Character objects use 'Players' by default. Most other objects don't have any permissions.") - # Lock storage - db_lock_storage = models.CharField('locks', max_length=512, blank=True, help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.") + # Lock storage + db_lock_storage = models.CharField('locks', max_length=512, blank=True, help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.") # Database manager objects = managers.TypedObjectManager() - # object cache and flags - _cached_typeclass = None + # object cache and flags + _cached_typeclass = None # lock handler self.locks def __init__(self, *args, **kwargs): "We must initialize the parent first - important!" SharedMemoryModel.__init__(self, *args, **kwargs) self.locks = LockHandler(self) - + class Meta: """ Django setup info. """ - abstract = True + abstract = True verbose_name = "Evennia Database Object" ordering = ['-db_date_created', 'id', 'db_typeclass_path', 'db_key'] - + # 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). # key property (wraps db_key) @@ -770,7 +770,7 @@ class TypedObject(SharedMemoryModel): # typeclass_path property #@property def typeclass_path_get(self): - "Getter. Allows for value = self.typeclass_path" + "Getter. Allows for value = self.typeclass_path" return get_cache(self, "typeclass_path") #@typeclass_path.setter def typeclass_path_set(self, value): @@ -809,7 +809,7 @@ class TypedObject(SharedMemoryModel): return [] #@permissions.setter def permissions_set(self, value): - "Setter. Allows for self.name = value. Stores as a comma-separated string." + "Setter. Allows for self.name = value. Stores as a comma-separated string." value = ",".join([utils.to_unicode(val).strip() for val in make_iter(value)]) set_cache(self, "permissions", value) #@permissions.deleter @@ -821,7 +821,7 @@ class TypedObject(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 get_cache(self, "lock_storage") @@ -839,16 +839,16 @@ class TypedObject(SharedMemoryModel): # # - # TypedObject main class methods and properties + # TypedObject main class methods and properties # # # these are identifiers for fast Attribute access and caching - typeclass_paths = settings.OBJECT_TYPECLASS_PATHS - attribute_class = Attribute # replaced by relevant attribute class for child + typeclass_paths = settings.OBJECT_TYPECLASS_PATHS + attribute_class = Attribute # replaced by relevant attribute class for child db_model_name = "typeclass" # used by attributes to safely store objects - def __eq__(self, other): + def __eq__(self, other): return other and hasattr(other, 'id') and self.id == other.id def __str__(self): @@ -872,9 +872,9 @@ class TypedObject(SharedMemoryModel): # (we make sure to not incur a loop by not triggering the # typeclass' __getattribute__, since that one would # try to look back to this very database object.) - typeclass = GA(self, 'typeclass') + typeclass = GA(self, 'typeclass') if typeclass: - return GA(typeclass, propname) + return GA(typeclass, propname) else: raise AttributeError @@ -883,7 +883,7 @@ class TypedObject(SharedMemoryModel): """ Returns the object's dbref id on the form #NN. Alternetively, use obj.id directly to get dbref - without any #. + without any #. """ return "#%s" % str(GA(self, "id")) dbref = property(dbref_get) @@ -899,9 +899,9 @@ class TypedObject(SharedMemoryModel): handles loading and initialization of the typeclass on the fly. Note: The liberal use of GA and __setattr__ (instead - of normal dot notation) is due to optimization: it avoids calling - the custom self.__getattribute__ more than necessary. - """ + of normal dot notation) is due to optimization: it avoids calling + the custom self.__getattribute__ more than necessary. + """ path = GA(self, "typeclass_path") typeclass = GA(self, "_cached_typeclass") @@ -916,19 +916,19 @@ class TypedObject(SharedMemoryModel): if not path: # this means we should get the default obj without giving errors. return GA(self, "get_default_typeclass")(cache=True, silent=True, save=True) - else: + else: # handle loading/importing of typeclasses, searching all paths. # (self.typeclass_paths is a shortcut to settings.TYPECLASS_*_PATHS # where '*' is either OBJECT, SCRIPT or PLAYER depending on the typed - # entities). + # entities). typeclass_paths = [path] + ["%s.%s" % (prefix, path) for prefix in GA(self, 'typeclass_paths')] - - for tpath in typeclass_paths: + + for tpath in typeclass_paths: # try to import and analyze the result typeclass = GA(self, "_path_import")(tpath) if callable(typeclass): - # we succeeded to import. Cache and return. + # we succeeded to import. Cache and return. SA(self, 'db_typeclass_path', tpath) GA(self, 'save')() SA(self, "_cached_db_typeclass_path", tpath) @@ -942,10 +942,10 @@ class TypedObject(SharedMemoryModel): elif hasattr(typeclass, '__file__'): errstring += "\n%s seems to be just the path to a module. You need" % tpath errstring += " to specify the actual typeclass name inside the module too." - else: - errstring += "\n%s" % typeclass # this will hold a growing error message. + else: + errstring += "\n%s" % typeclass # this will hold a growing error message. # If we reach this point we couldn't import any typeclasses. Return default. It's up to the calling - # method to use e.g. self.is_typeclass() to detect that the result is not the one asked for. + # method to use e.g. self.is_typeclass() to detect that the result is not the one asked for. GA(self, "_display_errmsg")(errstring) return GA(self, "get_default_typeclass")(cache=False, silent=False, save=False) @@ -954,9 +954,9 @@ class TypedObject(SharedMemoryModel): "Deleter. Disallow 'del self.typeclass'" raise Exception("The typeclass property should never be deleted, only changed in-place!") - # typeclass property + # typeclass property typeclass = property(typeclass_get, fdel=typeclass_del) - + def _path_import(self, path): """ Import a class from a python path of the @@ -964,80 +964,80 @@ class TypedObject(SharedMemoryModel): """ errstring = "" if not path: - # this needs not be bad, it just means + # this needs not be bad, it just means # we should use defaults. - return None - try: + return None + try: modpath, class_name = path.rsplit('.', 1) module = __import__(modpath, fromlist=[class_name]) return module.__dict__[class_name] - except ImportError: - trc = sys.exc_traceback + except ImportError: + trc = sys.exc_traceback if not trc.tb_next: # we separate between not finding the module, and finding a buggy one. errstring += "(Tried path '%s')." % path else: # a bug in the module is reported normally. - trc = traceback.format_exc() + trc = traceback.format_exc() errstring += "\n%sError importing '%s'." % (trc, path) except KeyError: - errstring = "No class '%s' was found in module '%s'." + errstring = "No class '%s' was found in module '%s'." errstring = errstring % (class_name, modpath) except Exception: - trc = traceback.format_exc() - errstring = "\n%sException importing '%s'." % (trc, path) + trc = traceback.format_exc() + errstring = "\n%sException importing '%s'." % (trc, path) # return the error. return errstring - def _display_errmsg(self, message): + def _display_errmsg(self, message): """ Helper function to display error. """ infochan = None - cmessage = message + cmessage = message try: from src.comms.models import Channel infochan = settings.CHANNEL_MUDINFO - infochan = Channel.objects.get_channel(infochan[0]) + infochan = Channel.objects.get_channel(infochan[0]) if infochan: cname = infochan.key cmessage = "\n".join(["[%s]: %s" % (cname, line) for line in message.split('\n') if line]) cmessage = cmessage.strip() infochan.msg(cmessage) else: - # no mudinfo channel is found. Log instead. + # no mudinfo channel is found. Log instead. cmessage = "\n".join(["[NO MUDINFO CHANNEL]: %s" % line for line in message.split('\n')]) logger.log_errmsg(cmessage) - except Exception: + except Exception: if ServerConfig.objects.conf("server_starting_mode"): print cmessage else: logger.log_trace(cmessage) - + def get_default_typeclass(self, cache=False, silent=False, save=False): """ - This is called when a typeclass fails to - load for whatever reason. - Overload this in different entities. + This is called when a typeclass fails to + load for whatever reason. + Overload this in different entities. Default operation is to load a default typeclass. """ - defpath = GA(self, "default_typeclass_path") + defpath = GA(self, "default_typeclass_path") typeclass = GA(self, "_path_import")(defpath) # if not silent: - # #errstring = "\n\nUsing Default class '%s'." % defpath + # #errstring = "\n\nUsing Default class '%s'." % defpath # GA(self, "_display_errmsg")(errstring) if not callable(typeclass): # if typeclass still doesn't exist at this point, we're in trouble. - # fall back to hardcoded core class which is wrong for e.g. scripts/players etc. + # fall back to hardcoded core class which is wrong for e.g. scripts/players etc. failpath = defpath defpath = "src.objects.objects.Object" typeclass = GA(self, "_path_import")(defpath) if not silent: #errstring = " %s\n%s" % (typeclass, errstring) errstring = " Default class '%s' failed to load." % failpath - errstring += "\n Using Evennia's default class '%s'." % defpath + errstring += "\n Using Evennia's default class '%s'." % defpath GA(self, "_display_errmsg")(errstring) if not callable(typeclass): # if this is still giving an error, Evennia is wrongly configured or buggy @@ -1050,32 +1050,32 @@ class TypedObject(SharedMemoryModel): SA(self, "_cached_db_typeclass_path", defpath) SA(self, "_cached_typeclass", typeclass) - try: + try: typeclass.at_init() except Exception: logger.log_trace() - return typeclass + return typeclass def is_typeclass(self, typeclass, exact=False): """ 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. - """ + """ try: typeclass = GA(typeclass, "path") except AttributeError: - pass + pass typeclasses = [typeclass] + ["%s.%s" % (path, typeclass) for path in GA(self, "typeclass_paths")] if exact: - current_path = GA(self, "_cached_db_typeclass_path") + current_path = GA(self, "_cached_db_typeclass_path") return typeclass and any((current_path == typec for typec in typeclasses)) else: # check parent chain @@ -1084,7 +1084,7 @@ class TypedObject(SharedMemoryModel): # # Object manipulation methods - # + # # def swap_typeclass(self, new_typeclass, clean_attributes=False, no_default=True): @@ -1092,18 +1092,18 @@ class TypedObject(SharedMemoryModel): 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 @@ -1115,8 +1115,8 @@ class TypedObject(SharedMemoryModel): 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. """ @@ -1128,8 +1128,8 @@ class TypedObject(SharedMemoryModel): # Try to set the new path # this will automatically save to database - old_typeclass_path = self.typeclass_path - self.typeclass_path = new_typeclass.strip() + old_typeclass_path = self.typeclass_path + self.typeclass_path = new_typeclass.strip() # this will automatically use a default class if # there is an error with the given typeclass. new_typeclass = self.typeclass @@ -1141,9 +1141,9 @@ class TypedObject(SharedMemoryModel): # something went wrong; the default was loaded instead, # and we don't allow that; instead we return to previous. SA(self, "typeclass_path", old_typeclass_path) - SA(self, "_cached_typeclass", None) + SA(self, "_cached_typeclass", None) return False - + if clean_attributes: # Clean out old attributes if is_iter(clean_attributes): @@ -1157,60 +1157,60 @@ class TypedObject(SharedMemoryModel): self.get_all_attributes() for attr in self.get_all_attributes(): attr.delete() - for nattr in self.ndb.all(): + for nattr in self.ndb.all(): del nattr # run hooks for this new typeclass new_typeclass.basetype_setup() new_typeclass.at_object_creation() - return True - + return True + # - # Attribute handler methods + # Attribute handler methods # - # - # Fully persistent attributes. You usually access these - # through the obj.db.attrname method. + # + # Fully persistent attributes. You usually access these + # through the obj.db.attrname method. + + # Helper methods for persistent attributes - # Helper methods for persistent attributes - def has_attribute(self, attribute_name): """ See if we have an attribute set on the object. - + attribute_name: (str) The attribute's name. - """ + """ return GA(self, "attribute_class").objects.filter(db_obj=self).filter( db_key__iexact=attribute_name).count() - + def set_attribute(self, attribute_name, new_value=None): """ Sets an attribute on an object. Creates the attribute if need be. - + attribute_name: (str) The attribute's name. new_value: (python obj) The value to set the attribute to. If this is not - a str, the object will be stored as a pickle. + a str, the object will be stored as a pickle. """ attrib_obj = None attrclass = GA(self, "attribute_class") try: - # use old attribute + # use old attribute attrib_obj = attrclass.objects.filter( db_obj=self).filter(db_key__iexact=attribute_name)[0] except IndexError: # no match; create new attribute - attrib_obj = attrclass(db_key=attribute_name, db_obj=self) - # re-set an old attribute value + attrib_obj = attrclass(db_key=attribute_name, db_obj=self) + # re-set an old attribute value attrib_obj.value = new_value - + def get_attribute(self, attribute_name, default=None): """ Returns the value of an attribute on an object. You may need to type cast the returned value from this function since the attribute - can be of any type. Returns default if no match is found. - + can be of any type. Returns default if no match is found. + attribute_name: (str) The attribute's name. default: What to return if no attribute is found """ @@ -1219,7 +1219,7 @@ class TypedObject(SharedMemoryModel): attrib_obj = self.attribute_class.objects.filter( db_obj=self).filter(db_key__iexact=attribute_name)[0] except IndexError: - return default + return default return attrib_obj.value def get_attribute_raise(self, attribute_name): @@ -1234,23 +1234,23 @@ class TypedObject(SharedMemoryModel): db_obj=self).filter(db_key__iexact=attribute_name)[0].value except IndexError: raise AttributeError - + def del_attribute(self, attribute_name): """ Removes an attribute entirely. - + attribute_name: (str) The attribute's name. """ try: self.attribute_class.objects.filter( db_obj=self).filter(db_key__iexact=attribute_name)[0].delete() except IndexError: - pass + pass def del_attribute_raise(self, attribute_name): """ - Removes and attribute. Raises AttributeError if - attribute is not found. + Removes and attribute. Raises AttributeError if + attribute is not found. attribute_name: (str) The attribute's name. """ @@ -1258,12 +1258,12 @@ class TypedObject(SharedMemoryModel): self.attribute_class.objects.filter( db_obj=self).filter(db_key__iexact=attribute_name)[0].delete() except IndexError: - raise AttributeError + raise AttributeError def get_all_attributes(self): """ Returns all attributes defined on the object. - """ + """ return list(self.attribute_class.objects.filter(db_obj=self)) def attr(self, attribute_name=None, value=None, delete=False): @@ -1273,14 +1273,14 @@ class TypedObject(SharedMemoryModel): and get_all_attributes. If value is None, attr will act like a getter, otherwise as a setter. - set delete=True to delete the named attribute. + set delete=True to delete the named attribute. Note that you cannot set the attribute value to None using this method. Use set_attribute. """ - if attribute_name == None: + if attribute_name == None: # act as a list method - return self.get_all_attributes() + return self.get_all_attributes() elif delete == True: self.del_attribute(attribute_name) elif value == None: @@ -1296,13 +1296,13 @@ class TypedObject(SharedMemoryModel): A second convenience wrapper for the the attribute methods. It allows for the syntax obj.db.attrname = value - and - value = obj.db.attrname and - del obj.db.attrname + value = obj.db.attrname + and + del obj.db.attrname and all_attr = obj.db.all() (if there is no attribute named 'all', in which - case that will be returned instead). + case that will be returned instead). """ try: return self._db_holder @@ -1311,18 +1311,18 @@ class TypedObject(SharedMemoryModel): "Holder for allowing property access of attributes" def __init__(self, obj): SA(self, 'obj', obj) - def __getattribute__(self, attrname): + def __getattribute__(self, attrname): if attrname == 'all': # we allow for overwriting the all() method - # with an attribute named 'all'. + # with an attribute named 'all'. attr = GA(self, 'obj').get_attribute("all") if attr: return attr - return GA(self, 'all') + return GA(self, 'all') return GA(self, 'obj').get_attribute(attrname) - def __setattr__(self, attrname, value): - GA(self, 'obj').set_attribute(attrname, value) - def __delattr__(self, attrname): + def __setattr__(self, attrname, value): + GA(self, 'obj').set_attribute(attrname, value) + def __delattr__(self, attrname): GA(self, 'obj').del_attribute(attrname) def all(self): return GA(self, 'obj').get_all_attributes() @@ -1349,13 +1349,13 @@ class TypedObject(SharedMemoryModel): This is the equivalence of self.attr but for non-persistent stores. """ - if attribute_name == None: + if attribute_name == None: # act as a list method if callable(self.ndb.all): return self.ndb.all() else: - return [val for val in self.ndb.__dict__.keys() - if not val.startswith['_']] + return [val for val in self.ndb.__dict__.keys() + if not val.startswith['_']] elif delete == True: if hasattr(self.ndb, attribute_name): DA(self.db, attribute_name) @@ -1364,15 +1364,15 @@ class TypedObject(SharedMemoryModel): if hasattr(self.ndb, attribute_name): GA(self.ndb, attribute_name) else: - return None + return None else: # act as a setter SA(self.db, attribute_name, value) - + #@property def ndb_get(self): """ - A non-persistent store (ndb: NonDataBase). Everything stored + A non-persistent store (ndb: NonDataBase). Everything stored to this is guaranteed to be cleared when a server is shutdown. Syntax is same as for the _get_db_holder() method and property, e.g. obj.ndb.attr = value etc. @@ -1383,14 +1383,14 @@ class TypedObject(SharedMemoryModel): class NdbHolder(object): "Holder for storing non-persistent attributes." def all(self): - return [val for val in self.__dict__.keys() + return [val for val in self.__dict__.keys() if not val.startswith['_']] def __getattribute__(self, key): - # return None if no matching attribute was found. + # return None if no matching attribute was found. try: return GA(self, key) except AttributeError: - return None + return None self._ndb_holder = NdbHolder() return self._ndb_holder #@ndb.setter @@ -1404,7 +1404,7 @@ class TypedObject(SharedMemoryModel): "Stop accidental deletion." raise Exception("Cannot delete the ndb object!") ndb = property(ndb_get, ndb_set, ndb_del) - + # # Lock / permission methods # @@ -1415,7 +1415,7 @@ class TypedObject(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) def has_perm(self, accessing_obj, access_type): @@ -1429,17 +1429,17 @@ class TypedObject(SharedMemoryModel): any locks. """ if self.player and self.player.is_superuser: - return True + return True if not permstring: - return False + return False perm = permstring.lower() if perm in [p.lower() for p in self.permissions]: # simplest case - we have a direct match - return True + return True if perm in PERMISSION_HIERARCHY: # check if we have a higher hierarchy position ppos = PERMISSION_HIERARCHY.index(perm) - return any(True for hpos, hperm in enumerate(PERMISSION_HIERARCHY) + return any(True for hpos, hperm in enumerate(PERMISSION_HIERARCHY) if hperm in [p.lower() for p in self.permissions] and hpos > ppos) - return False + return False diff --git a/src/typeclasses/typeclass.py b/src/typeclasses/typeclass.py index edd0a94301..cfbe153a75 100644 --- a/src/typeclasses/typeclass.py +++ b/src/typeclasses/typeclass.py @@ -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, diff --git a/src/utils/logger.py b/src/utils/logger.py index a037c56c9c..074ddfbe34 100644 --- a/src/utils/logger.py +++ b/src/utils/logger.py @@ -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: diff --git a/src/utils/utils.py b/src/utils/utils.py index 1ddca2cc3f..374a35ff3b 100644 --- a/src/utils/utils.py +++ b/src/utils/utils.py @@ -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