From 7090bdc8be75aa81714ae2675ad3c75b25c4123d Mon Sep 17 00:00:00 2001 From: Ragora Date: Sun, 9 Jun 2013 16:40:00 -0400 Subject: [PATCH] Added WWI Command Finally fixed a crash issue under MySQL where a DB action after a certain amount of inactivity time results in a crash Fixed SCommands events from not unbinding when being unloaded Alterations to the help command for command categorization Various Alterations to server.py and models.py Various Database system alterations and fixes General Document Changes --- .gitignore | 17 +- LICENSE.txt | 21 + application/config/exit_message.txt | 2 +- application/config/permissions.cfg | 8 +- application/config/settings_server.cfg | 97 +- application/config/welcome_message.txt | 12 +- application/daemon.py | 236 +-- application/game/__init__.py | 27 +- application/game/exception.py | 51 +- application/game/interface.py | 318 ++-- application/game/models.py | 1520 +++++++++-------- application/game/modloader.py | 355 ++-- application/game/permissions.py | 139 +- application/game/scommands/__init__.py | 48 +- application/game/scommands/scommands.py | 1294 +++++++------- application/game/settings.py | 138 +- application/game/world.py | 622 ++++--- application/main.py | 298 ++-- application/miniboa/ApacheLicense.txt | 202 +++ application/miniboa/__init__.py | 36 +- application/miniboa/async.py | 382 ++--- application/miniboa/error.py | 38 +- application/miniboa/telnet.py | 1474 ++++++++-------- application/miniboa/xterm.py | 220 +-- application/server.py | 592 ++++--- doc/Makefile | 0 doc/build/html/.buildinfo | 4 - doc/build/html/_modules/exception.html | 119 -- doc/build/html/_modules/game/exception.html | 117 -- doc/build/html/_modules/game/interface.html | 224 --- doc/build/html/_modules/game/models.html | 739 -------- doc/build/html/_modules/game/modloader.html | 239 --- doc/build/html/_modules/game/permissions.html | 151 -- doc/build/html/_modules/game/settings.html | 173 -- doc/build/html/_modules/game/world.html | 311 ---- doc/build/html/_modules/index.html | 100 -- doc/build/html/_modules/interface.html | 222 --- doc/build/html/_modules/io.html | 175 -- doc/build/html/_modules/models.html | 781 --------- doc/build/html/_modules/modloader.html | 270 --- doc/build/html/_modules/permissions.html | 166 -- doc/build/html/_modules/scommands.html | 694 -------- .../html/_modules/scommands/scommands.html | 658 ------- doc/build/html/_modules/server.html | 361 ---- doc/build/html/_modules/settings.html | 163 -- doc/build/html/_modules/world.html | 309 ---- doc/build/html/_sources/exception.txt | 9 - .../exceptions/modapplicationerror.txt | 7 - .../exceptions/modelargumenterror.txt | 7 - .../exceptions/worldargumenterror.txt | 7 - doc/build/html/_sources/index.txt | 34 - doc/build/html/_sources/interface.txt | 9 - doc/build/html/_sources/model_bot.txt | 8 - doc/build/html/_sources/model_exit.txt | 8 - doc/build/html/_sources/model_item.txt | 8 - doc/build/html/_sources/model_player.txt | 8 - doc/build/html/_sources/model_room.txt | 8 - doc/build/html/_sources/models.txt | 13 - doc/build/html/_sources/models/bot.txt | 8 - doc/build/html/_sources/models/common.txt | 8 - doc/build/html/_sources/models/exit.txt | 9 - doc/build/html/_sources/models/item.txt | 8 - doc/build/html/_sources/models/model_bot.txt | 8 - doc/build/html/_sources/models/model_exit.txt | 8 - doc/build/html/_sources/models/model_item.txt | 8 - .../html/_sources/models/model_player.txt | 8 - doc/build/html/_sources/models/model_room.txt | 8 - doc/build/html/_sources/models/player.txt | 8 - doc/build/html/_sources/models/room.txt | 8 - doc/build/html/_sources/modloader.txt | 9 - doc/build/html/_sources/permissions.txt | 12 - .../permissions/allowadminoverride.txt | 6 - .../permissions/allowowneroverride.txt | 5 - .../permissions/allowsuperadminoverride.txt | 5 - doc/build/html/_sources/scommands.txt | 8 - doc/build/html/_sources/server.txt | 9 - doc/build/html/_sources/settings.txt | 8 - doc/build/html/_sources/world.txt | 8 - doc/build/html/_static/ajax-loader.gif | Bin 673 -> 0 bytes doc/build/html/_static/basic.css | 540 ------ doc/build/html/_static/comment-bright.png | Bin 3500 -> 0 bytes doc/build/html/_static/comment-close.png | Bin 3578 -> 0 bytes doc/build/html/_static/comment.png | Bin 3445 -> 0 bytes doc/build/html/_static/default.css | 256 --- doc/build/html/_static/doctools.js | 247 --- doc/build/html/_static/down-pressed.png | Bin 368 -> 0 bytes doc/build/html/_static/down.png | Bin 363 -> 0 bytes doc/build/html/_static/file.png | Bin 392 -> 0 bytes doc/build/html/_static/jquery.js | 154 -- doc/build/html/_static/minus.png | Bin 199 -> 0 bytes doc/build/html/_static/plus.png | Bin 199 -> 0 bytes doc/build/html/_static/pygments.css | 62 - doc/build/html/_static/searchtools.js | 560 ------ doc/build/html/_static/sidebar.js | 151 -- doc/build/html/_static/underscore.js | 23 - doc/build/html/_static/up-pressed.png | Bin 372 -> 0 bytes doc/build/html/_static/up.png | Bin 363 -> 0 bytes doc/build/html/_static/websupport.js | 808 --------- doc/build/html/exception.html | 131 -- .../html/exceptions/modapplicationerror.html | 139 -- .../html/exceptions/modelargumenterror.html | 133 -- .../html/exceptions/worldargumenterror.html | 133 -- doc/build/html/genindex.html | 805 --------- doc/build/html/index.html | 203 --- doc/build/html/interface.html | 167 -- doc/build/html/model_bot.html | 152 -- doc/build/html/model_exit.html | 145 -- doc/build/html/model_item.html | 153 -- doc/build/html/model_player.html | 219 --- doc/build/html/model_room.html | 235 --- doc/build/html/models.html | 140 -- doc/build/html/models/bot.html | 186 -- doc/build/html/models/common.html | 207 --- doc/build/html/models/exit.html | 232 --- doc/build/html/models/item.html | 188 -- doc/build/html/models/model_bot.html | 129 -- doc/build/html/models/model_exit.html | 122 -- doc/build/html/models/model_item.html | 130 -- doc/build/html/models/model_player.html | 196 --- doc/build/html/models/model_room.html | 212 --- doc/build/html/models/player.html | 289 ---- doc/build/html/models/room.html | 303 ---- doc/build/html/modloader.html | 211 --- doc/build/html/objects.inv | Bin 1410 -> 0 bytes doc/build/html/permissions.html | 179 -- .../html/permissions/allowadminoverride.html | 123 -- .../html/permissions/allowowneroverride.html | 123 -- .../permissions/allowsuperadminoverride.html | 123 -- doc/build/html/py-modindex.html | 174 -- doc/build/html/scommands.html | 174 -- doc/build/html/search.html | 105 -- doc/build/html/searchindex.js | 1 - doc/build/html/server.html | 186 -- doc/build/html/settings.html | 167 -- doc/build/html/world.html | 251 --- doc/source/conf.py | 12 +- gpl.txt | 674 -------- requirements.txt | 11 +- 138 files changed, 4429 insertions(+), 20243 deletions(-) create mode 100644 LICENSE.txt create mode 100644 application/miniboa/ApacheLicense.txt mode change 100644 => 100755 doc/Makefile delete mode 100644 doc/build/html/.buildinfo delete mode 100644 doc/build/html/_modules/exception.html delete mode 100644 doc/build/html/_modules/game/exception.html delete mode 100644 doc/build/html/_modules/game/interface.html delete mode 100644 doc/build/html/_modules/game/models.html delete mode 100644 doc/build/html/_modules/game/modloader.html delete mode 100644 doc/build/html/_modules/game/permissions.html delete mode 100644 doc/build/html/_modules/game/settings.html delete mode 100644 doc/build/html/_modules/game/world.html delete mode 100644 doc/build/html/_modules/index.html delete mode 100644 doc/build/html/_modules/interface.html delete mode 100644 doc/build/html/_modules/io.html delete mode 100644 doc/build/html/_modules/models.html delete mode 100644 doc/build/html/_modules/modloader.html delete mode 100644 doc/build/html/_modules/permissions.html delete mode 100644 doc/build/html/_modules/scommands.html delete mode 100644 doc/build/html/_modules/scommands/scommands.html delete mode 100644 doc/build/html/_modules/server.html delete mode 100644 doc/build/html/_modules/settings.html delete mode 100644 doc/build/html/_modules/world.html delete mode 100644 doc/build/html/_sources/exception.txt delete mode 100644 doc/build/html/_sources/exceptions/modapplicationerror.txt delete mode 100644 doc/build/html/_sources/exceptions/modelargumenterror.txt delete mode 100644 doc/build/html/_sources/exceptions/worldargumenterror.txt delete mode 100644 doc/build/html/_sources/index.txt delete mode 100644 doc/build/html/_sources/interface.txt delete mode 100644 doc/build/html/_sources/model_bot.txt delete mode 100644 doc/build/html/_sources/model_exit.txt delete mode 100644 doc/build/html/_sources/model_item.txt delete mode 100644 doc/build/html/_sources/model_player.txt delete mode 100644 doc/build/html/_sources/model_room.txt delete mode 100644 doc/build/html/_sources/models.txt delete mode 100644 doc/build/html/_sources/models/bot.txt delete mode 100644 doc/build/html/_sources/models/common.txt delete mode 100644 doc/build/html/_sources/models/exit.txt delete mode 100644 doc/build/html/_sources/models/item.txt delete mode 100644 doc/build/html/_sources/models/model_bot.txt delete mode 100644 doc/build/html/_sources/models/model_exit.txt delete mode 100644 doc/build/html/_sources/models/model_item.txt delete mode 100644 doc/build/html/_sources/models/model_player.txt delete mode 100644 doc/build/html/_sources/models/model_room.txt delete mode 100644 doc/build/html/_sources/models/player.txt delete mode 100644 doc/build/html/_sources/models/room.txt delete mode 100644 doc/build/html/_sources/modloader.txt delete mode 100644 doc/build/html/_sources/permissions.txt delete mode 100644 doc/build/html/_sources/permissions/allowadminoverride.txt delete mode 100644 doc/build/html/_sources/permissions/allowowneroverride.txt delete mode 100644 doc/build/html/_sources/permissions/allowsuperadminoverride.txt delete mode 100644 doc/build/html/_sources/scommands.txt delete mode 100644 doc/build/html/_sources/server.txt delete mode 100644 doc/build/html/_sources/settings.txt delete mode 100644 doc/build/html/_sources/world.txt delete mode 100644 doc/build/html/_static/ajax-loader.gif delete mode 100644 doc/build/html/_static/basic.css delete mode 100644 doc/build/html/_static/comment-bright.png delete mode 100644 doc/build/html/_static/comment-close.png delete mode 100644 doc/build/html/_static/comment.png delete mode 100644 doc/build/html/_static/default.css delete mode 100644 doc/build/html/_static/doctools.js delete mode 100644 doc/build/html/_static/down-pressed.png delete mode 100644 doc/build/html/_static/down.png delete mode 100644 doc/build/html/_static/file.png delete mode 100644 doc/build/html/_static/jquery.js delete mode 100644 doc/build/html/_static/minus.png delete mode 100644 doc/build/html/_static/plus.png delete mode 100644 doc/build/html/_static/pygments.css delete mode 100644 doc/build/html/_static/searchtools.js delete mode 100644 doc/build/html/_static/sidebar.js delete mode 100644 doc/build/html/_static/underscore.js delete mode 100644 doc/build/html/_static/up-pressed.png delete mode 100644 doc/build/html/_static/up.png delete mode 100644 doc/build/html/_static/websupport.js delete mode 100644 doc/build/html/exception.html delete mode 100644 doc/build/html/exceptions/modapplicationerror.html delete mode 100644 doc/build/html/exceptions/modelargumenterror.html delete mode 100644 doc/build/html/exceptions/worldargumenterror.html delete mode 100644 doc/build/html/genindex.html delete mode 100644 doc/build/html/index.html delete mode 100644 doc/build/html/interface.html delete mode 100644 doc/build/html/model_bot.html delete mode 100644 doc/build/html/model_exit.html delete mode 100644 doc/build/html/model_item.html delete mode 100644 doc/build/html/model_player.html delete mode 100644 doc/build/html/model_room.html delete mode 100644 doc/build/html/models.html delete mode 100644 doc/build/html/models/bot.html delete mode 100644 doc/build/html/models/common.html delete mode 100644 doc/build/html/models/exit.html delete mode 100644 doc/build/html/models/item.html delete mode 100644 doc/build/html/models/model_bot.html delete mode 100644 doc/build/html/models/model_exit.html delete mode 100644 doc/build/html/models/model_item.html delete mode 100644 doc/build/html/models/model_player.html delete mode 100644 doc/build/html/models/model_room.html delete mode 100644 doc/build/html/models/player.html delete mode 100644 doc/build/html/models/room.html delete mode 100644 doc/build/html/modloader.html delete mode 100644 doc/build/html/objects.inv delete mode 100644 doc/build/html/permissions.html delete mode 100644 doc/build/html/permissions/allowadminoverride.html delete mode 100644 doc/build/html/permissions/allowowneroverride.html delete mode 100644 doc/build/html/permissions/allowsuperadminoverride.html delete mode 100644 doc/build/html/py-modindex.html delete mode 100644 doc/build/html/scommands.html delete mode 100644 doc/build/html/search.html delete mode 100644 doc/build/html/searchindex.js delete mode 100644 doc/build/html/server.html delete mode 100644 doc/build/html/settings.html delete mode 100644 doc/build/html/world.html delete mode 100644 gpl.txt diff --git a/.gitignore b/.gitignore index c627e8f..678b14d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,9 @@ -*.pyc -*.py~ -*.cfg~ -*.txt~ -*.rst~ -doc/build/doctrees -application/game/fuzzball -application/game/construction \ No newline at end of file +*.pyc +*.py~ +*.cfg~ +*.txt~ +*.rst~ +doc/build +application/game/fuzzball +application/game/construction +application/game/scmsa \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..c3ca6a2 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Robert MacGregor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/application/config/exit_message.txt b/application/config/exit_message.txt index 6e7d326..ba1438b 100644 --- a/application/config/exit_message.txt +++ b/application/config/exit_message.txt @@ -1,2 +1,2 @@ - + You fall asleep in the current area. \ No newline at end of file diff --git a/application/config/permissions.cfg b/application/config/permissions.cfg index ca7a421..d9607e2 100644 --- a/application/config/permissions.cfg +++ b/application/config/permissions.cfg @@ -1 +1,7 @@ -# Global permissions file for ScalyMUCK. \ No newline at end of file +# Global permissions file for ScalyMUCK. +# You should add values based on the documentation given by your modification. + +# These override settings determine whether or not admins, super admins and owners can override certain kinds of permission locks. IE: Can an admin override the ownership check when editing an item? +AllowAdminOverride=Yes +AllowSuperAdminOverride=Yes +AllowOwnerOverride=Yes \ No newline at end of file diff --git a/application/config/settings_server.cfg b/application/config/settings_server.cfg index 71f7ff3..bedfb27 100644 --- a/application/config/settings_server.cfg +++ b/application/config/settings_server.cfg @@ -1,48 +1,51 @@ -# Configuration file for ScalyMUCK Server - -# All you'd ever really need to adjust is this server port. Make sure it's some number above 1024 as on Linux systems, 1024 and below does not work as a -# valid port number. -ServerPort=2000 -ServerAddress=0.0.0.0 - -# The type of database to use. -# Available: mysql, postgresql, sqlite (locally stored) -DatabaseType=sqlite -# Database File Name -or- IP Address. It is a file location when using SQLite but is an IP address when using PostgreSQL/MySQL -# When using MySQL, this is in relation to ~/.scalyMUCK/ and you should just write it there. -TargetDatabase=database.db -# If using MySQL or PostGreSQL, you must specify a username to connect with. -DatabaseUser=Korthos -# Same as above except with a password. -DatabasePassword=FrozenFish -# Again, just with a database name now. -DatabaseName=SomeDatabase - -# This is used when hashing passwords to be stored in the database, it basically tells the software how many times the hashing algorithm should be applied -# before storing the password. With that being said, the higher the value the more computations that must occur and therefore should be adjusted accordingly -# for your specific server. This is used to prevent timing attacks. -WorkFactor=10 - -# Whether or not a connection log should be kept. Pretty useless really except it lists out IP addresses that have been in/out and who they signed in as. -LogConnections=Yes -# Whether or not to log mod data -LogMods=Yes -# Whether or not to log generic server events -LogServer=Yes -# Clear the logs when the server is started? -ClearLogsOnStart=Yes - -# List of mods to load. Separate each specific mod with semicolons ... THESE ARE CASE SENSITIVE. -LoadedMods=scommands - -I_DONT_KNOW_HOW_TO_SECURITY_LET_ME_RUN_AS_ROOT=No - -# Text configuration for the core server -AuthLowArgC=You did not specify all of the required arguments. -AuthInvalidCombination=You have specified an invalid username/password combination. -AuthConnected=%s has connected. -AuthReplaceConnection=Your connection has been replaced. -AuthConnectionReplaced=You boot off an old connection. -AuthReplaceConnectionGlobal=%s replaced their connection. -AuthConnectSuggestion=You must use the "connect" command:\nconnect +# Configuration file for ScalyMUCK Server + +# All you'd ever really need to adjust is this server port. Make sure it's some number above 1024 as on Linux systems, 1024 and below does not work as a +# valid port number. +ServerPort=2000 +ServerAddress=0.0.0.0 + +# The type of database to use. +# Available: mysql, postgresql, sqlite (locally stored) +DatabaseType=sqlite +# Database File Name -or- IP Address. It is a file location when using SQLite but is an IP address when using PostgreSQL/MySQL +# When using SQLite, this is in relation to ~/.scalyMUCK/ and you should just write it there. +TargetDatabase=database.db +# If using MySQL or PostGreSQL, you must specify a username to connect with. +DatabaseUser=Korthos +# Same as above except with a password. +DatabasePassword=FrozenFish +# Again, just with a database name now. +DatabaseName=SomeDatabase + +# This is used when hashing passwords to be stored in the database, it basically tells the software how many times the hashing algorithm should be applied +# before storing the password. With that being said, the higher the value the more computations that must occur and therefore should be adjusted accordingly +# for your specific server. This is used to prevent timing attacks. +WorkFactor=10 + +# Whether or not a connection log should be kept. Pretty useless really except it lists out IP addresses that have been in/out and who they signed in as. +LogConnections=Yes +# Whether or not to log mod data +LogMods=Yes +# Whether or not to log generic server events +LogServer=Yes +# Clear the logs when the server is started? +ClearLogsOnStart=Yes + +# List of mods to load. Separate each specific mod with semicolons ... THESE ARE CASE SENSITIVE. +LoadedMods=scommands + +# For server devs only. +# Debug=Yes + +I_DONT_KNOW_HOW_TO_SECURITY_LET_ME_RUN_AS_ROOT=No + +# Text configuration for the core server +AuthLowArgC=You did not specify all of the required arguments. +AuthInvalidCombination=You have specified an invalid username/password combination. +AuthConnected=%s has connected. +AuthReplaceConnection=Your connection has been replaced. +AuthConnectionReplaced=You boot off an old connection. +AuthReplaceConnectionGlobal=%s replaced their connection. +AuthConnectSuggestion=You must use the "connect" command:\nconnect GameClientDisconnect=%s has disconnected. \ No newline at end of file diff --git a/application/config/welcome_message.txt b/application/config/welcome_message.txt index b576a3e..3b9df30 100644 --- a/application/config/welcome_message.txt +++ b/application/config/welcome_message.txt @@ -1,6 +1,6 @@ -============================================================ -Welcome! You're on a MUCK server running ScalyMUCK -Copyright (c) 2013 Robert MacGregor -You may connect using the 'connect' command: -connect -============================================================ \ No newline at end of file +============================================================ +Welcome! You're on a MUCK server running ScalyMUCK +Copyright (c) 2014 Robert MacGregor +You may connect using the 'connect' command: +connect +============================================================ diff --git a/application/daemon.py b/application/daemon.py index 8015e5f..5254db7 100644 --- a/application/daemon.py +++ b/application/daemon.py @@ -1,118 +1,118 @@ -"""Generic linux daemon base class for python 3.x.""" -import os, sys, signal, time -class Daemon: - """A generic daemon class. Modified by Robert MacGregor - Original code by an Anonymous user from: http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ - Usage: subclass the daemon class and override the run() method.""" - - def __init__(self, pidfile): self.pidfile = pidfile - - def daemonize(self): - """Deamonize class. UNIX double fork mechanism.""" - - try: - pid = os.fork() - if pid > 0: - # exit first parent - sys.exit(0) - except OSError as err: - sys.stderr.write('fork #1 failed: {0}\n'.format(err)) - sys.exit(1) - - # decouple from parent environment - os.chdir('/') - os.setsid() - os.umask(0) - - # do second fork - try: - pid = os.fork() - if pid > 0: - - # exit from second parent - sys.exit(0) - except OSError as err: - sys.stderr.write('fork #2 failed: {0}\n'.format(err)) - sys.exit(1) - - # redirect standard file descriptors - sys.stdout.flush() - sys.stderr.flush() - si = open(os.devnull, 'r') - so = open(os.devnull, 'a+') - se = open(os.devnull, 'a+') - - os.dup2(si.fileno(), sys.stdin.fileno()) - os.dup2(so.fileno(), sys.stdout.fileno()) - os.dup2(se.fileno(), sys.stderr.fileno()) - - # write pidfile - #atexit.register(self.delpid) - - pid = str(os.getpid()) - with open(self.pidfile,'w+') as f: - f.write(pid + '\n') - - def delpid(self): - os.remove(self.pidfile) - - def start(self, **kwargs): - """Start the daemon.""" - # Check for a pidfile to see if the daemon already runs - try: - with open(self.pidfile,'r') as pf: - - pid = int(pf.read().strip()) - except IOError: - pid = None - - if pid: - message = "pidfile {0} already exist. " + \ - "Daemon already running?\n" - sys.stderr.write(message.format(self.pidfile)) - sys.exit(1) - - # Start the daemon - self.daemonize() - self.run(**kwargs) - - def stop(self): - """Stop the daemon.""" - - # Get the pid from the pidfile - try: - with open(self.pidfile,'r') as pf: - pid = int(pf.read().strip()) - except IOError: - pid = None - - if not pid: - message = "pidfile {0} does not exist. " + \ - "Daemon not running?\n" - sys.stderr.write(message.format(self.pidfile)) - return # not an error in a restart - - # Try killing the daemon process - try: - while 1: - os.kill(pid, signal.SIGTERM) - time.sleep(0.1) - except OSError as err: - e = str(err.args) - if e.find("No such process") > 0: - if os.path.exists(self.pidfile): - os.remove(self.pidfile) - else: - print (str(err.args)) - sys.exit(1) - - def restart(self): - """Restart the daemon.""" - self.stop() - self.start() - - def run(self, **kwargs): - """You should override this method when you subclass Daemon. - - It will be called after the process has been daemonized by - start() or restart().""" +"""Generic linux daemon base class for python 3.x.""" +import os, sys, signal, time +class Daemon: + """A generic daemon class. Modified by Robert MacGregor + Original code by an Anonymous user from: http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ + Usage: subclass the daemon class and override the run() method.""" + + def __init__(self, pidfile): self.pidfile = pidfile + + def daemonize(self): + """Deamonize class. UNIX double fork mechanism.""" + + try: + pid = os.fork() + if pid > 0: + # exit first parent + sys.exit(0) + except OSError as err: + sys.stderr.write('fork #1 failed: {0}\n'.format(err)) + sys.exit(1) + + # decouple from parent environment + os.chdir('/') + os.setsid() + os.umask(0) + + # do second fork + try: + pid = os.fork() + if pid > 0: + + # exit from second parent + sys.exit(0) + except OSError as err: + sys.stderr.write('fork #2 failed: {0}\n'.format(err)) + sys.exit(1) + + # redirect standard file descriptors + sys.stdout.flush() + sys.stderr.flush() + si = open(os.devnull, 'r') + so = open(os.devnull, 'a+') + se = open(os.devnull, 'a+') + + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + + # write pidfile + #atexit.register(self.delpid) + + pid = str(os.getpid()) + with open(self.pidfile,'w+') as f: + f.write(pid + '\n') + + def delpid(self): + os.remove(self.pidfile) + + def start(self, **kwargs): + """Start the daemon.""" + # Check for a pidfile to see if the daemon already runs + try: + with open(self.pidfile,'r') as pf: + + pid = int(pf.read().strip()) + except IOError: + pid = None + + if pid: + message = "pidfile {0} already exist. " + \ + "Daemon already running?\n" + sys.stderr.write(message.format(self.pidfile)) + sys.exit(1) + + # Start the daemon + self.daemonize() + self.run(**kwargs) + + def stop(self): + """Stop the daemon.""" + + # Get the pid from the pidfile + try: + with open(self.pidfile,'r') as pf: + pid = int(pf.read().strip()) + except IOError: + pid = None + + if not pid: + message = "pidfile {0} does not exist. " + \ + "Daemon not running?\n" + sys.stderr.write(message.format(self.pidfile)) + return # not an error in a restart + + # Try killing the daemon process + try: + while 1: + os.kill(pid, signal.SIGTERM) + time.sleep(0.1) + except OSError as err: + e = str(err.args) + if e.find("No such process") > 0: + if os.path.exists(self.pidfile): + os.remove(self.pidfile) + else: + print (str(err.args)) + sys.exit(1) + + def restart(self): + """Restart the daemon.""" + self.stop() + self.start() + + def run(self, **kwargs): + """You should override this method when you subclass Daemon. + + It will be called after the process has been daemonized by + start() or restart().""" diff --git a/application/game/__init__.py b/application/game/__init__.py index 0987613..ce7fc1a 100644 --- a/application/game/__init__.py +++ b/application/game/__init__.py @@ -1,15 +1,12 @@ -""" - __init__.py - - Copyright (c) 2013 Robert MacGregor - - This software is licensed under the GNU General - Public License version 3. Please refer to gpl.txt - for more information. -""" - -import exception -import settings -import world -import models -import interface +""" + __init__.py + + This software is licensed under the MIT license. + Please refer to LICENSE.txt for more information. +""" + +import exception +import settings +import world +import models +import interface diff --git a/application/game/exception.py b/application/game/exception.py index bab7393..ce7bf7c 100644 --- a/application/game/exception.py +++ b/application/game/exception.py @@ -1,25 +1,26 @@ -""" - ScalyMUCK has several base exceptions for the ScalyMUCK core and - ScalyMUCK modifications that may be loaded into the MUCK server. - - Copyright (c) 2013 Robert MacGregor - This software is licensed under the GNU General - Public License version 3. Please refer to gpl.txt - for more information. -""" - -class ModApplicationError(Exception): - """ Generic exception for ScalyMUCK modifications to subclass in order - to report errors to the error reporting mechanism. - - NOTE: - This should never be explictely raised by any code. This - is designed to be subclassed for proper exception support. - - """ - -class WorldArgumentError(ModApplicationError): - """ Raised when using the world API and an invalid argument is specified. """ - -class ModelArgumentError(ModApplicationError): - """ Raised when a model function is used improperly. """ +""" + ScalyMUCK has several base exceptions for the ScalyMUCK core and + ScalyMUCK modifications that may be loaded into the MUCK server. + + This software is licensed under the MIT license. + Please refer to LICENSE.txt for more information. +""" + +class ModApplicationError(Exception): + """ Generic exception for ScalyMUCK modifications to subclass in order + to report errors to the error reporting mechanism. + + NOTE: + This should never be explictely raised by any code. This + is designed to be subclassed for proper exception support. + + """ + +class WorldArgumentError(ModApplicationError): + """ Raised when using the world API and an invalid argument is specified. """ + +class ModelArgumentError(ModApplicationError): + """ Raised when a model function is used improperly. """ + +class DatabaseError(ModApplicationError): + """ Raised when an error occurs in the database. """ diff --git a/application/game/interface.py b/application/game/interface.py index 1635835..5b3ea1d 100644 --- a/application/game/interface.py +++ b/application/game/interface.py @@ -1,143 +1,175 @@ -""" - Basically the user interface for ScalyMUCK. - - Copyright (c) 2013 Robert MacGregor - This software is licensed under the GNU General - Public License version 3. Please refer to gpl.txt - for more information. -""" - -import string -import logging -import importlib -import inspect - -from blinker import signal - -import modloader -import permissions -from game import exception, settings - -logger = logging.getLogger('Mods') -class Interface: - """ Client-Server interface class. - - The interface class is exactly how it sounds; it's what the users interact - with directly when connected to the ScalyMUCK server, with an exception being - the login screen for simplicity and security. - - """ - world = None - config = None - session = None - server = None - permissions = None - workdir = '' - modloader = None - - pre_message = signal('pre_message_sent') - post_message = signal('post_message_sent') - - def __init__(self, config=None, world=None, workdir='', session=None, server=None): - """ Initializes an instance of the ScalyMUCK Client-Server interface class. - - The interface class is created internallu by the ScalyMUCK server. - - The server passes in an active instance of game.Settings and game.World - for the interface to talk to when loading mods as the configuration is - used to load relevant config files for the mods and is passed in while - the instance of game.World is assigned to each module so that they may - access the game world. - - Keyword arguments: - config -- An instance of Settings that is to be used to load relevant configuration data. - world -- An instance of the World to pass over to every initialized modification. - workdir -- The current working directory of the application. This should be an absolute path to application/. - session -- A working session object that points to the active database. - server -- The very instance of the ScalyMUCK server. - - """ - self.logger = logging.getLogger('Mods') - self.world = world - self.workdir = workdir - self.config = config - self.session = session - self.server = server - self.permissions = permissions.Permissions(workdir=workdir) - self.modloader = modloader.ModLoader(world=world, interface=self, session=session, workdir=workdir, permissions=self.permissions) - self.modloader.load(config.get_index('LoadedMods', str)) - - def parse_command(self, sender=None, input=None): - """ Called internally by the ScalyMUCK server. - - When a user sends a string of data to the server for processing and have passed - the initial authentification stage, that string of data is passed in here by the - server for processing. This function is what actually performs the command lookups - in the loaded command database. - - Keyword arguments: - sender -- The instance of Player that has trying to invoke a command. - input -- The text that the Player happened to send. - - """ - returns = self.pre_message.send(None, sender=sender, input=input) - intercept_input = False - for set in returns: - if (set[1] is True): - intercept_input = True - break - - data = string.split(input, ' ') - command = string.lower(data[0]) - command_data = self.modloader.find_command(command) - if (intercept_input is False and command_data is not None): - try: - privilege = command_data['privilege'] - if (privilege == 1 and sender.is_admin is False): - sender.send('You must be an administrator.') - return - elif (privilege == 2 and sender.is_sadmin is False): - sender.send('You must be a super administrator.') - return - elif (privilege == 3 and sender.is_owner is False): - sender.send('You must be the owner of the server.') - return - - # You're not trying to do something you shouldn't be? Good. - command_func = command_data['command'] - command_func(sender=sender, input=input[len(command)+1:], arguments=data[1:len(data)]) - except exception.ModApplicationError as e: - line_one = 'An error has occurred while executing the command: %s' % (command) - line_two = 'From modification: %s' % (self.modloader.commands[command]['modification']) - line_three = 'Error Condition: ' - line_four = str(e) - - self.logger.error(line_one) - self.logger.error(line_two) - self.logger.error(line_three) - self.logger.error(line_four) - sender.send(line_one) - sender.send(line_two) - sender.send(line_three) - sender.send(line_four) - sender.send('Please report this incident to your server administrator immediately.') - except StandardError as e: - line_one = 'A critical error has occurred while executing the command: %s' % (command) - line_two = 'From modification: %s' % (self.modloader.commands[command]['modification']) - line_three = 'Error Condition: ' - line_four = str(e) - - self.logger.error(line_one) - self.logger.error(line_two) - self.logger.error(line_three) - self.logger.error(line_four) - sender.send(line_one) - sender.send(line_two) - sender.send(line_three) - sender.send(line_four) - sender.send('Please report this incident to your server administrator immediately.') - - elif (intercept_input is False and command != ''): - sender.send('I do not know what it is to "%s".' % (command)) - - self.post_message.send(None, sender=sender, input=input) +""" + Basically the user interface for ScalyMUCK. + + This software is licensed under the MIT license. + Please refer to LICENSE.txt for more information. +""" + +import string +import logging +import importlib +import inspect + +from blinker import signal + +import modloader +import permissions +from game import exception, settings + +logger = logging.getLogger('Mods') +class Interface: + """ Client-Server interface class. + + The interface class is exactly how it sounds; it's what the users interact + with directly when connected to the ScalyMUCK server, with an exception being + the login screen for simplicity and security. + + """ + world = None + config = None + session = None + server = None + permissions = None + workdir = '' + modloader = None + + pre_message = signal('pre_message_sent') + post_message = signal('post_message_sent') + + def __init__(self, config=None, workdir='', server=None, debug=None): + """ Initializes an instance of the ScalyMUCK Client-Server interface class. + + The interface class is created internallu by the ScalyMUCK server. + + The server passes in an active instance of game.Settings and game.World + for the interface to talk to when loading mods as the configuration is + used to load relevant config files for the mods and is passed in while + the instance of game.World is assigned to each module so that they may + access the game world. + + Keyword arguments: + config -- An instance of Settings that is to be used to load relevant configuration data. + world -- An instance of the World to pass over to every initialized modification. + workdir -- The current working directory of the application. This should be an absolute path to application/. + session -- A working session object that points to the active database. + server -- The very instance of the ScalyMUCK server. + debug -- Whether or not the server is running in debugger mode right now. + + """ + self.logger = logging.getLogger('Mods') + self.permissions = permissions.Permissions(workdir=workdir) + self.modloader = modloader.ModLoader() + self.modloader.load(config.get_index('LoadedMods', str)) + + def initialize(self, **kwargs): + self.debug = kwargs['debug'] + self.server = kwargs['server'] + self.session = kwargs['session'] + self.config = kwargs['config'] + self.workdir = kwargs['workdir'] + self.world = kwargs['world'] + kwargs['interface'] = self + kwargs['permissions'] = self.permissions + kwargs['modloader'] = self.modloader + self.modloader.initialize(**kwargs) + + def get_online_players(self): + """ Returns a list of currently connected players. """ + result = [] + for connection in self.server.established_connection_list: + result.append(self.world.find_player(id=connection.id)) + return result + + + def parse_command(self, sender=None, input=None): + """ Called internally by the ScalyMUCK server. + + When a user sends a string of data to the server for processing and have passed + the initial authentification stage, that string of data is passed in here by the + server for processing. This function is what actually performs the command lookups + in the loaded command database. + + Keyword arguments: + sender -- The instance of Player that has trying to invoke a command. + input -- The text that the Player happened to send. + + """ + returns = self.pre_message.send(None, sender=sender, input=input) + intercept_input = False + for set in returns: + if (set[1] is True): + intercept_input = True + break + + data = string.split(input, ' ') + command = string.lower(data[0]) + command_data = self.modloader.find_command(command) + if (intercept_input is False and command_data is not None and self.debug is False): + try: + privilege = command_data['privilege'] + if (privilege == 1 and sender.is_admin is False): + sender.send('You must be an administrator.') + return + elif (privilege == 2 and sender.is_sadmin is False): + sender.send('You must be a super administrator.') + return + elif (privilege == 3 and sender.is_owner is False): + sender.send('You must be the owner of the server.') + return + + # You're not trying to do something you shouldn't be? Good. + command_func = command_data['command'] + command_func(sender=sender, input=input[len(command)+1:], arguments=data[1:len(data)]) + except exception.ModApplicationError as e: + line_one = 'An error has occurred while executing the command: %s' % (command) + line_two = 'From modification: %s' % (self.modloader.commands[command]['modification']) + line_three = 'Error Condition: ' + line_four = str(e) + + self.logger.error(line_one) + self.logger.error(line_two) + self.logger.error(line_three) + self.logger.error(line_four) + sender.send(line_one) + sender.send(line_two) + sender.send(line_three) + sender.send(line_four) + sender.send('Please report this incident to your server administrator immediately.') + except StandardError as e: + line_one = 'A critical error has occurred while executing the command: %s' % (command) + line_two = 'From modification: %s' % (self.modloader.commands[command]['modification']) + line_three = 'Error Condition: ' + line_four = str(e) + + self.logger.error(line_one) + self.logger.error(line_two) + self.logger.error(line_three) + self.logger.error(line_four) + sender.send(line_one) + sender.send(line_two) + sender.send(line_three) + sender.send(line_four) + sender.send('Please report this incident to your server administrator immediately.') + elif (intercept_input is False and command_data is not None): + privilege = command_data['privilege'] + if (privilege == 1 and sender.is_admin is False): + sender.send('You must be an administrator.') + return + elif (privilege == 2 and sender.is_sadmin is False): + sender.send('You must be a super administrator.') + return + elif (privilege == 3 and sender.is_owner is False): + sender.send('You must be the owner of the server.') + return + + # You're not trying to do something you shouldn't be? Good. + try: + command_func = command_data['command'] + command_func(sender=sender, input=input[len(command)+1:], arguments=data[1:len(data)]) + except exception.DatabaseError: + self.session.rollback() + + elif (intercept_input is False and command != ''): + sender.send('I do not know what it is to "%s".' % (command)) + + self.post_message.send(None, sender=sender, input=input) diff --git a/application/game/models.py b/application/game/models.py index 073cf82..659c6ba 100644 --- a/application/game/models.py +++ b/application/game/models.py @@ -1,687 +1,833 @@ -""" - This is where all of ScalyMUCK's model definitions are located, - save for the modifications that may extend the software in such - a way that it demands for extra models but that is beyond the - point. - - The "base" definitions located in models.py are: - * :class:`Exit` - * :class:`Player` - * :class:`Room` - * :class:`Item` - * :class:`Bot` - - All of the above classes inherit a few functions from :class:`ObjectBase` as well. - - Copyright (c) 2013 Robert MacGregor - This software is licensed under the GNU General - Public License version 3. Please refer to gpl.txt - for more information. -""" - -import string - -from blinker import signal -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import relationship, backref -from sqlalchemy import Table, Column, Integer, String, Text, Boolean, MetaData, ForeignKey -import bcrypt - -import exception - -server = None -world = None -Base = declarative_base() - -class ObjectBase: - """ Base class used for the inheritance of useful member functions that work accross all models. """ - def delete(self): - """ Deletes the object from the world. """ - self.session.delete(self) - self.session.commit() - - def set_name(self, name, commit=True): - """ Sets the name of the object. - - This sets the name of the object that is displayed and used to process requests towards it. - - Keyword arguments: - * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made - by any previous function calls thay may have set this to false. Default: True - - """ - self.name = name - if (type(self) is Player): - self.name = name.lower() - self.display_name = name - - if (commit is True): - self.session.add(self) - self.session.commit() - - def set_location(self, location, commit=True): - """ Sets the current location of this object. - - This sets the location of the object without any prior notification to the person - being moved (if it's a player) nor anyone in the original room or the target room. - That is the calling modification's job to provide any relevant messages. - - Keyword arguments: - * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made - by any previous function calls thay may have set this to false. Default: True - - """ - if (type(self) is Room): - return - - if (type(location) is Room): - self.location = location - self.location_id = location.id - if (commit): self.commit() - elif (type(location) is int): - location = self.session.query(Room).filter_by(id=location).first() - if (location is not None): - self.set_location(location, commit=commit) - - def set_description(self, description, commit=True): - """ Sets the description of this object. - - Sets the description of the calling object instance. - - Keyword arguments: - * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made - by any previous function calls thay may have set this to false. Default: True - - """ - self.description = description - if (commit): self.commit() - - def commit(self): - """ Commits any changes left in RAM to the database. """ - self.session.add(self) - self.session.commit() - - def delete(self): - """ Deletes the object from the world. If it is a :class:`Player` instance, the related - connection is dropped from the server. """ - if (type(self) is Player): - self.disconnect() - - self.session.delete(self) - self.session.commit() - -class Exit(Base, ObjectBase): - """ - - Exits are what the players use to exit and move into other rooms in the ScalyMUCK - world. They may only have one target room ID which is used to assign .target to them - when they are loaded or creates by the game.World instance. - - """ - - __tablename__ = 'exits' - - id = Column(Integer, primary_key=True) - """ This variable is the database number of the Exit. """ - name = Column(String(25)) - """ A short 25 character string that should be used for the name of the Exit. """ - description = Column(String(2000)) - """ A 2000 character string that is used to describe the Exit if it ever happens to be needed. """ - user_enter_message = Column(String(100)) - """ A 100 character string that represents the message displayed to the :class:`Player` using this exit. """ - room_enter_message = Column(String(100)) - """ A 100 character string that represents the message displayed to the user upon entering the target :class:`Room`. """ - user_exit_message = Column(String(100)) - """ A 100 character string that represents the message displayed to the :class:`Player` using this exit. """ - room_exit_message = Column(String(100)) - """ A 100 character string that is displayed to the inhabitants of the :class:`Room` this Exit is located in upon use. """ - target_id = Column(Integer) - """ This variable is the database number of the :class:`Room` that this Exit points to. """ - location_id = Column(Integer, ForeignKey('rooms.id')) - """ This variable is the database number of the :class:`Room` this Exit is in. """ - owner_id = Column(Integer, ForeignKey('players.id')) - """ This variable is the database number of the :class:`Player` this Exit belongs to. """ - - def __init__(self, name, target=None, owner=0): - """ Initializes an instance of the Exit model. - - The Exit is not constructed manually by any of the modifications, this should be - performed by calling the create_player function on the game.World instance provided - to every modification. - - Keyword arguments: - * target -- The ID or instance of a Room that this exit should be linking to. - * owner -- The ID or instance of a Player that this should should belong to. - - """ - if (target is None): - raise exception.ModelArgumentError('No target was specified. (or it was None)') - - # Set the name - self.name=name - if (type(target) is int): - self.target_id = target - else: - self.target_id = target.id - - # Set the owner - if (type(owner) is int): - self.owner_id = owner - else: - self.owner_id = owner.id - - self.user_enter_message = 'You move out.' - self.room_enter_message = 'left.' - self.user_exit_message = 'You arrive in the next room.' - self.room_exit_message = 'arrived from another room.' - self.description = '' - - def __repr__(self): - """ Produces a representation of the exit, as to be expected. """ - return "" % (self.name, self.target_id) - -class Player(Base, ObjectBase): - """ - - Players are well, the players that actually move and interact in the world. They - store their password hash and various other generic data that can be used across - just about anything. - - """ - __tablename__ = 'players' - - id = Column(Integer, primary_key=True) - """ This variable is the database number of the Player. """ - name = Column(String(25)) - """ A short 25 character string representing the name of the Player. It should be all lower case. """ - display_name = Column(String(25)) - """ A short 25 character string representing the name of the Player that is displayed to other users. """ - description = Column(String(2000)) - """ A 2000 character string representing the description of the Player. """ - hash = Column(String(128)) - """ A 128 character string representing the password hash of the Player. """ - - location_id = Column(Integer, ForeignKey('rooms.id')) - """ This variable is the database id of the :class:`Room` this Player is located in. """ - location = None - inventory_id = Column(Integer) - """ This variable is the database id of the :class:`Room` that represents the Player's inventory. """ - - is_admin = Column(Boolean) - """ A boolean representing whether or not this Player is a server administrator. """ - is_sadmin = Column(Boolean) - """ A boolean representing whether or not this Player is a server super administrator. """ - is_owner = Column(Boolean) - """ A boolean representing whether or not this Player is a server owner. """ - - connection = None - world = None - server = None - interface = None - - def __init__(self, name=None, password=None, workfactor=None, location=None, inventory=None, description='', admin=False, sadmin=False, owner=False): - """ Initializes an instance of the Player model. - - The Player is not constructed manually by any of the modifications, this should be - performed by calling the create_player function on the game.World instance provided - to every modification. When the instance is created, it automatically generates a salt - that the Player's password will be hashed with. This salt generation will cause a small - lockup as the calculations are pretty intense depending on the work factor. - - Keyword arguments: - * name -- The name of the Player that should be used. - * password -- The password that should be used for this Player in the database. - * workfactor -- The workfactor that should be used when generating this Player's hash salt. - * location -- An ID or instance of Room that this Player should be created at. - * inventory -- An ID or instance of Room that should represent this Player's inventory. - * description -- A description that is shown when the player is looked at. Default: - * admin -- A boolean representing whether or not the player is an administrator or not. Default: False - * sadmin -- A boolean representing whether or not the player is a super administrator or not. Default: False - * owner -- A boolean representing whether or not the player is the owner of the server. Default: False - - """ - self.name = string.lower(name) - self.display_name = name - self.description = description - self.work_factor = workfactor - - if (type(location) is Room): - location = location.id - self.location_id = location - if (type(inventory) is Room): - inventory = inventory.id - self.inventory_id = inventory - - self.hash = bcrypt.hashpw(password, bcrypt.gensalt(workfactor)) - self.is_admin = admin - self.is_sadmin = sadmin - self.is_owner = owner - - def __repr__(self): - """ Produces a representation of the player, as to be expected. """ - return "" % (self.name, self.description, self.hash, self.location_id) - - def send(self, message): - """ Sends a message to this Player if they are connected. - - This sends a message to the relevant connection if there happens to be one established - for this player object. If there is no active connection for this player, then the message - is simply dropped. - - """ - if (self.connection is None): - self.connection = server.find_connection(self.id) - - if (self.connection is not None): - self.connection.send(message + '\n') - return True - else: - return False - - def set_password(self, password, commit=True): - """ Sets the password of this Player. - - This sets the password of the Player in the database without any notification to the Player. This also - triggers the new password to be hashed with a randomly generated salt which means the current thread - of execution will probably hang for a few seconds unless the work factor is set just right. - - Keyword arguments: - commit -- Determines whether or not this data should be commited immediately. It also includes other changes made - by any previous function calls thay may have set this to false. Default: True - - """ - self.hash = bcrypt.hashpw(password, bcrypt.gensalt(server.work_factor)) - if (commit is True): self.commit() - - def disconnect(self): - """ Drops the Player's connection from the server. - - This drops the user's connection from the game, flushing any messages that - were destined for them before actually booting them out of the server. This triggers - the default disconnect message to be displayed by the ScalyMUCK core to whomever else - may be in the room with the user at the time of their disconnect. - - """ - if (self.connection is not None): - self.connection.socket_send() - self.connection.deactivate() - self.connection.sock.close() - - def set_is_admin(self, status, commit=True): - """ Sets the administrator status of this Player. - - This sets the state as to whether or not the calling player instance is an administrator - of the server or not. This may mean different things to different mods but -generally- it - should just provide basic administrator privileges. The commit parameter is used if you don't - want to dump changes to the database yet; if you're changing multiple properties at once, it - doesn't hit the database as often then. - - Keyword arguments: - * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made - by any previous function calls thay may have set this to false. Default: True - - """ - self.is_admin = status - if (self.is_sadmin is True and status is False): - self.is_sadmin = False - if (commit): self.commit() - - def set_is_super_admin(self, status, commit=True): - """ Sets the super administrator status of this Player. - - This sets the state as to whether or not the calling player instance is a super administrator - of the server or not. This may mean different things to different mods but -generally- it - should provide administrator functionalities more akin to what the owner may have, things that - may break the server if used improperly. The commit parameter is used if you don't want to dump - changes to the database yet; if you're changing multiple properties at once, it doesn't hit the database as - often then. - - Keyword arguments: - * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made - by any previous function calls thay may have set this to false. Default: True - - """ - self.is_sadmin = status - if (self.is_admin is False and status is True): - self.is_admin = True - if (commit): self.commit() - - def check_admin_trump(self, target): - """ Checks whether or not this Player trumps the target Player administratively. - - This returns True when the calling Player instance has greater privilege than that of the target they are being - compared against. It returns false in the opposite situation. - """ - if (type(target) is not Player): - raise exception.ModelArgumentError('Target is not an instance of Player!') - - if (self.is_admin is True and target.is_admin is False): - return True - elif (self.is_sadmin is True and target.is_sadmin is False): - return True - elif (self.is_owner is True and target.is_owner is False): - return True - else: - return False - -class Bot(Base, ObjectBase): - """ - - Bots are basically just the AI's of the game. They're not items, but they're not players - either. They have the special property of being interchangable with player object instances - for the most part except when it comes to certain functions -- such as having a password - hash. - - """ - __tablename__ = 'bots' - - id = Column(Integer, primary_key=True) - """ This variable is the database number of the Bot. """ - name = Column(String(25)) - """ A short 25 character string representing the name of the Bot. It should be all lowercase. """ - description = Column(String(2000)) - """ A 2000 character string representing the description of the Bot. """ - display_name = Column(String(25)) - """ A 25 character string representing the name of the Bot displayed to Players. """ - location_id = Column(Integer, ForeignKey('rooms.id')) - """ This variable is the database number of the :class:`Room` this Bot is located in. """ - - def __init__(self, name=None, description=None, location=None): - """ - - The Bot is not constructed manually by any of the modifications, this should be - performed by calling the create_player function on the game.World instance provided - to every modification. - - """ - self.name = name - self.description = description - self.display_name = name - self.name = name.lower() - - if (type(location) is Room): - location = location.id - self.location_id = location - - def send(self, message): - """ This is basically 'send' like for players except it does NOTHING. """ - -class Item(Base, ObjectBase): - """ Base item model that ScalyMUCK uses. - - Items are really a generic object in ScalyMUCK, they're not players nor rooms nor exits but - serve multiple purposes. They can quite literally be an item, stored in the player's inventory - to be used later (like a potion) or they may be used to decorate rooms with specific objects - such as furniture or they may even be used to represent dead bodies. - - """ - __tablename__ = 'items' - - id = Column(Integer, primary_key=True) - """ This variable is the database number of this Item. """ - name = Column(String(25)) - """ A short 25 character string representing the name of the Item that is displayed to every connection - related to a :class:`Player`. """ - owner_id = Column(Integer, ForeignKey('players.id')) - """ This is the database number of the :class:`Player` that this Item belongs to. """ - description = Column(String(2000)) - """ A 2000 character string representing the description of this Item. """ - location_id = Column(Integer, ForeignKey('rooms.id')) - """ This is the database number of the :class:`Room` that this Item is located in. """ - - def __init__(self, name=None, description='', owner=0): - """ Constructor for the base item model. - - The Item is not constructed manually by any of the modifications, this should be - performed by calling the create_player function on the game.World instance provided - to every modification. - - Keyword arguments: - * name -- The name of the item that should be used. - * description -- The description of the item that should be displayed. Default: - * owner -- Whoever should become the owner of this item, by instance or ID. Default: 0 - - """ - if (name is None): - raise exception.ModelArgumentError('No name specified when attempting to create an instance of Item! (Or it is none)') - - self.name = name - self.description = description - if (type(owner) is int): - self.owner_id = owner - else: - self.owner_id = owner.id - - def __repr__(self): - """ Produces a representation of the item, as to be expected. """ - return "" % (self.name, self.description) - - def set_owner(self, owner): - """ Sets a new owner for this item. """ - if (type(owner) is Player): - owner = owner.id - self.owner_id = owner - self.commit() - -class Room(Base, ObjectBase): - """ Base room model that ScalyMUCK uses. - - Rooms are what make up the world inside of just about any MUCK really. Even if the rooms are not described as such, they still - must be used to contain players, dropped items, bots, etc. - - """ - __tablename__ = 'rooms' - id = Column(Integer, primary_key=True) - """ This is the database number of the Room. """ - - name = Column(String(25)) - """ A short 25 character string representing the name of this Room that is displayed to every connection related to - an instance of :class:`Player`. """ - description = Column(String(2000)) - """ A 2000 character string representing the description of this Room. """ - items = relationship('Item') - """ An array that contains all instances of :class:`Item` contained in this Room. """ - players = relationship('Player') - """ An array that contains all instances of :class:`Player` contained in this Room. """ - bots = relationship('Bot') - """ An array that contains all instances of :class:`Bot` contained in this Room. """ - exits = relationship('Exit') - """ An array that contains all instances of :class:`Exit` contained in this Room. """ - owner_id = Column(Integer) - """ This is the database number of the :class:`Player` that this Room belongs to. """ - - def __init__(self, name=None, description='', owner=0): - """ Initiates an instance of the Room model. - - The Room is not constructed manually by any of the modifications, this should be - performed by calling the create_player function on the game.World instance provided - to every modification. - - Keyword arguments: - * name -- The name of the room that should be used. - * description -- The description of the room. - * owner -- The instance or ID of the relevant Player instance that should own this room. - - """ - if (name is None): - raise exception.ModelArgumentError('The name was not specified. (or it is None)') - - self.name = name - self.description = description - self.exits = [ ] - if (type(owner) is int): - self.owner_id = owner - else: - self.owner_id = owner.id - - def __repr__(self): - """ Produces a representation of the room, as to be expected. """ - return "" % (self.name, self.description) - - def add_exit(self, name=None, target=None, owner=0): - """ Gives this Room instance an Exit linking to anywhere. - - This produces an Exit link from the calling Room instance to another one elsewhere. The database - changes are automatically committed here due to the way the exit has to be created in the database - first for the changes to properly apply without any hackery to occur. - - Keyword arguments: - * name -- The name of the exit that should be used. - * target -- The ID or instance of a Room that this exit should be linking to. - * owner -- The ID or instance of a Player that should become the owner of this Exit. - - """ - if (name is None): - raise exception.ModelArgumentError('The name was not specified. (or it was None)') - elif (target is None): - raise exception.ModelArgumentError('The target room was not specified. (or it was None)') - - if (type(target) is int): - target = world.find_room(id=target) - if (target is not None): - self.add_exit(name, target) - elif (type(target) is Room): - exit = Exit(name, target, owner) - self.exits.append(exit) - self.session.add(self) - self.session.add(exit) - self.session.commit() - - def broadcast(self, message, *exceptions): - """ Broadcasts a message to all inhabitants of the Room except those specified. - - This broadcasts a set message to all inhabitants of the Room except those specified after the message: - some_room.broadcast('Dragons are best!', that_guy, this_guy, other_guy) - - The exceptions may be an ID or an instance. - - """ - for player in self.players: - if (player not in exceptions and player.id not in exceptions): - player.send(message) - - def find_player(self, id=None, name=None): - """ Locates a Player located in the calling instance of Room. - - This is a less computionally intensive version of the world's find_player as there is going to be much less data - to be sorting through since you actually know where the Player is located (otherwise you wouldn't be calling this!) - and all you need is the instance. - - Keyword arguments (one or the other): - * id -- The identification number of the Player to locate inside of this room. This overrides the name if - * both are specified. - * name -- The name of the Player to locate. - - """ - if (id is None and name is None): - raise exception.ModelArgumentError('No arguments specified (or were None)') - - player = None - if (id is not None): - for test_player in self.players: - if (id == test_player.id): - player = test_player - break - elif (name is not None): - name = string.lower(name) - for test_player in self.players: - if (name in test_player.name.lower()): - player = test_player - break - return player - - def find_bot(self, id=None, name=None): - """ Locates a Bot located in the calling instance of Room. - - This is a less computionally intensive version of the world's find_bot as there is going to be much less data - to be sorting through since you actually know where the Bot is located (otherwise you wouldn't be calling this!) - and all you need is the instance. - - Keyword arguments (one or the other): - * id -- The identification number of the Bot to locate inside of this room. This overrides the name if - * both are specified. - * name -- The name of the Bot to locate. - - """ - if (id is None and name is None): - raise exception.ModelArgumentError('No arguments specified (or were None)') - - bot = None - if (id is not None): - for test_bot in self.bots: - if (id == test_bot.id): - bot = test_bot - break - elif (name is not None): - name = string.lower(name) - for test_bot in self.bots: - if (name in test_bot.name.lower()): - bot = test_bot - break - return bot - - def get_exits(self): - """ Return a list of all exits. """ - for exit in self.exits: - exit.location = self - return self.exits - - def find_item(self, id=None, name=None): - """ Locates an Item located in the calling instance of Room. - - This is a less computionally intensive version of the world's find_item as there is going to be much less data - to be sorting through since you actually know where the Item is located (otherwise you wouldn't be calling this!) - and all you need is the instance. - - Keyword arguments (one or the other): - * id -- The identification number of the Item to locate inside of this room. This overrides the name if - * both are specified. - * name -- The name of the Item to locate. - - """ - if (id is None and name is None): - raise exception.ModelArgumentError('No arguments specified (or were None)') - - item = None - if (id is not None): - for test_item in self.items: - if (test_item.id == id): - item = test_item - break - elif (name is not None): - name = string.lower(name) - for test_item in self.items: - if (name in test_item.name.lower()): - item = test_item - break - return item - - def find_exit(self, id=None, name=None): - """ Locates an Item located in the calling instance of Room. - - This is a less computionally intensive version of the world's find_item as there is going to be much less data - to be sorting through since you actually know where the Item is located (otherwise you wouldn't be calling this!) - and all you need is the instance. - - Keyword arguments (one or the other): - * id -- The identification number of the Item to locate inside of this room. This overrides the name if - * both are specified. - * name -- The name of the Item to locate. - - """ - if (id is None and name is None): - raise exception.ModelArgumentError('No arguments specified (or were None)') - - exit = None - if (id is not None): - for test_exit in self.exits: - if (test_exit.id == id): - exit = test_exit - break - elif (name is not None): - name = string.lower(name) - for test_exit in self.exits: - if (name in test_exit.name.lower()): - exit = test_exit - break - return exit +""" + This is where all of ScalyMUCK's model definitions are located, + save for the modifications that may extend the software in such + a way that it demands for extra models but that is beyond the + point. + + The "base" definitions located in models.py are: + * :class:`Exit` + * :class:`Player` + * :class:`Room` + * :class:`Item` + * :class:`Bot` + + All of the above classes inherit a few functions from :class:`ObjectBase` as well. + + This software is licensed under the MIT license. + Please refer to LICENSE.txt for more information. +""" + +import string + +from blinker import signal +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship, backref, Session +from sqlalchemy.exc import OperationalError, InvalidRequestError, StatementError +from sqlalchemy import Table, Column, Integer, String, Text, Boolean, MetaData, ForeignKey +import bcrypt + +import exception + +server = None +world = None +Base = declarative_base() +database_status = signal('database_status') + +class ObjectBase: + """ Base class used for the inheritance of useful member functions that work accross all models. """ + def delete(self): + """ Deletes the object from the world. """ + connection = self.connect() + try: + self.session.delete(self) + self.session.commit() + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def set_name(self, name, commit=True): + """ Sets the name of the object. + + This sets the name of the object that is displayed and used to process requests towards it. + + Keyword arguments: + * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made + by any previous function calls thay may have set this to false. Default: True + + """ + self.name = name + if (type(self) is Player): + self.name = name.lower() + self.display_name = name + + try: + if (commit is True): self.commit() + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def set_location(self, location, commit=True): + """ Sets the current location of this object. + + This sets the location of the object without any prior notification to the person + being moved (if it's a player) nor anyone in the original room or the target room. + That is the calling modification's job to provide any relevant messages. + + Keyword arguments: + * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made + by any previous function calls thay may have set this to false. Default: True + + """ + if (type(self) is Room): + return + + try: + if (type(location) is Room): + self.location = location + self.location_id = location.id + if (commit): self.commit() + elif (type(location) is int): + location = self.session.query(Room).filter_by(id=location).first() + if (location is not None): + self.set_location(location, commit=commit) + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def set_description(self, description, commit=True): + """ Sets the description of this object. + + Sets the description of the calling object instance. + + Keyword arguments: + * commit -- Determines whether or not this data should be committed immediately. It also includes other changes made + by any previous function calls thay may have set this to false. Default: True + + """ + self.description = description + try: + if (commit): self.commit() + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def commit(self): + """ Commits any changes left in RAM to the database. """ + connection = self.connect() + try: + self.session.add(self) + self.session.commit() + connection.close() + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def delete(self): + """ Deletes the object from the world. If it is a :class:`Player` instance, the related + connection is dropped from the server. """ + if (type(self) is Player): + self.disconnect() + + connection = self.connect() + try: + self.session.delete(self) + self.session.commit() + connection.close() + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def connect(self): + """ Establishes a connection to the database server. """ + return world.connect() + +class Exit(Base, ObjectBase): + """ + + Exits are what the players use to exit and move into other rooms in the ScalyMUCK + world. They may only have one target room ID which is used to assign .target to them + when they are loaded or creates by the game.World instance. + + """ + + __tablename__ = 'exits' + + id = Column(Integer, primary_key=True) + """ This variable is the database number of the Exit. """ + name = Column(String(25)) + """ A short 25 character string that should be used for the name of the Exit. """ + description = Column(String(2000)) + """ A 2000 character string that is used to describe the Exit if it ever happens to be needed. """ + user_enter_message = Column(String(100)) + """ A 100 character string that represents the message displayed to the :class:`Player` using this exit. """ + room_enter_message = Column(String(100)) + """ A 100 character string that represents the message displayed to the user upon entering the target :class:`Room`. """ + user_exit_message = Column(String(100)) + """ A 100 character string that represents the message displayed to the :class:`Player` using this exit. """ + room_exit_message = Column(String(100)) + """ A 100 character string that is displayed to the inhabitants of the :class:`Room` this Exit is located in upon use. """ + target_id = Column(Integer) + """ This variable is the database number of the :class:`Room` that this Exit points to. """ + location_id = Column(Integer, ForeignKey('rooms.id')) + """ This variable is the database number of the :class:`Room` this Exit is in. """ + owner_id = Column(Integer, ForeignKey('players.id')) + """ This variable is the database number of the :class:`Player` this Exit belongs to. """ + + def __init__(self, name, target=None, owner=0): + """ Initializes an instance of the Exit model. + + The Exit is not constructed manually by any of the modifications, this should be + performed by calling the create_player function on the game.World instance provided + to every modification. + + Keyword arguments: + * target -- The ID or instance of a Room that this exit should be linking to. + * owner -- The ID or instance of a Player that this should should belong to. + + """ + if (target is None): + raise exception.ModelArgumentError('No target was specified. (or it was None)') + + # Set the name + self.name=name + if (type(target) is int): + self.target_id = target + else: + self.target_id = target.id + + # Set the owner + if (type(owner) is int): + self.owner_id = owner + else: + self.owner_id = owner.id + + self.user_enter_message = 'You move out.' + self.room_enter_message = 'left.' + self.user_exit_message = 'You arrive in the next room.' + self.room_exit_message = 'arrived from another room.' + self.description = '' + + def __repr__(self): + """ Produces a representation of the exit, as to be expected. """ + return "" % (self.name, self.target_id) + + def set_owner(self, owner, commit=True): + """ Sets the owner of this Room by either an ID or an instance of :class:`Player`. + + Keyword arguments: + * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made + by any previous function calls thay may have set this to false. Default: True + + """ + if (owner is None): + raise exception.ModelArgumentError('No arguments specified (or were None)') + + if (type(owner) is Player): + owner = owner.id + + self.owner_id = owner + try: + if (commit): self.commit() + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + +class Player(Base, ObjectBase): + """ + + Players are well, the players that actually move and interact in the world. They + store their password hash and various other generic data that can be used across + just about anything. + + """ + __tablename__ = 'players' + + id = Column(Integer, primary_key=True) + """ This variable is the database number of the Player. """ + name = Column(String(25)) + """ A short 25 character string representing the name of the Player. It should be all lower case. """ + display_name = Column(String(25)) + """ A short 25 character string representing the name of the Player that is displayed to other users. """ + description = Column(String(2000)) + """ A 2000 character string representing the description of the Player. """ + hash = Column(String(128)) + """ A 128 character string representing the password hash of the Player. """ + + location_id = Column(Integer, ForeignKey('rooms.id')) + """ This variable is the database id of the :class:`Room` this Player is located in. """ + inventory_id = Column(Integer) + """ This variable is the database id of the :class:`Room` that represents the Player's inventory. """ + + is_admin = Column(Boolean) + """ A boolean representing whether or not this Player is a server administrator. """ + is_sadmin = Column(Boolean) + """ A boolean representing whether or not this Player is a server super administrator. """ + is_owner = Column(Boolean) + """ A boolean representing whether or not this Player is a server owner. """ + + location = None + """ This variable is the :class:`Room` instance that `location_id` corresponds to. """ + inventory = None + """ This variable is the :class:`Room` instance that `inventory_id` corresponds to. """ + + connection = None + world = None + server = None + interface = None + + def __init__(self, name=None, password=None, workfactor=None, location=None, inventory=None, description='', admin=False, sadmin=False, owner=False): + """ Initializes an instance of the Player model. + + The Player is not constructed manually by any of the modifications, this should be + performed by calling the create_player function on the game.World instance provided + to every modification. When the instance is created, it automatically generates a salt + that the Player's password will be hashed with. This salt generation will cause a small + lockup as the calculations are pretty intense depending on the work factor. + + Keyword arguments: + * name -- The name of the Player that should be used. + * password -- The password that should be used for this Player in the database. + * workfactor -- The workfactor that should be used when generating this Player's hash salt. + * location -- An ID or instance of Room that this Player should be created at. + * inventory -- An ID or instance of Room that should represent this Player's inventory. + * description -- A description that is shown when the player is looked at. Default: + * admin -- A boolean representing whether or not the player is an administrator or not. Default: False + * sadmin -- A boolean representing whether or not the player is a super administrator or not. Default: False + * owner -- A boolean representing whether or not the player is the owner of the server. Default: False + + """ + self.name = string.lower(name) + self.display_name = name + self.description = description + self.work_factor = workfactor + + if (type(location) is Room): + location = location.id + self.location_id = location + if (type(inventory) is Room): + inventory = inventory.id + self.inventory_id = inventory + + self.hash = bcrypt.hashpw(password, bcrypt.gensalt(workfactor)) + self.is_admin = admin + self.is_sadmin = sadmin + self.is_owner = owner + + def __repr__(self): + """ Produces a representation of the player, as to be expected. """ + return "" % (self.name, self.description, self.hash, self.location_id) + + def send(self, message): + """ Sends a message to this Player if they are connected. + + This sends a message to the relevant connection if there happens to be one established + for this player object. If there is no active connection for this player, then the message + is simply dropped. + + """ + if (self.connection is None): + self.connection = server.find_connection(self.id) + + if (self.connection is not None): + self.connection.send(message + '\n') + return True + else: + return False + + def set_password(self, password, commit=True): + """ Sets the password of this Player. + + This sets the password of the Player in the database without any notification to the Player. This also + triggers the new password to be hashed with a randomly generated salt which means the current thread + of execution will probably hang for a few seconds unless the work factor is set just right. + + Keyword arguments: + commit -- Determines whether or not this data should be commited immediately. It also includes other changes made + by any previous function calls thay may have set this to false. Default: True + + """ + self.hash = bcrypt.hashpw(password, bcrypt.gensalt(server.work_factor)) + try: + if (commit is True): self.commit() + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def disconnect(self): + """ Drops the Player's connection from the server. + + This drops the user's connection from the game, flushing any messages that + were destined for them before actually booting them out of the server. This triggers + the default disconnect message to be displayed by the ScalyMUCK core to whomever else + may be in the room with the user at the time of their disconnect. + + """ + if (self.connection is not None): + self.connection.socket_send() + self.connection.deactivate() + self.connection.sock.close() + + def set_is_admin(self, status, commit=True): + """ Sets the administrator status of this Player. + + This sets the state as to whether or not the calling player instance is an administrator + of the server or not. This may mean different things to different mods but -generally- it + should just provide basic administrator privileges. The commit parameter is used if you don't + want to dump changes to the database yet; if you're changing multiple properties at once, it + doesn't hit the database as often then. + + Keyword arguments: + * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made + by any previous function calls thay may have set this to false. Default: True + + """ + self.is_admin = status + if (self.is_sadmin is True and status is False): + self.is_sadmin = False + try: + if (commit): self.commit() + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def set_is_super_admin(self, status, commit=True): + """ Sets the super administrator status of this Player. + + This sets the state as to whether or not the calling player instance is a super administrator + of the server or not. This may mean different things to different mods but -generally- it + should provide administrator functionalities more akin to what the owner may have, things that + may break the server if used improperly. The commit parameter is used if you don't want to dump + changes to the database yet; if you're changing multiple properties at once, it doesn't hit the database as + often then. + + Keyword arguments: + * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made + by any previous function calls thay may have set this to false. Default: True + + """ + self.is_sadmin = status + if (self.is_admin is False and status is True): + self.is_admin = True + try: + if (commit): self.commit() + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def check_admin_trump(self, target): + """ Checks whether or not this Player trumps the target Player administratively. + + This returns True when the calling Player instance has greater privilege than that of the target they are being + compared against. It returns false in the opposite situation. + """ + if (type(target) is not Player): + raise exception.ModelArgumentError('Target is not an instance of Player!') + + if (self.is_admin is True and target.is_admin is False): + return True + elif (self.is_sadmin is True and target.is_sadmin is False): + return True + elif (self.is_owner is True and target.is_owner is False): + return True + else: + return False + + def set_location(self, room, commit=True): + """ Sets the location of this :class:`Player` instance. + + Keyword arguments: + * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made + by any previous function calls thay may have set this to false. Default: True + + """ + super(Player, self).set_location(room, commit) + +class Bot(Base, ObjectBase): + """ + + Bots are basically just the AI's of the game. They're not items, but they're not players + either. They have the special property of being interchangable with player object instances + for the most part except when it comes to certain functions -- such as having a password + hash. + + """ + __tablename__ = 'bots' + + id = Column(Integer, primary_key=True) + """ This variable is the database number of the Bot. """ + name = Column(String(25)) + """ A short 25 character string representing the name of the Bot. It should be all lowercase. """ + description = Column(String(2000)) + """ A 2000 character string representing the description of the Bot. """ + display_name = Column(String(25)) + """ A 25 character string representing the name of the Bot displayed to Players. """ + location_id = Column(Integer, ForeignKey('rooms.id')) + """ This variable is the database number of the :class:`Room` this Bot is located in. """ + + location = None + """ This variable is the :class:`Room` instance that `location_id` corresponds to. """ + + def __init__(self, name=None, description=None, location=None): + """ + + The Bot is not constructed manually by any of the modifications, this should be + performed by calling the create_player function on the game.World instance provided + to every modification. + + """ + self.name = name + self.description = description + self.display_name = name + self.name = name.lower() + + if (type(location) is Room): + location = location.id + self.location_id = location + + def send(self, message): + """ This is basically 'send' like for players except it does NOTHING. """ + + def set_location(self, room, commit=True): + """ Sets the location of this :class:`Bot` instance. + + Keyword arguments: + * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made + by any previous function calls thay may have set this to false. Default: True + + """ + super(Bot, self).set_location(room, commit) + +class Item(Base, ObjectBase): + """ Base item model that ScalyMUCK uses. + + Items are really a generic object in ScalyMUCK, they're not players nor rooms nor exits but + serve multiple purposes. They can quite literally be an item, stored in the player's inventory + to be used later (like a potion) or they may be used to decorate rooms with specific objects + such as furniture or they may even be used to represent dead bodies. + + """ + __tablename__ = 'items' + + id = Column(Integer, primary_key=True) + """ This variable is the database number of this Item. """ + name = Column(String(25)) + """ A short 25 character string representing the name of the Item that is displayed to every connection + related to a :class:`Player`. """ + owner_id = Column(Integer, ForeignKey('players.id')) + """ This is the database number of the :class:`Player` that this Item belongs to. """ + description = Column(String(2000)) + """ A 2000 character string representing the description of this Item. """ + location_id = Column(Integer, ForeignKey('rooms.id')) + """ This is the database number of the :class:`Room` that this Item is located in. """ + + location = None + """ This variable is the :class:`Room` instance that `location_id` corresponds to. """ + owner = None + """ This variable is the :class:`Player` instance that `owner_id` corresponds to. """ + + def __init__(self, name=None, description='', owner=0): + """ Constructor for the base item model. + + The Item is not constructed manually by any of the modifications, this should be + performed by calling the create_player function on the game.World instance provided + to every modification. + + Keyword arguments: + * name -- The name of the item that should be used. + * description -- The description of the item that should be displayed. Default: + * owner -- Whoever should become the owner of this item, by instance or ID. Default: 0 + + """ + if (name is None): + raise exception.ModelArgumentError('No name specified when attempting to create an instance of Item! (Or it is none)') + + self.name = name + self.description = description + if (type(owner) is int): + self.owner_id = owner + else: + self.owner_id = owner.id + + def __repr__(self): + """ Produces a representation of the item, as to be expected. """ + return "" % (self.name, self.description) + + def set_owner(self, owner, commit=True): + """ Sets a new owner for this item. + + Keyword arguments: + * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made + by any previous function calls thay may have set this to false. Default: True + + """ + if (type(owner) is Player): + owner = owner.id + self.owner_id = owner + try: + if (commit): self.commit() + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def set_location(self, room, commit=True): + """ Sets the location of this :class:`Item` instance. + + Keyword arguments: + * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made + by any previous function calls thay may have set this to false. Default: True + + """ + super(Item, self).set_location(room, commit) + +class Room(Base, ObjectBase): + """ Base room model that ScalyMUCK uses. + + Rooms are what make up the world inside of just about any MUCK really. Even if the rooms are not described as such, they still + must be used to contain players, dropped items, bots, etc. + + """ + __tablename__ = 'rooms' + id = Column(Integer, primary_key=True) + """ This is the database number of the Room. """ + + name = Column(String(25)) + """ A short 25 character string representing the name of this Room that is displayed to every connection related to + an instance of :class:`Player`. """ + description = Column(String(2000)) + """ A 2000 character string representing the description of this Room. """ + items = relationship('Item') + """ An array that contains all instances of :class:`Item` contained in this Room. """ + players = relationship('Player') + """ An array that contains all instances of :class:`Player` contained in this Room. """ + bots = relationship('Bot') + """ An array that contains all instances of :class:`Bot` contained in this Room. """ + exits = relationship('Exit') + """ An array that contains all instances of :class:`Exit` contained in this Room. """ + owner_id = Column(Integer) + """ This is the database number of the :class:`Player` that this Room belongs to. """ + + owner=None + """ This is the :class:`Player` instance that this Room belongs to. """ + + def __init__(self, name=None, description='', owner=0): + """ Initiates an instance of the Room model. + + The Room is not constructed manually by any of the modifications, this should be + performed by calling the create_player function on the game.World instance provided + to every modification. + + Keyword arguments: + * name -- The name of the room that should be used. + * description -- The description of the room. + * owner -- The instance or ID of the relevant Player instance that should own this room. + + """ + if (name is None): + raise exception.ModelArgumentError('The name was not specified. (or it is None)') + + self.name = name + self.description = description + self.exits = [ ] + if (type(owner) is int): + self.owner_id = owner + else: + self.owner_id = owner.id + + def __repr__(self): + """ Produces a representation of the room, as to be expected. """ + return "" % (self.name, self.description) + + def add_exit(self, name=None, target=None, owner=0): + """ Gives this Room instance an Exit linking to anywhere. + + This produces an Exit link from the calling Room instance to another one elsewhere. The database + changes are automatically committed here due to the way the exit has to be created in the database + first for the changes to properly apply without any hackery to occur. + + Keyword arguments: + * name -- The name of the exit that should be used. + * target -- The ID or instance of a Room that this exit should be linking to. + * owner -- The ID or instance of a Player that should become the owner of this Exit. + + """ + if (name is None): + raise exception.ModelArgumentError('The name was not specified. (or it was None)') + elif (target is None): + raise exception.ModelArgumentError('The target room was not specified. (or it was None)') + + try: + if (type(target) is int): + target = world.find_room(id=target) + if (target is not None): + self.add_exit(name, target) + elif (type(target) is Room): + exit = Exit(name, target, owner) + self.exits.append(exit) + connection = self.engine.connect() + self.session.add(self) + self.session.add(exit) + self.session.commit() + connection.close() + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def broadcast(self, message, *exceptions): + """ Broadcasts a message to all inhabitants of the Room except those specified. + + This broadcasts a set message to all inhabitants of the Room except those specified after the message: + some_room.broadcast('Dragons are best!', that_guy, this_guy, other_guy) + + The exceptions may be an ID or an instance. + + """ + for player in self.players: + if (player not in exceptions and player.id not in exceptions): + player.send(message) + + def find_player(self, id=None, name=None): + """ Locates a Player located in the calling instance of Room. + + This is a less computionally intensive version of the world's find_player as there is going to be much less data + to be sorting through since you actually know where the Player is located (otherwise you wouldn't be calling this!) + and all you need is the instance. + + Keyword arguments (one or the other): + * id -- The identification number of the Player to locate inside of this room. This overrides the name if + * both are specified. + * name -- The name of the Player to locate. + + """ + if (id is None and name is None): + raise exception.ModelArgumentError('No arguments specified (or were None)') + + player = None + if (id is not None): + for test_player in self.players: + if (id == test_player.id): + player = test_player + break + elif (name is not None): + name = string.lower(name) + for test_player in self.players: + if (name in test_player.name.lower()): + player = test_player + break + return player + + def find_bot(self, id=None, name=None): + """ Locates a Bot located in the calling instance of Room. + + This is a less computionally intensive version of the world's find_bot as there is going to be much less data + to be sorting through since you actually know where the Bot is located (otherwise you wouldn't be calling this!) + and all you need is the instance. + + Keyword arguments (one or the other): + * id -- The identification number of the Bot to locate inside of this room. This overrides the name if + * both are specified. + * name -- The name of the Bot to locate. + + """ + if (id is None and name is None): + raise exception.ModelArgumentError('No arguments specified (or were None)') + + bot = None + if (id is not None): + for test_bot in self.bots: + if (id == test_bot.id): + bot = test_bot + break + elif (name is not None): + name = string.lower(name) + for test_bot in self.bots: + if (name in test_bot.name.lower()): + bot = test_bot + break + return bot + + def get_exits(self): + """ Return a list of all exits. """ + for exit in self.exits: + exit.location = self + return self.exits + + def find_item(self, id=None, name=None): + """ Locates an Item located in the calling instance of Room. + + This is a less computionally intensive version of the world's find_item as there is going to be much less data + to be sorting through since you actually know where the Item is located (otherwise you wouldn't be calling this!) + and all you need is the instance. + + Keyword arguments (one or the other): + * id -- The identification number of the Item to locate inside of this room. This overrides the name if + * both are specified. + * name -- The name of the Item to locate. + + """ + if (id is None and name is None): + raise exception.ModelArgumentError('No arguments specified (or were None)') + + item = None + if (id is not None): + for test_item in self.items: + if (test_item.id == id): + item = test_item + break + elif (name is not None): + name = string.lower(name) + for test_item in self.items: + if (name in test_item.name.lower()): + item = test_item + break + return item + + def find_exit(self, id=None, name=None): + """ Locates an Item located in the calling instance of Room. + + This is a less computionally intensive version of the world's find_item as there is going to be much less data + to be sorting through since you actually know where the Item is located (otherwise you wouldn't be calling this!) + and all you need is the instance. + + Keyword arguments (one or the other): + * id -- The identification number of the Item to locate inside of this room. This overrides the name if + * both are specified. + * name -- The name of the Item to locate. + + """ + if (id is None and name is None): + raise exception.ModelArgumentError('No arguments specified (or were None)') + + exit = None + if (id is not None): + for test_exit in self.exits: + if (test_exit.id == id): + exit = test_exit + break + elif (name is not None): + name = string.lower(name) + for test_exit in self.exits: + if (name in test_exit.name.lower()): + exit = test_exit + break + return exit + + def set_owner(self, owner=None, commit=True): + """ Sets the owner of this Room by either an ID or an instance of :class:`Player`. + + Keyword arguments: + * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made + by any previous function calls thay may have set this to false. Default: True + + """ + if (owner is None): + raise exception.ModelArgumentError('No arguments specified (or were None)') + + if (type(owner) is Player): + owner = owner.id + + self.owner_id = owner + try: + if (commit): self.commit() + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) diff --git a/application/game/modloader.py b/application/game/modloader.py index 478e206..a9e575e 100644 --- a/application/game/modloader.py +++ b/application/game/modloader.py @@ -1,177 +1,178 @@ -""" - Modification loader class code for ScalyMUCK. This class - is used to store and track loaded modifications that - are in use by the server application - - Copyright (c) 2013 Robert MacGregor - This software is licensed under the GNU General - Public License version 3. Please refer to gpl.txt - for more information. -""" - -import logging -import inspect -import importlib - -import settings - -logger = logging.getLogger('Mods') -class ModLoader: - """ Mod loading class for ScalyMUCK. """ - workdir = None - world = None - interface = None - session = None - permissions = None - commands = { } - """ A dictionary of all loaded commands. The keys are the actual name a command is referred to by and said keys point to - Python functions to be called upon use. """ - modifications = { } - """ A dictionary of all loaded modifications. The keys are the internal name of the mod and each key refers to a tuple - with the following format: (instance, module). """ - - def __init__(self, world=None, interface=None, session=None, workdir=None, permissions=None): - """ Initializes an instance of the ScalyMUCK mod loader. - - There are several keyword arguments that should be used with this __init__ command: - * world -- An instance of the game.World object to be used with this ModLoader. - * interface -- An instance of the game.Interface object to be used with this ModLoader. - * session -- An active database session from SQLAlchemy to be used with this ModLoader. - * workdir -- The work directory of the current running application. - * permissions -- An instance of the game.Permissions object to be used with this ModLoader. - - """ - self.workdir = workdir - self.world = world - self.interface = interface - self.session = session - self.permissions = permissions - self.set_defaults() - - def load(self, modifications): - """ Loads a semicolon deliminated list of modifications from application/game. - - If any of the specified modifications happen to already be loaded into ScalyMUCK, - they are merely reloaded so that any changes made since the last load will be applied - and take affect. - - NOTE: - If there happens to be a low-level Python error (IE: SyntaxError) then the server - application will merely crash as of the moment. Same goes for any internal errors in a mod - code base that happen to raise an exception that is not derived from one of the exception - classes in game.Exception. - - """ - for mod_name in modifications.split(';'): - mod_name = mod_name.lower() - if (mod_name in self.modifications): - # Reloads the modification - modification, module = self.modifications[mod_name] - modules = inspect.getmembers(module, inspect.ismodule) - for name, sub_module in modules: - reload(sub_module) - module = reload(module) - config = settings.Settings('%s/config/%s.cfg' % (self.workdir, mod_name)) - instance = module.Modification(config=config, world=self.world, interface=self.interface, session=self.session, permissions=self.permissions, modloader=self) - self.modifications[mod_name] = (instance, module) - else: - try: - module = importlib.import_module('game.%s' % (mod_name)) - except ImportError as e: - self.logger.warning(str(e)) - return False - else: - config = settings.Settings('%s/config/%s.cfg' % (self.workdir, mod_name)) - - modification = module.Modification(config=config, world=self.world, interface=self.interface, session=self.session, permissions=self.permissions, modloader=self) - self.modifications.setdefault(mod_name, (modification, module)) - logger.info('Processed modification %s.' % (mod_name)) - - # Process aliases first - aliases = { } - commands = modification.get_commands() - for command in commands: - for alias in commands[command]['aliases']: - aliases.setdefault(alias, commands[command]) - commands.update(aliases) - # Then process the dictionary - for command in commands: - commands[command]['modification'] = mod_name - if (command in self.commands): - logger.warn('Overlapping command definitions for command "%s"! %s -> %s' % (command, mod_name, self.commands[command]['modification'])) - self.commands.update(commands) - self.set_defaults() - - return True - - def set_defaults(self): - """ This command merely makes sure that the core commands are loaded. - - It should be called everytime a modification is loaded in order to guarantee - that a modification doesn't attempt to overwrite the core commands, even though - by standard they shouldn't be defining these commands in the first place. - - """ - self.commands['mods'] = { } - self.commands['mods']['command'] = self.command_mods - self.commands['mods']['description'] = 'Lists all loaded mods.' - self.commands['mods']['usage'] = 'mods' - self.commands['mods']['privilege'] = 3 - self.commands['mods']['modification'] = '' - - self.commands['load'] = { } - self.commands['load']['command'] = self.command_load - self.commands['load']['description'] = 'Loads the specified mod.' - self.commands['load']['usage'] = 'load ' - self.commands['load']['privilege'] = 3 - self.commands['load']['modification'] = '' - - self.commands['unload'] = { } - self.commands['unload']['command'] = self.command_unload - self.commands['unload']['description'] = 'Unloads the specified mod.' - self.commands['unload']['usage'] = 'unload ' - self.commands['unload']['privilege'] = 3 - self.commands['unload']['modification'] = '' - - def find_command(self, name): - """ Returns a command by name. """ - if (name not in self.commands): - return None - else: - return self.commands[name] - - # CORE Commands - def command_mods(self, **kwargs): - """ Internal command to list installed mods. """ - sender = kwargs['sender'] - loaded = '' - sender.send('Loaded modifications: ') - for key in self.modifications.keys(): - loaded += '%s, ' % (key) - sender.send(loaded.rstrip(', ')) - - def command_load(self, **kwargs): - """ Internal command to load mods. """ - sender = kwargs['sender'] - input = kwargs['input'].lower() - if (input.strip() == ' '): - sender.send('No input.') - - if (self.load(input) is False): - sender.send('Failed to load mod "%s".' % (input)) - else: - sender.send('Loaded mod "%s".' % (input)) - - def command_unload(self, **kwargs): - """ Internal command to unload mods. """ - sender = kwargs['sender'] - input = kwargs['input'].lower() - if (input in self.modifications): - instance, module = self.modifications[input] - self.modifications.pop(input) - commands = instance.get_commands() - for command in commands.keys(): - self.commands.pop(command) - sender.send('Mod "%s" unloaded.' % (input)) - else: - sender.send('Unknown modification.') +""" + Modification loader class code for ScalyMUCK. This class + is used to store and track loaded modifications that + are in use by the server application + + This software is licensed under the MIT license. + Please refer to LICENSE.txt for more information. +""" + +import logging +import inspect +import importlib + +import settings + +logger = logging.getLogger('Mods') +class ModLoader: + """ Mod loading class for ScalyMUCK. """ + workdir = None + world = None + interface = None + session = None + permissions = None + initialized = False + commands = { } + """ A dictionary of all loaded commands. The keys are the actual name a command is referred to by and said keys point to + Python functions to be called upon use. """ + modifications = { } + """ A dictionary of all loaded modifications. The keys are the internal name of the mod and each key refers to a tuple + with the following format: (instance, module). """ + + def initialize(self, **kwargs): + self.workdir = kwargs['workdir'] + self.world = kwargs['world'] + self.interface = kwargs['interface'] + self.session = kwargs['session'] + self.permissions = kwargs['permissions'] + self.set_defaults() + + for modification_name in self.modifications.keys(): + modification_instance, module = self.modifications[modification_name] + modification_instance.initialize(**kwargs) + + self.initialized = True + + def load(self, modifications): + """ Loads a semicolon deliminated list of modifications from application/game. + + If any of the specified modifications happen to already be loaded into ScalyMUCK, + they are merely reloaded so that any changes made since the last load will be applied + and take affect. + + NOTE: + If there happens to be a low-level Python error (IE: SyntaxError) then the server + application will merely crash as of the moment. Same goes for any internal errors in a mod + code base that happen to raise an exception that is not derived from one of the exception + classes in game.Exception. + + """ + for mod_name in modifications.split(';'): + mod_name = mod_name.lower() + if (mod_name in self.modifications): + # Reloads the modification + modification, module = self.modifications[mod_name] + modules = inspect.getmembers(module, inspect.ismodule) + for name, sub_module in modules: + reload(sub_module) + module = reload(module) + config = settings.Settings('%s/config/%s.cfg' % (self.workdir, mod_name)) + instance = module.Modification() + self.modifications[mod_name] = (instance, module) + else: + try: + module = importlib.import_module('game.%s' % (mod_name)) + except ImportError as e: + logger.warning(str(e)) + return False + else: + config = settings.Settings('%s/config/%s.cfg' % (self.workdir, mod_name)) + + modification = module.Modification() + if (self.initialized): + modification.initialize(world=self.world, config=config, session=self.session, permissions=self.permissions, interface=self.interface, modloader=self) + self.modifications.setdefault(mod_name, (modification, module)) + + logger.info('Processed modification %s.' % (mod_name)) + + # Process aliases first + aliases = { } + commands = modification.get_commands() + for command in commands: + for alias in commands[command]['aliases']: + aliases.setdefault(alias, commands[command]) + commands.update(aliases) + # Then process the dictionary + for command in commands: + commands[command]['modification'] = mod_name + if (command in self.commands): + logger.warn('Overlapping command definitions for command "%s"! %s -> %s' % (command, mod_name, self.commands[command]['modification'])) + self.commands.update(commands) + self.set_defaults() + + return True + + def set_defaults(self): + """ This command merely makes sure that the core commands are loaded. + + It should be called everytime a modification is loaded in order to guarantee + that a modification doesn't attempt to overwrite the core commands, even though + by standard they shouldn't be defining these commands in the first place. + + """ + self.commands['mods'] = { } + self.commands['mods']['command'] = self.command_mods + self.commands['mods']['description'] = 'Lists all loaded mods.' + self.commands['mods']['usage'] = 'mods' + self.commands['mods']['privilege'] = 3 + self.commands['mods']['modification'] = '' + self.commands['mods']['category'] = 'Core' + + self.commands['load'] = { } + self.commands['load']['command'] = self.command_load + self.commands['load']['description'] = 'Loads the specified mod.' + self.commands['load']['usage'] = 'load ' + self.commands['load']['privilege'] = 3 + self.commands['load']['modification'] = '' + self.commands['load']['category'] = 'Core' + + self.commands['unload'] = { } + self.commands['unload']['command'] = self.command_unload + self.commands['unload']['description'] = 'Unloads the specified mod.' + self.commands['unload']['usage'] = 'unload ' + self.commands['unload']['privilege'] = 3 + self.commands['unload']['modification'] = '' + self.commands['unload']['category'] = 'Core' + + def find_command(self, name): + """ Returns a command by name. """ + if (name not in self.commands): + return None + else: + return self.commands[name] + + # CORE Commands + def command_mods(self, **kwargs): + """ Internal command to list installed mods. """ + sender = kwargs['sender'] + loaded = '' + sender.send('Loaded modifications: ') + for key in self.modifications.keys(): + loaded += '%s, ' % (key) + sender.send(loaded.rstrip(', ')) + + def command_load(self, **kwargs): + """ Internal command to load mods. """ + sender = kwargs['sender'] + input = kwargs['input'].lower() + if (input.strip() == ' '): + sender.send('No input.') + + if (self.load(input) is False): + sender.send('Failed to load mod "%s".' % (input)) + else: + sender.send('Loaded mod "%s".' % (input)) + + def command_unload(self, **kwargs): + """ Internal command to unload mods. """ + sender = kwargs['sender'] + input = kwargs['input'].lower() + if (input in self.modifications): + instance, module = self.modifications[input] + self.modifications.pop(input) + commands = instance.get_commands() + for command in commands.keys(): + self.commands.pop(command) + sender.send('Mod "%s" unloaded.' % (input)) + else: + sender.send('Unknown modification.') diff --git a/application/game/permissions.py b/application/game/permissions.py index 8d1c2ec..7b79139 100644 --- a/application/game/permissions.py +++ b/application/game/permissions.py @@ -1,72 +1,67 @@ -""" - Permission handler for the ScalyMUCK server. It - employs a sort of repository system used for storing - and evaluating permissions with variable terms and - exceptions. - - Copyright (c) 2013 Robert MacGregor - This software is licensed under the GNU General - Public License version 3. Please refer to gpl.txt - for more information. -""" - -import logging - -import settings - -logger = logging.getLogger('Mods') -class Permissions: - """ Main class for permission handling in ScalyMUCK. """ - permissions = { } - workdir = None - - def __init__(self, workdir=None): - """ Initializes a new instance of the permissions repository class. - - NOTE: - You must pass in a good work directory in order for the permissions repository - class to be able to load the server global config/permissions.cfg file. - - """ - self.workdir = workdir - permission_settings = settings.Settings('%s/config/permissions.cfg' % (self.workdir)) - for index in permission_settings.get_indices(): - self.set_permission(index, permission_settings.get_index(index, bool)) - - def set_permission(self, name=None, value=None, evaluator=None): - """ Sets a permission in the repo. """ - if (evaluator is None): - evaluator = self.standard_evaluator - self.permissions[name] = (value, evaluator) - - def has_permission(self, name): - """ Determines whether or not a permission is actually set in the repo. """ - if (name in self.permissions): - return True - else: - return False - - def test(self, name=None, player=None, *arbitrary): - """ Tests the permission availability against a player. """ - if (name in self.permissions): - value, evaluator = self.permissions[name] - return evaluator(name, player, value, *arbitrary) - - def standard_evaluator(self, name=None, player=None, value=None): - """ Tests all standard permissions built into the server. - - Keyword arguments: - * name -- The name of the permission to attempt to evaluate. - * player -- An instance of game.models.Player to evaluate against. - * value -- The specific variable used to configure this permission setting. - - """ - if (name == 'AllowAdminOverride'): - return value - elif (name == 'AllowSuperAdminOverride'): - return value - elif (name == 'AllowOwnerOverride'): - return value - else: - logger.warn('Attempted to evaluate undefined permission: "%s"!' % (name)) - return False +""" + Permission handler for the ScalyMUCK server. It + employs a sort of repository system used for storing + and evaluating permissions with variable terms and + exceptions. + + This software is licensed under the MIT license. + Please refer to LICENSE.txt for more information. +""" + +import logging + +import settings + +logger = logging.getLogger('Mods') +class Permissions: + """ Main class for permission handling in ScalyMUCK. """ + permissions = { } + workdir = None + + def __init__(self, workdir=None): + """ Initializes a new instance of the permissions repository class. + + NOTE: + You must pass in a good work directory in order for the permissions repository + class to be able to load the server global config/permissions.cfg file. + + """ + self.workdir = workdir + permission_settings = settings.Settings('%s/config/permissions.cfg' % (self.workdir)) + for index in permission_settings.get_indices(): + self.set_permission(index, permission_settings.get_index(index, bool)) + + def set_permission(self, name=None, value=None, evaluator=None): + """ Sets a permission in the repo. """ + if (evaluator is None): + evaluator = self.standard_evaluator + self.permissions[name] = (value, evaluator) + + def has_permission(self, name): + """ Determines whether or not a permission is actually set in the repo. """ + if (name in self.permissions): + return True + else: + return False + + def test(self, name=None, player=None, *arbitrary): + """ Tests the permission availability against a player. """ + if (name in self.permissions): + value, evaluator = self.permissions[name] + return evaluator(name, player, value, *arbitrary) + + def standard_evaluator(self, name=None, player=None, value=None): + """ Tests all standard permissions built into the server. + + Keyword arguments: + * name -- The name of the permission to attempt to evaluate. + * player -- An instance of game.models.Player to evaluate against. + * value -- The specific variable used to configure this permission setting. + + """ + if (name == 'AllowAdminOverride'): + return value + elif (name == 'AllowSuperAdminOverride'): + return value + elif (name == 'AllowOwnerOverride'): + return value diff --git a/application/game/scommands/__init__.py b/application/game/scommands/__init__.py index 35466da..a505e77 100644 --- a/application/game/scommands/__init__.py +++ b/application/game/scommands/__init__.py @@ -1,25 +1,23 @@ -""" - ScalyMUCK Normal Commands - - This mod code is simply ScalyMUCK's - default user commands. - - Copyright (c) 2013 Robert MacGregor - This software is licensed under the GNU General - Public License version 3. Please refer to gpl.txt - for more information. -""" - -server_version_major = 1 -server_version_minor = 0 -server_version_revision = 0 - -version_major = 1 -version_minor = 0 -version_revision = 0 -name = 'ScalyMUCK Commands' -description = 'This modification implements various normal MU* commands into ScalyMUCK.' -copyright = 'Copyright (c) 2013 Liukcairo' -author = 'Liukcairo' - -from scommands import Modification +""" + ScalyMUCK Normal Commands + + This mod code is simply ScalyMUCK's + default user commands. + + This software is licensed under the MIT license. + Please refer to LICENSE.txt for more information. +""" + +server_version_major = 1 +server_version_minor = 0 +server_version_revision = 0 + +version_major = 1 +version_minor = 0 +version_revision = 0 +name = 'ScalyMUCK Commands' +description = 'This modification implements various normal MU* commands into ScalyMUCK.' +copyright = 'Copyright (c) 2013 Liukcairo' +author = 'Liukcairo' + +from scommands import Modification diff --git a/application/game/scommands/scommands.py b/application/game/scommands/scommands.py index fc523c2..c58e934 100644 --- a/application/game/scommands/scommands.py +++ b/application/game/scommands/scommands.py @@ -1,604 +1,692 @@ -""" - Found here is a great example of how modifications are implemented inside of ScalyMUCK. All modifications that - are intended to be loaded into ScalyMUCK must be provided in the form of a module you can merely drag and drop - into application/game/ and from there the server host can modify their server_config.cfg file to add the modification - to the loaded modification listing. - - Copyright (c) 2013 Robert MacGregor - This software is licensed under the GNU General - Public License version 3. Please refer to gpl.txt - for more information. -""" - -import string - -from blinker import signal - -import game.models - -class Modification: - """ Main class object to load and initialize the scommands modification. """ - world = None - interface = None - session = None - modloader = None - - pre_user_look = signal('pre_user_look') - post_user_look = signal('post_user_look') - pre_user_say = signal('pre_user_say') - post_user_say = signal('post_user_say') - pre_user_pose = signal('pre_user_pose') - post_user_pose = signal('post_user_pose') - pre_item_pickup = signal('pre_item_pickup') - post_item_pickup = signal('post_item_pickup') - pre_item_drop = signal('pre_item_drop') - post_item_drop = signal('post_item_drop') - post_user_create = signal('post_user_create') - pre_exit_room = signal('pre_exit_room') - post_exit_room = signal('post_exit_room') - pre_show_description = signal('pre_show_description') - - def __init__(self, **kwargs): - """ - - This initializes an instance of the scommands modification and it will remain loaded in memory - until the server owner either unloads it or reloads it which therefore will reset - any associated data unless the data had been defined on the class definition itself - rather than being initialized in this function. - - Keyword arguments: - * config -- This is the instance of Settings that contains all loaded configuration settings available for this modification, if the file exists. If the file does not exist, then this will simply be None. - * interface -- This is the instance of the user interface used internally by ScalyMUCK. Generally, you won't need access to this for any reason and is currently deprecated for later removal. - - Actions such as binding your Blinker signals should be performed here so that events will be - received properly when they occur. - - Along with initializing the modification, __init__ acts as a gateway for other important - data passed in by the modloader under the **kwargs argument. - - """ - - self.config = kwargs['config'] - self.interface = kwargs['interface'] - self.session = kwargs['session'] - self.world = kwargs['world'] - self.permissions = kwargs['permissions'] - self.modloader = kwargs['modloader'] - - signal('post_client_authenticated').connect(self.callback_client_authenticated) - signal('pre_message_sent').connect(self.callback_message_sent) - - # Commands - def command_say(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - - if (input == ''): - sender.send('Usage: say ') - return - - results = self.pre_user_say.send(None, sender=sender, input=input) - for result in results: - if (result[1] is True): - return - - sender.location.broadcast('%s says, "%s"' % (sender.display_name, input), sender) - sender.send('You say, "%s"' % (input)) - self.post_user_say.send(None, sender=sender, input=input) - - def command_pose(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - results = self.pre_user_pose.send(None, sender=sender, input=input) - for result in results: - if (result[1] is True): - return - - sender.location.broadcast('%s %s' % (sender.display_name, kwargs['input'])) - self.post_user_pose.send(None, sender=sender, input=input) - - def command_look(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - target = sender.location - name = sender.location.name - - self.pre_user_look.send(sender=sender, input=input) - - if (input != ''): - target = sender.location.find_player(name=input) - if (target is None): - target = sender.location.find_bot(name=input) - if (target is None): - target = sender.location.find_item(name=input) - - if (target is not None): - name = target.display_name - if (type(target) is game.models.Player): - target.send('++++++++ %s is looking at you!' % (sender.display_name)) - else: - target = sender.location.find_item(name=input) - if (target is None): - target = sender.inventory.find_item(name=input) - - if (target is not None): - name = target.name - else: - sender.send('I do not see that.') - return - - sender.send('<' + name + '>') - - if (type(target) is game.models.Room): - sender.send('Obvious Exits: ') - if (len(target.exits) != 0): - for exit in target.exits: - sender.send(' %s' % (exit.name)) - else: - sender.send(' None') - - sender.send('People: ') - for player in target.players: - sender.send(' %s' % (player.display_name)) - - sender.send('Bots: ') - if (len(target.bots) != 0): - for bot in target.bots: - sender.send(' %s' % (bot.display_name)) - else: - sender.send(' None') - - sender.send('Items: ') - if (len(target.items) != 0): - for item in target.items: - sender.send(' %s' % (item.name)) - else: - sender.send(' None') - - self.pre_show_description.send(None, sender=sender, target=target) - sender.send('Description:') - sender.send(target.description) - - self.post_user_look.send(sender=sender, input=input, target=target) - - def command_move(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - - if (input == ''): - sender.send('Usage: move ') - return - - exit = sender.location.find_exit(name=input) - if (exit is not None): - sender.send(exit.user_enter_message) - sender.location.broadcast('%s %s' % (sender.display_name, exit.room_enter_message), sender) - results = self.pre_exit_room.send(None, sender=sender, target=exit.target_id) - for result in results: - if (result[1] is True): - return - sender.set_location(exit.target_id) - sender.location.broadcast('%s %s' % (sender.display_name, exit.room_exit_message), sender) - sender.send(exit.user_exit_message) - self.command_look(sender=sender, input='') - self.post_exit_room.send(None, sender=sender, target=sender.location) - else: - sender.send('I do not see that.') - - def command_inventory(self, **kwargs): - sender = kwargs['sender'] - - sender.send('Items:') - if (len(sender.inventory.items) != 0): - for item in sender.inventory.items: - sender.send(' %s' % (item.name)) - else: - sender.send(' None') - - # Pocket dimension anybody? - if (len(sender.inventory.players) != 0): - sender.send('People: ') - for player in sender.inventory.players: - sender.send(' %s' % (player.display_name)) - - def command_take(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - - if (input == ''): - sender.send('Usage: take ') - return - - item = sender.location.find_item(name=input) - if (item is not None): - results = self.pre_item_pickup.send(None, sender=sender, item=item) - for result in results: - if (result[1] is True): - return - - sender.send('Taken.') - - item.set_location(sender.inventory) - self.post_item_pickup.send(None, sender=sender, item=item) - return - - sender.send('I do not see that.') - - def command_passwd(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - - if (input == ''): - sender.send('Usage: passwd ') - return - - sender.set_password(input) - sender.send('Your password has been changed. Remember it well.') - - def command_drop(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - - if (input == ''): - sender.send('Usage: drop ') - return - - item = sender.inventory.find_item(name=input) - if (item is not None): - results = self.pre_item_drop.send(sender=sender, item=item) - for result in results: - if (result[1] is True): - return - - sender.send('You dropped a/an %s.' % (item.name )) - sender.location.broadcast('%s drops a/an %s.' % (sender.display_name, item.name) ,sender) - item.set_location(sender.location) - self.post_item_drop.send(sender=sender, item=item) - else: - sender.send('I see no such item.') - - def command_help(self, **kwargs): - sender = kwargs['sender'] - input = string.lower(kwargs['input']) - - if (input not in self.modloader.commands): - sender.send('For more information, type: help ') - sender.send('Command listing: ') - out = '' - for command in self.modloader.commands: - privilege = self.modloader.commands[command]['privilege'] - if ((privilege == 1 and sender.is_admin is False) or (privilege == 2 and sender.is_sadmin is False) or (privilege == 3 and sender.is_owner is False)): - continue - out += command + ', ' - # Cheap trick to strip off the last comma (and space) but eh! - sender.send(out[:len(out)-2]) - return - else: - sender.send('From: %s' % (self.modloader.commands[input]['modification'])) - sender.send('Usage: %s' % (self.modloader.commands[input]['usage'])) - sender.send(self.modloader.commands[input]['description']) - - def command_quit(self, **kwargs): - sender = kwargs['sender'] - sender.disconnect() - - def command_froguser(self, **kwargs): - sender = kwargs['sender'] - args = kwargs['arguments'] - if (len(args) < 1): - sender.send('Usage: frog ') - return - - name = args[0] - target = self.world.find_player(name=string.lower(name)) - if (target is not None): - if (target is sender): - sender.send('That is yourself!') - return - elif (target.is_sadmin ==1 or target.is_owner == 1): - sender.send('You cannot do that, they are too powerful.') - return - - item = self.world.create_item(target.display_name, target.description, sender, sender.inventory) - sender.send('User "%s" frogged. Check your inventory.' % (target.display_name)) - target.send('%s has turned you into a small plastic figurine, never to move again and discreetly places you in their inventory.' % (sender.display_name)) - sender.location.broadcast('%s has turned %s into a small plastic figurine, never to move again.' % (sender.display_name, target.display_name), sender, target) - target.delete() - else: - sender.send('User "%s" does not exist anywhere.' % (name)) - - def command_adduser(self, **kwargs): - sender = kwargs['sender'] - args = kwargs['arguments'] - if (len(args) < 2): - sender.send('Usage: adduser ') - return - - name = args[0] - password = args[1] - - if (self.world.find_player(name=string.lower(name)) is not None): - sender.send('User already exists.') - return - - # TODO: Make this take server prefs into consideration, and also let this have a default location ... - player = self.world.create_player(name, password, game.models.server.work_factor, sender.location) - sender.send('User "%s" created.' % (name)) - self.post_user_create.send(None, creator=sender, created=player) - - def command_admin(self, **kwargs): - sender = kwargs['sender'] - args = kwargs['arguments'] - if (len(args) < 1): - sender.send('Usage: admin ') - return - - name = args[0] - - target = self.world.find_player(name=string.lower(name)) - if (target is not None): - if (target is sender): - sender.send('That is yourself!') - return - elif (sender.check_admin_trump(target) is False): - sender.send('You cannot do that. They are too strong.') - target.send('%s tried to take your administrator privileges away.' % (sender.display_name)) - return - - target.set_is_admin(target.is_admin is False) - if (target.is_admin == 0): - sender.send('%s is no longer an administrator.' % (target.display_name)) - target.send('%s took your adminship.' % (sender.display_name)) - return - else: - sender.send('%s is now an administrator.' % (target.display_name)) - target.send('%s gave you adminship rights.' % (sender.display_name)) - return - sender.send('Unknown user.') - - def command_sadmin(self, **kwargs): - sender = kwargs['sender'] - args = kwargs['arguments'] - if (len(args) < 1): - sender.send('Usage: sadmin ') - return - - name = args[0] - - target = self.world.find_player(name=string.lower(name)) - if (target is not None): - if (target is sender): - sender.send('That is yourself!') - return - elif (sender.check_admin_trump(target) is False): - sender.send('You cannot do that. They are too strong.') - target.send('%s tried to take your super administrator privileges away.' % (sender.display_name)) - return - - target.set_is_super_admin(target.is_sadmin is False) - if (target.is_sadmin is False): - sender.send('%s is no longer a super administrator.' % (target.display_name)) - target.send('%s took your super adminship.' % (sender.display_name)) - return - else: - sender.send('%s is now a super administrator.' % (target.display_name)) - target.send('%s gave you super adminship rights.' % (sender.display_name)) - return - sender.send('Unknown user.') - - def command_chown(self, **kwargs): - sender = kwargs['sender'] - args = kwargs['arguments'] - - if (len(args) < 2): - sender.send('Usage: chown ') - return - - item_name = args[0] - owner_name = args[1] - - item = sender.inventory.find_item(name=item_name) - if (item.owner_id != sender.id): - sender.send('This is not your item.') - return - - player = self.world.find_player(name=owner_name) - if (player is None): - sender.send('There is no such player.') - else: - item.set_owner(player) - sender.send('%s now owns that item.' % (player.display_name)) - player.send('%s has given you a %s.' % (sender.display_name, item.name)) - - def command_ping(self, **kwargs): - kwargs['sender'].send('Pong.') - - # Callbacks - def callback_client_authenticated(self, trigger, sender): - """ Callback that is bound to the "post_client_authenticated" event. - - Callbacks like this one are helpful in cases that if you want to initialize - certain data upon the authentication of a certain client -- perhaps you're loading - mod data that is related to this client. - - Refer to the :command:`__init__` function. - - """ - self.command_look(sender=sender, input='') - - def callback_message_sent(self, trigger, sender, input): - """ Callback that is bound to the "pre_message_sent" event. - - Callbacks like this one are helpful in cases that if you want to intercept - input for any reason, such as an interactive menu that will handle it's own - text parsing for handling menu functions as that if the callback at any point - returns true, the server will not pass the input text into the core parser. - - Refer to the :command:`__init__` function. - - """ - if (len(input) != 0): - if (input[0] == ':'): - self.command_pose(sender=sender, input=input[1:].lstrip()) - return True - elif (input[0] == '"'): - self.command_say(sender=sender, input=input[1:].lstrip()) - return True - - return False - - def get_commands(self): - """ Returns a dictionary mapping the available commands in this modification. - - This is a function call merely for the purpose of being able to provide variable - output, so that if the modification has an accompanying configuration file it can - omit or include certain commands based on the configuration settings loaded in the - modification's :command:`__init__` function. - - """ - command_dict = { - 'say': - { - 'command': self.command_say, - 'description': 'Makes you say something. Only visible to the current room you\'re in.', - 'usage': 'say | "', - 'aliases': [ 'speak' ], - 'privilege': 0 - }, - - 'pose': - { - 'command': self.command_pose, - 'description': 'Used to show arbitrary action. Only visible to the current room you\'re in.', - 'usage': 'pose | :', - 'aliases': [ ], - 'privilege': 0 - }, - - 'look': - { - 'command': self.command_look, - 'description': 'Get your bearings. Look around in the local area to see what you can see.', - 'usage': 'look [room name | item name | player name]', - 'aliases': [ ], - 'privilege': 0 - }, - - 'move': - { - 'command': self.command_move, - 'description': 'Moves to a new location.', - 'usage': 'move ', - 'aliases': [ 'go' ], - 'privilege': 0 - }, - - 'inventory': - { - 'command': self.command_inventory, - 'description': 'View your inventory.', - 'usage': 'inventory', - 'aliases': [ ], - 'privilege': 0 - }, - - 'take': - { - 'command': self.command_take, - 'description': 'Take an item from the current room.', - 'usage': 'take ', - 'aliases': [ 'get' ], - 'privilege': 0 - }, - - 'passwd': - { - 'command': self.command_passwd, - 'description': 'Changes your password.', - 'usage': 'passwd ', - 'aliases': [ ], - 'privilege': 0 - }, - - 'drop': - { - 'command': self.command_drop, - 'description': 'Drops an item from your inventory.', - 'usage': 'drop ', - 'aliases': [ ], - 'privilege': 0 - }, - - 'help': - { - 'command': self.command_help, - 'description': 'Displays the help text.', - 'usage': 'help [command name]', - 'aliases': [ ], - 'privilege': 0 - }, - - 'quit': - { - 'command': self.command_quit, - 'description': 'Drops your connection from the server.', - 'usage': 'quit', - 'aliases': [ 'leave' ], - 'privilege': 0 - }, - - 'frog': - { - 'command': self.command_froguser, - 'description': 'Super Admin only: Deletes a user from the world -- making them an item in your inventory to with as you please.', - 'usage': 'frog ', - 'aliases': [ ], - 'privilege': 2 - }, - - 'adduser': - { - 'command': self.command_adduser, - 'description': 'Creates a new player in the world.', - 'usage': 'adduser ', - 'aliases': [ ], - 'privilege': 2 - }, - - 'admin': - { - 'command': self.command_admin, - 'description': 'Admin only: Toggles the admin status of a specified player.', - 'usage': 'admin ', - 'aliases': [ ], - 'privilege': 1 - }, - - 'sadmin': - { - 'command': self.command_sadmin, - 'description': 'Super Admin only: Toggles the super admin status of a specified player.', - 'usage': 'sadmin ', - 'aliases': [ ], - 'privilege': 2 - }, - - 'chown': - { - 'command': self.command_chown, - 'description': 'Transfers ownership of an item in your inventory or in the room to a specified player providied you are the original owner.', - 'usage': 'chown ', - 'aliases': [ ], - 'privilege': 0 - }, - - 'ping': - { - 'command': self.command_ping, - 'description': 'Ping-Pong.', - 'usage': 'ping', - 'aliases': [ ], - 'privilege': 0 - } - } +""" + Found here is a great example of how modifications are implemented inside of ScalyMUCK. All modifications that + are intended to be loaded into ScalyMUCK must be provided in the form of a module you can merely drag and drop + into application/game/ and from there the server host can modify their server_config.cfg file to add the modification + to the loaded modification listing. + + This software is licensed under the MIT license. + Please refer to LICENSE.txt for more information. +""" + +import string + +from blinker import signal + +import game.models +import game.exception + +class Modification: + """ Main class object to load and initialize the scommands modification. """ + world = None + interface = None + session = None + modloader = None + + pre_user_look = signal('pre_user_look') + post_user_look = signal('post_user_look') + pre_user_say = signal('pre_user_say') + post_user_say = signal('post_user_say') + pre_user_pose = signal('pre_user_pose') + post_user_pose = signal('post_user_pose') + pre_item_pickup = signal('pre_item_pickup') + post_item_pickup = signal('post_item_pickup') + pre_item_drop = signal('pre_item_drop') + post_item_drop = signal('post_item_drop') + post_user_create = signal('post_user_create') + pre_exit_room = signal('pre_exit_room') + post_exit_room = signal('post_exit_room') + pre_show_description = signal('pre_show_description') + + def initialize(self, **kwargs): + """ + + This initializes an instance of the scommands modification and it will remain loaded in memory + + until the server owner either unloads it or reloads it which therefore will reset + any associated data unless the data had been defined on the class definition itself + rather than being initialized in this function. + + + Keyword arguments: + * config -- This is the instance of Settings that contains all loaded configuration settings available for this modification, if the file exists. If the file does not exist, then this will simply be None. + * interface -- This is the instance of the user interface used internally by ScalyMUCK. Generally, you won't need access to this for any reason and is currently deprecated for later removal. + + + Actions such as binding your Blinker signals should be performed here so that events will be + received properly when they occur. + + Along with initializing the modification, __init__ acts as a gateway for other important + + data passed in by the modloader under the **kwargs argument. + + """ + + self.config = kwargs['config'] + self.interface = kwargs['interface'] + self.session = kwargs['session'] + self.world = kwargs['world'] + self.permissions = kwargs['permissions'] + self.modloader = kwargs['modloader'] + + signal('post_client_authenticated').connect(self.callback_client_authenticated) + signal('pre_message_sent').connect(self.callback_message_sent) + + def __del__(self): + signal('post_client_authenticated').disconnect(self.callback_client_authenticated) + signal('pre_message_sent').disconnect(self.callback_message_sent) + + # Commands + def command_say(self, **kwargs): + sender = kwargs['sender'] + input = kwargs['input'] + + if (input == ''): + sender.send('Usage: say ') + return + + results = self.pre_user_say.send(None, sender=sender, input=input) + for result in results: + if (result[1] is True): + return + + sender.location.broadcast('%s says, "%s"' % (sender.display_name, input), sender) + sender.send('You say, "%s"' % (input)) + self.post_user_say.send(None, sender=sender, input=input) + + def command_pose(self, **kwargs): + sender = kwargs['sender'] + input = kwargs['input'] + results = self.pre_user_pose.send(None, sender=sender, input=input) + for result in results: + if (result[1] is True): + return + + sender.location.broadcast('%s %s' % (sender.display_name, kwargs['input'])) + self.post_user_pose.send(None, sender=sender, input=input) + + def command_look(self, **kwargs): + sender = kwargs['sender'] + input = kwargs['input'] + target = sender.location + name = sender.location.name + + self.pre_user_look.send(sender=sender, input=input) + + if (input != ''): + target = sender.location.find_player(name=input) + if (target is None): + target = sender.location.find_bot(name=input) + + if (target is not None): + name = target.display_name + if (type(target) is game.models.Player): + target.send('++++++++ %s is looking at you!' % (sender.display_name)) + else: + target = sender.location.find_item(name=input) + if (target is None): + target = sender.inventory.find_item(name=input) + + if (target is not None): + name = target.name + else: + sender.send('I do not see that.') + return + sender.send('<' + name + '>') + + if (type(target) is game.models.Room): + sender.send('Obvious Exits: ') + if (len(target.exits) != 0): + for exit in target.exits: + sender.send(' %s' % (exit.name)) + else: + sender.send(' None') + + sender.send('People: ') + for player in target.players: + sender.send(' %s' % (player.display_name)) + + sender.send('Bots: ') + if (len(target.bots) != 0): + for bot in target.bots: + sender.send(' %s' % (bot.display_name)) + else: + sender.send(' None') + + sender.send('Items: ') + if (len(target.items) != 0): + for item in target.items: + sender.send(' %s' % (item.name)) + else: + sender.send(' None') + + self.pre_show_description.send(None, sender=sender, target=target) + sender.send('Description:') + sender.send(target.description) + + self.post_user_look.send(sender=sender, input=input, target=target) + + def command_move(self, **kwargs): + sender = kwargs['sender'] + input = kwargs['input'] + + if (input == ''): + sender.send('Usage: move ') + return + + exit = sender.location.find_exit(name=input) + if (exit is not None): + sender.send(exit.user_enter_message) + sender.location.broadcast('%s %s' % (sender.display_name, exit.room_enter_message), sender) + results = self.pre_exit_room.send(None, sender=sender, target=exit.target_id) + for result in results: + if (result[1] is True): + return + sender.set_location(exit.target_id) + sender.location.broadcast('%s %s' % (sender.display_name, exit.room_exit_message), sender) + sender.send(exit.user_exit_message) + self.command_look(sender=sender, input='') + self.post_exit_room.send(None, sender=sender, target=sender.location) + else: + sender.send('I do not see that.') + + def command_inventory(self, **kwargs): + sender = kwargs['sender'] + + sender.send('Items:') + if (len(sender.inventory.items) != 0): + for item in sender.inventory.items: + sender.send(' %s' % (item.name)) + else: + sender.send(' None') + + # Pocket dimension anybody? + if (len(sender.inventory.players) != 0): + sender.send('People: ') + for player in sender.inventory.players: + sender.send(' %s' % (player.display_name)) + + def command_take(self, **kwargs): + sender = kwargs['sender'] + input = kwargs['input'] + + if (input == ''): + sender.send('Usage: take ') + return + + item = sender.location.find_item(name=input) + if (item is not None): + results = self.pre_item_pickup.send(None, sender=sender, item=item) + for result in results: + if (result[1] is True): + return + + sender.send('Taken.') + + item.set_location(sender.inventory) + self.post_item_pickup.send(None, sender=sender, item=item) + else: + sender.send('I do not see that.') + + def command_passwd(self, **kwargs): + sender = kwargs['sender'] + input = kwargs['input'] + + if (input == ''): + sender.send('Usage: passwd ') + return + + sender.set_password(input) + sender.send('Your password has been changed. Remember it well.') + + def command_drop(self, **kwargs): + sender = kwargs['sender'] + input = kwargs['input'] + + if (input == ''): + sender.send('Usage: drop ') + return + + item = sender.inventory.find_item(name=input) + if (item is not None): + results = self.pre_item_drop.send(sender=sender, item=item) + for result in results: + if (result[1] is True): + return + + item.set_location(sender.location) + sender.send('You dropped a/an %s.' % (item.name )) + sender.location.broadcast('%s drops a/an %s.' % (sender.display_name, item.name), sender) + self.post_item_drop.send(sender=sender, item=item) + else: + sender.send('I see no such item.') + + def command_help(self, **kwargs): + sender = kwargs['sender'] + input = kwargs['input'] + + sender.send('For more information, type: help ') + + if (input == ''): + sender.send('Available command categories:') + categories = [] + for command in self.modloader.commands: + privilege = self.modloader.commands[command]['privilege'] + if ((privilege == 1 and sender.is_admin is False) or (privilege == 2 and sender.is_sadmin is False) or (privilege == 3 and sender.is_owner is False)): + continue + + if ('category' in self.modloader.commands[command]): + category = self.modloader.commands[command]['category'] + if (category not in categories): + categories.append(category) + sender.send('\t+ ' + category) + elif ('Unclassed' not in categories): + categories.append('Unclassed') + sender.send('\t+ Unclassed') + + + else: + l_input = input.lower() + if (input not in self.modloader.commands): + category_contents = [ ] + + for command in self.modloader.commands: + privilege = self.modloader.commands[command]['privilege'] + if ((privilege == 1 and sender.is_admin is False) or (privilege == 2 and sender.is_sadmin is False) or (privilege == 3 and sender.is_owner is False)): + continue + + if ('category' in self.modloader.commands[command]): + category = self.modloader.commands[command]['category'] + if (category.lower() == l_input): + category_contents.append(command) + elif (l_input == 'unclassed'): + category_contents.append(command) + + if (len(category_contents) == 0): + sender.send('No such command or category: %s' % input) + else: + sender.send('Available commands in category %s:' % input) + for command_name in category_contents: + sender.send('\t+ %s - %s' % (command_name, self.modloader.commands[command_name]['description'])) + else: + privilege = self.modloader.commands[l_input]['privilege'] + if ((privilege == 1 and sender.is_admin is False) or (privilege == 2 and sender.is_sadmin is False) or (privilege == 3 and sender.is_owner is False)): + sender.send('No such command or category: %s' % input) + return + + sender.send('From: %s' % (self.modloader.commands[input]['modification'])) + sender.send('Usage: %s' % (self.modloader.commands[input]['usage'])) + sender.send(self.modloader.commands[input]['description']) + + + def command_quit(self, **kwargs): + sender = kwargs['sender'] + sender.disconnect() + + def command_froguser(self, **kwargs): + sender = kwargs['sender'] + args = kwargs['arguments'] + if (len(args) < 1): + sender.send('Usage: frog ') + return + + name = args[0] + target = self.world.find_player(name=string.lower(name)) + if (target is not None): + if (target is sender): + sender.send('That is yourself!') + return + elif (target.is_sadmin ==1 or target.is_owner == 1): + sender.send('You cannot do that, they are too powerful.') + return + else: + sender.send('User "%s" frogged. Check your inventory.' % (target.display_name)) + sender.location.broadcast('%s has turned %s into a small plastic figurine, never to move again.' % (sender.display_name, target.display_name), sender, target) + item = self.world.create_item(target.display_name, target.description, sender, sender.inventory) + target.send('%s has turned you into a small plastic figurine, never to move again and discreetly places you in their inventory.' % (sender.display_name)) + target.delete() + else: + sender.send('User "%s" does not exist anywhere.' % (name)) + + def command_adduser(self, **kwargs): + sender = kwargs['sender'] + args = kwargs['arguments'] + if (len(args) < 2): + sender.send('Usage: adduser ') + return + + name = args[0] + password = args[1] + + if (self.world.find_player(name=string.lower(name)) is not None): + sender.send('User already exists.') + return + + # TODO: Make this take server prefs into consideration, and also let this have a default location ... + player = self.world.create_player(name, password, game.models.server.work_factor, sender.location) + sender.send('User "%s" created.' % (name)) + self.post_user_create.send(None, creator=sender, created=player) + + def command_admin(self, **kwargs): + sender = kwargs['sender'] + args = kwargs['arguments'] + if (len(args) < 1): + sender.send('Usage: admin ') + return + + name = args[0] + + target = self.world.find_player(name=string.lower(name)) + if (target is not None): + if (target is sender): + sender.send('That is yourself!') + return + elif (sender.check_admin_trump(target) is False): + sender.send('You cannot do that. They are too strong.') + target.send('%s tried to take your administrator privileges away.' % (sender.display_name)) + return + + target.set_is_admin(target.is_admin is False) + if (target.is_admin == 0): + sender.send('%s is no longer an administrator.' % (target.display_name)) + target.send('%s took your adminship.' % (sender.display_name)) + return + else: + sender.send('%s is now an administrator.' % (target.display_name)) + target.send('%s gave you adminship rights.' % (sender.display_name)) + return + sender.send('Unknown user.') + + def command_sadmin(self, **kwargs): + sender = kwargs['sender'] + args = kwargs['arguments'] + if (len(args) < 1): + sender.send('Usage: sadmin ') + return + + name = args[0] + + target = self.world.find_player(name=string.lower(name)) + if (target is not None): + if (target is sender): + sender.send('That is yourself!') + return + elif (sender.check_admin_trump(target) is False): + sender.send('You cannot do that. They are too strong.') + target.send('%s tried to take your super administrator privileges away.' % (sender.display_name)) + return + + target.set_is_super_admin(target.is_sadmin is False) + if (target.is_sadmin is False): + sender.send('%s is no longer a super administrator.' % (target.display_name)) + target.send('%s took your super adminship.' % (sender.display_name)) + return + else: + sender.send('%s is now a super administrator.' % (target.display_name)) + target.send('%s gave you super adminship rights.' % (sender.display_name)) + return + sender.send('Unknown user.') + + def command_chown(self, **kwargs): + sender = kwargs['sender'] + args = kwargs['arguments'] + + if (len(args) < 2): + sender.send('Usage: chown ') + return + + item_name = args[0] + owner_name = args[1] + + item = sender.inventory.find_item(name=item_name) + if (item.owner_id != sender.id): + sender.send('This is not your item.') + return + + player = self.world.find_player(name=owner_name) + if (player is None): + sender.send('There is no such player.') + else: + item.set_owner(player) + sender.send('%s now owns that item.' % (player.display_name)) + player.send('%s has given you a %s.' % (sender.display_name, item.name)) + + def command_ping(self, **kwargs): + kwargs['sender'].send('Pong.') + + def command_wwi(self, **kwargs): + sender = kwargs['sender'] + + listing = self.interface.get_online_players() + longest_length = 0 + for player in listing: + length = len(player.display_name) + if (length > longest_length): + longest_length = length + + spacing = '' + for i in range(longest_length+7): + spacing += ' ' + + sender.send('Name%sLocation' % spacing) + for player in listing: + spacing = '' + for i in range((longest_length+11)-len(player.display_name)): + spacing += ' ' + sender.send('%s%s%s' % (player.display_name, spacing, player.location.name)) + sender.send('Who/Where/Idle') + + # Callbacks + def callback_client_authenticated(self, trigger, sender): + """ Callback that is bound to the "post_client_authenticated" event. + + Callbacks like this one are helpful in cases that if you want to initialize + certain data upon the authentication of a certain client -- perhaps you're loading + mod data that is related to this client. + + Refer to the :command:`__init__` function. + + """ + self.command_look(sender=sender, input='') + + def callback_message_sent(self, trigger, sender, input): + """ Callback that is bound to the "pre_message_sent" event. + + Callbacks like this one are helpful in cases that if you want to intercept + input for any reason, such as an interactive menu that will handle it's own + text parsing for handling menu functions as that if the callback at any point + returns true, the server will not pass the input text into the core parser. + + Refer to the :command:`__init__` function. + + """ + if (len(input) != 0): + if (input[0] == ':'): + self.command_pose(sender=sender, input=input[1:].lstrip()) + return True + elif (input[0] == '"'): + self.command_say(sender=sender, input=input[1:].lstrip()) + return True + + return False + + def get_commands(self): + """ Returns a dictionary mapping the available commands in this modification. + + This is a function call merely for the purpose of being able to provide variable + output, so that if the modification has an accompanying configuration file it can + omit or include certain commands based on the configuration settings loaded in the + modification's :command:`__init__` function. + + """ + command_dict = { + 'say': + { + 'command': self.command_say, + 'description': 'Makes you say something. Only visible to the current room you\'re in.', + 'usage': 'say | "', + 'aliases': [ 'speak' ], + 'privilege': 0, + 'category': 'General' + }, + + 'pose': + { + 'command': self.command_pose, + 'description': 'Used to show arbitrary action. Only visible to the current room you\'re in.', + 'usage': 'pose | :', + 'aliases': [ ], + 'privilege': 0, + 'category': 'General' + }, + + 'look': + { + 'command': self.command_look, + 'description': 'Get your bearings. Look around in the local area to see what you can see.', + 'usage': 'look [room name | item name | player name]', + 'aliases': [ ], + 'privilege': 0, + 'category': 'General' + }, + + 'move': + { + 'command': self.command_move, + 'description': 'Moves to a new location.', + 'usage': 'move ', + 'aliases': [ 'go' ], + 'privilege': 0, + 'category': 'General' + }, + + 'inventory': + { + 'command': self.command_inventory, + 'description': 'View your inventory.', + 'usage': 'inventory', + 'aliases': [ ], + 'privilege': 0, + 'category': 'General' + }, + + 'take': + { + 'command': self.command_take, + 'description': 'Take an item from the current room.', + 'usage': 'take ', + 'aliases': [ 'get' ], + 'privilege': 0, + 'category': 'General' + }, + + 'passwd': + { + 'command': self.command_passwd, + 'description': 'Changes your password.', + 'usage': 'passwd ', + 'aliases': [ ], + 'privilege': 0, + 'category': 'General' + }, + + 'drop': + { + 'command': self.command_drop, + 'description': 'Drops an item from your inventory.', + 'usage': 'drop ', + 'aliases': [ ], + 'privilege': 0, + 'category': 'General' + }, + + 'help': + { + 'command': self.command_help, + 'description': 'Displays the help text.', + 'usage': 'help [command name]', + 'aliases': [ ], + 'privilege': 0, + 'category': 'General' + }, + + 'quit': + { + 'command': self.command_quit, + 'description': 'Drops your connection from the server.', + 'usage': 'quit', + 'aliases': [ 'leave' ], + 'privilege': 0, + 'category': 'General' + }, + + 'frog': + { + 'command': self.command_froguser, + 'description': 'Deletes a user from the world -- making them an item in your inventory to with as you please.', + 'usage': 'frog ', + 'aliases': [ ], + 'privilege': 2, + 'category': 'Administration' + }, + + 'adduser': + { + 'command': self.command_adduser, + 'description': 'Creates a new player in the world.', + 'usage': 'adduser ', + 'aliases': [ ], + 'privilege': 2, + 'category': 'Administration' + }, + + 'admin': + { + 'command': self.command_admin, + 'description': 'Toggles the admin status of a specified player.', + 'usage': 'admin ', + 'aliases': [ ], + 'privilege': 1, + 'category': 'Administration' + }, + + 'sadmin': + { + 'command': self.command_sadmin, + 'description': 'Toggles the super admin status of a specified player.', + 'usage': 'sadmin ', + 'aliases': [ ], + 'privilege': 2, + 'category': 'Administration' + }, + + 'chown': + { + 'command': self.command_chown, + 'description': 'Transfers ownership of an item in your inventory or in the room to a specified player providied you are the original owner.', + 'usage': 'chown ', + 'aliases': [ ], + 'privilege': 0, + 'category': 'General' + }, + + 'ping': + { + 'command': self.command_ping, + 'description': 'Ping-Pong.', + 'usage': 'ping', + 'aliases': [ ], + 'privilege': 0, + 'category': 'General' + }, + + 'wwi': + { + 'command': self.command_wwi, + 'description': 'See a list of connected people.', + 'usage': 'wwi', + 'aliases': [ ], + 'privilege': 0, + 'category': 'General' + } + } return command_dict diff --git a/application/game/settings.py b/application/game/settings.py index f5f27b3..fd47af7 100644 --- a/application/game/settings.py +++ b/application/game/settings.py @@ -1,69 +1,69 @@ -""" - Basic settings loader that provides easy to use functionality to load from simple - configuration files that have take the following form on every line: - - * Option=Yes - * Number=20 - * String=Whatever - - Copyright (c) 2013 Robert MacGregor - This software is licensed under the GNU General - Public License version 3. Please refer to gpl.txt - for more information. -""" - -import string - -class Settings: - """ The Settings loader class provides simple functionality to load from simple configuration files. """ - _settings_entries = None - _yes = ['1', 'true', 'y', 'yes', 'enable', 'toggle', 'enabled'] - - def __init__(self, target_file): - """ Initializes an instance of the Settings loader. """ - self._settings_entries = { } - self.load(target_file) - - def load(self, target_file): - """ Loads a configuration file from the hard disk. """ - try: - file_handle = open(target_file, 'r') - except IOError: - return - - for line_data in file_handle: - preference_data = string.split(line_data, '=') - line_data = line_data.lstrip() - - if (len(preference_data) == 2): - data = preference_data[1] - self._settings_entries[preference_data[0]] = data[:len(data)].replace('\n','') - # TODO: Make this actually do some work to make post-fix comments work ... - elif(line_data.find('#') and line_data != ''): - continue - - file_handle.close() - - def get_indices(self): - """ Returns all known indices. This is a list of the indices you would use in :func:`get_index`. """ - return self._settings_entries.keys() - - def get_index(self, index=None, datatype=None): - """ Returns a loaded configuration setting from the Settings loader. - - Keyword arguments: - * index -- The name of the setting that is to be loaded from the file. - * datatype -- The datatype that is supposed to be used to represent this setting in the return value. - - """ - if(index in self._settings_entries): - entries = self._settings_entries - if (datatype is bool): - if (string.lower(self._settings_entries[index]) in self._yes): - return True - else: - return False - else: - return datatype(entries[index]) - else: - return None +""" + Basic settings loader that provides easy to use functionality to load from simple + configuration files that have take the following form on every line: + + * Option=Yes + * Number=20 + * String=Whatever + + This software is licensed under the MIT license. + Please refer to LICENSE.txt for more information. +""" + +import string + +class Settings: + """ The Settings loader class provides simple functionality to load from simple configuration files. """ + _settings_entries = None + _yes = ['1', 'true', 'y', 'yes', 'enable', 'toggle', 'enabled'] + + def __init__(self, target_file): + """ Initializes an instance of the Settings loader. """ + self._settings_entries = { } + self.load(target_file) + + def load(self, target_file): + """ Loads a configuration file from the hard disk. """ + try: + file_handle = open(target_file, 'r') + except IOError: + return + + for line_data in file_handle: + preference_data = string.split(line_data, '=') + line_data = line_data.lstrip() + + if (len(preference_data) == 2): + data = preference_data[1] + entry_data = data[:len(data)].replace('\n','') + entry_data = entry_data.replace("\r", "") + self._settings_entries[preference_data[0]] = entry_data + # TODO: Make this actually do some work to make post-fix comments work ... + elif(line_data.find('#') and line_data != ''): + continue + + file_handle.close() + + def get_indices(self): + """ Returns all known indices. This is a list of the indices you would use in :func:`get_index`. """ + return self._settings_entries.keys() + + def get_index(self, index=None, datatype=None): + """ Returns a loaded configuration setting from the Settings loader. + + Keyword arguments: + * index -- The name of the setting that is to be loaded from the file. + * datatype -- The datatype that is supposed to be used to represent this setting in the return value. + + """ + if(index in self._settings_entries): + entries = self._settings_entries + if (datatype is bool): + if (string.lower(self._settings_entries[index]) in self._yes): + return True + else: + return False + else: + return datatype(entries[index]) + else: + return None diff --git a/application/game/world.py b/application/game/world.py index b68ac97..be088a4 100644 --- a/application/game/world.py +++ b/application/game/world.py @@ -1,239 +1,383 @@ -""" - This contains "global" functions that is to - be called by the various mods of the MUCK to perform actions such as creating - Players. - - Copyright (c) 2013 Robert MacGregor - This software is licensed under the GNU General - Public License version 3. Please refer to gpl.txt - for more information. -""" - -import sqlalchemy.orm -from sqlalchemy.orm import sessionmaker, scoped_session - -from models import Room, Player, Item, Bot -import exception - -class World(): - """ - - The "singleton" class that represents the ScalyMUCK world in memory and is to - be passed to every mod that actually manages to initialize when loaded. - - """ - engine = None - session = None - - def __init__(self, engine): - """ Initializes an instance of the World with an SQLAlchemy engine. """ - self.engine = engine - # Not sure if we need to keep this - self.session = scoped_session(sessionmaker(bind=self.engine)) - sqlalchemy.orm.Session = self.session - - def create_room(self, name, description='', owner=0): - """ Creates a new Room if the World. - - Keyword arguments: - description -- The description that is to be used with the new Room instance. - owner -- The ID or instance of Player that is to become the owner of this Room. - - """ - room = Room(name, description, owner) - session = sqlalchemy.orm.Session() - session.add(room) - session.commit() - session.refresh(room) - room.session = session - return room - - def find_room(self, **kwargs): - """ Locates the specified Room in the ScalyMUCK world. - - This can be a bit computionally intense if you are running a very large world. - - Keyword arguments (one or the other): - id -- The id of the requested room to return an instance of. This overrides the name if both are specified. - name -- The name of the requested room to return an instance of. - - """ - session = sqlalchemy.orm.Session() - target_room = session.query(Room).filter_by(**kwargs).first() - return target_room - - def create_player(self, name=None, password=None, workfactor=None, location=None, admin=False, sadmin=False, owner=False): - """ Creates a new instance of a Player. - - Keyword arguments: - name -- The name of the new Player instance to be used. - password -- The password that is to be used for the Player. - workfactor -- The work factor # to be used when hasing the Player's password. - location -- The ID or instance of Room that the new Player is to be created at. - admin -- A boolean representing whether or not this new Player is an administrator. - sadmin -- A boolean representing whether or not this new Player is a super administrator. - owner -- A boolean representing whether or not this new Player is an owner. - - """ - if (name is None or password is None or workfactor is None or location is None): - raise exception.WorldArgumentError('All of the arguments to create_player are mandatory! (or None was passed in)') - - if (type(location) is int): - location = self.find_room(id=location) - - player_inventory = self.create_room('%s\'s Inventory' % (name)) - player = Player(name, password, workfactor, location.id, 0, admin=admin, sadmin=sadmin, owner=owner) - player.inventory_id = player_inventory.id - session = sqlalchemy.orm.Session() - session.add(player) - - location.players.append(player) - session.add(location) - - session.add(player_inventory) - session.commit() - - session.refresh(player) - session.refresh(player_inventory) - - player.location = location - player.inventory = player_inventory - player.session = session - player.location.session = session - player.inventory = session - return player - - def create_bot(self, name=None, location=None): - """ Creates a new instance of a Bot. - - Keyword arguments: - name -- The name of the new Player instance to be used. - location -- The ID or instance of Room that the new Player is to be created at. - - """ - if (name is None or location is None): - raise exception.WorldArgumentError('All of the arguments to create_bot are mandatory! (or None was passed in)') - - if (type(location) is int): - location = self.find_room(id=location) - - bot = bot(name, '', location) - session = sqlalchemy.orm.Session() - session.add(bot) - location.bots.append(bot) - session.add(location) - session.commit() - - session.refresh(bot) - - bot.location = location - return bot - - def find_player(self, **kwargs): - """ Locates a Player inside of the ScalyMUCK world. - - This searches the entire WORLD for the specified Player so if you happen to be running a very, very - large world this search will end up getting slow and it is recommended in that case that you try and - use the Room level find_player function whenever possible. - - Keyword arguments (one or the other): - id -- The ID of the Player to locate. This overrides the name if both are specified. - name -- The name of the Player to locate. - - """ - session = sqlalchemy.orm.Session() - target_player = session.query(Player).filter_by(**kwargs).first() - if (target_player is not None): - target_player.location = self.find_room(id=target_player.location_id) - target_player.inventory = self.find_room(id=target_player.inventory_id) - target_player.session = session - - return target_player - - def find_bot(self, **kwargs): - """ Locates a Bot inside of the ScalyMUCK world. - - This searches the entire WORLD for the specified Bot so if you happen to be running a very, very - large world this search will end up getting slow and it is recommended in that case that you try and - use the Room level find_player function whenever possible. - - Keyword arguments (one or the other): - id -- The ID of the Bot to locate. This overrides the name if both are specified. - - """ - session = sqlalchemy.orm.Session() - target_bot = session.query(Bot).filter_by(**kwargs).first() - if (target_bot is not None): - target_bot.location = self.find_room(id=target_bot.location_id) - target_bot.session = session - - return target_bot - - def get_players(self): - """ Returns a list of all Players in the ScalyMUCK world. """ - list = [ ] - session = sqlalchemy.orm.Session() - results = session.query(Player).filter_by() - for player in results: - load_test = self.find_player(id=player.id) - if (load_test is None): - list.append(self.find_player(id=player.id)) - else: - list.append(load_test) - return list - - def find_item(self, **kwargs): - """ Locates an item by any specifications. - - If the ID number does not exist then None is returned. - - """ - session = sqlalchemy.orm.Session() - target_item = session.query(Item).filter_by(**kwargs).first() - if (target_item is not None): - target_item.location = self.find_room(id=target_item.location_id) - target_item.session = session - - return target_item - - def create_item(self, name=None, description='', owner=0, location=None): - """ Creates a new item in the ScalyMUCK world. - - Keyword arguments: - name -- The name of the Item that is to be used. - description -- The description of the Item that is to be used. Default: - owner -- The ID or instance of Player that is to become the owner of this Item. - location -- The ID or instance of Room that is to become the location of this Item. - """ - if (name is None or location is None): - raise exception.WorldArgumentError('Either the name or location was not specified. (or they were None)') - - item = Item(name, description, owner) - if (type(location) is int): - item.location_id = location - item.location = self.find_room(id=location) - else: - item.location = location - item.location_id = location.id - - session = sqlalchemy.orm.Session() - session.add(item) - session.commit() - session.refresh(item) - item.session = session - return item - - def get_rooms(self, **kwargs): - """ Returns all rooms in the database that meet the specified criterion. - - Keyword arguments: - owner -- The owner we are to filter by. If not specified, this filter is not used. - - """ - session = sqlalchemy.orm.Session() - list = [ ] - rooms = session.query(Room).filter_by(**kwargs) - for room in rooms: - list.append[self.find_room(id=room.id)] - - return rooms +""" + This contains "global" functions that is to + be called by the various mods of the MUCK to perform actions such as creating + Players. + + This software is licensed under the MIT license. + Please refer to LICENSE.txt for more information. +""" + +import sqlalchemy.orm +from blinker import signal +from sqlalchemy.exc import OperationalError +from sqlalchemy.orm import sessionmaker, scoped_session + +import exception +from models import Room, Player, Item, Bot + +class FakeConnection: + closed = True + def close(self): + return + +class World(): + """ + + The "singleton" class that represents the ScalyMUCK world in memory and is to + be passed to every mod that actually manages to initialize when loaded. + + """ + engine = None + session = None + fake_connection = FakeConnection() + database_status = signal('database_status') + + def __init__(self, engine=None, server=None): + """ Initializes an instance of the World with an SQLAlchemy engine. """ + self.engine = engine + self.session = scoped_session(sessionmaker(bind=self.engine)) + self.server = server + sqlalchemy.orm.Session = self.session + + def create_room(self, name, description='', owner=0): + """ Creates a new Room if the World. + + Keyword arguments: + description -- The description that is to be used with the new Room instance. + owner -- The ID or instance of Player that is to become the owner of this Room. + + """ + try: + room = Room(name, description, owner) + connection = self.connect() + self.session.add(room) + self.session.commit() + self.session.refresh(room) + room.session = self.session + room.engine = self.engine + connection.close() + return room + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def find_room(self, **kwargs): + """ Locates the specified Room in the ScalyMUCK world. + + This can be a bit computionally intense if you are running a very large world. + + Keyword arguments (one or the other): + id -- The id of the requested room to return an instance of. This overrides the name if both are specified. + name -- The name of the requested room to return an instance of. + + """ + connection = self.connect() + try: + target_room = self.session.query(Room).filter_by(**kwargs).first() + + if (target_room is not None): + target_room.owner = self.session.query(Player).filter_by(id=target_room.owner_id).first() + target_room.session = self.session + target_room.engine = self.engine + + # Iterate and set all of our other custom attributes not defined by SQLAlchemy + for item in target_room.items: + item.location = target_room + item.owner = self.session.query(Player).filter_by(id=item.owner_id).first() + item.session = self.session + item.engine = self.engine + + # NOTE: The above and below create separate Player instances, which hopefully both shouldn't be used within the same context ... + for player in target_room.players: + player.location = target_room + + player.inventory = self.session.query(Room).filter_by(id=player.inventory_id).first() + player.inventory.session = self.session + player.inventory.engine = self.engine + player.session = self.session + player.engine = self.engine + + for bot in target_room.bots: + bot.location = target_room + bot.session = self.session + bot.engine = self.engine + + for exit in target_room.exits: + exit.session = self.session + exit.engine = self.engine + + connection.close() + return target_room + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def create_player(self, name=None, password=None, workfactor=None, location=None, admin=False, sadmin=False, owner=False): + """ Creates a new instance of a Player. + + Keyword arguments: + name -- The name of the new Player instance to be used. + password -- The password that is to be used for the Player. + workfactor -- The work factor # to be used when hasing the Player's password. + location -- The ID or instance of Room that the new Player is to be created at. + admin -- A boolean representing whether or not this new Player is an administrator. + sadmin -- A boolean representing whether or not this new Player is a super administrator. + owner -- A boolean representing whether or not this new Player is an owner. + + """ + if (name is None or password is None or workfactor is None or location is None): + raise exception.WorldArgumentError('All of the arguments to create_player are mandatory! (or None was passed in)') + + try: + if (type(location) is int): + location = self.find_room(id=location) + + player_inventory = self.create_room('%s\'s Inventory' % (name)) + player = Player(name, password, workfactor, location.id, 0, admin=admin, sadmin=sadmin, owner=owner) + player.inventory_id = player_inventory.id + + connection = self.connect() + self.session.add(player) + + location.players.append(player) + self.session.add(location) + + self.session.add(player_inventory) + self.session.commit() + + self.session.refresh(player) + self.session.refresh(player_inventory) + + player.location = location + player.inventory = player_inventory + player.session = self.session + player.engine = self.engine + player.location.session = self.session + player.location.engine = self.engine + player.inventory = player_inventory + player_inventory.session = self.session + player_inventory.engine = self.engine + connection.close() + return player + except exception.DatabaseError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def create_bot(self, name=None, location=None): + """ Creates a new instance of a Bot. + + Keyword arguments: + name -- The name of the new Player instance to be used. + location -- The ID or instance of Room that the new Player is to be created at. + + """ + if (name is None or location is None): + raise exception.WorldArgumentError('All of the arguments to create_bot are mandatory! (or None was passed in)') + + try: + if (type(location) is int): + location = self.find_room(id=location) + + bot = bot(name, '', location) + self.session.add(bot) + location.bots.append(bot) + self.session.add(location) + self.session.commit() + + self.session.refresh(bot) + + bot.location = location + bot.session = self.session + bot.engine = self.engine + return bot + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def find_player(self, **kwargs): + """ Locates a Player inside of the ScalyMUCK world. + + This searches the entire WORLD for the specified Player so if you happen to be running a very, very + large world this search will end up getting slow and it is recommended in that case that you try and + use the Room level find_player function whenever possible. + + Keyword arguments (one or the other): + id -- The ID of the Player to locate. This overrides the name if both are specified. + name -- The name of the Player to locate. + + """ + connection = self.connect() + try: + target_player = self.session.query(Player).filter_by(**kwargs).first() + + if (target_player is not None): + target_player.location = self.find_room(id=target_player.location_id) + target_player.location.session = self.session + target_player.location.engine = self.engine + + target_player.inventory = self.find_room(id=target_player.inventory_id) + target_player.inventory.session = self.session + target_player.inventory.engine = self.engine + + target_player.session = self.session + target_player.engine = self.engine + connection.close() + return target_player + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def find_bot(self, **kwargs): + """ Locates a Bot inside of the ScalyMUCK world. + + This searches the entire WORLD for the specified Bot so if you happen to be running a very, very + large world this search will end up getting slow and it is recommended in that case that you try and + use the Room level find_player function whenever possible. + + Keyword arguments (one or the other): + id -- The ID of the Bot to locate. This overrides the name if both are specified. + + """ + connection = self.connect() + try: + target_bot = self.session.query(Bot).filter_by(**kwargs).first() + + if (target_bot is not None): + target_bot.location = self.find_room(id=target_bot.location_id) + target_bot.session = self.session + target_bot.engine = self.engine + connection.close() + + return target_bot + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def get_players(self): + """ Returns a list of all Players in the ScalyMUCK world. """ + list = [ ] + connection = self.connect() + try: + results = self.session.query(Player).filter_by() + for player in results: + load_test = self.find_player(id=player.id) + if (load_test is None): + list.append(self.find_player(id=player.id)) + else: + load_test.session = self.session + load_test.engine = self.engine + connection.close() + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + return list + + def find_item(self, **kwargs): + """ Locates an item by any specifications. + + If the ID number does not exist then None is returned. + + """ + connection = self.connect() + try: + target_item = self.session.query(Item).filter_by(**kwargs).first() + if (target_item is not None): + target_item.location = self.find_room(id=target_item.location_id) + target_item.session = self.session + target_item.engine = self.engine + target_item.location.session = self.session + target_item.location.engine = self.engine + connection.close() + + return target_item + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def create_item(self, name=None, description='', owner=0, location=None): + """ Creates a new item in the ScalyMUCK world. + + Keyword arguments: + name -- The name of the Item that is to be used. + description -- The description of the Item that is to be used. Default: + owner -- The ID or instance of Player that is to become the owner of this Item. + location -- The ID or instance of Room that is to become the location of this Item. + """ + if (name is None or location is None): + raise exception.WorldArgumentError('Either the name or location was not specified. (or they were None)') + + try: + item = Item(name, description, owner) + if (type(location) is int): + item.location_id = location + item.location = self.find_room(id=location) + else: + item.location = location + item.location_id = location.id + + connection = self.connect() + self.session.add(item) + self.session.commit() + self.session.refresh(item) + item.session = self.session + item.engine = self.engine + connection.close() + return item + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def get_rooms(self, **kwargs): + """ Returns all rooms in the database that meet the specified criterion. + + Keyword arguments: + owner -- The owner we are to filter by. If not specified, this filter is not used. + + """ + try: + connection = self.connect() + rooms = self.session.query(Room).filter_by(**kwargs) + for room in rooms: + room.session = self.session + room.engine = self.engine + + for item in room.items: + item.location = target_room + item.owner = self.session.query(Player).filter_by(id=item.owner_id).first() + item.session = self.session + item.engine = self.engine + + for player in room.players: + player.location = target_room + + player.inventory = self.session.query(Room).filter_by(id=player.inventory_id).first() + player.inventory.session = self.session + player.inventory.engine = self.engine + player.session = self.session + player.engine = self.engine + + for bot in room.bots: + bot.location = target_room + bot.session = self.session + bot.engine = self.engine + + for exit in room.exits: + exit.session = self.session + exit.engine = self.engine + + connection.close() + return rooms + except OperationalError: + self.session.rollback() + self.database_status.send(sender=self, status=False) + + def connect(self): + """ Establishes a connection to the database server. """ + try: + connection = self.engine.connect() + except OperationalError: + self.database_status.send(sender=self, status=False) + return self.fake_connection + else: + return connection diff --git a/application/main.py b/application/main.py index 85b3b0b..241e313 100644 --- a/application/main.py +++ b/application/main.py @@ -1,150 +1,148 @@ -""" - Main file for ScalyMUCK -- you just run this file! - - Copyright (c) 2013 Robert MacGregor - This software is licensed under the GNU General - Public License version 3. Please refer to gpl.txt - for more information. -""" - -import sys -import os -import signal -import string -import logging -from time import strftime - -from daemon import Daemon -from server import Server -from game import settings - -class Application: - """ Main application class. """ - server = None - - def sighandle(self, signum, frame): - """ Used for asynchronous signal handling. """ - print(' ') - print('Killing ScalyMUCK server...') - self.server.shutdown() - logging.shutdown() - - def __init__(self, workdir, is_daemon=False): - """ Called by main() or by the daemoniser code. """ - workdir += '/' - - home_path = os.path.expanduser('~') - data_path = '%s/.scalyMUCK/' % (home_path) - - config = settings.Settings('%sconfig/settings_server.cfg' % (workdir)) - - # Make sure the folder exists. (doesn't cause any issues if it already exists) - os.system('mkdir %s' % (data_path)) - - # Reset any logs - if (config.get_index('ClearLogsOnStart', bool) is True): - if ('win' in sys.platform): - os.system('del %s*.txt' % (data_path)) - elif ('linux' in sys.platform): - os.system('rm %s*.txt' % (data_path)) - - # Prepare the server log - # NOTE: This code looks sucky, could it be improved to look better? - formatting = logging.Formatter('%(levelname)s (%(asctime)s): %(message)s', '%d/%m/%y at %I:%M:%S %p') - console_handle = logging.StreamHandler() - if (config.get_index(index='LogConnections', datatype=bool)): - logger = logging.getLogger('Connections') - logger.setLevel(logging.INFO) - - file_handle = logging.FileHandler('%sconnection_log.txt' % (data_path)) - file_handle.setLevel(logging.DEBUG) - file_handle.setFormatter(formatting) - - logger.info('ScalyMUCK Server Server Start') - logger.addHandler(file_handle) - if (is_daemon is False): - logger.addHandler(console_handle) - - if (config.get_index(index='LogServer', datatype=bool)): - logger = logging.getLogger('Server') - logger.setLevel(logging.INFO) - - file_handle = logging.FileHandler('%sserver_log.txt' % (data_path)) - file_handle.setLevel(logging.INFO) - file_handle.setFormatter(formatting) - - logger.addHandler(file_handle) - if (is_daemon is False): - logger.addHandler(console_handle) - logger.info('ScalyMUCK Server Server Start') - - # Check for if we're trying to run as root (if we're on linux) - if ('linux' in sys.platform): - run_as_root = config.get_index(index='I_DONT_KNOW_HOW_TO_SECURITY_LET_ME_RUN_AS_ROOT',datatype=bool) - if (os.geteuid() == 0 and run_as_root is False): - logger.error('FATAL -- You should not run this application as root!') - return - elif (os.geteuid() == 0 and run_as_root): - logger.warn('WARNING -- It is your death wish running this application as root.') - - # Prepare the other logs - if (config.get_index(index='LogMods', datatype=bool)): - logger = logging.getLogger('Mods') - logger.setLevel(logging.INFO) - - file_handle = logging.FileHandler('%smod_log.txt' % (data_path)) - file_handle.setLevel(logging.INFO) - file_handle.setFormatter(formatting) - - logger.info('ScalyMUCK Server Server Start') - logger.addHandler(file_handle) - if (is_daemon is False): - logger.addHandler(console_handle) - - self.server = Server(config=config, path=data_path, workdir=workdir) - - # Set the signals for asynchronous events - signal.signal(signal.SIGINT, self.sighandle) - signal.signal(signal.SIGTERM, self.sighandle) - - while (self.server.is_running): - self.server.update() - - print(' ') - print('Killing ScalyMUCK server ...') - self.server.shutdown() - logging.shutdown() - -class MUCKDaemon(Daemon): - """ Used for daemonising the code. """ - def run(self, **kwargs): - """ Called by the Daemonizer code. """ - Application(kwargs['workdir'], is_daemon=True) - -def main(): - """ Called in the 'init' if below. """ - command = None - if (len(sys.argv) == 2): - command = string.lower(sys.argv[1]) - if (command != 'start' and command != 'stop' and command != 'restart'): - print('Usage: %s ' % (sys.argv[0])) - return - - if (command is None): - Application(os.getcwd()) - else: - daemon = MUCKDaemon('/tmp/scaly_muck_pid.pid') - if command == 'start': - daemon.start(workdir=os.getcwd()) - elif command == 'stop': - daemon.stop() - elif command == 'restart': - daemon.restart() - else: - print('Usage: %s ' % (sys.argv[0])) - sys.exit(2) - sys.exit(0) - - -if __name__ == '__main__': - main() +""" + Main file for ScalyMUCK -- you just run this file! + + This software is licensed under the MIT license. + Please refer to LICENSE.txt for more information. +""" + +import sys +import os +import signal +import string +import logging +from time import strftime + +from daemon import Daemon +from server import Server +from game import settings + +class Application: + """ Main application class. """ + server = None + + def sighandle(self, signum, frame): + """ Used for asynchronous signal handling. """ + print(' ') + print('Killing ScalyMUCK server...') + self.server.shutdown() + logging.shutdown() + + def __init__(self, workdir, is_daemon=False): + """ Called by main() or by the daemoniser code. """ + workdir += '/' + + home_path = os.path.expanduser('~') + data_path = '%s/.scalyMUCK/' % (home_path) + + config = settings.Settings('%sconfig/settings_server.cfg' % (workdir)) + + # Make sure the folder exists. (doesn't cause any issues if it already exists) + os.system('mkdir %s' % (data_path)) + + # Reset any logs + if (config.get_index('ClearLogsOnStart', bool) is True): + if ('win' in sys.platform): + os.system('del %s*.txt' % (data_path)) + elif ('linux' in sys.platform): + os.system('rm %s*.txt' % (data_path)) + + # Prepare the server log + # NOTE: This code looks sucky, could it be improved to look better? + formatting = logging.Formatter('%(levelname)s (%(asctime)s): %(message)s', '%d/%m/%y at %I:%M:%S %p') + console_handle = logging.StreamHandler() + if (config.get_index(index='LogConnections', datatype=bool)): + logger = logging.getLogger('Connections') + logger.setLevel(logging.INFO) + + file_handle = logging.FileHandler('%sconnection_log.txt' % (data_path)) + file_handle.setLevel(logging.DEBUG) + file_handle.setFormatter(formatting) + + logger.info('ScalyMUCK Server Server Start') + logger.addHandler(file_handle) + if (is_daemon is False): + logger.addHandler(console_handle) + + if (config.get_index(index='LogServer', datatype=bool)): + logger = logging.getLogger('Server') + logger.setLevel(logging.INFO) + + file_handle = logging.FileHandler('%sserver_log.txt' % (data_path)) + file_handle.setLevel(logging.INFO) + file_handle.setFormatter(formatting) + + logger.addHandler(file_handle) + if (is_daemon is False): + logger.addHandler(console_handle) + logger.info('ScalyMUCK Server Server Start') + + # Check for if we're trying to run as root (if we're on linux) + if ('linux' in sys.platform): + run_as_root = config.get_index(index='I_DONT_KNOW_HOW_TO_SECURITY_LET_ME_RUN_AS_ROOT',datatype=bool) + if (os.geteuid() == 0 and run_as_root is False): + logger.error('FATAL -- You should not run this application as root!') + return + elif (os.geteuid() == 0 and run_as_root): + logger.warn('WARNING -- It is your death wish running this application as root.') + + # Prepare the other logs + if (config.get_index(index='LogMods', datatype=bool)): + logger = logging.getLogger('Mods') + logger.setLevel(logging.INFO) + + file_handle = logging.FileHandler('%smod_log.txt' % (data_path)) + file_handle.setLevel(logging.INFO) + file_handle.setFormatter(formatting) + + logger.info('ScalyMUCK Server Server Start') + logger.addHandler(file_handle) + if (is_daemon is False): + logger.addHandler(console_handle) + + self.server = Server(config=config, path=data_path, workdir=workdir) + + # Set the signals for asynchronous events + signal.signal(signal.SIGINT, self.sighandle) + signal.signal(signal.SIGTERM, self.sighandle) + + while (self.server.is_running): + self.server.update() + + print(' ') + print('Killing ScalyMUCK server ...') + self.server.shutdown() + logging.shutdown() + +class MUCKDaemon(Daemon): + """ Used for daemonising the code. """ + def run(self, **kwargs): + """ Called by the Daemonizer code. """ + Application(kwargs['workdir'], is_daemon=True) + +def main(): + """ Called in the 'init' if below. """ + command = None + if (len(sys.argv) == 2): + command = string.lower(sys.argv[1]) + if (command != 'start' and command != 'stop' and command != 'restart'): + print('Usage: %s ' % (sys.argv[0])) + return + + if (command is None): + Application(os.getcwd()) + else: + daemon = MUCKDaemon('/tmp/scaly_muck_pid.pid') + if command == 'start': + daemon.start(workdir=os.getcwd()) + elif command == 'stop': + daemon.stop() + elif command == 'restart': + daemon.restart() + else: + print('Usage: %s ' % (sys.argv[0])) + sys.exit(2) + sys.exit(0) + + +if __name__ == '__main__': + main() diff --git a/application/miniboa/ApacheLicense.txt b/application/miniboa/ApacheLicense.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/application/miniboa/ApacheLicense.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/application/miniboa/__init__.py b/application/miniboa/__init__.py index eec5169..923d15a 100644 --- a/application/miniboa/__init__.py +++ b/application/miniboa/__init__.py @@ -1,18 +1,18 @@ -# -*- coding: utf-8 -*- -#------------------------------------------------------------------------------ -# miniboa/__init__.py -# Copyright 2009 Jim Storch -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain a -# copy of the License at http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -#------------------------------------------------------------------------------ - -from miniboa.async import TelnetServer - - - +# -*- coding: utf-8 -*- +#------------------------------------------------------------------------------ +# miniboa/__init__.py +# Copyright 2009 Jim Storch +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain a +# copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +#------------------------------------------------------------------------------ + +from miniboa.async import TelnetServer + + + diff --git a/application/miniboa/async.py b/application/miniboa/async.py index 8e94a4a..e98dba6 100644 --- a/application/miniboa/async.py +++ b/application/miniboa/async.py @@ -1,191 +1,191 @@ -# -*- coding: utf-8 -*- -#------------------------------------------------------------------------------ -# miniboa/async.py -# Copyright 2009 Jim Storch -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain a -# copy of the License at http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -#------------------------------------------------------------------------------ - -""" -Handle Asynchronous Telnet Connections. -""" - -import socket -import select -import sys - -from miniboa.telnet import TelnetClient -from miniboa.error import BogConnectionLost - -## Cap sockets to 512 on Windows because winsock can only process 512 at time -if sys.platform == 'win32': - MAX_CONNECTIONS = 512 -## Cap sockets to 1000 on Linux because you can only have 1024 file descriptors -else: - MAX_CONNECTIONS = 1000 - - -#-----------------------------------------------------Dummy Connection Handlers - -def _on_connect(client): - """ - Placeholder new connection handler. - """ - print "++ Opened connection to %s, sending greeting..." % client.addrport() - client.send("Greetings from Miniboa! " - " Now it's time to add your code.\n") - -def _on_disconnect(client): - """ - Placeholder lost connection handler. - """ - print "-- Lost connection to %s" % client.addrport() - - -#-----------------------------------------------------------------Telnet Server - -class TelnetServer(object): - """ - Poll sockets for new connections and sending/receiving data from clients. - """ - def __init__(self, port=7777, address='', on_connect=_on_connect, - on_disconnect=_on_disconnect, timeout=0.005): - """ - Create a new Telnet Server. - - port -- Port to listen for new connection on. On UNIX-like platforms, - you made need root access to use ports under 1025. - - address -- Address of the LOCAL network interface to listen on. You - can usually leave this blank unless you want to restrict traffic - to a specific network device. This will usually NOT be the same - as the Internet address of your server. - - on_connect -- function to call with new telnet connections - - on_disconnect -- function to call when a client's connection dies, - either through a terminated session or client.active being set - to False. - - timeout -- amount of time that Poll() will wait from user inport - before returning. Also frees a slice of CPU time. - """ - - self.port = port - self.address = address - self.on_connect = on_connect - self.on_disconnect = on_disconnect - self.timeout = timeout - - server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - try: - server_socket.bind((address, port)) - server_socket.listen(5) - except socket.error, err: - print >> sys.stderr, "Unable to create the server socket:", err - sys.exit(1) - - self.server_socket = server_socket - self.server_fileno = server_socket.fileno() - - ## Dictionary of active clients, - ## key = file descriptor, value = TelnetClient (see miniboa.telnet) - self.clients = {} - - def client_count(self): - """ - Returns the number of active connections. - """ - return len(self.clients) - - def client_list(self): - """ - Returns a list of connected clients. - """ - return self.clients.values() - - - def poll(self): - """ - Perform a non-blocking scan of recv and send states on the server - and client connection sockets. Process new connection requests, - read incomming data, and send outgoing data. Sends and receives may - be partial. - """ - #print len(self.connections) - ## Build a list of connections to test for receive data pending - recv_list = [self.server_fileno] # always add the server - - for client in self.clients.values(): - if client.active: - recv_list.append(client.fileno) - ## Delete inactive connections from the dictionary - else: - #print "-- Lost connection to %s" % client.addrport() - #client.sock.close() - self.on_disconnect(client) - del self.clients[client.fileno] - - - ## Build a list of connections that need to send data - send_list = [] - for client in self.clients.values(): - if client.send_pending: - send_list.append(client.fileno) - - ## Get active socket file descriptors from select.select() - try: - rlist, slist, elist = select.select(recv_list, send_list, [], - self.timeout) - - except select.error, err: - ## If we can't even use select(), game over man, game over - print >> sys.stderr, ("!! FATAL SELECT error '%d:%s'!" - % (err[0], err[1])) - sys.exit(1) - - ## Process socket file descriptors with data to recieve - for sock_fileno in rlist: - - ## If it's coming from the server's socket then this is a new - ## connection request. - if sock_fileno == self.server_fileno: - - try: - sock, addr_tup = self.server_socket.accept() - - except socket.error, err: - print >> sys.stderr, ("!! ACCEPT error '%d:%s'." % - (err[0], err[1])) - continue - - ## Check for maximum connections - if self.client_count() >= MAX_CONNECTIONS: - print '?? Refusing new connection; maximum in use.' - sock.close() - continue - - new_client = TelnetClient(sock, addr_tup) - #print "++ Opened connection to %s" % new_client.addrport() - ## Add the connection to our dictionary and call handler - self.clients[new_client.fileno] = new_client - self.on_connect(new_client) - - else: - ## Call the connection's recieve method - try: - self.clients[sock_fileno].socket_recv() - except BogConnectionLost: - self.clients[sock_fileno].deactivate() - - ## Process sockets with data to send - for sock_fileno in slist: - ## Call the connection's send method - self.clients[sock_fileno].socket_send() +# -*- coding: utf-8 -*- +#------------------------------------------------------------------------------ +# miniboa/async.py +# Copyright 2009 Jim Storch +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain a +# copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +#------------------------------------------------------------------------------ + +""" +Handle Asynchronous Telnet Connections. +""" + +import socket +import select +import sys + +from miniboa.telnet import TelnetClient +from miniboa.error import BogConnectionLost + +## Cap sockets to 512 on Windows because winsock can only process 512 at time +if sys.platform == 'win32': + MAX_CONNECTIONS = 512 +## Cap sockets to 1000 on Linux because you can only have 1024 file descriptors +else: + MAX_CONNECTIONS = 1000 + + +#-----------------------------------------------------Dummy Connection Handlers + +def _on_connect(client): + """ + Placeholder new connection handler. + """ + print "++ Opened connection to %s, sending greeting..." % client.addrport() + client.send("Greetings from Miniboa! " + " Now it's time to add your code.\n") + +def _on_disconnect(client): + """ + Placeholder lost connection handler. + """ + print "-- Lost connection to %s" % client.addrport() + + +#-----------------------------------------------------------------Telnet Server + +class TelnetServer(object): + """ + Poll sockets for new connections and sending/receiving data from clients. + """ + def __init__(self, port=7777, address='', on_connect=_on_connect, + on_disconnect=_on_disconnect, timeout=0.005): + """ + Create a new Telnet Server. + + port -- Port to listen for new connection on. On UNIX-like platforms, + you made need root access to use ports under 1025. + + address -- Address of the LOCAL network interface to listen on. You + can usually leave this blank unless you want to restrict traffic + to a specific network device. This will usually NOT be the same + as the Internet address of your server. + + on_connect -- function to call with new telnet connections + + on_disconnect -- function to call when a client's connection dies, + either through a terminated session or client.active being set + to False. + + timeout -- amount of time that Poll() will wait from user inport + before returning. Also frees a slice of CPU time. + """ + + self.port = port + self.address = address + self.on_connect = on_connect + self.on_disconnect = on_disconnect + self.timeout = timeout + + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + server_socket.bind((address, port)) + server_socket.listen(5) + except socket.error, err: + print >> sys.stderr, "Unable to create the server socket:", err + sys.exit(1) + + self.server_socket = server_socket + self.server_fileno = server_socket.fileno() + + ## Dictionary of active clients, + ## key = file descriptor, value = TelnetClient (see miniboa.telnet) + self.clients = {} + + def client_count(self): + """ + Returns the number of active connections. + """ + return len(self.clients) + + def client_list(self): + """ + Returns a list of connected clients. + """ + return self.clients.values() + + + def poll(self): + """ + Perform a non-blocking scan of recv and send states on the server + and client connection sockets. Process new connection requests, + read incomming data, and send outgoing data. Sends and receives may + be partial. + """ + #print len(self.connections) + ## Build a list of connections to test for receive data pending + recv_list = [self.server_fileno] # always add the server + + for client in self.clients.values(): + if client.active: + recv_list.append(client.fileno) + ## Delete inactive connections from the dictionary + else: + #print "-- Lost connection to %s" % client.addrport() + #client.sock.close() + self.on_disconnect(client) + del self.clients[client.fileno] + + + ## Build a list of connections that need to send data + send_list = [] + for client in self.clients.values(): + if client.send_pending: + send_list.append(client.fileno) + + ## Get active socket file descriptors from select.select() + try: + rlist, slist, elist = select.select(recv_list, send_list, [], + self.timeout) + + except select.error, err: + ## If we can't even use select(), game over man, game over + print >> sys.stderr, ("!! FATAL SELECT error '%d:%s'!" + % (err[0], err[1])) + sys.exit(1) + + ## Process socket file descriptors with data to recieve + for sock_fileno in rlist: + + ## If it's coming from the server's socket then this is a new + ## connection request. + if sock_fileno == self.server_fileno: + + try: + sock, addr_tup = self.server_socket.accept() + + except socket.error, err: + print >> sys.stderr, ("!! ACCEPT error '%d:%s'." % + (err[0], err[1])) + continue + + ## Check for maximum connections + if self.client_count() >= MAX_CONNECTIONS: + print '?? Refusing new connection; maximum in use.' + sock.close() + continue + + new_client = TelnetClient(sock, addr_tup) + #print "++ Opened connection to %s" % new_client.addrport() + ## Add the connection to our dictionary and call handler + self.clients[new_client.fileno] = new_client + self.on_connect(new_client) + + else: + ## Call the connection's recieve method + try: + self.clients[sock_fileno].socket_recv() + except BogConnectionLost: + self.clients[sock_fileno].deactivate() + + ## Process sockets with data to send + for sock_fileno in slist: + ## Call the connection's send method + self.clients[sock_fileno].socket_send() diff --git a/application/miniboa/error.py b/application/miniboa/error.py index d39c212..a637e37 100644 --- a/application/miniboa/error.py +++ b/application/miniboa/error.py @@ -1,19 +1,19 @@ -# -*- coding: utf-8 -*- -#------------------------------------------------------------------------------ -# miniboa/error.py -# Copyright 2009 Jim Storch -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain a -# copy of the License at http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -#------------------------------------------------------------------------------ - -class BogConnectionLost(Exception): - """ - Custom exception to signal a lost connection to the Telnet Server. - """ - pass +# -*- coding: utf-8 -*- +#------------------------------------------------------------------------------ +# miniboa/error.py +# Copyright 2009 Jim Storch +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain a +# copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +#------------------------------------------------------------------------------ + +class BogConnectionLost(Exception): + """ + Custom exception to signal a lost connection to the Telnet Server. + """ + pass diff --git a/application/miniboa/telnet.py b/application/miniboa/telnet.py index 15eb47f..af3ad99 100644 --- a/application/miniboa/telnet.py +++ b/application/miniboa/telnet.py @@ -1,737 +1,737 @@ -# -*- coding: utf-8 -*- -#------------------------------------------------------------------------------ -# miniboa/telnet.py -# Copyright 2009 Jim Storch -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain a -# copy of the License at http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -#------------------------------------------------------------------------------ - -""" -Manage one Telnet client connected via a TCP/IP socket. -""" - -import socket -import time - -from miniboa.error import BogConnectionLost -from miniboa.xterm import colorize -from miniboa.xterm import word_wrap - - -#---[ Telnet Notes ]----------------------------------------------------------- -# (See RFC 854 for more information) -# -# Negotiating a Local Option -# -------------------------- -# -# Side A begins with: -# -# "IAC WILL/WONT XX" Meaning "I would like to [use|not use] option XX." -# -# Side B replies with either: -# -# "IAC DO XX" Meaning "OK, you may use option XX." -# "IAC DONT XX" Meaning "No, you cannot use option XX." -# -# -# Negotiating a Remote Option -# ---------------------------- -# -# Side A begins with: -# -# "IAC DO/DONT XX" Meaning "I would like YOU to [use|not use] option XX." -# -# Side B replies with either: -# -# "IAC WILL XX" Meaning "I will begin using option XX" -# "IAC WONT XX" Meaning "I will not begin using option XX" -# -# -# The syntax is designed so that if both parties receive simultaneous requests -# for the same option, each will see the other's request as a positive -# acknowledgement of it's own. -# -# If a party receives a request to enter a mode that it is already in, the -# request should not be acknowledged. - -## Where you see DE in my comments I mean 'Distant End', e.g. the client. - -UNKNOWN = -1 - -#--[ Telnet Commands ]--------------------------------------------------------- - -SE = chr(240) # End of subnegotiation parameters -NOP = chr(241) # No operation -DATMK = chr(242) # Data stream portion of a sync. -BREAK = chr(243) # NVT Character BRK -IP = chr(244) # Interrupt Process -AO = chr(245) # Abort Output -AYT = chr(246) # Are you there -EC = chr(247) # Erase Character -EL = chr(248) # Erase Line -GA = chr(249) # The Go Ahead Signal -SB = chr(250) # Sub-option to follow -WILL = chr(251) # Will; request or confirm option begin -WONT = chr(252) # Wont; deny option request -DO = chr(253) # Do = Request or confirm remote option -DONT = chr(254) # Don't = Demand or confirm option halt -IAC = chr(255) # Interpret as Command -SEND = chr(001) # Sub-process negotiation SEND command -IS = chr(000) # Sub-process negotiation IS command - -#--[ Telnet Options ]---------------------------------------------------------- - -BINARY = chr( 0) # Transmit Binary -ECHO = chr( 1) # Echo characters back to sender -RECON = chr( 2) # Reconnection -SGA = chr( 3) # Suppress Go-Ahead -TTYPE = chr( 24) # Terminal Type -NAWS = chr( 31) # Negotiate About Window Size -LINEMO = chr( 34) # Line Mode - - -#-----------------------------------------------------------------Telnet Option - -class TelnetOption(object): - """ - Simple class used to track the status of an extended Telnet option. - """ - def __init__(self): - self.local_option = UNKNOWN # Local state of an option - self.remote_option = UNKNOWN # Remote state of an option - self.reply_pending = False # Are we expecting a reply? - - -#------------------------------------------------------------------------Telnet - -class TelnetClient(object): - - """ - Represents a client connection via Telnet. - - First argument is the socket discovered by the Telnet Server. - Second argument is the tuple (ip address, port number). - """ - - def __init__(self, sock, addr_tup): - self.protocol = 'telnet' - self.active = True # Turns False when the connection is lost - self.sock = sock # The connection's socket - self.fileno = sock.fileno() # The socket's file descriptor - self.address = addr_tup[0] # The client's remote TCP/IP address - self.port = addr_tup[1] # The client's remote port - self.terminal_type = 'unknown client' # set via request_terminal_type() - self.use_ansi = True - self.columns = 80 - self.rows = 24 - self.send_pending = False - self.send_buffer = '' - self.recv_buffer = '' - self.bytes_sent = 0 - self.bytes_received = 0 - self.cmd_ready = False - self.command_list = [] - self.connect_time = time.time() - self.last_input_time = time.time() - - ## State variables for interpreting incoming telnet commands - self.telnet_got_iac = False # Are we inside an IAC sequence? - self.telnet_got_cmd = None # Did we get a telnet command? - self.telnet_got_sb = False # Are we inside a subnegotiation? - self.telnet_opt_dict = {} # Mapping for up to 256 TelnetOptions - self.telnet_echo = False # Echo input back to the client? - self.telnet_echo_password = False # Echo back '*' for passwords? - self.telnet_sb_buffer = '' # Buffer for sub-negotiations - -# def __del__(self): - -# print "Telnet destructor called" -# pass - - def get_command(self): - """ - Get a line of text that was received from the DE. The class's - cmd_ready attribute will be true if lines are available. - """ - cmd = None - count = len(self.command_list) - if count > 0: - cmd = self.command_list.pop(0) - ## If that was the last line, turn off lines_pending - if count == 1: - self.cmd_ready = False - return cmd - - def send(self, text): - """ - Send raw text to the distant end. - """ - if text: - self.send_buffer += text.replace('\n', '\r\n') - self.send_pending = True - - def send_cc(self, text): - """ - Send text with caret codes converted to ansi. - """ - self.send(colorize(text, self.use_ansi)) - - def send_wrapped(self, text): - """ - Send text padded and wrapped to the user's screen width. - """ - lines = word_wrap(text, self.columns) - for line in lines: - self.send_cc(line + '\n') - - def deactivate(self): - """ - Set the client to disconnect on the next server poll. - """ - self.active = False - - def addrport(self): - """ - Return the DE's IP address and port number as a string. - """ - return "%s:%s" % (self.address, self.port) - - def idle(self): - """ - Returns the number of seconds that have elasped since the DE - last sent us some input. - """ - return time.time() - self.last_input_time - - def duration(self): - """ - Returns the number of seconds the DE has been connected. - """ - return time.time() - self.connect_time - - def request_do_sga(self): - """ - Request DE to Suppress Go-Ahead. See RFC 858. - """ - self._iac_do(SGA) - self._note_reply_pending(SGA, True) - - def request_will_echo(self): - """ - Tell the DE that we would like to echo their text. See RFC 857. - """ - self._iac_will(ECHO) - self._note_reply_pending(ECHO, True) - self.telnet_echo = True - - def request_wont_echo(self): - """ - Tell the DE that we would like to stop echoing their text. - See RFC 857. - """ - self._iac_wont(ECHO) - self._note_reply_pending(ECHO, True) - self.telnet_echo = False - - def password_mode_on(self): - """ - Tell DE we will echo (but don't) so typed passwords don't show. - """ - self._iac_will(ECHO) - self._note_reply_pending(ECHO, True) - - def password_mode_off(self): - """ - Tell DE we are done echoing (we lied) and show typing again. - """ - self._iac_wont(ECHO) - self._note_reply_pending(ECHO, True) - - def request_naws(self): - """ - Request to Negotiate About Window Size. See RFC 1073. - """ - self._iac_do(NAWS) - self._note_reply_pending(NAWS, True) - - def request_terminal_type(self): - """ - Begins the Telnet negotiations to request the terminal type from - the client. See RFC 779. - """ - self._iac_do(TTYPE) - self._note_reply_pending(TTYPE, True) - - def socket_send(self): - """ - Called by TelnetServer when send data is ready. - """ - if len(self.send_buffer): - try: - sent = self.sock.send(self.send_buffer) - except socket.error, err: - print("!! SEND error '%d:%s' from %s" % (err[0], err[1], - self.addrport())) - self.active = False - return - self.bytes_sent += sent - self.send_buffer = self.send_buffer[sent:] - else: - self.send_pending = False - - def socket_recv(self): - """ - Called by TelnetServer when recv data is ready. - """ - try: - data = self.sock.recv(2048) - except socket.error, ex: - print ("?? socket.recv() error '%d:%s' from %s" % - (ex[0], ex[1], self.addrport())) - raise BogConnectionLost() - - ## Did they close the connection? - size = len(data) - if size == 0: - raise BogConnectionLost() - - ## Update some trackers - self.last_input_time = time.time() - self.bytes_received += size - - ## Test for telnet commands - for byte in data: - self._iac_sniffer(byte) - - ## Look for newline characters to get whole lines from the buffer - while True: - mark = self.recv_buffer.find('\n') - if mark == -1: - break - cmd = self.recv_buffer[:mark].strip() - self.command_list.append(cmd) - self.cmd_ready = True - self.recv_buffer = self.recv_buffer[mark+1:] - - def _recv_byte(self, byte): - """ - Non-printable filtering currently disabled because it did not play - well with extended character sets. - """ - ## Filter out non-printing characters - #if (byte >= ' ' and byte <= '~') or byte == '\n': - if self.telnet_echo: - self._echo_byte(byte) - self.recv_buffer += byte - - def _echo_byte(self, byte): - """ - Echo a character back to the client and convert LF into CR\LF. - """ - if byte == '\n': - self.send_buffer += '\r' - if self.telnet_echo_password: - self.send_buffer += '*' - else: - self.send_buffer += byte - - def _iac_sniffer(self, byte): - """ - Watches incomming data for Telnet IAC sequences. - Passes the data, if any, with the IAC commands stripped to - _recv_byte(). - """ - ## Are we not currently in an IAC sequence coming from the DE? - if self.telnet_got_iac is False: - - if byte == IAC: - ## Well, we are now - self.telnet_got_iac = True - return - - ## Are we currenty in a sub-negotion? - elif self.telnet_got_sb is True: - ## Sanity check on length - if len(self.telnet_sb_buffer) < 64: - self.telnet_sb_buffer += byte - else: - self.telnet_got_sb = False - self.telnet_sb_buffer = "" - return - - else: - ## Just a normal NVT character - self._recv_byte(byte) - return - - ## Byte handling when already in an IAC sequence sent from the DE - - else: - - ## Did we get sent a second IAC? - if byte == IAC and self.telnet_got_sb is True: - ## Must be an escaped 255 (IAC + IAC) - self.telnet_sb_buffer += byte - self.telnet_got_iac = False - return - - ## Do we already have an IAC + CMD? - elif self.telnet_got_cmd: - ## Yes, so handle the option - self._three_byte_cmd(byte) - return - - ## We have IAC but no CMD - else: - - ## Is this the middle byte of a three-byte command? - if byte == DO: - self.telnet_got_cmd = DO - return - - elif byte == DONT: - self.telnet_got_cmd = DONT - return - - elif byte == WILL: - self.telnet_got_cmd = WILL - return - - elif byte == WONT: - self.telnet_got_cmd = WONT - return - - else: - ## Nope, must be a two-byte command - self._two_byte_cmd(byte) - - - def _two_byte_cmd(self, cmd): - """ - Handle incoming Telnet commands that are two bytes long. - """ - #print "got two byte cmd %d" % ord(cmd) - - if cmd == SB: - ## Begin capturing a sub-negotiation string - self.telnet_got_sb = True - self.telnet_sb_buffer = '' - - elif cmd == SE: - ## Stop capturing a sub-negotiation string - self.telnet_got_sb = False - self._sb_decoder() - - elif cmd == NOP: - pass - - elif cmd == DATMK: - pass - - elif cmd == IP: - pass - - elif cmd == AO: - pass - - elif cmd == AYT: - pass - - elif cmd == EC: - pass - - elif cmd == EL: - pass - - elif cmd == GA: - pass - - else: - print "2BC: Should not be here." - - self.telnet_got_iac = False - self.telnet_got_cmd = None - - def _three_byte_cmd(self, option): - """ - Handle incoming Telnet commmands that are three bytes long. - """ - cmd = self.telnet_got_cmd - #print "got three byte cmd %d:%d" % (ord(cmd), ord(option)) - - ## Incoming DO's and DONT's refer to the status of this end - - #---[ DO ]------------------------------------------------------------- - - if cmd == DO: - - if option == BINARY: - - if self._check_reply_pending(BINARY): - self._note_reply_pending(BINARY, False) - self._note_local_option(BINARY, True) - - elif (self._check_local_option(BINARY) is False or - self._check_local_option(BINARY) is UNKNOWN): - self._note_local_option(BINARY, True) - self._iac_will(BINARY) - ## Just nod - - elif option == ECHO: - - if self._check_reply_pending(ECHO): - self._note_reply_pending(ECHO, False) - self._note_local_option(ECHO, True) - - elif (self._check_local_option(ECHO) is False or - self._check_local_option(ECHO) is UNKNOWN): - self._note_local_option(ECHO, True) - self._iac_will(ECHO) - self.telnet_echo = True - - elif option == SGA: - - if self._check_reply_pending(SGA): - self._note_reply_pending(SGA, False) - self._note_local_option(SGA, True) - - elif (self._check_local_option(SGA) is False or - self._check_local_option(SGA) is UNKNOWN): - self._note_local_option(SGA, True) - self._iac_will(SGA) - ## Just nod - - else: - - ## ALL OTHER OTHERS = Default to refusing once - if self._check_local_option(option) is UNKNOWN: - self._note_local_option(option, False) - self._iac_wont(option) - - - #---[ DONT ]----------------------------------------------------------- - - elif cmd == DONT: - - if option == BINARY: - - if self._check_reply_pending(BINARY): - self._note_reply_pending(BINARY, False) - self._note_local_option(BINARY, False) - - elif (self._check_local_option(BINARY) is True or - self._check_local_option(BINARY) is UNKNOWN): - self._note_local_option(BINARY, False) - self._iac_wont(BINARY) - ## Just nod - - elif option == ECHO: - - if self._check_reply_pending(ECHO): - self._note_reply_pending(ECHO, False) - self._note_local_option(ECHO, True) - self.telnet_echo = False - - elif (self._check_local_option(BINARY) is True or - self._check_local_option(BINARY) is UNKNOWN): - self._note_local_option(ECHO, False) - self._iac_wont(ECHO) - self.telnet_echo = False - - elif option == SGA: - - if self._check_reply_pending(SGA): - self._note_reply_pending(SGA, False) - self._note_local_option(SGA, False) - - elif (self._check_remote_option(SGA) is True or - self._check_remote_option(SGA) is UNKNOWN): - self._note_local_option(SGA, False) - self._iac_will(SGA) - ## Just nod - - else: - - ## ALL OTHER OPTIONS = Default to ignoring - pass - - - ## Incoming WILL's and WONT's refer to the status of the DE - - #---[ WILL ]----------------------------------------------------------- - - elif cmd == WILL: - - if option == ECHO: - - ## Nutjob DE offering to echo the server... - if self._check_remote_option(ECHO) is UNKNOWN: - self._note_remote_option(ECHO, False) - # No no, bad DE! - self._iac_dont(ECHO) - - elif option == NAWS: - - if self._check_reply_pending(NAWS): - self._note_reply_pending(NAWS, False) - self._note_remote_option(NAWS, True) - ## Nothing else to do, client follow with SB - - elif (self._check_remote_option(NAWS) is False or - self._check_remote_option(NAWS) is UNKNOWN): - self._note_remote_option(NAWS, True) - self._iac_do(NAWS) - ## Client should respond with SB - - elif option == SGA: - - if self._check_reply_pending(SGA): - self._note_reply_pending(SGA, False) - self._note_remote_option(SGA, True) - - elif (self._check_remote_option(SGA) is False or - self._check_remote_option(SGA) is UNKNOWN): - self._note_remote_option(SGA, True) - self._iac_do(SGA) - ## Just nod - - elif option == TTYPE: - - if self._check_reply_pending(TTYPE): - self._note_reply_pending(TTYPE, False) - self._note_remote_option(TTYPE, True) - ## Tell them to send their terminal type - self.send('%c%c%c%c%c%c' % (IAC, SB, TTYPE, SEND, IAC, SE)) - - elif (self._check_remote_option(TTYPE) is False or - self._check_remote_option(TTYPE) is UNKNOWN): - self._note_remote_option(TTYPE, True) - self._iac_do(TTYPE) - - - #---[ WONT ]----------------------------------------------------------- - - elif cmd == WONT: - - if option == ECHO: - - ## DE states it wont echo us -- good, they're not suppose to. - if self._check_remote_option(ECHO) is UNKNOWN: - self._note_remote_option(ECHO, False) - self._iac_dont(ECHO) - - elif option == SGA: - - if self._check_reply_pending(SGA): - self._note_reply_pending(SGA, False) - self._note_remote_option(SGA, False) - - elif (self._check_remote_option(SGA) is True or - self._check_remote_option(SGA) is UNKNOWN): - self._note_remote_option(SGA, False) - self._iac_dont(SGA) - - if self._check_reply_pending(TTYPE): - self._note_reply_pending(TTYPE, False) - self._note_remote_option(TTYPE, False) - - elif (self._check_remote_option(TTYPE) is True or - self._check_remote_option(TTYPE) is UNKNOWN): - self._note_remote_option(TTYPE, False) - self._iac_dont(TTYPE) - - else: - print "3BC: Should not be here." - - self.telnet_got_iac = False - self.telnet_got_cmd = None - - def _sb_decoder(self): - """ - Figures out what to do with a received sub-negotiation block. - """ - #print "at decoder" - bloc = self.telnet_sb_buffer - if len(bloc) > 2: - - if bloc[0] == TTYPE and bloc[1] == IS: - self.terminal_type = bloc[2:] - #print "Terminal type = '%s'" % self.terminal_type - - if bloc[0] == NAWS: - if len(bloc) != 5: - print "Bad length on NAWS SB:", len(bloc) - else: - self.columns = (256 * ord(bloc[1])) + ord(bloc[2]) - self.rows = (256 * ord(bloc[3])) + ord(bloc[4]) - - #print "Screen is %d x %d" % (self.columns, self.rows) - - self.telnet_sb_buffer = '' - - - #---[ State Juggling for Telnet Options ]---------------------------------- - - ## Sometimes verbiage is tricky. I use 'note' rather than 'set' here - ## because (to me) set infers something happened. - - def _check_local_option(self, option): - """Test the status of local negotiated Telnet options.""" - if not self.telnet_opt_dict.has_key(option): - self.telnet_opt_dict[option] = TelnetOption() - return self.telnet_opt_dict[option].local_option - - def _note_local_option(self, option, state): - """Record the status of local negotiated Telnet options.""" - if not self.telnet_opt_dict.has_key(option): - self.telnet_opt_dict[option] = TelnetOption() - self.telnet_opt_dict[option].local_option = state - - def _check_remote_option(self, option): - """Test the status of remote negotiated Telnet options.""" - if not self.telnet_opt_dict.has_key(option): - self.telnet_opt_dict[option] = TelnetOption() - return self.telnet_opt_dict[option].remote_option - - def _note_remote_option(self, option, state): - """Record the status of local negotiated Telnet options.""" - if not self.telnet_opt_dict.has_key(option): - self.telnet_opt_dict[option] = TelnetOption() - self.telnet_opt_dict[option].remote_option = state - - def _check_reply_pending(self, option): - """Test the status of requested Telnet options.""" - if not self.telnet_opt_dict.has_key(option): - self.telnet_opt_dict[option] = TelnetOption() - return self.telnet_opt_dict[option].reply_pending - - def _note_reply_pending(self, option, state): - """Record the status of requested Telnet options.""" - if not self.telnet_opt_dict.has_key(option): - self.telnet_opt_dict[option] = TelnetOption() - self.telnet_opt_dict[option].reply_pending = state - - - #---[ Telnet Command Shortcuts ]------------------------------------------- - - def _iac_do(self, option): - """Send a Telnet IAC "DO" sequence.""" - self.send('%c%c%c' % (IAC, DO, option)) - - def _iac_dont(self, option): - """Send a Telnet IAC "DONT" sequence.""" - self.send('%c%c%c' % (IAC, DONT, option)) - - def _iac_will(self, option): - """Send a Telnet IAC "WILL" sequence.""" - self.send('%c%c%c' % (IAC, WILL, option)) - - def _iac_wont(self, option): - """Send a Telnet IAC "WONT" sequence.""" - self.send('%c%c%c' % (IAC, WONT, option)) +# -*- coding: utf-8 -*- +#------------------------------------------------------------------------------ +# miniboa/telnet.py +# Copyright 2009 Jim Storch +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain a +# copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +#------------------------------------------------------------------------------ + +""" +Manage one Telnet client connected via a TCP/IP socket. +""" + +import socket +import time + +from miniboa.error import BogConnectionLost +from miniboa.xterm import colorize +from miniboa.xterm import word_wrap + + +#---[ Telnet Notes ]----------------------------------------------------------- +# (See RFC 854 for more information) +# +# Negotiating a Local Option +# -------------------------- +# +# Side A begins with: +# +# "IAC WILL/WONT XX" Meaning "I would like to [use|not use] option XX." +# +# Side B replies with either: +# +# "IAC DO XX" Meaning "OK, you may use option XX." +# "IAC DONT XX" Meaning "No, you cannot use option XX." +# +# +# Negotiating a Remote Option +# ---------------------------- +# +# Side A begins with: +# +# "IAC DO/DONT XX" Meaning "I would like YOU to [use|not use] option XX." +# +# Side B replies with either: +# +# "IAC WILL XX" Meaning "I will begin using option XX" +# "IAC WONT XX" Meaning "I will not begin using option XX" +# +# +# The syntax is designed so that if both parties receive simultaneous requests +# for the same option, each will see the other's request as a positive +# acknowledgement of it's own. +# +# If a party receives a request to enter a mode that it is already in, the +# request should not be acknowledged. + +## Where you see DE in my comments I mean 'Distant End', e.g. the client. + +UNKNOWN = -1 + +#--[ Telnet Commands ]--------------------------------------------------------- + +SE = chr(240) # End of subnegotiation parameters +NOP = chr(241) # No operation +DATMK = chr(242) # Data stream portion of a sync. +BREAK = chr(243) # NVT Character BRK +IP = chr(244) # Interrupt Process +AO = chr(245) # Abort Output +AYT = chr(246) # Are you there +EC = chr(247) # Erase Character +EL = chr(248) # Erase Line +GA = chr(249) # The Go Ahead Signal +SB = chr(250) # Sub-option to follow +WILL = chr(251) # Will; request or confirm option begin +WONT = chr(252) # Wont; deny option request +DO = chr(253) # Do = Request or confirm remote option +DONT = chr(254) # Don't = Demand or confirm option halt +IAC = chr(255) # Interpret as Command +SEND = chr(001) # Sub-process negotiation SEND command +IS = chr(000) # Sub-process negotiation IS command + +#--[ Telnet Options ]---------------------------------------------------------- + +BINARY = chr( 0) # Transmit Binary +ECHO = chr( 1) # Echo characters back to sender +RECON = chr( 2) # Reconnection +SGA = chr( 3) # Suppress Go-Ahead +TTYPE = chr( 24) # Terminal Type +NAWS = chr( 31) # Negotiate About Window Size +LINEMO = chr( 34) # Line Mode + + +#-----------------------------------------------------------------Telnet Option + +class TelnetOption(object): + """ + Simple class used to track the status of an extended Telnet option. + """ + def __init__(self): + self.local_option = UNKNOWN # Local state of an option + self.remote_option = UNKNOWN # Remote state of an option + self.reply_pending = False # Are we expecting a reply? + + +#------------------------------------------------------------------------Telnet + +class TelnetClient(object): + + """ + Represents a client connection via Telnet. + + First argument is the socket discovered by the Telnet Server. + Second argument is the tuple (ip address, port number). + """ + + def __init__(self, sock, addr_tup): + self.protocol = 'telnet' + self.active = True # Turns False when the connection is lost + self.sock = sock # The connection's socket + self.fileno = sock.fileno() # The socket's file descriptor + self.address = addr_tup[0] # The client's remote TCP/IP address + self.port = addr_tup[1] # The client's remote port + self.terminal_type = 'unknown client' # set via request_terminal_type() + self.use_ansi = True + self.columns = 80 + self.rows = 24 + self.send_pending = False + self.send_buffer = '' + self.recv_buffer = '' + self.bytes_sent = 0 + self.bytes_received = 0 + self.cmd_ready = False + self.command_list = [] + self.connect_time = time.time() + self.last_input_time = time.time() + + ## State variables for interpreting incoming telnet commands + self.telnet_got_iac = False # Are we inside an IAC sequence? + self.telnet_got_cmd = None # Did we get a telnet command? + self.telnet_got_sb = False # Are we inside a subnegotiation? + self.telnet_opt_dict = {} # Mapping for up to 256 TelnetOptions + self.telnet_echo = False # Echo input back to the client? + self.telnet_echo_password = False # Echo back '*' for passwords? + self.telnet_sb_buffer = '' # Buffer for sub-negotiations + +# def __del__(self): + +# print "Telnet destructor called" +# pass + + def get_command(self): + """ + Get a line of text that was received from the DE. The class's + cmd_ready attribute will be true if lines are available. + """ + cmd = None + count = len(self.command_list) + if count > 0: + cmd = self.command_list.pop(0) + ## If that was the last line, turn off lines_pending + if count == 1: + self.cmd_ready = False + return cmd + + def send(self, text): + """ + Send raw text to the distant end. + """ + if text: + self.send_buffer += text.replace('\n', '\r\n') + self.send_pending = True + + def send_cc(self, text): + """ + Send text with caret codes converted to ansi. + """ + self.send(colorize(text, self.use_ansi)) + + def send_wrapped(self, text): + """ + Send text padded and wrapped to the user's screen width. + """ + lines = word_wrap(text, self.columns) + for line in lines: + self.send_cc(line + '\n') + + def deactivate(self): + """ + Set the client to disconnect on the next server poll. + """ + self.active = False + + def addrport(self): + """ + Return the DE's IP address and port number as a string. + """ + return "%s:%s" % (self.address, self.port) + + def idle(self): + """ + Returns the number of seconds that have elasped since the DE + last sent us some input. + """ + return time.time() - self.last_input_time + + def duration(self): + """ + Returns the number of seconds the DE has been connected. + """ + return time.time() - self.connect_time + + def request_do_sga(self): + """ + Request DE to Suppress Go-Ahead. See RFC 858. + """ + self._iac_do(SGA) + self._note_reply_pending(SGA, True) + + def request_will_echo(self): + """ + Tell the DE that we would like to echo their text. See RFC 857. + """ + self._iac_will(ECHO) + self._note_reply_pending(ECHO, True) + self.telnet_echo = True + + def request_wont_echo(self): + """ + Tell the DE that we would like to stop echoing their text. + See RFC 857. + """ + self._iac_wont(ECHO) + self._note_reply_pending(ECHO, True) + self.telnet_echo = False + + def password_mode_on(self): + """ + Tell DE we will echo (but don't) so typed passwords don't show. + """ + self._iac_will(ECHO) + self._note_reply_pending(ECHO, True) + + def password_mode_off(self): + """ + Tell DE we are done echoing (we lied) and show typing again. + """ + self._iac_wont(ECHO) + self._note_reply_pending(ECHO, True) + + def request_naws(self): + """ + Request to Negotiate About Window Size. See RFC 1073. + """ + self._iac_do(NAWS) + self._note_reply_pending(NAWS, True) + + def request_terminal_type(self): + """ + Begins the Telnet negotiations to request the terminal type from + the client. See RFC 779. + """ + self._iac_do(TTYPE) + self._note_reply_pending(TTYPE, True) + + def socket_send(self): + """ + Called by TelnetServer when send data is ready. + """ + if len(self.send_buffer): + try: + sent = self.sock.send(self.send_buffer) + except socket.error, err: + print("!! SEND error '%d:%s' from %s" % (err[0], err[1], + self.addrport())) + self.active = False + return + self.bytes_sent += sent + self.send_buffer = self.send_buffer[sent:] + else: + self.send_pending = False + + def socket_recv(self): + """ + Called by TelnetServer when recv data is ready. + """ + try: + data = self.sock.recv(2048) + except socket.error, ex: + print ("?? socket.recv() error '%d:%s' from %s" % + (ex[0], ex[1], self.addrport())) + raise BogConnectionLost() + + ## Did they close the connection? + size = len(data) + if size == 0: + raise BogConnectionLost() + + ## Update some trackers + self.last_input_time = time.time() + self.bytes_received += size + + ## Test for telnet commands + for byte in data: + self._iac_sniffer(byte) + + ## Look for newline characters to get whole lines from the buffer + while True: + mark = self.recv_buffer.find('\n') + if mark == -1: + break + cmd = self.recv_buffer[:mark].strip() + self.command_list.append(cmd) + self.cmd_ready = True + self.recv_buffer = self.recv_buffer[mark+1:] + + def _recv_byte(self, byte): + """ + Non-printable filtering currently disabled because it did not play + well with extended character sets. + """ + ## Filter out non-printing characters + #if (byte >= ' ' and byte <= '~') or byte == '\n': + if self.telnet_echo: + self._echo_byte(byte) + self.recv_buffer += byte + + def _echo_byte(self, byte): + """ + Echo a character back to the client and convert LF into CR\LF. + """ + if byte == '\n': + self.send_buffer += '\r' + if self.telnet_echo_password: + self.send_buffer += '*' + else: + self.send_buffer += byte + + def _iac_sniffer(self, byte): + """ + Watches incomming data for Telnet IAC sequences. + Passes the data, if any, with the IAC commands stripped to + _recv_byte(). + """ + ## Are we not currently in an IAC sequence coming from the DE? + if self.telnet_got_iac is False: + + if byte == IAC: + ## Well, we are now + self.telnet_got_iac = True + return + + ## Are we currenty in a sub-negotion? + elif self.telnet_got_sb is True: + ## Sanity check on length + if len(self.telnet_sb_buffer) < 64: + self.telnet_sb_buffer += byte + else: + self.telnet_got_sb = False + self.telnet_sb_buffer = "" + return + + else: + ## Just a normal NVT character + self._recv_byte(byte) + return + + ## Byte handling when already in an IAC sequence sent from the DE + + else: + + ## Did we get sent a second IAC? + if byte == IAC and self.telnet_got_sb is True: + ## Must be an escaped 255 (IAC + IAC) + self.telnet_sb_buffer += byte + self.telnet_got_iac = False + return + + ## Do we already have an IAC + CMD? + elif self.telnet_got_cmd: + ## Yes, so handle the option + self._three_byte_cmd(byte) + return + + ## We have IAC but no CMD + else: + + ## Is this the middle byte of a three-byte command? + if byte == DO: + self.telnet_got_cmd = DO + return + + elif byte == DONT: + self.telnet_got_cmd = DONT + return + + elif byte == WILL: + self.telnet_got_cmd = WILL + return + + elif byte == WONT: + self.telnet_got_cmd = WONT + return + + else: + ## Nope, must be a two-byte command + self._two_byte_cmd(byte) + + + def _two_byte_cmd(self, cmd): + """ + Handle incoming Telnet commands that are two bytes long. + """ + #print "got two byte cmd %d" % ord(cmd) + + if cmd == SB: + ## Begin capturing a sub-negotiation string + self.telnet_got_sb = True + self.telnet_sb_buffer = '' + + elif cmd == SE: + ## Stop capturing a sub-negotiation string + self.telnet_got_sb = False + self._sb_decoder() + + elif cmd == NOP: + pass + + elif cmd == DATMK: + pass + + elif cmd == IP: + pass + + elif cmd == AO: + pass + + elif cmd == AYT: + pass + + elif cmd == EC: + pass + + elif cmd == EL: + pass + + elif cmd == GA: + pass + + else: + print "2BC: Should not be here." + + self.telnet_got_iac = False + self.telnet_got_cmd = None + + def _three_byte_cmd(self, option): + """ + Handle incoming Telnet commmands that are three bytes long. + """ + cmd = self.telnet_got_cmd + #print "got three byte cmd %d:%d" % (ord(cmd), ord(option)) + + ## Incoming DO's and DONT's refer to the status of this end + + #---[ DO ]------------------------------------------------------------- + + if cmd == DO: + + if option == BINARY: + + if self._check_reply_pending(BINARY): + self._note_reply_pending(BINARY, False) + self._note_local_option(BINARY, True) + + elif (self._check_local_option(BINARY) is False or + self._check_local_option(BINARY) is UNKNOWN): + self._note_local_option(BINARY, True) + self._iac_will(BINARY) + ## Just nod + + elif option == ECHO: + + if self._check_reply_pending(ECHO): + self._note_reply_pending(ECHO, False) + self._note_local_option(ECHO, True) + + elif (self._check_local_option(ECHO) is False or + self._check_local_option(ECHO) is UNKNOWN): + self._note_local_option(ECHO, True) + self._iac_will(ECHO) + self.telnet_echo = True + + elif option == SGA: + + if self._check_reply_pending(SGA): + self._note_reply_pending(SGA, False) + self._note_local_option(SGA, True) + + elif (self._check_local_option(SGA) is False or + self._check_local_option(SGA) is UNKNOWN): + self._note_local_option(SGA, True) + self._iac_will(SGA) + ## Just nod + + else: + + ## ALL OTHER OTHERS = Default to refusing once + if self._check_local_option(option) is UNKNOWN: + self._note_local_option(option, False) + self._iac_wont(option) + + + #---[ DONT ]----------------------------------------------------------- + + elif cmd == DONT: + + if option == BINARY: + + if self._check_reply_pending(BINARY): + self._note_reply_pending(BINARY, False) + self._note_local_option(BINARY, False) + + elif (self._check_local_option(BINARY) is True or + self._check_local_option(BINARY) is UNKNOWN): + self._note_local_option(BINARY, False) + self._iac_wont(BINARY) + ## Just nod + + elif option == ECHO: + + if self._check_reply_pending(ECHO): + self._note_reply_pending(ECHO, False) + self._note_local_option(ECHO, True) + self.telnet_echo = False + + elif (self._check_local_option(BINARY) is True or + self._check_local_option(BINARY) is UNKNOWN): + self._note_local_option(ECHO, False) + self._iac_wont(ECHO) + self.telnet_echo = False + + elif option == SGA: + + if self._check_reply_pending(SGA): + self._note_reply_pending(SGA, False) + self._note_local_option(SGA, False) + + elif (self._check_remote_option(SGA) is True or + self._check_remote_option(SGA) is UNKNOWN): + self._note_local_option(SGA, False) + self._iac_will(SGA) + ## Just nod + + else: + + ## ALL OTHER OPTIONS = Default to ignoring + pass + + + ## Incoming WILL's and WONT's refer to the status of the DE + + #---[ WILL ]----------------------------------------------------------- + + elif cmd == WILL: + + if option == ECHO: + + ## Nutjob DE offering to echo the server... + if self._check_remote_option(ECHO) is UNKNOWN: + self._note_remote_option(ECHO, False) + # No no, bad DE! + self._iac_dont(ECHO) + + elif option == NAWS: + + if self._check_reply_pending(NAWS): + self._note_reply_pending(NAWS, False) + self._note_remote_option(NAWS, True) + ## Nothing else to do, client follow with SB + + elif (self._check_remote_option(NAWS) is False or + self._check_remote_option(NAWS) is UNKNOWN): + self._note_remote_option(NAWS, True) + self._iac_do(NAWS) + ## Client should respond with SB + + elif option == SGA: + + if self._check_reply_pending(SGA): + self._note_reply_pending(SGA, False) + self._note_remote_option(SGA, True) + + elif (self._check_remote_option(SGA) is False or + self._check_remote_option(SGA) is UNKNOWN): + self._note_remote_option(SGA, True) + self._iac_do(SGA) + ## Just nod + + elif option == TTYPE: + + if self._check_reply_pending(TTYPE): + self._note_reply_pending(TTYPE, False) + self._note_remote_option(TTYPE, True) + ## Tell them to send their terminal type + self.send('%c%c%c%c%c%c' % (IAC, SB, TTYPE, SEND, IAC, SE)) + + elif (self._check_remote_option(TTYPE) is False or + self._check_remote_option(TTYPE) is UNKNOWN): + self._note_remote_option(TTYPE, True) + self._iac_do(TTYPE) + + + #---[ WONT ]----------------------------------------------------------- + + elif cmd == WONT: + + if option == ECHO: + + ## DE states it wont echo us -- good, they're not suppose to. + if self._check_remote_option(ECHO) is UNKNOWN: + self._note_remote_option(ECHO, False) + self._iac_dont(ECHO) + + elif option == SGA: + + if self._check_reply_pending(SGA): + self._note_reply_pending(SGA, False) + self._note_remote_option(SGA, False) + + elif (self._check_remote_option(SGA) is True or + self._check_remote_option(SGA) is UNKNOWN): + self._note_remote_option(SGA, False) + self._iac_dont(SGA) + + if self._check_reply_pending(TTYPE): + self._note_reply_pending(TTYPE, False) + self._note_remote_option(TTYPE, False) + + elif (self._check_remote_option(TTYPE) is True or + self._check_remote_option(TTYPE) is UNKNOWN): + self._note_remote_option(TTYPE, False) + self._iac_dont(TTYPE) + + else: + print "3BC: Should not be here." + + self.telnet_got_iac = False + self.telnet_got_cmd = None + + def _sb_decoder(self): + """ + Figures out what to do with a received sub-negotiation block. + """ + #print "at decoder" + bloc = self.telnet_sb_buffer + if len(bloc) > 2: + + if bloc[0] == TTYPE and bloc[1] == IS: + self.terminal_type = bloc[2:] + #print "Terminal type = '%s'" % self.terminal_type + + if bloc[0] == NAWS: + if len(bloc) != 5: + print "Bad length on NAWS SB:", len(bloc) + else: + self.columns = (256 * ord(bloc[1])) + ord(bloc[2]) + self.rows = (256 * ord(bloc[3])) + ord(bloc[4]) + + #print "Screen is %d x %d" % (self.columns, self.rows) + + self.telnet_sb_buffer = '' + + + #---[ State Juggling for Telnet Options ]---------------------------------- + + ## Sometimes verbiage is tricky. I use 'note' rather than 'set' here + ## because (to me) set infers something happened. + + def _check_local_option(self, option): + """Test the status of local negotiated Telnet options.""" + if not self.telnet_opt_dict.has_key(option): + self.telnet_opt_dict[option] = TelnetOption() + return self.telnet_opt_dict[option].local_option + + def _note_local_option(self, option, state): + """Record the status of local negotiated Telnet options.""" + if not self.telnet_opt_dict.has_key(option): + self.telnet_opt_dict[option] = TelnetOption() + self.telnet_opt_dict[option].local_option = state + + def _check_remote_option(self, option): + """Test the status of remote negotiated Telnet options.""" + if not self.telnet_opt_dict.has_key(option): + self.telnet_opt_dict[option] = TelnetOption() + return self.telnet_opt_dict[option].remote_option + + def _note_remote_option(self, option, state): + """Record the status of local negotiated Telnet options.""" + if not self.telnet_opt_dict.has_key(option): + self.telnet_opt_dict[option] = TelnetOption() + self.telnet_opt_dict[option].remote_option = state + + def _check_reply_pending(self, option): + """Test the status of requested Telnet options.""" + if not self.telnet_opt_dict.has_key(option): + self.telnet_opt_dict[option] = TelnetOption() + return self.telnet_opt_dict[option].reply_pending + + def _note_reply_pending(self, option, state): + """Record the status of requested Telnet options.""" + if not self.telnet_opt_dict.has_key(option): + self.telnet_opt_dict[option] = TelnetOption() + self.telnet_opt_dict[option].reply_pending = state + + + #---[ Telnet Command Shortcuts ]------------------------------------------- + + def _iac_do(self, option): + """Send a Telnet IAC "DO" sequence.""" + self.send('%c%c%c' % (IAC, DO, option)) + + def _iac_dont(self, option): + """Send a Telnet IAC "DONT" sequence.""" + self.send('%c%c%c' % (IAC, DONT, option)) + + def _iac_will(self, option): + """Send a Telnet IAC "WILL" sequence.""" + self.send('%c%c%c' % (IAC, WILL, option)) + + def _iac_wont(self, option): + """Send a Telnet IAC "WONT" sequence.""" + self.send('%c%c%c' % (IAC, WONT, option)) diff --git a/application/miniboa/xterm.py b/application/miniboa/xterm.py index bdce8b2..5f5ffc1 100644 --- a/application/miniboa/xterm.py +++ b/application/miniboa/xterm.py @@ -1,110 +1,110 @@ -# -*- coding: utf-8 -*- -#------------------------------------------------------------------------------ -# mudlib/usr/xterm.py -# Copyright 2009 Jim Storch -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain a -# copy of the License at http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -#------------------------------------------------------------------------------ - -""" -Support for color and formatting for Xterm style clients. -""" - -import re - - -_PARA_BREAK = re.compile(r"(\n\s*\n)", re.MULTILINE) - -#--[ Caret Code to ANSI TABLE ]------------------------------------------------ - -_ANSI_CODES = ( - ( '^k', '\x1b[22;30m' ), # black - ( '^K', '\x1b[1;30m' ), # bright black (grey) - ( '^r', '\x1b[22;31m' ), # red - ( '^R', '\x1b[1;31m' ), # bright red - ( '^g', '\x1b[22;32m' ), # green - ( '^G', '\x1b[1;32m' ), # bright green - ( '^y', '\x1b[22;33m' ), # yellow - ( '^Y', '\x1b[1;33m' ), # bright yellow - ( '^b', '\x1b[22;34m' ), # blue - ( '^B', '\x1b[1;34m' ), # bright blue - ( '^m', '\x1b[22;35m' ), # magenta - ( '^M', '\x1b[1;35m' ), # bright magenta - ( '^c', '\x1b[22;36m' ), # cyan - ( '^C', '\x1b[1;36m' ), # bright cyan - ( '^w', '\x1b[22;37m' ), # white - ( '^W', '\x1b[1;37m' ), # bright white - ( '^0', '\x1b[40m' ), # black background - ( '^1', '\x1b[41m' ), # red background - ( '^2', '\x1b[42m' ), # green background - ( '^3', '\x1b[43m' ), # yellow background - ( '^4', '\x1b[44m' ), # blue background - ( '^5', '\x1b[45m' ), # magenta background - ( '^6', '\x1b[46m' ), # cyan background - ( '^d', '\x1b[39m' ), # default (should be white on black) - ( '^I', '\x1b[7m' ), # inverse text on - ( '^i', '\x1b[27m' ), # inverse text off - ( '^~', '\x1b[0m' ), # reset all - ( '^U', '\x1b[4m' ), # underline on - ( '^u', '\x1b[24m' ), # underline off - ( '^!', '\x1b[1m' ), # bold on - ( '^.', '\x1b[22m'), # bold off - ( '^s', '\x1b[2J'), # clear screen - ( '^l', '\x1b[2K'), # clear to end of line - ) - - -def strip_caret_codes(text): - """ - Strip out any caret codes from a string. - """ - ## temporarily escape out ^^ - text = text.replace('^^', '\x00') - for token, foo in _ANSI_CODES: - text = text.replace(token, '') - return text.replace('\x00', '^') - - -def colorize(text, ansi=True): - """ - If the client wants ansi, replace the tokens with ansi sequences -- - otherwise, simply strip them out. - """ - if ansi: - text = text.replace('^^', '\x00') - for token, code in _ANSI_CODES: - text = text.replace(token, code) - text = text.replace('\x00', '^') - else: - text = strip_caret_codes(text) - return text - - -def word_wrap(text, columns=80, indent=4, padding=2): - """ - Given a block of text, breaks into a list of lines wrapped to - length. - """ - paragraphs = _PARA_BREAK.split(text) - lines = [] - columns -= padding - for para in paragraphs: - if para.isspace(): - continue - line = ' ' * indent - for word in para.split(): - if (len(line) + 1 + len(word)) > columns: - lines.append(line) - line = ' ' * padding - line += word - else: - line += ' ' + word - if not line.isspace(): - lines.append(line) - return lines +# -*- coding: utf-8 -*- +#------------------------------------------------------------------------------ +# mudlib/usr/xterm.py +# Copyright 2009 Jim Storch +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain a +# copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +#------------------------------------------------------------------------------ + +""" +Support for color and formatting for Xterm style clients. +""" + +import re + + +_PARA_BREAK = re.compile(r"(\n\s*\n)", re.MULTILINE) + +#--[ Caret Code to ANSI TABLE ]------------------------------------------------ + +_ANSI_CODES = ( + ( '^k', '\x1b[22;30m' ), # black + ( '^K', '\x1b[1;30m' ), # bright black (grey) + ( '^r', '\x1b[22;31m' ), # red + ( '^R', '\x1b[1;31m' ), # bright red + ( '^g', '\x1b[22;32m' ), # green + ( '^G', '\x1b[1;32m' ), # bright green + ( '^y', '\x1b[22;33m' ), # yellow + ( '^Y', '\x1b[1;33m' ), # bright yellow + ( '^b', '\x1b[22;34m' ), # blue + ( '^B', '\x1b[1;34m' ), # bright blue + ( '^m', '\x1b[22;35m' ), # magenta + ( '^M', '\x1b[1;35m' ), # bright magenta + ( '^c', '\x1b[22;36m' ), # cyan + ( '^C', '\x1b[1;36m' ), # bright cyan + ( '^w', '\x1b[22;37m' ), # white + ( '^W', '\x1b[1;37m' ), # bright white + ( '^0', '\x1b[40m' ), # black background + ( '^1', '\x1b[41m' ), # red background + ( '^2', '\x1b[42m' ), # green background + ( '^3', '\x1b[43m' ), # yellow background + ( '^4', '\x1b[44m' ), # blue background + ( '^5', '\x1b[45m' ), # magenta background + ( '^6', '\x1b[46m' ), # cyan background + ( '^d', '\x1b[39m' ), # default (should be white on black) + ( '^I', '\x1b[7m' ), # inverse text on + ( '^i', '\x1b[27m' ), # inverse text off + ( '^~', '\x1b[0m' ), # reset all + ( '^U', '\x1b[4m' ), # underline on + ( '^u', '\x1b[24m' ), # underline off + ( '^!', '\x1b[1m' ), # bold on + ( '^.', '\x1b[22m'), # bold off + ( '^s', '\x1b[2J'), # clear screen + ( '^l', '\x1b[2K'), # clear to end of line + ) + + +def strip_caret_codes(text): + """ + Strip out any caret codes from a string. + """ + ## temporarily escape out ^^ + text = text.replace('^^', '\x00') + for token, foo in _ANSI_CODES: + text = text.replace(token, '') + return text.replace('\x00', '^') + + +def colorize(text, ansi=True): + """ + If the client wants ansi, replace the tokens with ansi sequences -- + otherwise, simply strip them out. + """ + if ansi: + text = text.replace('^^', '\x00') + for token, code in _ANSI_CODES: + text = text.replace(token, code) + text = text.replace('\x00', '^') + else: + text = strip_caret_codes(text) + return text + + +def word_wrap(text, columns=80, indent=4, padding=2): + """ + Given a block of text, breaks into a list of lines wrapped to + length. + """ + paragraphs = _PARA_BREAK.split(text) + lines = [] + columns -= padding + for para in paragraphs: + if para.isspace(): + continue + line = ' ' * indent + for word in para.split(): + if (len(line) + 1 + len(word)) > columns: + lines.append(line) + line = ' ' * padding + line += word + else: + line += ' ' + word + if not line.isspace(): + lines.append(line) + return lines diff --git a/application/server.py b/application/server.py index 0f83ad3..7991670 100644 --- a/application/server.py +++ b/application/server.py @@ -1,270 +1,324 @@ -""" - This is the main ScalyMUCK server code, - it performs the initialisation of various - systems and is the binding of everything. - - Copyright (c) 2013 Robert MacGregor - This software is licensed under the GNU General - Public License version 3. Please refer to gpl.txt - for more information. -""" - -import sys -import string -import logging - -import bcrypt -from blinker import signal -from miniboa import TelnetServer -from sqlalchemy import create_engine -from sqlalchemy.exc import OperationalError -from sqlalchemy.engine.reflection import Inspector - -import daemon -import game.models -from game import interface, world - -class Server(daemon.Daemon): - """ - - Server class that is initialized by the main.py script to act as the MUCK server. - It performs all of the core functions of the MUCK server, which is mainly accepting - connections and performing the login sequence before giving them access to the - server and its world. - - """ - is_running = False - telnet_server = None - logger = None - connection_logger = None - world = None - interface = None - work_factor = 10 - - welcome_message_data = 'Unable to load welcome message!\n' - exit_message_data = 'Unable to load exit message!\n' - - pending_connection_list = [ ] - established_connection_list = [ ] - - post_client_connect = signal('post_client_connect') - pre_client_disconnect = signal('pre_client_disconnect') - post_client_authenticated = signal('post_client_authenticated') - world_tick = signal('world_tick') - - auth_low_argc = None - auth_invalid_combination = None - auth_connected = None - auth_replace_Connection = None - auth_connection_replaced = None - auth_connect_suggestion = None - game_client_disconnect = None - - def __init__(self, config=None, path=None, workdir=None): - """ The server class is created and managed by the main.py script. - - When created, the server automatically initiates a Telnet server provided - by Miniboa which immediately listens for incoming connections and will query - them for login details upon connection. - - Keyword arguments: - config -- An instance of game.Settings that is to be used when loading configuration data. - path -- The data path that all permenant data should be written to. - workdir -- The current working directory of the server. This should be an absolute path to application/. - - """ - self.connection_logger = logging.getLogger('Connections') - self.logger = logging.getLogger('Server') - - # Loading all of the configuration variables - database_type = string.lower(config.get_index(index='DatabaseType', datatype=str)) - database = config.get_index(index='DatabaseName', datatype=str) - user = config.get_index(index='DatabaseUser', datatype=str) - password = config.get_index(index='DatabasePassword', datatype=str) - self.work_factor = config.get_index(index='WorkFactor', datatype=int) - if (database_type == 'sqlite'): - database_location = path + config.get_index(index='TargetDatabase', datatype=str) - else: - database_location = config.get_index(index='TargetDatabase', datatype=str) - - # Load server messages - self.auth_low_argc = config.get_index('AuthLowArgC', str) - self.auth_invalid_combination = config.get_index('AuthInvalidCombination', str) - self.auth_connected = config.get_index('AuthConnected', str) - self.auth_replace_connection = config.get_index('AuthReplaceConnection', str) - self.auth_connection_replaced = config.get_index('AuthConnectionReplaced', str) - self.auth_connect_suggestion = config.get_index('AuthConnectSuggestion', str).replace('\\n','\n') - self.auth_replace_connection_global = config.get_index('AuthReplaceConnectionGlobal', str) - self.game_client_disconnect = config.get_index('GameClientDisconnect', str) - - # Loading welcome/exit messages - with open(workdir + 'config/welcome_message.txt') as f: - self.welcome_message_data = f.read() + '\n' - with open(workdir + 'config/exit_message.txt') as f: - self.exit_message_data = f.read() + '\n' - - # Connect/Create our database is required - database_exists = True - if (database_type == 'sqlite'): - try: - with open(database_location) as f: pass - except IOError as e: - self.logger.info('This appears to be your first time running the ScalyMUCK server. We must initialise your database ...') - database_exists = False - - database_engine = create_engine('sqlite:////%s' % (database_location), echo=False) - else: - url = database_type + '://' + user + ':' + password + '@' + database_location + '/' + database - try: - database_engine = create_engine(url, echo=False) - connection = database_engine.connect() - except OperationalError as e: - self.logger.error(str(e)) - self.logger.error('URL: ' + url) - self.is_running = False - return - - self.world = world.World(database_engine) - self.interface = interface.Interface(config=config, world=self.world, workdir=workdir, session=self.world.session, server=self) - game.models.Base.metadata.create_all(database_engine) - - # Check to see if our root user exists - if (database_type != 'sqlite'): - root_user = self.world.find_player(name='RaptorJesus') - if (root_user is None): - database_exists = False - - game.models.server = self - game.models.world = self.world - - if (database_exists is False): - room = self.world.create_room('Portal Room Main') - user = self.world.create_player(name='RaptorJesus', password='ChangeThisPasswordNowPlox', workfactor=self.work_factor, location=room, admin=True, sadmin=True, owner=True) - room.set_owner(user) - self.logger.info('The database has been successfully initialised.') - - self.telnet_server = TelnetServer(port=config.get_index(index='ServerPort', datatype=int), - address=config.get_index(index='ServerAddress', datatype=str), - on_connect = self.on_client_connect, - on_disconnect = self.on_client_disconnect, - timeout = 0.05) - - self.logger.info('ScalyMUCK successfully initialised.') - self.is_running = True - - def update(self): - """ The update command is called by the main.py script file. - - The update command does as it says, it causes the server to go through and - poll for data from any of the clients and processes this data differently - based on whether or not that they had actually logged in. When this function - finishes calling, a single world tick has passed. - - """ - try: - self.telnet_server.poll() - except UnicodeDecodeError: - return - - for connection in self.pending_connection_list: - if (connection.cmd_ready is True): - data = "".join(filter(lambda x: ord(x)<127 and ord(x)>31, connection.get_command())) - command_data = string.split(data, ' ') - - # Try and perform the authentification process - if (len(command_data) < 3): - connection.send('%s\n' % (self.auth_low_argc)) - elif (len(command_data) >= 3 and string.lower(command_data[0]) == 'connect'): - name = string.lower(command_data[1]) - password = command_data[2] - - target_player = self.world.find_player(name=name) - if (target_player is None): - connection.send('%s\n' % self.auth_invalid_combination) - else: - player_hash = target_player.hash - if (player_hash == bcrypt.hashpw(password, player_hash) == player_hash): - connection.id = target_player.id - target_player.connection = connection - - # Check if our work factors differ - work_factor = int(player_hash.split('$')[2]) - if (work_factor != self.work_factor): - target_player.set_password(password) - self.logger.info('%s had their hash updated.' % (target_player.display_name)) - - self.connection_logger.info('Client %s:%u signed in as user %s.' % (connection.address, connection.port, target_player.display_name)) - self.post_client_authenticated.send(None, sender=target_player) - for player in target_player.location.players: - if (player is not target_player): - player.send(self.auth_connected % target_player.display_name) - - for player in self.established_connection_list: - if (player.id == connection.id): - player.send('%s\n' % self.auth_replace_connection) - player.socket_send() - player.deactivate() - player.sock.close() - connection.send('%s\n' % self.auth_connection_replaced) - self.world.find_room(id=target_player.location_id).broadcast(self.auth_replace_connection_global % target_player.display_name, target_player) - self.established_connection_list.remove(player) - break - self.pending_connection_list.remove(connection) - self.established_connection_list.append(connection) - else: - connection.send('You have specified an invalid username/password combination.\n') - elif (len(command_data) >= 3 and string.lower(command_data[0]) != 'connect'): - connection.send('%s\n' % (self.auth_connect_suggestion)) - #connection.send('You must use the "connect" command:\n') - #connection.send('connect \n') - - # With already connected clients, we'll now deploy the command interface. - for connection in self.established_connection_list: - if (connection.cmd_ready): - input = "".join(filter(lambda x: ord(x)<127 and ord(x)>31, connection.get_command())) - sending_player = self.world.find_player(id=connection.id) - sending_player.connection = connection - self.interface.parse_command(sender=sending_player, input=input) - - self.world_tick.send(None) - - def shutdown(self): - """ Shuts down the ScalyMUCK server. - - This command shuts down the ScalyMUCK server and gracefully disconnects all connected clients - by sending a message before their disconnection which currently reads: "The server has been shutdown - adruptly by the server owner." This message cannot be changed. - - """ - self.is_running = False - for connection in self.established_connection_list: - connection.send('The server has been shutdown adruptly by the server owner.\n') - connection.socket_send() - - def find_connection(self, id): - """ Finds a player connection by their database ID. """ - for player in self.established_connection_list: - if (player.id == id): - return player - - def on_client_connect(self, client): - """ This is merely a callback for Miniboa to refer to when receiving a client connection from somewhere. """ - self.connection_logger.info('Received client connection from %s:%u' % (client.address, client.port)) - client.send(self.welcome_message_data) - self.pending_connection_list.append(client) - self.post_client_connect.send(sender=client) - - def on_client_disconnect(self, client): - """ This is merely a callback for Miniboa to refer to when receiving a client disconnection. """ - self.pre_client_disconnect.send(sender=client) - self.connection_logger.info('Received client disconnection from %s:%u' % (client.address, client.port)) - # Iterate over anyone who had connected but did not authenticate - if (client in self.pending_connection_list): - self.pending_connection_list.remove(client) - # Otherwise run over the list of people who had authenticated - elif (client in self.established_connection_list): - player = self.world.find_player(id=client.id) - room = self.world.find_room(id=player.location_id) - room.broadcast(self.game_client_disconnect % player.display_name, player) - +""" + This is the main ScalyMUCK server code, + it performs the initialisation of various + systems and is the binding of everything. + + This software is licensed under the MIT license. + Please refer to LICENSE.txt for more information. +""" + +import sys +import string +import logging +import apscheduler + +import bcrypt +from blinker import signal +from miniboa import TelnetServer +from sqlalchemy import create_engine, event +from sqlalchemy.interfaces import PoolListener +from apscheduler.scheduler import Scheduler +from sqlalchemy.exc import OperationalError, DisconnectionError +from sqlalchemy.engine.reflection import Inspector +from sqlalchemy.orm import Session +from sqlalchemy.pool import Pool + +import daemon +import game.models +from game import interface, world + +class Server(daemon.Daemon): + """ + + Server class that is initialized by the main.py script to act as the MUCK server. + It performs all of the core functions of the MUCK server, which is mainly accepting + connections and performing the login sequence before giving them access to the + server and its world. + + """ + is_running = False + telnet_server = None + logger = None + connection_logger = None + world = None + interface = None + work_factor = 10 + db_connection = True + + welcome_message_data = 'Unable to load welcome message!\n' + exit_message_data = 'Unable to load exit message!\n' + + pending_connection_list = [ ] + established_connection_list = [ ] + + post_client_connect = signal('post_client_connect') + pre_client_disconnect = signal('pre_client_disconnect') + post_client_authenticated = signal('post_client_authenticated') + database_status = signal('database_status') + world_tick = signal('world_tick') + scheduler = Scheduler() + + auth_low_argc = None + auth_invalid_combination = None + auth_connected = None + auth_replace_Connection = None + auth_connection_replaced = None + auth_connect_suggestion = None + game_client_disconnect = None + + def __init__(self, config=None, path=None, workdir=None): + """ The server class is created and managed by the main.py script. + + When created, the server automatically initiates a Telnet server provided + by Miniboa which immediately listens for incoming connections and will query + them for login details upon connection. + + Keyword arguments: + config -- An instance of game.Settings that is to be used when loading configuration data. + path -- The data path that all permenant data should be written to. + workdir -- The current working directory of the server. This should be an absolute path to application/. + + """ + self.connection_logger = logging.getLogger('Connections') + self.logger = logging.getLogger('Server') + + # Loading all of the configuration variables + database_type = string.lower(config.get_index(index='DatabaseType', datatype=str)) + database = config.get_index(index='DatabaseName', datatype=str) + user = config.get_index(index='DatabaseUser', datatype=str) + password = config.get_index(index='DatabasePassword', datatype=str) + self.work_factor = config.get_index(index='WorkFactor', datatype=int) + debug = config.get_index(index='Debug', datatype=bool) + if (database_type == 'sqlite'): + database_location = path + config.get_index(index='TargetDatabase', datatype=str) + else: + database_location = config.get_index(index='TargetDatabase', datatype=str) + + # Load server messages + self.auth_low_argc = config.get_index('AuthLowArgC', str) + self.auth_invalid_combination = config.get_index('AuthInvalidCombination', str) + self.auth_connected = config.get_index('AuthConnected', str) + self.auth_replace_connection = config.get_index('AuthReplaceConnection', str) + self.auth_connection_replaced = config.get_index('AuthConnectionReplaced', str) + self.auth_connect_suggestion = config.get_index('AuthConnectSuggestion', str).replace('\\n','\n') + self.auth_replace_connection_global = config.get_index('AuthReplaceConnectionGlobal', str) + self.game_client_disconnect = config.get_index('GameClientDisconnect', str) + + # Loading welcome/exit messages + with open(workdir + 'config/welcome_message.txt') as f: + self.welcome_message_data = f.read() + '\n' + with open(workdir + 'config/exit_message.txt') as f: + self.exit_message_data = f.read() + '\n' + + self.interface = interface.Interface(config=config, workdir=workdir, server=self, debug=debug) + + # Connect/Create our database is required + database_exists = True + if (database_type == 'sqlite'): + try: + with open(database_location) as f: pass + except IOError as e: + self.logger.info('This appears to be your first time running the ScalyMUCK server. We must initialise your database ...') + database_exists = False + + database_engine = create_engine('sqlite:///%s' % (database_location), echo=False) + else: + url = database_type + '://' + user + ':' + password + '@' + database_location + '/' + database + try: + database_engine = create_engine(url, echo=False, pool_size=20, max_overflow=0) + except OperationalError as e: + self.logger.error(str(e)) + self.logger.error('URL: ' + url) + self.is_running = False + return + + self.world = world.World(engine=database_engine, server=self) + game.models.Base.metadata.create_all(database_engine) + + # Check to see if our root user exists + if (database_type != 'sqlite'): + root_user = self.world.find_player(name='RaptorJesus') + if (root_user is None): + database_exists = False + + game.models.server = self + game.models.world = self.world + + if (database_exists is False): + room = self.world.create_room('Portal Room Main') + user = self.world.create_player(name='RaptorJesus', password='ChangeThisPasswordNowPlox', workfactor=self.work_factor, location=room, admin=True, sadmin=True, owner=True) + room.set_owner(user) + + self.logger.info('The database has been successfully initialised.') + + self.interface.initialize(config=config, world=self.world, session=self.world.session, engine=database_engine, workdir=workdir, server=self, debug=debug) + + self.telnet_server = TelnetServer(port=config.get_index(index='ServerPort', datatype=int), + address=config.get_index(index='ServerAddress', datatype=str), + on_connect = self.on_client_connect, + on_disconnect = self.on_client_disconnect, + timeout = 0.05) + + self.database_status.connect(self.callback_database_status) + + self.logger.info('ScalyMUCK successfully initialised.') + self.is_running = True + + def callback_database_status(self, trigger, sender, status): + self.db_connection = status + if (status is False): + for connection in self.established_connection_list: + connection.send('A critical database error has occurred. Please reconnect later.\n') + connection.socket_send() + connection.deactivate() + connection.sock.close() + for connection in self.pending_connection_list: + connection.send('A critical database error has occurred. Please reconnect later.\n') + connection.socket_send() + connection.deactivate() + connection.sock.close() + self.established_connection_list = [ ] + self.pending_connection_list = [ ] + + self.scheduler.start() + # Actually apparently we don't need the scheduler it appears ... + self.scheduler.add_interval_job(self.remote_db_ping, seconds=2) + else: + self.scheduler.stop() + + def remote_db_ping(self): + player = self.world.session.query(game.models.Player).filter_by(id=1).first() + self.database_status.send(sender=self, status=True) + self.world.session.rollback() + return + + def update(self): + """ The update command is called by the main.py script file. + + The update command does as it says, it causes the server to go through and + poll for data from any of the clients and processes this data differently + based on whether or not that they had actually logged in. When this function + finishes calling, a single world tick has passed. + + """ + try: + self.telnet_server.poll() + except UnicodeDecodeError: + return + + for connection in self.pending_connection_list: + if (connection.cmd_ready is True): + data = "".join(filter(lambda x: ord(x)<127 and ord(x)>31, connection.get_command())) + command_data = string.split(data, ' ') + + # Try and perform the authentification process + if (len(command_data) < 3): + connection.send('%s\n' % (self.auth_low_argc)) + elif (len(command_data) >= 3 and string.lower(command_data[0]) == 'connect'): + name = string.lower(command_data[1]) + password = command_data[2] + + target_player = self.world.find_player(name=name) + if (target_player is None): + connection.send('%s\n' % self.auth_invalid_combination) + else: + player_hash = target_player.hash + if (player_hash == bcrypt.hashpw(password, player_hash) == player_hash): + connection.id = target_player.id + target_player.connection = connection + + # Check if our work factors differ + work_factor = int(player_hash.split('$')[2]) + if (work_factor != self.work_factor): + target_player.set_password(password) + self.logger.info('%s had their hash updated.' % (target_player.display_name)) + + self.connection_logger.info('Client %s:%u signed in as user %s.' % (connection.address, connection.port, target_player.display_name)) + self.post_client_authenticated.send(None, sender=target_player) + for player in target_player.location.players: + if (player is not target_player): + player.send(self.auth_connected % target_player.display_name) + + for player in self.established_connection_list: + if (player.id == connection.id): + player.send('%s\n' % self.auth_replace_connection) + player.socket_send() + player.deactivate() + player.sock.close() + connection.send('%s\n' % self.auth_connection_replaced) + self.world.find_room(id=target_player.location_id).broadcast(self.auth_replace_connection_global % target_player.display_name, target_player) + self.established_connection_list.remove(player) + break + self.pending_connection_list.remove(connection) + self.established_connection_list.append(connection) + else: + connection.send('You have specified an invalid username/password combination.\n') + elif (len(command_data) >= 3 and string.lower(command_data[0]) != 'connect'): + connection.send('%s\n' % (self.auth_connect_suggestion)) + #connection.send('You must use the "connect" command:\n') + #connection.send('connect \n') + + # With already connected clients, we'll now deploy the command interface. + for index, connection in enumerate(self.established_connection_list): + if (connection.cmd_ready): + input = "".join(filter(lambda x: ord(x)<127 and ord(x)>31, connection.get_command())) + try: + sending_player = self.world.find_player(id=connection.id) + except game.exception.DatabaseError: + connection.send('A critical error has occurred. Please reconnect later.\n') + connection.socket_send() + connection.deactivate() + connection.sock.close() + else: + if (sending_player is not None): + sending_player.connection = connection + self.interface.parse_command(sender=sending_player, input=input) + + self.world_tick.send(None) + + def shutdown(self): + """ Shuts down the ScalyMUCK server. + + This command shuts down the ScalyMUCK server and gracefully disconnects all connected clients + by sending a message before their disconnection which currently reads: "The server has been shutdown + adruptly by the server owner." This message cannot be changed. + + """ + self.is_running = False + for connection in self.established_connection_list: + connection.send('The server has been shutdown adruptly by the server owner.\n') + connection.socket_send() + + def find_connection(self, id): + """ Finds a player connection by their database ID. """ + for player in self.established_connection_list: + if (player.id == id): + return player + + def on_client_connect(self, client): + """ This is merely a callback for Miniboa to refer to when receiving a client connection from somewhere. """ + self.connection_logger.info('Received client connection from %s:%u' % (client.address, client.port)) + if (self.db_connection is False): + client.send('A critical database error has occurred. Please reconnect later.\n') + client.socket_send() + client.deactivate() + client.sock.close() + return + client.send(self.welcome_message_data) + self.pending_connection_list.append(client) + self.post_client_connect.send(sender=client) + + def on_client_disconnect(self, client): + """ This is merely a callback for Miniboa to refer to when receiving a client disconnection. """ + self.pre_client_disconnect.send(sender=client) + self.connection_logger.info('Received client disconnection from %s:%u' % (client.address, client.port)) + # Iterate over anyone who had connected but did not authenticate + if (client in self.pending_connection_list): + self.pending_connection_list.remove(client) + # Otherwise run over the list of people who had authenticated + elif (client in self.established_connection_list): + player = self.world.find_player(id=client.id) + room = self.world.find_room(id=player.location_id) + room.broadcast(self.game_client_disconnect % player.display_name, player) + self.established_connection_list.remove(client) diff --git a/doc/Makefile b/doc/Makefile old mode 100644 new mode 100755 diff --git a/doc/build/html/.buildinfo b/doc/build/html/.buildinfo deleted file mode 100644 index 19512a6..0000000 --- a/doc/build/html/.buildinfo +++ /dev/null @@ -1,4 +0,0 @@ -# Sphinx build info version 1 -# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 4088e5d940bbe42526ea78efb5e24c49 -tags: fbb0d17656682115ca4d033fb2f83ba1 diff --git a/doc/build/html/_modules/exception.html b/doc/build/html/_modules/exception.html deleted file mode 100644 index deb22fd..0000000 --- a/doc/build/html/_modules/exception.html +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - - - - - exception — ScalyMUCK 3.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -

Source code for exception

-"""
-	ScalyMUCK has several base exceptions for the ScalyMUCK core and 
-	ScalyMUCK modifications that may be loaded into the MUCK server.
-
-	Copyright (c) 2013 Robert MacGregor
-	This software is licensed under the GNU General
-	Public License version 3. Please refer to gpl.txt 
-	for more information.
-"""
-
-
[docs]class ModApplicationError(Exception): - """ Generic exception for ScalyMUCK modifications to subclass in order - to report errors to the error reporting mechanism. - - NOTE: - This should never be explictely raised by any code. This - is designed to be subclassed for proper exception support. - - """ -
-
[docs]class WorldArgumentError(ModApplicationError): - """ Raised when using the world API and an invalid argument is specified. """ -
-
[docs]class ModelArgumentError(ModApplicationError): - """ Raised when a model function is used improperly. """
-
- -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_modules/game/exception.html b/doc/build/html/_modules/game/exception.html deleted file mode 100644 index e3cadd3..0000000 --- a/doc/build/html/_modules/game/exception.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - game.exception — ScalyMUCK 3.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -

Source code for game.exception

-"""
-	exception.py
-
-	Main file for ScalyMUCK -- you just run this file!
-	Copyright (c) 2013 Robert MacGregor
-
-	This software is licensed under the GNU General
-	Public License version 3. Please refer to gpl.txt 
-	for more information.
-"""
-
-
[docs]class ModApplicationError(Exception): - """ Generic """ -
-
[docs]class WorldArgumentError(ModApplicationError): - """ Raised when using the world API and an invalid - argument is specified. - - """ -
-
[docs]class ModelArgumentError(ModApplicationError): - """ Raised when a model function is used improperly. - """
-
- -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_modules/game/interface.html b/doc/build/html/_modules/game/interface.html deleted file mode 100644 index e86defc..0000000 --- a/doc/build/html/_modules/game/interface.html +++ /dev/null @@ -1,224 +0,0 @@ - - - - - - - - - - game.interface — ScalyMUCK 3.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -

Source code for game.interface

-"""
-	interface.py
-
-	Basically the user interface for ScalyMUCK.
-
-	Copyright (c) 2013 Robert MacGregor
-	This software is licensed under the GNU General
-	Public License version 3. Please refer to gpl.txt 
-	for more information.
-"""
-
-import string
-import logging
-import importlib
-import inspect
-
-from blinker import signal
-
-import modloader
-import permissions
-from game import exception, settings
-
-logger = logging.getLogger('Mods')
-
[docs]class Interface: - """ Client-Server interface class. - - The interface class is exactly how it sounds; it's what the users interact - with directly when connected to the ScalyMUCK server, with an exception being - the login screen for simplicity and security. - - """ - world = None - config = None - session = None - server = None - permissions = None - workdir = '' - modloader = None - - pre_message = signal('pre_message_sent') - post_message = signal('post_message_sent') - - def __init__(self, config=None, world=None, workdir='', session=None, server=None): - """ Initializes an instance of the ScalyMUCK Client-Server interface class. - - The interface class is created internallu by the ScalyMUCK server. - - The server passes in an active instance of game.Settings and game.World - for the interface to talk to when loading mods as the configuration is - used to load relevant config files for the mods and is passed in while - the instance of game.World is assigned to each module so that they may - access the game world. - - Keyword arguments: - config -- An instance of Settings that is to be used to load relevant configuration data. - world -- An instance of the World to pass over to every initialized modification. - workdir -- The current working directory of the application. This should be an absolute path to application/. - session -- A working session object that points to the active database. - server -- The very instance of the ScalyMUCK server. - - """ - self.logger = logging.getLogger('Mods') - self.world = world - self.workdir = workdir - self.config = config - self.session = session - self.server = server - self.permissions = permissions.Permissions(workdir=workdir) - self.modloader = modloader.ModLoader(world=world, interface=self, session=session, workdir=workdir, permissions=self.permissions) - self.modloader.load(config.get_index('LoadedMods', str)) - -
[docs] def parse_command(self, sender=None, input=None): - """ Called internally by the ScalyMUCK server. - - When a user sends a string of data to the server for processing and have passed - the initial authentification stage, that string of data is passed in here by the - server for processing. This function is what actually performs the command lookups - in the loaded command database. - - Keyword arguments: - sender -- The instance of Player that has trying to invoke a command. - input -- The text that the Player happened to send. - - """ - returns = self.pre_message.send(None, sender=sender, input=input) - intercept_input = False - for set in returns: - if (set[1] is True): - intercept_input = True - break - - data = string.split(input, ' ') - command = string.lower(data[0]) - command_data = self.modloader.find_command(command) - if (intercept_input is False and command_data is not None): - try: - privilege = command_data['privilege'] - if (privilege == 1 and sender.is_admin is False): - sender.send('You must be an administrator.') - return - elif (privilege == 2 and sender.is_sadmin is False): - sender.send('You must be a super administrator.') - return - elif (privilege == 3 and sender.is_owner is False): - sender.send('You must be the owner of the server.') - return - - # You're not trying to do something you shouldn't be? Good. - command_func = command_data['command'] - command_func(sender=sender, input=input[len(command)+1:], arguments=data[1:len(data)]) - except exception.ModApplicationError as e: - line_one = 'An error has occurred while executing the command: %s' % (command) - line_two = 'From modification: %s' % (self.commands[command]['modification']) - line_three = 'Error Condition: ' - line_four = str(e) - - self.logger.error(line_one) - self.logger.error(line_two) - self.logger.error(line_three) - self.logger.error(line_four) - sender.send(line_one) - sender.send(line_two) - sender.send(line_three) - sender.send(line_four) - sender.send('Please report this incident to your server administrator immediately.') - - elif (intercept_input is False and command != ''): - sender.send('I do not know what it is to "%s".' % (command)) - - self.post_message.send(None, sender=sender, input=input)
-
- -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_modules/game/models.html b/doc/build/html/_modules/game/models.html deleted file mode 100644 index a3f4d19..0000000 --- a/doc/build/html/_modules/game/models.html +++ /dev/null @@ -1,739 +0,0 @@ - - - - - - - - - - game.models — ScalyMUCK 3.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -

Source code for game.models

-"""
-	models.py
-
-	This is where all of ScalyMUCK's model definitions are located,
-	save for the modifications that may extend the software in such
-	a way that it demands for extra models but that is beyond the
-	point. 
-
-	The "base" definitions located here are:
-		Exit
-		Player
-		Room
-		Item
-		Bot
-
-	Copyright (c) 2013 Robert MacGregor
-	This software is licensed under the GNU General
-	Public License version 3. Please refer to gpl.txt 
-	for more information.
-"""
-
-import string
-
-from blinker import signal
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import relationship, backref
-from sqlalchemy import Table, Column, Integer, String, Text, Boolean, MetaData, ForeignKey
-import bcrypt
-
-import exception
-
-server = None
-world = None
-Base = declarative_base()
-
-
[docs]class ObjectBase: - """ Base class for the inheritance of useful member functions that work accross all models. """ - def delete(self): - """ Deletes the object from the world. """ - world.session.delete(self) - world.session.commit() - -
[docs] def set_name(self, name, commit=True): - """ Sets the name of the object. - - This sets the name of the object that is displayed and used to process requests towards it. - - Keyword arguments: - commit -- Determines whether or not this data should be commited immediately. It also includes other changes made - by any previous function calls thay may have set this to false. Default: True - - """ - self.name = name - if (type(self) is Player): - self.name = name.lower() - self.display_name = name - - if (commit is True): - world.session.add(self) - world.session.commit() -
-
[docs] def set_location(self, location, commit=True): - """ Sets the current location of this object. - - This sets the location of the object without any prior notification to the person - being moved (if it's a player) nor anyone in the original room or the target room. - That is the calling modification's job to provide any relevant messages. - - Keyword arguments: - commit -- Determines whether or not this data should be commited immediately. It also includes other changes made - by any previous function calls thay may have set this to false. Default: True - - """ - if (type(self) is Room): - return - - if (type(location) is Room): - self.location = location - self.location_id = location.id - if (commit): self.commit() - elif (type(location) is int): - location = world.session.query(Room).filter_by(id=location).first() - if (location is not None): - self.set_location(location, commit=commit) -
-
[docs] def set_description(self, description, commit=True): - """ Sets the description of this object. - - Sets the description of the calling object instance. - - Keyword arguments: - commit -- Determines whether or not this data should be commited immediately. It also includes other changes made - by any previous function calls thay may have set this to false. Default: True - - """ - self.description = description - if (commit): self.commit() -
-
[docs] def commit(self): - """ Commits any changes left in RAM to the database. """ - world.session.add(self) - world.session.commit() -
-
[docs] def delete(self): - """ Deletes the object from the world. """ - if (type(self) is Player): - self.disconnect() - - world.session.delete(self) - world.session.commit() -
-
[docs]class Exit(Base, ObjectBase): - """ - - Exits are what the players use to exit and move into other rooms in the ScalyMUCK - world. They may only have one target room ID which is used to assign .target to them - when they are loaded or creates by the game.World instance. - - """ - __tablename__ = 'exits' - - id = Column(Integer, primary_key=True) - name = Column(String(25)) - description = Column(String(2000)) - user_enter_message = Column(String(100)) - room_enter_message = Column(String(100)) - user_exit_message = Column(String(100)) - room_exit_message = Column(String(100)) - target_id = Column(Integer) - location_id = Column(Integer, ForeignKey('rooms.id')) - owner_id = Column(Integer, ForeignKey('players.id')) - - def __init__(self, name, target=None, owner=0): - """ Initializes an instance of the Exit model. - - The Exit is not constructed manually by any of the modifications, this should be - performed by calling the create_player function on the game.World instance provided - to every modification. - - Keyword arguments: - target -- The ID or instance of a Room that this exit should be linking to. - owner -- The ID or instance of a Player that this should should belong to. - - """ - if (target is None): - raise exception.ModelArgumentError('No target was specified. (or it was None)') - - # Set the name - self.name=name - if (type(target) is int): - self.target_id = target - else: - self.target_id = target.id - - # Set the owner - if (type(owner) is int): - self.owner_id = owner - else: - self.owner_id = owner.id - - self.user_enter_message = 'You move out.' - self.room_enter_message = 'left.' - self.user_exit_message = 'You arrive in the next room.' - self.room_exit_message = 'arrived from another room.' - self.description = '<Unset>' - - def __repr__(self): - """ Produces a representation of the exit, as to be expected. """ - return "<Exit('%s','%u'>" % (self.name, self.target_id) -
-
[docs]class Player(Base, ObjectBase): - """ - - Players are well, the players that actually move and interact in the world. They - store their password hash and various other generic data that can be used across - just about anything. - - """ - __tablename__ = 'players' - - id = Column(Integer, primary_key=True) - name = Column(String(25)) - display_name = Column(String(25)) - description = Column(String(200)) - hash = Column(String(128)) - - location_id = Column(Integer, ForeignKey('rooms.id')) - location = None - inventory_id = Column(Integer) - - is_admin = Column(Boolean) - is_sadmin = Column(Boolean) - is_owner = Column(Boolean) - - connection = None - world = None - server = None - interface = None - - def __init__(self, name=None, password=None, workfactor=None, location=None, inventory=None, description='<Unset>', admin=False, sadmin=False, owner=False): - """ Initializes an instance of the Player model. - - The Player is not constructed manually by any of the modifications, this should be - performed by calling the create_player function on the game.World instance provided - to every modification. When the instance is created, it automatically generates a salt - that the Player's password will be hashed with. This salt generation will cause a small - lockup as the calculations are pretty intense depending on the work factor. - - Keyword arguments: - name -- The name of the Player that should be used. - password -- The password that should be used for this Player in the database. - workfactor -- The workfactor that should be used when generating this Player's hash salt. - location -- An ID or instance of Room that this Player should be created at. - inventory -- An ID or instance of Room that should represent this Player's inventory. - description -- A description that is shown when the player is looked at. Default: <Unset> - admin -- A boolean representing whether or not the player is an administrator or not. Default: False - sadmin -- A boolean representing whether or not the player is a super administrator or not. Default: False - owner -- A boolean representing whether or not the player is the owner of the server. Default: False - - """ - self.name = string.lower(name) - self.display_name = name - self.description = description - self.work_factor = workfactor - - if (type(location) is Room): - location = location.id - self.location_id = location - if (type(inventory) is Room): - inventory = inventory.id - self.inventory_id = inventory - - self.hash = bcrypt.hashpw(password, bcrypt.gensalt(workfactor)) - self.is_admin = admin - self.is_sadmin = sadmin - self.is_owner = owner - - def __repr__(self): - """ Produces a representation of the player, as to be expected. """ - return "<Player('%s','%s','%s','%u')>" % (self.name, self.description, self.hash, self.location_id) - -
[docs] def send(self, message): - """ Sends a message to this Player if they are connected. - - This sends a message to the relevant connection if there happens to be one established - for this player object. If there is no active connection for this player, then the message - is simply dropped. - - """ - if (self.connection is None): - self.connection = server.find_connection(self.id) - - if (self.connection is not None): - self.connection.send(message + '\n') - return True - else: - return False -
-
[docs] def set_password(self, password, commit=True): - """ Sets the password of this Player. - - This sets the password of the Player in the database without any notification to the Player. This also - triggers the new password to be hashed with a randomly generated salt which means the current thread - of execution will probably hang for a few seconds unless the work factor is set just right. - - Keyword arguments: - commit -- Determines whether or not this data should be commited immediately. It also includes other changes made - by any previous function calls thay may have set this to false. Default: True - - """ - self.hash = bcrypt.hashpw(password, bcrypt.gensalt(server.work_factor)) - if (commit is True): self.commit() -
-
[docs] def disconnect(self): - """ Drops the Player's connection from the server. - - This drops the user's connection from the game, flushing any messages that - were destined for them before actually booting them out of the server. This triggers - the default disconnect message to be displayed by the ScalyMUCK core to whomever else - may be in the room with the user at the time of their disconnect. - - """ - if (self.connection is not None): - self.connection.socket_send() - self.connection.deactivate() - self.connection.sock.close() -
-
[docs] def set_is_admin(self, status, commit=True): - """ Sets the administrator status of this Player. - - This sets the state as to whether or not the calling player instance is an administrator - of the server or not. This may mean different things to different mods but -generally- it - should just provide basic administrator privileges. The commit parameter is used if you don't - want to dump changes to the database yet; if you're changing multiple properties at once, it - doesn't hit the database as often then. - - Keyword arguments: - commit -- Determines whether or not this data should be commited immediately. It also includes other changes made - by any previous function calls thay may have set this to false. Default: True - - """ - self.is_admin = status - if (self.is_sadmin is True and status is False): - self.is_sadmin = False - if (commit): self.commit() -
-
[docs] def set_is_super_admin(self, status, commit=True): - """ Sets the super administrator status of this Player. - - This sets the state as to whether or not the calling player instance is a super administrator - of the server or not. This may mean different things to different mods but -generally- it - should provide administrator functionalities more akin to what the owner may have, things that - may break the server if used improperly. The commit parameter is used if you don't want to dump - changes to the database yet; if you're changing multiple properties at once, it doesn't hit the database as - often then. - - Keyword arguments: - commit -- Determines whether or not this data should be commited immediately. It also includes other changes made - by any previous function calls thay may have set this to false. Default: True - - """ - self.is_sadmin = status - if (self.is_admin is False and status is True): - self.is_admin = True - if (commit): self.commit() -
-
[docs] def check_admin_trump(self, target): - """ Checks whether or not this Player trumps the target Player administratively. - - This returns True when the calling Player instance has greater privilege than that of the target they are being - compared against. It returns false in the opposite situation. - """ - if (type(target) is not Player): - raise exception.ModelArgumentError('Target is not an instance of Player!') - - if (self.is_admin is True and target.is_admin is False): - return True - elif (self.is_sadmin is True and target.is_sadmin is False): - return True - elif (self.is_owner is True and target.is_owner is False): - return True - else: - return False -
-
[docs]class Bot(Base, ObjectBase): - """ - - Bots are basically just the AI's of the game. They're not items, but they're not players - either. They have the special property of being interchangable with player object instances - for the most part except when it comes to certain functions -- such as having a password - hash. - - """ - __tablename__ = 'bots' - - id = Column(Integer, primary_key=True) - name = Column(String(25)) - description = Column(String(2000)) - display_name = Column(String(25)) - location_id = Column(Integer, ForeignKey('rooms.id')) - - def __init__(self, name=None, description=None, location=None): - """ - - The Bot is not constructed manually by any of the modifications, this should be - performed by calling the create_player function on the game.World instance provided - to every modification. - - """ - self.name = name - self.description = description - self.display_name = name - self.name = name.lower() - - if (type(location) is Room): - location = location.id - self.location_id = location - -
[docs] def send(self, message): - """ This is basically 'send' like for players except it does NOTHING. """ -
-
[docs]class Item(Base, ObjectBase): - """ Base item model that ScalyMUCK uses. - - Items are really a generic object in ScalyMUCK, they're not players nor rooms nor exits but - serve multiple purposes. They can quite literally be an item, stored in the player's inventory - to be used later (like a potion) or they may be used to decorate rooms with specific objects - such as furniture or they may even be used to represent dead bodies. - - """ - __tablename__ = 'items' - - id = Column(Integer, primary_key=True) - name = Column(String(25)) - owner_id = Column(Integer, ForeignKey('players.id')) - description = Column(String(2000)) - location_id = Column(Integer, ForeignKey('rooms.id')) - - def __init__(self, name=None, description='<Unset>', owner=0): - """ Constructor for the base item model. - - The Item is not constructed manually by any of the modifications, this should be - performed by calling the create_player function on the game.World instance provided - to every modification. - - Keyword arguments: - name -- The name of the item that should be used. - description -- The description of the item that should be displayed. Default: <Unset> - owner -- Whoever should become the owner of this item, by instance or ID. Default: 0 - - """ - if (name is None): - raise exception.ModelArgumentError('No name specified when attempting to create an instance of Item! (Or it is none)') - - self.name = name - self.description = description - if (type(owner) is int): - self.owner_id = owner - else: - self.owner_id = owner.id - - def __repr__(self): - """ Produces a representation of the item, as to be expected. """ - return "<Item('%s','%s')>" % (self.name, self.description) - -
[docs] def set_owner(self, owner): - """ Sets a new owner for this item. """ - if (type(owner) is Player): - owner = owner.id - self.owner_id = owner - self.commit() -
-
[docs]class Room(Base, ObjectBase): - """ Base room model that ScalyMUCK uses. - - Rooms are what make up the world inside of just about any MUCK really. Even if the rooms are not described as such, they still - must be used to contain players, dropped items, bots, etc. - - """ - __tablename__ = 'rooms' - id = Column(Integer, primary_key=True) - - name = Column(String(25)) - description = Column(String(2000)) - items = relationship('Item') - players = relationship('Player') - bots = relationship('Bot') - exits = relationship('Exit') - owner_id = Column(Integer) - - def __init__(self, name=None, description='<Unset>', owner=0): - """ Initiates an instance of the Room model. - - The Room is not constructed manually by any of the modifications, this should be - performed by calling the create_player function on the game.World instance provided - to every modification. - - Keyword arguments: - name -- The name of the room that should be used. - description -- The description of the room. - owner -- The instance or ID of the relevant Player instance that should own this room. - - """ - if (name is None): - raise exception.ModelArgumentError('The name was not specified. (or it is None)') - - self.name = name - self.description = description - self.exits = [ ] - if (type(owner) is int): - self.owner_id = owner - else: - self.owner_id = owner.id - - def __repr__(self): - """ Produces a representation of the room, as to be expected. """ - return "<Room('%s','%s')>" % (self.name, self.description) - -
[docs] def add_exit(self, name=None, target=None, owner=0): - """ Gives this Room instance an Exit linking to anywhere. - - This produces an Exit link from the calling Room instance to another one elsewhere. The database - changes are automatically committed here due to the way the exit has to be created in the database - first for the changes to properly apply without any hackery to occur. - - Keyword arguments: - name -- The name of the exit that should be used. - target -- The ID or instance of a Room that this exit should be linking to. - owner -- The ID or instance of a Player that should become the owner of this Exit. - - """ - if (name is None): - raise exception.ModelArgumentError('The name was not specified. (or it was None)') - elif (target is None): - raise exception.ModelArgumentError('The target room was not specified. (or it was None)') - - if (type(target) is int): - target = world.find_room(id=target) - if (target is not None): - self.add_exit(name, target) - elif (type(target) is Room): - exit = Exit(name, target, owner) - self.exits.append(exit) - world.session.add(self) - world.session.add(exit) - world.session.commit() -
-
[docs] def broadcast(self, message, *exceptions): - """ Broadcasts a message to all inhabitants of the Room except those specified. - - This broadcasts a set message to all inhabitants of the Room except those specified after the message: - some_room.broadcast('Dragons are best!', that_guy, this_guy, other_guy) - - The exceptions may be an ID or an instance. - - """ - for player in self.players: - if (player not in exceptions and player.id not in exceptions): - player.send(message) -
-
[docs] def find_player(self, id=None, name=None): - """ Locates a Player located in the calling instance of Room. - - This is a less computionally intensive version of the world's find_player as there is going to be much less data - to be sorting through since you actually know where the Player is located (otherwise you wouldn't be calling this!) - and all you need is the instance. - - Keyword arguments (one or the other): - id -- The identification number of the Player to locate inside of this room. This overrides the name if - both are specified. - name -- The name of the Player to locate. - - """ - if (id is None and name is None): - raise exception.ModelArgumentError('No arguments specified (or were None)') - - player = None - if (id is not None): - for test_player in self.players: - if (id == test_player.id): - player = test_player - break - elif (name is not None): - name = string.lower(name) - for test_player in self.players: - if (name in test_player.name.lower()): - player = test_player - break - return player -
-
[docs] def find_bot(self, id=None, name=None): - """ Locates a Bot located in the calling instance of Room. - - This is a less computionally intensive version of the world's find_bot as there is going to be much less data - to be sorting through since you actually know where the Bot is located (otherwise you wouldn't be calling this!) - and all you need is the instance. - - Keyword arguments (one or the other): - id -- The identification number of the Bot to locate inside of this room. This overrides the name if - both are specified. - name -- The name of the Bot to locate. - - """ - if (id is None and name is None): - raise exception.ModelArgumentError('No arguments specified (or were None)') - - bot = None - if (id is not None): - for test_bot in self.bots: - if (id == test_bot.id): - bot = test_bot - break - elif (name is not None): - name = string.lower(name) - for test_bot in self.bots: - if (name in test_bot.name.lower()): - bot = test_bot - break - return bot -
-
[docs] def get_exits(self): - """ Return a list of all exits. """ - for exit in self.exits: - exit.location = self - return self.exits -
-
[docs] def find_item(self, id=None, name=None): - """ Locates an Item located in the calling instance of Room. - - This is a less computionally intensive version of the world's find_item as there is going to be much less data - to be sorting through since you actually know where the Item is located (otherwise you wouldn't be calling this!) - and all you need is the instance. - - Keyword arguments (one or the other): - id -- The identification number of the Item to locate inside of this room. This overrides the name if - both are specified. - name -- The name of the Item to locate. - - """ - if (id is None and name is None): - raise exception.ModelArgumentError('No arguments specified (or were None)') - - item = None - if (id is not None): - for test_item in self.items: - if (test_item.id == id): - item = test_item - break - elif (name is not None): - name = string.lower(name) - for test_item in self.items: - if (name in test_item.name.lower()): - item = test_item - break - return item -
-
[docs] def find_exit(self, id=None, name=None): - """ Locates an Item located in the calling instance of Room. - - This is a less computionally intensive version of the world's find_item as there is going to be much less data - to be sorting through since you actually know where the Item is located (otherwise you wouldn't be calling this!) - and all you need is the instance. - - Keyword arguments (one or the other): - id -- The identification number of the Item to locate inside of this room. This overrides the name if - both are specified. - name -- The name of the Item to locate. - - """ - if (id is None and name is None): - raise exception.ModelArgumentError('No arguments specified (or were None)') - - exit = None - if (id is not None): - for test_exit in self.exits: - if (test_exit.id == id): - exit = test_exit - break - elif (name is not None): - name = string.lower(name) - for test_exit in self.exits: - if (name in test_exit.name.lower()): - exit = test_exit - break - return exit
-
- -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_modules/game/modloader.html b/doc/build/html/_modules/game/modloader.html deleted file mode 100644 index 7bca4d4..0000000 --- a/doc/build/html/_modules/game/modloader.html +++ /dev/null @@ -1,239 +0,0 @@ - - - - - - - - - - game.modloader — ScalyMUCK 3.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -

Source code for game.modloader

-"""
-	modloader.py
-
-	Modification loader for ScalyMUCK.
-
-	Copyright (c) 2013 Robert MacGregor
-	This software is licensed under the GNU General
-	Public License version 3. Please refer to gpl.txt 
-	for more information.
-"""
-
-import logging
-import inspect
-import importlib
-
-import settings
-
-logger = logging.getLogger('Mods')
-
[docs]class ModLoader: - """ Mod loading class for ScalyMUCK. """ - workdir = None - world = None - interface = None - session = None - permissions = None - commands = { } - modifications = { } - - def __init__(self, world=None, interface=None, session=None, workdir=None, permissions=None): - """ Initializes an instance of the ScalyMUCK mod loader. """ - self.workdir = workdir - self.world = world - self.interface = interface - self.session = session - self.permissions = permissions - self.set_defaults() - -
[docs] def load(self, modifications): - """ Loads a semicolon deliminated list of modifications from application/game """ - for mod_name in modifications.split(';'): - mod_name = mod_name.lower() - if (mod_name in self.modifications): - # Reloads the modification - module = self.modifications[mod_name] - modules = inspect.getmembers(module, inspect.ismodule) - for name, sub_module in modules: - reload(sub_module) - module = reload(module) - modification = module.Modification(config=self.config, world=self.world, interface=self.interface, session=self.session, permissions=self.permissions, modloader=self) - self.modifications[mod_name] = (instance, module) - else: - try: - module = importlib.import_module('game.%s' % (mod_name)) - except ImportError as e: - self.logger.warning(str(e)) - return False - else: - config = settings.Settings('%s/config/%s.cfg' % (self.workdir, mod_name)) - - modification = module.Modification(config=config, world=self.world, interface=self.interface, session=self.session, permissions=self.permissions, modloader=self) - self.modifications.setdefault(mod_name, (modification, module)) - commands = modification.get_commands() - logger.info('Processed modification %s.' % (mod_name)) - - # Process aliases first - aliases = { } - for command in commands: - for alias in commands[command]['aliases']: - aliases.setdefault(alias, commands[command]) - commands.update(aliases) - # Then process the dictionary - for command in commands: - commands[command]['modification'] = mod_name - if (command in self.commands): - logger.warn('Overlapping command definitions for command "%s"! %s -> %s' % (command, mod_name, self.commands[command]['modification'])) - self.commands.update(commands) - self.set_defaults() - - return True -
-
[docs] def set_defaults(self): - """ This command merely makes sure that the core commands are loaded. """ - self.commands['mods'] = { } - self.commands['mods']['command'] = self.command_mods - self.commands['mods']['description'] = 'Lists all loaded mods.' - self.commands['mods']['usage'] = 'mods' - self.commands['mods']['privilege'] = 3 - self.commands['mods']['modification'] = '<CORE>' - - self.commands['load'] = { } - self.commands['load']['command'] = self.command_load - self.commands['load']['description'] = 'Loads the specified mod.' - self.commands['load']['usage'] = 'load <name>' - self.commands['load']['privilege'] = 3 - self.commands['load']['modification'] = '<CORE>' - - self.commands['unload'] = { } - self.commands['unload']['command'] = self.command_unload - self.commands['unload']['description'] = 'Unloads the specified mod.' - self.commands['unload']['usage'] = 'unload <name>' - self.commands['unload']['privilege'] = 3 - self.commands['unload']['modification'] = '<CORE>' -
-
[docs] def find_command(self, name): - """ Returns a command by name. """ - if (name not in self.commands): - return None - else: - return self.commands[name] - - # CORE Commands
-
[docs] def command_mods(self, **kwargs): - """ Internal command to list installed mods. """ - sender = kwargs['sender'] - loaded = '' - sender.send('Loaded modifications: ') - for key in self.modifications.keys(): - loaded += '%s, ' % (key) - sender.send(loaded.rstrip(', ')) -
-
[docs] def command_load(self, **kwargs): - """ Internal command to load mods. """ - sender = kwargs['sender'] - input = kwargs['input'].lower() - if (input.strip() == ' '): - sender.send('No input.') - - if (self.load(input) is False): - sender.send('Failed to load mod "%s".' % (input)) - else: - sender.send('Loaded mod "%s".' % (input)) -
-
[docs] def command_unload(self, **kwargs): - """ Internal command to unload mods. """ - sender = kwargs['sender'] - input = kwargs['input'].lower() - if (input in self.modifications): - instance, module = self.modifications[input] - self.modifications.pop(input) - commands = instance.get_commands() - for command in commands.keys(): - self.commands.pop(command) - sender.send('Mod "%s" unloaded.' % (input)) - else: - sender.send('Unknown modification.')
-
- -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_modules/game/permissions.html b/doc/build/html/_modules/game/permissions.html deleted file mode 100644 index cb7f86c..0000000 --- a/doc/build/html/_modules/game/permissions.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - - - - - game.permissions — ScalyMUCK 3.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -

Source code for game.permissions

-"""
-	permissions.py
-
-	Permission handler for the ScalyMUCK server.
-
-	Copyright (c) 2013 Robert MacGregor
-	This software is licensed under the GNU General
-	Public License version 3. Please refer to gpl.txt 
-	for more information.
-"""
-
-import logging
-
-import settings
-
-logger = logging.getLogger('Mods')
-
[docs]class Permissions: - """ Main class for permission handling in ScalyMUCK. """ - permissions = { } - workdir = None - - def __init__(self, workdir=None): - self.workdir = workdir - permission_settings = settings.Settings('%s/config/permissions.cfg' % (self.workdir)) - for index in permission_settings.get_indices(): - self.set_permission(index, permission_settings.get_index(index, bool)) - -
[docs] def set_permission(self, name=None, value=None, evaluator=None): - """ Sets a permission in the repo. """ - if (evaluator is None): - evaluator = self.standard_evaluator - self.permissions[name] = (value, evaluator) -
-
[docs] def has_permission(self, name): - """ Determines whether or not a permission is actually set in the repo. """ - if (name in self.permissions): - return True - else: - return False -
-
[docs] def test(self, name=None, player=None): - """ Tests the permission availability against a player. """ - if (name in self.permissions): - value, evaluator = self.permissions[name] - return evaluator(name, player, value) -
-
[docs] def standard_evaluator(self, name=None, player=None, value=None): - """ Tests all standard permissions built into the server. """ - if (name == 'AllowAdminOverride'): - return value - elif (name == 'AllowSuperAdminOverride'): - return value - elif (name == 'AllowOwnerOverride'): - return value - else: - logger.warn('Attempted to evaluate undefined permission: "%s"!' % (name)) - return False
-
- -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_modules/game/settings.html b/doc/build/html/_modules/game/settings.html deleted file mode 100644 index edd377d..0000000 --- a/doc/build/html/_modules/game/settings.html +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - - - - - game.settings — ScalyMUCK 3.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -

Source code for game.settings

-"""
-	settings.py
-
-	Basic settings loader that provides easy to use functionality to load from simple
-	configuration files that are formatted as such:
-	
-	Option=Yes
-	Number=20
-	String=Whatever
-
-	When loading the above data, you would specify the datatype that it should be loaded as:
-	some_number = config.get_index('Number', int)
-
-	Copyright (c) 2013 Robert MacGregor
-	This software is licensed under the GNU General
-	Public License version 3. Please refer to gpl.txt 
-	for more information.
-"""
-
-import string
-
-
[docs]class Settings: - """ - - The Settings loader class provides simple functionality to load from simple configuration - files. - - """ - _settings_entries = None - _yes = ['1', 'true', 'y', 'yes', 'enable', 'toggle', 'enabled'] - - def __init__(self, target_file): - """ Initializes an instance of the Settings loader. """ - self._settings_entries = { } - self.load(target_file) - -
[docs] def load(self, target_file): - """ Loads a configuration file from the hard disk. """ - try: - file_handle = open(target_file, 'r') - except IOError: - return - - for line_data in file_handle: - preference_data = string.split(line_data, '=') - line_data = line_data.lstrip() - - if (len(preference_data) == 2): - data = preference_data[1] - self._settings_entries[preference_data[0]] = data[:len(data)].replace('\n','') - # TODO: Make this actually do some work to make post-fix comments work ... - elif(line_data.find('#') and line_data != ''): - continue - - file_handle.close() -
-
[docs] def get_indices(self): - """ Returns all known indices. """ - return self._settings_entries.keys() -
-
[docs] def get_index(self, index=None, datatype=None): - """ Returns a loaded configuration setting from the Settings loader. - - Keyword arguments: - index -- The name of the setting that is to be loaded from the file. - datatype -- The datatype that is supposed to be used to represent this setting. - - """ - if(index in self._settings_entries): - entries = self._settings_entries - if (datatype is bool): - if (string.lower(self._settings_entries[index]) in self._yes): - return True - else: - return False - else: - return datatype(entries[index]) - else: - return None
-
- -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_modules/game/world.html b/doc/build/html/_modules/game/world.html deleted file mode 100644 index 1fd8ee8..0000000 --- a/doc/build/html/_modules/game/world.html +++ /dev/null @@ -1,311 +0,0 @@ - - - - - - - - - - game.world — ScalyMUCK 3.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -

Source code for game.world

-"""
-	world.py
-
-	Game world code for ScalyMUCK. This contains "global" functions that is to
-	be called by the various mods of the MUCK to perform actions such as creating
-	Players.
-
-	Copyright (c) 2013 Robert MacGregor
-	This software is licensed under the GNU General
-	Public License version 3. Please refer to gpl.txt 
-	for more information.
-"""
-
-from sqlalchemy.orm import sessionmaker
-from sqlalchemy.orm import scoped_session
-
-from models import Room, Player, Item, Bot
-import exception
-
-
[docs]class World(): - """ - - The "singleton" class that represents the ScalyMUCK world in memory and is to - be passed to every mod that actually manages to initialize when loaded. - - """ - engine = None - session = None - - def __init__(self, engine): - """ Initializes an instance of the World with an SQLAlchemy engine. """ - self.engine = engine - self.session = scoped_session(sessionmaker(bind=self.engine)) - -
[docs] def create_room(self, name, description='<Unset>', owner=0): - """ Creates a new Room if the World. - - Keyword arguments: - description -- The description that is to be used with the new Room instance. - owner -- The ID or instance of Player that is to become the owner of this Room. - - """ - room = Room(name, description, owner) - self.session.add(room) - self.session.commit() - self.session.refresh(room) - return room -
-
[docs] def find_room(self, **kwargs): - """ Locates the specified Room in the ScalyMUCK world. - - This can be a bit computionally intense if you are running a very large world. - - Keyword arguments (one or the other): - id -- The id of the requested room to return an instance of. This overrides the name if both are specified. - name -- The name of the requested room to return an instance of. - - """ - target_room = self.session.query(Room).filter_by(**kwargs).first() - return target_room -
-
[docs] def create_player(self, name=None, password=None, workfactor=None, location=None, admin=False, sadmin=False, owner=False): - """ Creates a new instance of a Player. - - Keyword arguments: - name -- The name of the new Player instance to be used. - password -- The password that is to be used for the Player. - workfactor -- The work factor # to be used when hasing the Player's password. - location -- The ID or instance of Room that the new Player is to be created at. - admin -- A boolean representing whether or not this new Player is an administrator. - sadmin -- A boolean representing whether or not this new Player is a super administrator. - owner -- A boolean representing whether or not this new Player is an owner. - - """ - if (name is None or password is None or workfactor is None or location is None): - raise exception.WorldArgumentError('All of the arguments to create_player are mandatory! (or None was passed in)') - - if (type(location) is int): - location = self.find_room(id=location) - - player_inventory = self.create_room('%s\'s Inventory' % (name)) - player = Player(name, password, workfactor, location.id, 0, admin=admin, sadmin=sadmin, owner=owner) - player.inventory_id = player_inventory.id - self.session.add(player) - - location.players.append(player) - self.session.add(location) - - self.session.add(player_inventory) - self.session.commit() - - self.session.refresh(player) - self.session.refresh(player_inventory) - - player.location = location - player.inventory = player_inventory - return player -
-
[docs] def create_bot(self, name=None, location=None): - """ Creates a new instance of a Bot. - - Keyword arguments: - name -- The name of the new Player instance to be used. - location -- The ID or instance of Room that the new Player is to be created at. - - """ - if (name is None or location is None): - raise exception.WorldArgumentError('All of the arguments to create_bot are mandatory! (or None was passed in)') - - if (type(location) is int): - location = self.find_room(id=location) - - bot = bot(name, '<Unset>', location) - self.session.add(bot) - location.bots.append(bot) - self.session.add(location) - self.session.commit() - - self.session.refresh(bot) - - bot.location = location - return bot -
-
[docs] def find_player(self, **kwargs): - """ Locates a Player inside of the ScalyMUCK world. - - This searches the entire WORLD for the specified Player so if you happen to be running a very, very - large world this search will end up getting slow and it is recommended in that case that you try and - use the Room level find_player function whenever possible. - - Keyword arguments (one or the other): - id -- The ID of the Player to locate. This overrides the name if both are specified. - name -- The name of the Player to locate. - - """ - target_player = self.session.query(Player).filter_by(**kwargs).first() - if (target_player is not None): - target_player.location = self.find_room(id=target_player.location_id) - target_player.inventory = self.find_room(id=target_player.inventory_id) - - return target_player -
-
[docs] def find_bot(self, **kwargs): - """ Locates a Bot inside of the ScalyMUCK world. - - This searches the entire WORLD for the specified Bot so if you happen to be running a very, very - large world this search will end up getting slow and it is recommended in that case that you try and - use the Room level find_player function whenever possible. - - Keyword arguments (one or the other): - id -- The ID of the Bot to locate. This overrides the name if both are specified. - - """ - target_bot = self.session.query(Bot).filter_by(**kwargs).first() - if (target_bot is not None): - target_bot.location = self.find_room(id=target_bot.location_id) - - return target_bot -
-
[docs] def get_players(self): - """ Returns a list of all Players in the ScalyMUCK world. """ - list = [ ] - results = self.session.query(Player).filter_by() - for player in results: - load_test = self.find_player(id=player.id) - if (load_test is None): - list.append(self.find_player(id=player.id)) - else: - list.append(load_test) - return list -
-
[docs] def find_item(self, **kwargs): - """ Locates an item by any specifications. - - If the ID number does not exist then None is returned. - - """ - target_item = self.session.query(Item).filter_by(**kwargs).first() - if (target_item is not None): - target_item.location = self.find_room(id=target_item.location_id) - - return target_item -
-
[docs] def create_item(self, name=None, description='<Unset>', owner=0, location=None): - """ Creates a new item in the ScalyMUCK world. - - Keyword arguments: - name -- The name of the Item that is to be used. - description -- The description of the Item that is to be used. Default: <Unset> - owner -- The ID or instance of Player that is to become the owner of this Item. - location -- The ID or instance of Room that is to become the location of this Item. - """ - if (name is None or location is None): - raise exception.WorldArgumentError('Either the name or location was not specified. (or they were None)') - - item = Item(name, description, owner) - if (type(location) is int): - item.location_id = location - item.location = self.find_room(id=location) - else: - item.location = location - item.location_id = location.id - - self.session.add(item) - self.session.commit() - self.session.refresh(item) - return item -
-
[docs] def get_rooms(self, **kwargs): - """ Returns all rooms in the database that meet the specified criterion. - - Keyword arguments: - owner -- The owner we are to filter by. If not specified, this filter is not used. - - """ - rooms = self.session.query(Room).filter_by(**kwargs) - return rooms
-
- -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_modules/index.html b/doc/build/html/_modules/index.html deleted file mode 100644 index 9544549..0000000 --- a/doc/build/html/_modules/index.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - - Overview: module code — ScalyMUCK 3.0.0 documentation - - - - - - - - - - - - - -
-
-
-
- -

All modules for which code is available

- - -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_modules/interface.html b/doc/build/html/_modules/interface.html deleted file mode 100644 index e52b201..0000000 --- a/doc/build/html/_modules/interface.html +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - - - - - interface — ScalyMUCK 3.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -

Source code for interface

-"""
-	Basically the user interface for ScalyMUCK.
-
-	Copyright (c) 2013 Robert MacGregor
-	This software is licensed under the GNU General
-	Public License version 3. Please refer to gpl.txt 
-	for more information.
-"""
-
-import string
-import logging
-import importlib
-import inspect
-
-from blinker import signal
-
-import modloader
-import permissions
-from game import exception, settings
-
-logger = logging.getLogger('Mods')
-
[docs]class Interface: - """ Client-Server interface class. - - The interface class is exactly how it sounds; it's what the users interact - with directly when connected to the ScalyMUCK server, with an exception being - the login screen for simplicity and security. - - """ - world = None - config = None - session = None - server = None - permissions = None - workdir = '' - modloader = None - - pre_message = signal('pre_message_sent') - post_message = signal('post_message_sent') - -
[docs] def __init__(self, config=None, world=None, workdir='', session=None, server=None): - """ Initializes an instance of the ScalyMUCK Client-Server interface class. - - The interface class is created internallu by the ScalyMUCK server. - - The server passes in an active instance of game.Settings and game.World - for the interface to talk to when loading mods as the configuration is - used to load relevant config files for the mods and is passed in while - the instance of game.World is assigned to each module so that they may - access the game world. - - Keyword arguments: - config -- An instance of Settings that is to be used to load relevant configuration data. - world -- An instance of the World to pass over to every initialized modification. - workdir -- The current working directory of the application. This should be an absolute path to application/. - session -- A working session object that points to the active database. - server -- The very instance of the ScalyMUCK server. - - """ - self.logger = logging.getLogger('Mods') - self.world = world - self.workdir = workdir - self.config = config - self.session = session - self.server = server - self.permissions = permissions.Permissions(workdir=workdir) - self.modloader = modloader.ModLoader(world=world, interface=self, session=session, workdir=workdir, permissions=self.permissions) - self.modloader.load(config.get_index('LoadedMods', str)) -
-
[docs] def parse_command(self, sender=None, input=None): - """ Called internally by the ScalyMUCK server. - - When a user sends a string of data to the server for processing and have passed - the initial authentification stage, that string of data is passed in here by the - server for processing. This function is what actually performs the command lookups - in the loaded command database. - - Keyword arguments: - sender -- The instance of Player that has trying to invoke a command. - input -- The text that the Player happened to send. - - """ - returns = self.pre_message.send(None, sender=sender, input=input) - intercept_input = False - for set in returns: - if (set[1] is True): - intercept_input = True - break - - data = string.split(input, ' ') - command = string.lower(data[0]) - command_data = self.modloader.find_command(command) - if (intercept_input is False and command_data is not None): - try: - privilege = command_data['privilege'] - if (privilege == 1 and sender.is_admin is False): - sender.send('You must be an administrator.') - return - elif (privilege == 2 and sender.is_sadmin is False): - sender.send('You must be a super administrator.') - return - elif (privilege == 3 and sender.is_owner is False): - sender.send('You must be the owner of the server.') - return - - # You're not trying to do something you shouldn't be? Good. - command_func = command_data['command'] - command_func(sender=sender, input=input[len(command)+1:], arguments=data[1:len(data)]) - except exception.ModApplicationError as e: - line_one = 'An error has occurred while executing the command: %s' % (command) - line_two = 'From modification: %s' % (self.commands[command]['modification']) - line_three = 'Error Condition: ' - line_four = str(e) - - self.logger.error(line_one) - self.logger.error(line_two) - self.logger.error(line_three) - self.logger.error(line_four) - sender.send(line_one) - sender.send(line_two) - sender.send(line_three) - sender.send(line_four) - sender.send('Please report this incident to your server administrator immediately.') - - elif (intercept_input is False and command != ''): - sender.send('I do not know what it is to "%s".' % (command)) - - self.post_message.send(None, sender=sender, input=input)
-
- -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_modules/io.html b/doc/build/html/_modules/io.html deleted file mode 100644 index 47a406d..0000000 --- a/doc/build/html/_modules/io.html +++ /dev/null @@ -1,175 +0,0 @@ - - - - - - - - io — ScalyMUCK 3.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -

Source code for io

-"""The io module provides the Python interfaces to stream handling. The
-builtin open function is defined in this module.
-
-At the top of the I/O hierarchy is the abstract base class IOBase. It
-defines the basic interface to a stream. Note, however, that there is no
-separation between reading and writing to streams; implementations are
-allowed to throw an IOError if they do not support a given operation.
-
-Extending IOBase is RawIOBase which deals simply with the reading and
-writing of raw bytes to a stream. FileIO subclasses RawIOBase to provide
-an interface to OS files.
-
-BufferedIOBase deals with buffering on a raw byte stream (RawIOBase). Its
-subclasses, BufferedWriter, BufferedReader, and BufferedRWPair buffer
-streams that are readable, writable, and both respectively.
-BufferedRandom provides a buffered interface to random access
-streams. BytesIO is a simple stream of in-memory bytes.
-
-Another IOBase subclass, TextIOBase, deals with the encoding and decoding
-of streams into text. TextIOWrapper, which extends it, is a buffered text
-interface to a buffered raw stream (`BufferedIOBase`). Finally, StringIO
-is a in-memory stream for text.
-
-Argument names are not part of the specification, and only the arguments
-of open() are intended to be used as keyword arguments.
-
-data:
-
-DEFAULT_BUFFER_SIZE
-
-   An int containing the default buffer size used by the module's buffered
-   I/O classes. open() uses the file's blksize (as obtained by os.stat) if
-   possible.
-"""
-# New I/O library conforming to PEP 3116.
-
-__author__ = ("Guido van Rossum <guido@python.org>, "
-              "Mike Verdone <mike.verdone@gmail.com>, "
-              "Mark Russell <mark.russell@zen.co.uk>, "
-              "Antoine Pitrou <solipsis@pitrou.net>, "
-              "Amaury Forgeot d'Arc <amauryfa@gmail.com>, "
-              "Benjamin Peterson <benjamin@python.org>")
-
-__all__ = ["BlockingIOError", "open", "IOBase", "RawIOBase", "FileIO",
-           "BytesIO", "StringIO", "BufferedIOBase",
-           "BufferedReader", "BufferedWriter", "BufferedRWPair",
-           "BufferedRandom", "TextIOBase", "TextIOWrapper",
-           "UnsupportedOperation", "SEEK_SET", "SEEK_CUR", "SEEK_END"]
-
-
-import _io
-import abc
-
-from _io import (DEFAULT_BUFFER_SIZE, BlockingIOError, UnsupportedOperation,
-                 open, FileIO, BytesIO, StringIO, BufferedReader,
-                 BufferedWriter, BufferedRWPair, BufferedRandom,
-                 IncrementalNewlineDecoder, TextIOWrapper)
-
-OpenWrapper = _io.open # for compatibility with _pyio
-
-# for seek()
-SEEK_SET = 0
-SEEK_CUR = 1
-SEEK_END = 2
-
-# Declaring ABCs in C is tricky so we do it here.
-# Method descriptions and default implementations are inherited from the C
-# version however.
-class IOBase(_io._IOBase):
-    __metaclass__ = abc.ABCMeta
-
-class RawIOBase(_io._RawIOBase, IOBase):
-    pass
-
-class BufferedIOBase(_io._BufferedIOBase, IOBase):
-    pass
-
-class TextIOBase(_io._TextIOBase, IOBase):
-    pass
-
-RawIOBase.register(FileIO)
-
-for klass in (BytesIO, BufferedReader, BufferedWriter, BufferedRandom,
-              BufferedRWPair):
-    BufferedIOBase.register(klass)
-
-for klass in (StringIO, TextIOWrapper):
-    TextIOBase.register(klass)
-del klass
-
- -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_modules/models.html b/doc/build/html/_modules/models.html deleted file mode 100644 index 736e81e..0000000 --- a/doc/build/html/_modules/models.html +++ /dev/null @@ -1,781 +0,0 @@ - - - - - - - - - - models — ScalyMUCK 3.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -

Source code for models

-"""
-	This is where all of ScalyMUCK's model definitions are located,
-	save for the modifications that may extend the software in such
-	a way that it demands for extra models but that is beyond the
-	point. 
-
-	The "base" definitions located in models.py are:
-		* :class:`Exit`
-		* :class:`Player`
-		* :class:`Room`
-		* :class:`Item`
-		* :class:`Bot`
-
-	All of the above classes inherit a few functions from :class:`ObjectBase` as well.
-
-	Copyright (c) 2013 Robert MacGregor
-	This software is licensed under the GNU General
-	Public License version 3. Please refer to gpl.txt 
-	for more information.
-"""
-
-import string
-
-from blinker import signal
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import relationship, backref
-from sqlalchemy import Table, Column, Integer, String, Text, Boolean, MetaData, ForeignKey
-import bcrypt
-
-import exception
-
-server = None
-world = None
-Base = declarative_base()
-
-
[docs]class ObjectBase: - """ Base class used for the inheritance of useful member functions that work accross all models. """ - def delete(self): - """ Deletes the object from the world. """ - world.session.delete(self) - world.session.commit() - -
[docs] def set_name(self, name, commit=True): - """ Sets the name of the object. - - This sets the name of the object that is displayed and used to process requests towards it. - - Keyword arguments: - * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made - by any previous function calls thay may have set this to false. Default: True - - """ - self.name = name - if (type(self) is Player): - self.name = name.lower() - self.display_name = name - - if (commit is True): - world.session.add(self) - world.session.commit() -
-
[docs] def set_location(self, location, commit=True): - """ Sets the current location of this object. - - This sets the location of the object without any prior notification to the person - being moved (if it's a player) nor anyone in the original room or the target room. - That is the calling modification's job to provide any relevant messages. - - Keyword arguments: - * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made - by any previous function calls thay may have set this to false. Default: True - - """ - if (type(self) is Room): - return - - if (type(location) is Room): - self.location = location - self.location_id = location.id - if (commit): self.commit() - elif (type(location) is int): - location = world.session.query(Room).filter_by(id=location).first() - if (location is not None): - self.set_location(location, commit=commit) -
-
[docs] def set_description(self, description, commit=True): - """ Sets the description of this object. - - Sets the description of the calling object instance. - - Keyword arguments: - * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made - by any previous function calls thay may have set this to false. Default: True - - """ - self.description = description - if (commit): self.commit() -
-
[docs] def commit(self): - """ Commits any changes left in RAM to the database. """ - world.session.add(self) - world.session.commit() -
-
[docs] def delete(self): - """ Deletes the object from the world. If it is a :class:`Player` instance, the related - connection is dropped from the server. """ - if (type(self) is Player): - self.disconnect() - - world.session.delete(self) - world.session.commit() -
-
[docs]class Exit(Base, ObjectBase): - """ - - Exits are what the players use to exit and move into other rooms in the ScalyMUCK - world. They may only have one target room ID which is used to assign .target to them - when they are loaded or creates by the game.World instance. - - """ - - __tablename__ = 'exits' - - id = Column(Integer, primary_key=True) - """ This variable is the database number of the Exit. """ - name = Column(String(25)) - """ A short 25 character string that should be used for the name of the Exit. """ - description = Column(String(2000)) - """ A 2000 character string that is used to describe the Exit if it ever happens to be needed. """ - user_enter_message = Column(String(100)) - """ A 100 character string that represents the message displayed to the :class:`Player` using this exit. """ - room_enter_message = Column(String(100)) - """ A 100 character string that represents the message displayed to the user upon entering the target :class:`Room`. """ - user_exit_message = Column(String(100)) - """ A 100 character string that represents the message displayed to the :class:`Player` using this exit. """ - room_exit_message = Column(String(100)) - """ A 100 character string that is displayed to the inhabitants of the :class:`Room` this Exit is located in upon use. """ - target_id = Column(Integer) - """ This variable is the database number of the :class:`Room` that this Exit points to. """ - location_id = Column(Integer, ForeignKey('rooms.id')) - """ This variable is the database number of the :class:`Room` this Exit is in. """ - owner_id = Column(Integer, ForeignKey('players.id')) - """ This variable is the database number of the :class:`Player` this Exit belongs to. """ - -
[docs] def __init__(self, name, target=None, owner=0): - """ Initializes an instance of the Exit model. - - The Exit is not constructed manually by any of the modifications, this should be - performed by calling the create_player function on the game.World instance provided - to every modification. - - Keyword arguments: - * target -- The ID or instance of a Room that this exit should be linking to. - * owner -- The ID or instance of a Player that this should should belong to. - - """ - if (target is None): - raise exception.ModelArgumentError('No target was specified. (or it was None)') - - # Set the name - self.name=name - if (type(target) is int): - self.target_id = target - else: - self.target_id = target.id - - # Set the owner - if (type(owner) is int): - self.owner_id = owner - else: - self.owner_id = owner.id - - self.user_enter_message = 'You move out.' - self.room_enter_message = 'left.' - self.user_exit_message = 'You arrive in the next room.' - self.room_exit_message = 'arrived from another room.' - self.description = '<Unset>' -
-
[docs] def __repr__(self): - """ Produces a representation of the exit, as to be expected. """ - return "<Exit('%s','%u'>" % (self.name, self.target_id) -
-
[docs]class Player(Base, ObjectBase): - """ - - Players are well, the players that actually move and interact in the world. They - store their password hash and various other generic data that can be used across - just about anything. - - """ - __tablename__ = 'players' - - id = Column(Integer, primary_key=True) - """ This variable is the database number of the Player. """ - name = Column(String(25)) - """ A short 25 character string representing the name of the Player. It should be all lower case. """ - display_name = Column(String(25)) - """ A short 25 character string representing the name of the Player that is displayed to other users. """ - description = Column(String(2000)) - """ A 2000 character string representing the description of the Player. """ - hash = Column(String(128)) - """ A 128 character string representing the password hash of the Player. """ - - location_id = Column(Integer, ForeignKey('rooms.id')) - """ This variable is the database id of the :class:`Room` this Player is located in. """ - location = None - inventory_id = Column(Integer) - """ This variable is the database id of the :class:`Room` that represents the Player's inventory. """ - - is_admin = Column(Boolean) - """ A boolean representing whether or not this Player is a server administrator. """ - is_sadmin = Column(Boolean) - """ A boolean representing whether or not this Player is a server super administrator. """ - is_owner = Column(Boolean) - """ A boolean representing whether or not this Player is a server owner. """ - - connection = None - world = None - server = None - interface = None - - def __init__(self, name=None, password=None, workfactor=None, location=None, inventory=None, description='<Unset>', admin=False, sadmin=False, owner=False): - """ Initializes an instance of the Player model. - - The Player is not constructed manually by any of the modifications, this should be - performed by calling the create_player function on the game.World instance provided - to every modification. When the instance is created, it automatically generates a salt - that the Player's password will be hashed with. This salt generation will cause a small - lockup as the calculations are pretty intense depending on the work factor. - - Keyword arguments: - * name -- The name of the Player that should be used. - * password -- The password that should be used for this Player in the database. - * workfactor -- The workfactor that should be used when generating this Player's hash salt. - * location -- An ID or instance of Room that this Player should be created at. - * inventory -- An ID or instance of Room that should represent this Player's inventory. - * description -- A description that is shown when the player is looked at. Default: <Unset> - * admin -- A boolean representing whether or not the player is an administrator or not. Default: False - * sadmin -- A boolean representing whether or not the player is a super administrator or not. Default: False - * owner -- A boolean representing whether or not the player is the owner of the server. Default: False - - """ - self.name = string.lower(name) - self.display_name = name - self.description = description - self.work_factor = workfactor - - if (type(location) is Room): - location = location.id - self.location_id = location - if (type(inventory) is Room): - inventory = inventory.id - self.inventory_id = inventory - - self.hash = bcrypt.hashpw(password, bcrypt.gensalt(workfactor)) - self.is_admin = admin - self.is_sadmin = sadmin - self.is_owner = owner - - def __repr__(self): - """ Produces a representation of the player, as to be expected. """ - return "<Player('%s','%s','%s','%u')>" % (self.name, self.description, self.hash, self.location_id) - -
[docs] def send(self, message): - """ Sends a message to this Player if they are connected. - - This sends a message to the relevant connection if there happens to be one established - for this player object. If there is no active connection for this player, then the message - is simply dropped. - - """ - if (self.connection is None): - self.connection = server.find_connection(self.id) - - if (self.connection is not None): - self.connection.send(message + '\n') - return True - else: - return False -
-
[docs] def set_password(self, password, commit=True): - """ Sets the password of this Player. - - This sets the password of the Player in the database without any notification to the Player. This also - triggers the new password to be hashed with a randomly generated salt which means the current thread - of execution will probably hang for a few seconds unless the work factor is set just right. - - Keyword arguments: - commit -- Determines whether or not this data should be commited immediately. It also includes other changes made - by any previous function calls thay may have set this to false. Default: True - - """ - self.hash = bcrypt.hashpw(password, bcrypt.gensalt(server.work_factor)) - if (commit is True): self.commit() -
-
[docs] def disconnect(self): - """ Drops the Player's connection from the server. - - This drops the user's connection from the game, flushing any messages that - were destined for them before actually booting them out of the server. This triggers - the default disconnect message to be displayed by the ScalyMUCK core to whomever else - may be in the room with the user at the time of their disconnect. - - """ - if (self.connection is not None): - self.connection.socket_send() - self.connection.deactivate() - self.connection.sock.close() -
-
[docs] def set_is_admin(self, status, commit=True): - """ Sets the administrator status of this Player. - - This sets the state as to whether or not the calling player instance is an administrator - of the server or not. This may mean different things to different mods but -generally- it - should just provide basic administrator privileges. The commit parameter is used if you don't - want to dump changes to the database yet; if you're changing multiple properties at once, it - doesn't hit the database as often then. - - Keyword arguments: - * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made - by any previous function calls thay may have set this to false. Default: True - - """ - self.is_admin = status - if (self.is_sadmin is True and status is False): - self.is_sadmin = False - if (commit): self.commit() -
-
[docs] def set_is_super_admin(self, status, commit=True): - """ Sets the super administrator status of this Player. - - This sets the state as to whether or not the calling player instance is a super administrator - of the server or not. This may mean different things to different mods but -generally- it - should provide administrator functionalities more akin to what the owner may have, things that - may break the server if used improperly. The commit parameter is used if you don't want to dump - changes to the database yet; if you're changing multiple properties at once, it doesn't hit the database as - often then. - - Keyword arguments: - * commit -- Determines whether or not this data should be commited immediately. It also includes other changes made - by any previous function calls thay may have set this to false. Default: True - - """ - self.is_sadmin = status - if (self.is_admin is False and status is True): - self.is_admin = True - if (commit): self.commit() -
-
[docs] def check_admin_trump(self, target): - """ Checks whether or not this Player trumps the target Player administratively. - - This returns True when the calling Player instance has greater privilege than that of the target they are being - compared against. It returns false in the opposite situation. - """ - if (type(target) is not Player): - raise exception.ModelArgumentError('Target is not an instance of Player!') - - if (self.is_admin is True and target.is_admin is False): - return True - elif (self.is_sadmin is True and target.is_sadmin is False): - return True - elif (self.is_owner is True and target.is_owner is False): - return True - else: - return False -
-
[docs]class Bot(Base, ObjectBase): - """ - - Bots are basically just the AI's of the game. They're not items, but they're not players - either. They have the special property of being interchangable with player object instances - for the most part except when it comes to certain functions -- such as having a password - hash. - - """ - __tablename__ = 'bots' - - id = Column(Integer, primary_key=True) - """ This variable is the database number of the Bot. """ - name = Column(String(25)) - """ A short 25 character string representing the name of the Bot. It should be all lowercase. """ - description = Column(String(2000)) - """ A 2000 character string representing the description of the Bot. """ - display_name = Column(String(25)) - """ A 25 character string representing the name of the Bot displayed to Players. """ - location_id = Column(Integer, ForeignKey('rooms.id')) - """ This variable is the database number of the :class:`Room` this Bot is located in. """ - - def __init__(self, name=None, description=None, location=None): - """ - - The Bot is not constructed manually by any of the modifications, this should be - performed by calling the create_player function on the game.World instance provided - to every modification. - - """ - self.name = name - self.description = description - self.display_name = name - self.name = name.lower() - - if (type(location) is Room): - location = location.id - self.location_id = location - -
[docs] def send(self, message): - """ This is basically 'send' like for players except it does NOTHING. """ -
-
[docs]class Item(Base, ObjectBase): - """ Base item model that ScalyMUCK uses. - - Items are really a generic object in ScalyMUCK, they're not players nor rooms nor exits but - serve multiple purposes. They can quite literally be an item, stored in the player's inventory - to be used later (like a potion) or they may be used to decorate rooms with specific objects - such as furniture or they may even be used to represent dead bodies. - - """ - __tablename__ = 'items' - - id = Column(Integer, primary_key=True) - """ This variable is the database number of this Item. """ - name = Column(String(25)) - """ A short 25 character string representing the name of the Item that is displayed to every connection - related to a :class:`Player`. """ - owner_id = Column(Integer, ForeignKey('players.id')) - """ This is the database number of the :class:`Player` that this Item belongs to. """ - description = Column(String(2000)) - """ A 2000 character string representing the description of this Item. """ - location_id = Column(Integer, ForeignKey('rooms.id')) - """ This is the database number of the :class:`Room` that this Item is located in. """ - - def __init__(self, name=None, description='<Unset>', owner=0): - """ Constructor for the base item model. - - The Item is not constructed manually by any of the modifications, this should be - performed by calling the create_player function on the game.World instance provided - to every modification. - - Keyword arguments: - * name -- The name of the item that should be used. - * description -- The description of the item that should be displayed. Default: <Unset> - * owner -- Whoever should become the owner of this item, by instance or ID. Default: 0 - - """ - if (name is None): - raise exception.ModelArgumentError('No name specified when attempting to create an instance of Item! (Or it is none)') - - self.name = name - self.description = description - if (type(owner) is int): - self.owner_id = owner - else: - self.owner_id = owner.id - - def __repr__(self): - """ Produces a representation of the item, as to be expected. """ - return "<Item('%s','%s')>" % (self.name, self.description) - -
[docs] def set_owner(self, owner): - """ Sets a new owner for this item. """ - if (type(owner) is Player): - owner = owner.id - self.owner_id = owner - self.commit() -
-
[docs]class Room(Base, ObjectBase): - """ Base room model that ScalyMUCK uses. - - Rooms are what make up the world inside of just about any MUCK really. Even if the rooms are not described as such, they still - must be used to contain players, dropped items, bots, etc. - - """ - __tablename__ = 'rooms' - id = Column(Integer, primary_key=True) - """ This is the database number of the Room. """ - - name = Column(String(25)) - """ A short 25 character string representing the name of this Room that is displayed to every connection related to - an instance of :class:`Player`. """ - description = Column(String(2000)) - """ A 2000 character string representing the description of this Room. """ - items = relationship('Item') - """ An array that contains all instances of :class:`Item` contained in this Room. """ - players = relationship('Player') - """ An array that contains all instances of :class:`Player` contained in this Room. """ - bots = relationship('Bot') - """ An array that contains all instances of :class:`Bot` contained in this Room. """ - exits = relationship('Exit') - """ An array that contains all instances of :class:`Exit` contained in this Room. """ - owner_id = Column(Integer) - """ This is the database number of the :class:`Player` that this Room belongs to. """ - - def __init__(self, name=None, description='<Unset>', owner=0): - """ Initiates an instance of the Room model. - - The Room is not constructed manually by any of the modifications, this should be - performed by calling the create_player function on the game.World instance provided - to every modification. - - Keyword arguments: - * name -- The name of the room that should be used. - * description -- The description of the room. - * owner -- The instance or ID of the relevant Player instance that should own this room. - - """ - if (name is None): - raise exception.ModelArgumentError('The name was not specified. (or it is None)') - - self.name = name - self.description = description - self.exits = [ ] - if (type(owner) is int): - self.owner_id = owner - else: - self.owner_id = owner.id - - def __repr__(self): - """ Produces a representation of the room, as to be expected. """ - return "<Room('%s','%s')>" % (self.name, self.description) - -
[docs] def add_exit(self, name=None, target=None, owner=0): - """ Gives this Room instance an Exit linking to anywhere. - - This produces an Exit link from the calling Room instance to another one elsewhere. The database - changes are automatically committed here due to the way the exit has to be created in the database - first for the changes to properly apply without any hackery to occur. - - Keyword arguments: - * name -- The name of the exit that should be used. - * target -- The ID or instance of a Room that this exit should be linking to. - * owner -- The ID or instance of a Player that should become the owner of this Exit. - - """ - if (name is None): - raise exception.ModelArgumentError('The name was not specified. (or it was None)') - elif (target is None): - raise exception.ModelArgumentError('The target room was not specified. (or it was None)') - - if (type(target) is int): - target = world.find_room(id=target) - if (target is not None): - self.add_exit(name, target) - elif (type(target) is Room): - exit = Exit(name, target, owner) - self.exits.append(exit) - world.session.add(self) - world.session.add(exit) - world.session.commit() -
-
[docs] def broadcast(self, message, *exceptions): - """ Broadcasts a message to all inhabitants of the Room except those specified. - - This broadcasts a set message to all inhabitants of the Room except those specified after the message: - some_room.broadcast('Dragons are best!', that_guy, this_guy, other_guy) - - The exceptions may be an ID or an instance. - - """ - for player in self.players: - if (player not in exceptions and player.id not in exceptions): - player.send(message) -
-
[docs] def find_player(self, id=None, name=None): - """ Locates a Player located in the calling instance of Room. - - This is a less computionally intensive version of the world's find_player as there is going to be much less data - to be sorting through since you actually know where the Player is located (otherwise you wouldn't be calling this!) - and all you need is the instance. - - Keyword arguments (one or the other): - * id -- The identification number of the Player to locate inside of this room. This overrides the name if - * both are specified. - * name -- The name of the Player to locate. - - """ - if (id is None and name is None): - raise exception.ModelArgumentError('No arguments specified (or were None)') - - player = None - if (id is not None): - for test_player in self.players: - if (id == test_player.id): - player = test_player - break - elif (name is not None): - name = string.lower(name) - for test_player in self.players: - if (name in test_player.name.lower()): - player = test_player - break - return player -
-
[docs] def find_bot(self, id=None, name=None): - """ Locates a Bot located in the calling instance of Room. - - This is a less computionally intensive version of the world's find_bot as there is going to be much less data - to be sorting through since you actually know where the Bot is located (otherwise you wouldn't be calling this!) - and all you need is the instance. - - Keyword arguments (one or the other): - * id -- The identification number of the Bot to locate inside of this room. This overrides the name if - * both are specified. - * name -- The name of the Bot to locate. - - """ - if (id is None and name is None): - raise exception.ModelArgumentError('No arguments specified (or were None)') - - bot = None - if (id is not None): - for test_bot in self.bots: - if (id == test_bot.id): - bot = test_bot - break - elif (name is not None): - name = string.lower(name) - for test_bot in self.bots: - if (name in test_bot.name.lower()): - bot = test_bot - break - return bot -
-
[docs] def get_exits(self): - """ Return a list of all exits. """ - for exit in self.exits: - exit.location = self - return self.exits -
-
[docs] def find_item(self, id=None, name=None): - """ Locates an Item located in the calling instance of Room. - - This is a less computionally intensive version of the world's find_item as there is going to be much less data - to be sorting through since you actually know where the Item is located (otherwise you wouldn't be calling this!) - and all you need is the instance. - - Keyword arguments (one or the other): - * id -- The identification number of the Item to locate inside of this room. This overrides the name if - * both are specified. - * name -- The name of the Item to locate. - - """ - if (id is None and name is None): - raise exception.ModelArgumentError('No arguments specified (or were None)') - - item = None - if (id is not None): - for test_item in self.items: - if (test_item.id == id): - item = test_item - break - elif (name is not None): - name = string.lower(name) - for test_item in self.items: - if (name in test_item.name.lower()): - item = test_item - break - return item -
-
[docs] def find_exit(self, id=None, name=None): - """ Locates an Item located in the calling instance of Room. - - This is a less computionally intensive version of the world's find_item as there is going to be much less data - to be sorting through since you actually know where the Item is located (otherwise you wouldn't be calling this!) - and all you need is the instance. - - Keyword arguments (one or the other): - * id -- The identification number of the Item to locate inside of this room. This overrides the name if - * both are specified. - * name -- The name of the Item to locate. - - """ - if (id is None and name is None): - raise exception.ModelArgumentError('No arguments specified (or were None)') - - exit = None - if (id is not None): - for test_exit in self.exits: - if (test_exit.id == id): - exit = test_exit - break - elif (name is not None): - name = string.lower(name) - for test_exit in self.exits: - if (name in test_exit.name.lower()): - exit = test_exit - break - return exit
-
- -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_modules/modloader.html b/doc/build/html/_modules/modloader.html deleted file mode 100644 index 9e016df..0000000 --- a/doc/build/html/_modules/modloader.html +++ /dev/null @@ -1,270 +0,0 @@ - - - - - - - - - - modloader — ScalyMUCK 3.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -

Source code for modloader

-"""
-	Modification loader class code for ScalyMUCK. This class
-	is used to store and track loaded modifications that
-	are in use by the server application
-
-	Copyright (c) 2013 Robert MacGregor
-	This software is licensed under the GNU General
-	Public License version 3. Please refer to gpl.txt 
-	for more information.
-"""
-
-import logging
-import inspect
-import importlib
-
-import settings
-
-logger = logging.getLogger('Mods')
-
[docs]class ModLoader: - """ Mod loading class for ScalyMUCK. """ - workdir = None - world = None - interface = None - session = None - permissions = None - commands = { } - """ A dictionary of all loaded commands. The keys are the actual name a command is referred to by and said keys point to - Python functions to be called upon use. """ - modifications = { } - """ A dictionary of all loaded modifications. The keys are the internal name of the mod and each key refers to a tuple - with the following format: (instance, module). """ - -
[docs] def __init__(self, world=None, interface=None, session=None, workdir=None, permissions=None): - """ Initializes an instance of the ScalyMUCK mod loader. - - There are several keyword arguments that should be used with this __init__ command: - * world -- An instance of the game.World object to be used with this ModLoader. - * interface -- An instance of the game.Interface object to be used with this ModLoader. - * session -- An active database session from SQLAlchemy to be used with this ModLoader. - * workdir -- The work directory of the current running application. - * permissions -- An instance of the game.Permissions object to be used with this ModLoader. - - """ - self.workdir = workdir - self.world = world - self.interface = interface - self.session = session - self.permissions = permissions - self.set_defaults() -
-
[docs] def load(self, modifications): - """ Loads a semicolon deliminated list of modifications from application/game. - - If any of the specified modifications happen to already be loaded into ScalyMUCK, - they are merely reloaded so that any changes made since the last load will be applied - and take affect. - - NOTE: - If there happens to be a low-level Python error (IE: SyntaxError) then the server - application will merely crash as of the moment. Same goes for any internal errors in a mod - code base that happen to raise an exception that is not derived from one of the exception - classes in game.Exception. - - """ - for mod_name in modifications.split(';'): - mod_name = mod_name.lower() - if (mod_name in self.modifications): - # Reloads the modification - module = self.modifications[mod_name] - modules = inspect.getmembers(module, inspect.ismodule) - for name, sub_module in modules: - reload(sub_module) - module = reload(module) - modification = module.Modification(config=self.config, world=self.world, interface=self.interface, session=self.session, permissions=self.permissions, modloader=self) - self.modifications[mod_name] = (instance, module) - else: - try: - module = importlib.import_module('game.%s' % (mod_name)) - except ImportError as e: - self.logger.warning(str(e)) - return False - else: - config = settings.Settings('%s/config/%s.cfg' % (self.workdir, mod_name)) - - modification = module.Modification(config=config, world=self.world, interface=self.interface, session=self.session, permissions=self.permissions, modloader=self) - self.modifications.setdefault(mod_name, (modification, module)) - commands = modification.get_commands() - logger.info('Processed modification %s.' % (mod_name)) - - # Process aliases first - aliases = { } - for command in commands: - for alias in commands[command]['aliases']: - aliases.setdefault(alias, commands[command]) - commands.update(aliases) - # Then process the dictionary - for command in commands: - commands[command]['modification'] = mod_name - if (command in self.commands): - logger.warn('Overlapping command definitions for command "%s"! %s -> %s' % (command, mod_name, self.commands[command]['modification'])) - self.commands.update(commands) - self.set_defaults() - - return True -
-
[docs] def set_defaults(self): - """ This command merely makes sure that the core commands are loaded. - - It should be called everytime a modification is loaded in order to guarantee - that a modification doesn't attempt to overwrite the core commands, even though - by standard they shouldn't be defining these commands in the first place. - - """ - self.commands['mods'] = { } - self.commands['mods']['command'] = self.command_mods - self.commands['mods']['description'] = 'Lists all loaded mods.' - self.commands['mods']['usage'] = 'mods' - self.commands['mods']['privilege'] = 3 - self.commands['mods']['modification'] = '<CORE>' - - self.commands['load'] = { } - self.commands['load']['command'] = self.command_load - self.commands['load']['description'] = 'Loads the specified mod.' - self.commands['load']['usage'] = 'load <name>' - self.commands['load']['privilege'] = 3 - self.commands['load']['modification'] = '<CORE>' - - self.commands['unload'] = { } - self.commands['unload']['command'] = self.command_unload - self.commands['unload']['description'] = 'Unloads the specified mod.' - self.commands['unload']['usage'] = 'unload <name>' - self.commands['unload']['privilege'] = 3 - self.commands['unload']['modification'] = '<CORE>' -
-
[docs] def find_command(self, name): - """ Returns a command by name. """ - if (name not in self.commands): - return None - else: - return self.commands[name] - - # CORE Commands
-
[docs] def command_mods(self, **kwargs): - """ Internal command to list installed mods. """ - sender = kwargs['sender'] - loaded = '' - sender.send('Loaded modifications: ') - for key in self.modifications.keys(): - loaded += '%s, ' % (key) - sender.send(loaded.rstrip(', ')) -
-
[docs] def command_load(self, **kwargs): - """ Internal command to load mods. """ - sender = kwargs['sender'] - input = kwargs['input'].lower() - if (input.strip() == ' '): - sender.send('No input.') - - if (self.load(input) is False): - sender.send('Failed to load mod "%s".' % (input)) - else: - sender.send('Loaded mod "%s".' % (input)) -
-
[docs] def command_unload(self, **kwargs): - """ Internal command to unload mods. """ - sender = kwargs['sender'] - input = kwargs['input'].lower() - if (input in self.modifications): - instance, module = self.modifications[input] - self.modifications.pop(input) - commands = instance.get_commands() - for command in commands.keys(): - self.commands.pop(command) - sender.send('Mod "%s" unloaded.' % (input)) - else: - sender.send('Unknown modification.')
-
- -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_modules/permissions.html b/doc/build/html/_modules/permissions.html deleted file mode 100644 index 5aa33dd..0000000 --- a/doc/build/html/_modules/permissions.html +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - - - - - permissions — ScalyMUCK 3.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -

Source code for permissions

-"""
-	Permission handler for the ScalyMUCK server. It
-	employs a sort of repository system used for storing
-	and evaluating permissions with variable terms and
-	exceptions.
-
-	Copyright (c) 2013 Robert MacGregor
-	This software is licensed under the GNU General
-	Public License version 3. Please refer to gpl.txt 
-	for more information.
-"""
-
-import logging
-
-import settings
-
-logger = logging.getLogger('Mods')
-
[docs]class Permissions: - """ Main class for permission handling in ScalyMUCK. """ - permissions = { } - workdir = None - -
[docs] def __init__(self, workdir=None): - """ Initializes a new instance of the permissions repository class. - - NOTE: - You must pass in a good work directory in order for the permissions repository - class to be able to load the server global config/permissions.cfg file. - - """ - self.workdir = workdir - permission_settings = settings.Settings('%s/config/permissions.cfg' % (self.workdir)) - for index in permission_settings.get_indices(): - self.set_permission(index, permission_settings.get_index(index, bool)) -
-
[docs] def set_permission(self, name=None, value=None, evaluator=None): - """ Sets a permission in the repo. """ - if (evaluator is None): - evaluator = self.standard_evaluator - self.permissions[name] = (value, evaluator) -
-
[docs] def has_permission(self, name): - """ Determines whether or not a permission is actually set in the repo. """ - if (name in self.permissions): - return True - else: - return False -
-
[docs] def test(self, name=None, player=None): - """ Tests the permission availability against a player. """ - if (name in self.permissions): - value, evaluator = self.permissions[name] - return evaluator(name, player, value) -
-
[docs] def standard_evaluator(self, name=None, player=None, value=None): - """ Tests all standard permissions built into the server. - - Keyword arguments: - * name -- The name of the permission to attempt to evaluate. - * player -- An instance of game.models.Player to evaluate against. - * value -- The specific variable used to configure this permission setting. - - """ - if (name == 'AllowAdminOverride'): - return value - elif (name == 'AllowSuperAdminOverride'): - return value - elif (name == 'AllowOwnerOverride'): - return value - else: - logger.warn('Attempted to evaluate undefined permission: "%s"!' % (name)) - return False
-
- -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_modules/scommands.html b/doc/build/html/_modules/scommands.html deleted file mode 100644 index 304f83d..0000000 --- a/doc/build/html/_modules/scommands.html +++ /dev/null @@ -1,694 +0,0 @@ - - - - - - - - - - scommands — ScalyMUCK 3.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -

Source code for scommands

-"""
-	Found here is a great example of how modifications are implemented inside of ScalyMUCK. All modifications that
-	are intended to be loaded into ScalyMUCK must be provided in the form of a module you can merely drag and drop
-	into application/game/ and from there the server host can modify their server_config.cfg file to add the modification
-	to the loaded modification listing.
-
-	Copyright (c) 2013 Robert MacGregor
-	This software is licensed under the GNU General
-	Public License version 3. Please refer to gpl.txt 
-	for more information.
-"""
-
-import string
-
-from blinker import signal
-
-import game.models
-
-
[docs]class Modification: - """ Main class object to load and initialize the scommands modification. """ - world = None - interface = None - session = None - - pre_user_look = signal('pre_user_look') - post_user_look = signal('post_user_look') - pre_user_say = signal('pre_user_say') - post_user_say = signal('post_user_say') - pre_user_pose = signal('pre_user_pose') - post_user_pose = signal('post_user_pose') - pre_item_pickup = signal('pre_item_pickup') - post_item_pickup = signal('post_item_pickup') - pre_item_drop = signal('pre_item_drop') - post_item_drop = signal('post_item_drop') - post_user_create = signal('post_user_create') - pre_exit_room = signal('pre_exit_room') - post_exit_room = signal('post_exit_room') - pre_show_description = signal('pre_show_description') - -
[docs] def __init__(self, **kwargs): - """ - - This initializes an instance of the scommands modification and it will remain loaded in memory - until the server owner either unloads it or reloads it which therefore will reset - any associated data unless the data had been defined on the class definition itself - rather than being initialized in this function. - - Keyword arguments: - * config -- This is the instance of Settings that contains all loaded configuration settings available for this modification, if the file exists. If the file does not exist, then this will simply be None. - * interface -- This is the instance of the user interface used internally by ScalyMUCK. Generally, you won't need access to this for any reason and is currently deprecated for later removal. - - Actions such as binding your Blinker signals should be performed here so that events will be - received properly when they occur. - - Along with initializing the modification, __init__ acts as a gateway for other important - data passed in by the modloader under the **kwargs argument. - - """ - - self.config = kwargs['config'] - self.interface = kwargs['interface'] - self.session = kwargs['session'] - self.world = kwargs['world'] - self.permissions = kwargs['permissions'] - - signal('post_client_authenticated').connect(self.callback_client_authenticated) - signal('pre_message_sent').connect(self.callback_message_sent) - - # Commands
- def command_say(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - - if (input == ''): - sender.send('Usage: say <message>') - return - - results = self.pre_user_say.send(None, sender=sender, input=input) - for result in results: - if (result[1] is True): - return - - sender.location.broadcast('%s says, "%s"' % (sender.display_name, input), sender) - sender.send('You say, "%s"' % (input)) - self.post_user_say.send(None, sender=sender, input=input) - - def command_pose(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - results = self.pre_user_pose.send(None, sender=sender, input=input) - for result in results: - if (result[1] is True): - return - - sender.location.broadcast('%s %s' % (sender.display_name, kwargs['input'])) - self.post_user_pose.send(None, sender=sender, input=input) - - def command_look(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - target = sender.location - name = sender.location.name - - self.pre_user_look.send(sender=sender, input=input) - - if (input != ''): - target = sender.location.find_player(name=input) - if (target is None): - target = sender.location.find_bot(name=input) - - if (target is not None): - name = target.display_name - if (type(target) is game.models.Player): - target.send('++++++++ %s is looking at you!' % (sender.display_name)) - else: - target = sender.location.find_item(name=input) - if (target is None): - target = sender.inventory.find_item(name=input) - - if (target is not None): - name = target.name - else: - sender.send('I do not see that.') - return - - sender.send('<' + name + '>') - - if (type(target) is game.models.Room): - sender.send('Obvious Exits: ') - if (len(target.exits) != 0): - for exit in target.exits: - sender.send(' %s' % (exit.name)) - else: - sender.send(' None') - - sender.send('People: ') - for player in target.players: - sender.send(' %s' % (player.display_name)) - - sender.send('Bots: ') - if (len(target.bots) != 0): - for bot in target.bots: - sender.send(' %s' % (bot.display_name)) - else: - sender.send(' None') - - sender.send('Items: ') - if (len(target.items) != 0): - for item in target.items: - sender.send(' %s' % (item.name)) - else: - sender.send(' None') - - self.pre_show_description.send(None, sender=sender, target=target) - sender.send('Description:') - sender.send(target.description) - - self.post_user_look.send(sender=sender, input=input, target=target) - - def command_move(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - - if (input == ''): - sender.send('Usage: move <name of exit>') - return - - exit = sender.location.find_exit(name=input) - if (exit is not None): - sender.send(exit.user_enter_message) - sender.location.broadcast('%s %s' % (sender.display_name, exit.room_enter_message), sender) - results = self.pre_exit_room.send(None, sender=sender, target=exit.target_id) - for result in results: - if (result[1] is True): - return - sender.set_location(exit.target_id) - sender.location.broadcast('%s %s' % (sender.display_name, exit.room_exit_message), sender) - sender.send(exit.user_exit_message) - self.command_look(sender=sender, input='') - self.post_exit_room.send(None, sender=sender, target=sender.location) - else: - sender.send('I do not see that.') - - def command_inventory(self, **kwargs): - sender = kwargs['sender'] - - sender.send('Items:') - if (len(sender.inventory.items) != 0): - for item in sender.inventory.items: - sender.send(' %s' % (item.name)) - else: - sender.send(' None') - - # Pocket dimension anybody? - if (len(sender.inventory.players) != 0): - sender.send('People: ') - for player in sender.inventory.players: - sender.send(' %s' % (player.display_name)) - - def command_take(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - - if (input == ''): - sender.send('Usage: take <object name>') - return - - item = sender.location.find_item(name=input) - if (item is not None): - results = self.pre_item_pickup.send(None, sender=sender, item=item) - for result in results: - if (result[1] is True): - return - - sender.send('Taken.') - - item.set_location(sender.inventory) - self.post_item_pickup.send(None, sender=sender, item=item) - return - - sender.send('I do not see that.') - - def command_passwd(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - - if (input == ''): - sender.send('Usage: passwd <password>') - return - - sender.set_password(input) - sender.send('Your password has been changed. Remember it well.') - - def command_drop(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - - if (input == ''): - sender.send('Usage: drop <item name>') - return - - item = sender.inventory.find_item(name=input) - if (item is not None): - results = self.pre_item_drop.send(sender=sender, item=item) - for result in results: - if (result[1] is True): - return - - sender.send('You dropped a/an %s.' % (item.name )) - sender.location.broadcast('%s drops a/an %s.' % (sender.display_name, item.name) ,sender) - item.set_location(sender.location) - self.post_item_drop.send(sender=sender, item=item) - else: - sender.send('I see no such item.') - - def command_help(self, **kwargs): - sender = kwargs['sender'] - input = string.lower(kwargs['input']) - - if (input not in self.interface.commands): - sender.send('For more information, type: help <command>') - sender.send('Command listing: ') - out = '' - for command in self.interface.commands: - privilege = self.interface.commands[command]['privilege'] - if ((privilege == 1 and sender.is_admin is False) or (privilege == 2 and sender.is_sadmin is False) or (privilege == 3 and sender.is_owner is False)): - continue - out += command + ', ' - # Cheap trick to strip off the last comma (and space) but eh! - sender.send(out[:len(out)-2]) - return - else: - sender.send('From: %s' % (self.interface.commands[input]['modification'])) - sender.send('Usage: %s' % (self.interface.commands[input]['usage'])) - sender.send(self.interface.commands[input]['description']) - - def command_quit(self, **kwargs): - sender = kwargs['sender'] - sender.disconnect() - - def command_froguser(self, **kwargs): - sender = kwargs['sender'] - args = kwargs['arguments'] - if (len(args) < 1): - sender.send('Usage: frog <name>') - return - - name = args[0] - target = self.world.find_player(name=string.lower(name)) - if (target is not None): - if (target is sender): - sender.send('That is yourself!') - return - elif (target.is_sadmin ==1 or target.is_owner == 1): - sender.send('You cannot do that, they are too powerful.') - return - - item = self.world.create_item(target.display_name, target.description, sender, sender.inventory) - sender.send('User "%s" frogged. Check your inventory.' % (target.display_name)) - target.send('%s has turned you into a small plastic figurine, never to move again and discreetly places you in their inventory.' % (sender.display_name)) - sender.location.broadcast('%s has turned %s into a small plastic figurine, never to move again.' % (sender.display_name, target.display_name), sender, target) - target.delete() - else: - sender.send('User "%s" does not exist anywhere.' % (name)) - - def command_adduser(self, **kwargs): - sender = kwargs['sender'] - args = kwargs['arguments'] - if (len(args) < 2): - sender.send('Usage: adduser <name> <password>') - return - - name = args[0] - password = args[1] - - if (self.world.find_player(name=string.lower(name)) is not None): - sender.send('User already exists.') - return - - # TODO: Make this take server prefs into consideration, and also let this have a default location ... - player = self.world.create_player(name, password, game.models.server.work_factor, sender.location) - sender.send('User "%s" created.' % (name)) - self.post_user_create.send(None, creator=sender, created=player) - - def command_admin(self, **kwargs): - sender = kwargs['sender'] - args = kwargs['arguments'] - if (len(args) < 1): - sender.send('Usage: admin <name>') - return - - name = args[0] - - target = self.world.find_player(name=string.lower(name)) - if (target is not None): - if (target is sender): - sender.send('That is yourself!') - return - elif (sender.check_admin_trump(target) is False): - sender.send('You cannot do that. They are too strong.') - target.send('%s tried to take your administrator privileges away.' % (sender.display_name)) - return - - target.set_is_admin(target.is_admin is False) - if (target.is_admin == 0): - sender.send('%s is no longer an administrator.' % (target.display_name)) - target.send('%s took your adminship.' % (sender.display_name)) - return - else: - sender.send('%s is now an administrator.' % (target.display_name)) - target.send('%s gave you adminship rights.' % (sender.display_name)) - return - sender.send('Unknown user.') - - def command_sadmin(self, **kwargs): - sender = kwargs['sender'] - args = kwargs['arguments'] - if (len(args) < 1): - sender.send('Usage: sadmin <name>') - return - - name = args[0] - - target = self.world.find_player(name=string.lower(name)) - if (target is not None): - if (target is sender): - sender.send('That is yourself!') - return - elif (sender.check_admin_trump(target) is False): - sender.send('You cannot do that. They are too strong.') - target.send('%s tried to take your super administrator privileges away.' % (sender.display_name)) - return - - target.set_is_super_admin(target.is_sadmin is False) - if (target.is_sadmin is False): - sender.send('%s is no longer a super administrator.' % (target.display_name)) - target.send('%s took your super adminship.' % (sender.display_name)) - return - else: - sender.send('%s is now a super administrator.' % (target.display_name)) - target.send('%s gave you super adminship rights.' % (sender.display_name)) - return - sender.send('Unknown user.') - - def command_chown(self, **kwargs): - sender = kwargs['sender'] - args = kwargs['arguments'] - - if (len(args) < 2): - sender.send('Usage: chown <item name> <new owner>') - return - - item_name = args[0] - owner_name = args[1] - - item = sender.inventory.find_item(name=item_name) - if (item.owner_id != sender.id): - sender.send('This is not your item.') - return - - player = self.world.find_player(name=owner_name) - if (player is None): - sender.send('There is no such player.') - else: - item.set_owner(player) - sender.send('%s now owns that item.' % (player.display_name)) - player.send('%s has given you a %s.' % (sender.display_name, item.name)) - - def command_ping(self, **kwargs): - kwargs['sender'].send('Pong.') - - # Callbacks -
[docs] def callback_client_authenticated(self, trigger, sender): - """ Callback that is bound to the "post_client_authenticated" event. - - Callbacks like this one are helpful in cases that if you want to initialize - certain data upon the authentication of a certain client -- perhaps you're loading - mod data that is related to this client. - - Refer to the :command:`__init__` function. - - """ - self.command_look(sender=sender, input='') -
-
[docs] def callback_message_sent(self, trigger, sender, input): - """ Callback that is bound to the "pre_message_sent" event. - - Callbacks like this one are helpful in cases that if you want to intercept - input for any reason, such as an interactive menu that will handle it's own - text parsing for handling menu functions as that if the callback at any point - returns true, the server will not pass the input text into the core parser. - - Refer to the :command:`__init__` function. - - """ - if (len(input) != 0): - if (input[0] == ':'): - self.command_pose(sender=sender, input=input[1:].lstrip()) - return True - elif (input[0] == '"'): - self.command_say(sender=sender, input=input[1:].lstrip()) - return True - - return False -
-
[docs] def get_commands(self): - """ Returns a dictionary mapping the available commands in this modification. - - This is a function call merely for the purpose of being able to provide variable - output, so that if the modification has an accompanying configuration file it can - omit or include certain commands based on the configuration settings loaded in the - modification's :command:`__init__` function. - - """ - command_dict = { - 'say': - { - 'command': self.command_say, - 'description': 'Makes you say something. Only visible to the current room you\'re in.', - 'usage': 'say <arbitrary text> | "<arbitrary text>', - 'aliases': [ 'speak' ], - 'privilege': 0 - }, - - 'pose': - { - 'command': self.command_pose, - 'description': 'Used to show arbitrary action. Only visible to the current room you\'re in.', - 'usage': 'pose <arbitrary pose> | :<arbitrary pose>', - 'aliases': [ ], - 'privilege': 0 - }, - - 'look': - { - 'command': self.command_look, - 'description': 'Get your bearings. Look around in the local area to see what you can see.', - 'usage': 'look [room name | item name | player name]', - 'aliases': [ ], - 'privilege': 0 - }, - - 'move': - { - 'command': self.command_move, - 'description': 'Moves to a new location.', - 'usage': 'move <exit name>', - 'aliases': [ 'go' ], - 'privilege': 0 - }, - - 'inventory': - { - 'command': self.command_inventory, - 'description': 'View your inventory.', - 'usage': 'inventory', - 'aliases': [ ], - 'privilege': 0 - }, - - 'take': - { - 'command': self.command_take, - 'description': 'Take an item from the current room.', - 'usage': 'take <item>', - 'aliases': [ 'get' ], - 'privilege': 0 - }, - - 'passwd': - { - 'command': self.command_passwd, - 'description': 'Changes your password.', - 'usage': 'passwd <new password>', - 'aliases': [ ], - 'privilege': 0 - }, - - 'drop': - { - 'command': self.command_drop, - 'description': 'Drops an item from your inventory.', - 'usage': 'drop <item name>', - 'aliases': [ ], - 'privilege': 0 - }, - - 'help': - { - 'command': self.command_help, - 'description': 'Displays the help text.', - 'usage': 'help [command name]', - 'aliases': [ ], - 'privilege': 0 - }, - - 'quit': - { - 'command': self.command_quit, - 'description': 'Drops your connection from the server.', - 'usage': 'quit', - 'aliases': [ 'leave' ], - 'privilege': 0 - }, - - 'frog': - { - 'command': self.command_froguser, - 'description': 'Super Admin only: Deletes a user from the world -- making them an item in your inventory to with as you please.', - 'usage': 'frog <player name>', - 'aliases': [ ], - 'privilege': 2 - }, - - 'adduser': - { - 'command': self.command_adduser, - 'description': 'Creates a new player in the world.', - 'usage': 'adduser <name> <password>', - 'aliases': [ ], - 'privilege': 2 - }, - - 'admin': - { - 'command': self.command_admin, - 'description': 'Admin only: Toggles the admin status of a specified player.', - 'usage': 'admin <name>', - 'aliases': [ ], - 'privilege': 1 - }, - - 'sadmin': - { - 'command': self.command_sadmin, - 'description': 'Super Admin only: Toggles the super admin status of a specified player.', - 'usage': 'sadmin <name>', - 'aliases': [ ], - 'privilege': 2 - }, - - 'chown': - { - 'command': self.command_chown, - 'description': 'Transfers ownership of an item in your inventory or in the room to a specified player providied you are the original owner.', - 'usage': 'chown <item name> <new owner name>', - 'aliases': [ ], - 'privilege': 0 - }, - - 'ping': - { - 'command': self.command_ping, - 'description': 'Ping-Pong.', - 'usage': 'ping', - 'aliases': [ ], - 'privilege': 0 - } - } - return command_dict
-
- -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_modules/scommands/scommands.html b/doc/build/html/_modules/scommands/scommands.html deleted file mode 100644 index 927fb38..0000000 --- a/doc/build/html/_modules/scommands/scommands.html +++ /dev/null @@ -1,658 +0,0 @@ - - - - - - - - - - scommands.scommands — ScalyMUCK 3.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -

Source code for scommands.scommands

-"""
-	scommands.py
-
-	ScalyMUCK generic commands.
-
-	Copyright (c) 2013 Robert MacGregor
-	This software is licensed under the GNU General
-	Public License version 3. Please refer to gpl.txt 
-	for more information.
-"""
-
-import string
-
-import game.models
-
-from blinker import signal
-
-
[docs]class Modification: - """ Main class object to load and initialize the scommands modification. """ - world = None - interface = None - session = None - - pre_user_look = signal('pre_user_look') - post_user_look = signal('post_user_look') - pre_user_say = signal('pre_user_say') - post_user_say = signal('post_user_say') - pre_user_pose = signal('pre_user_pose') - post_user_pose = signal('post_user_pose') - pre_item_pickup = signal('pre_item_pickup') - post_item_pickup = signal('post_item_pickup') - pre_item_drop = signal('pre_item_drop') - post_item_drop = signal('post_item_drop') - post_user_create = signal('post_user_create') - pre_exit_room = signal('pre_exit_room') - post_exit_room = signal('post_exit_room') - pre_show_description = signal('pre_show_description') - - def __init__(self, **kwargs): - """ Initializes an instance of the modification. """ - self.config = kwargs['config'] - self.interface = kwargs['interface'] - self.session = kwargs['session'] - self.world = kwargs['world'] - self.permissions = kwargs['permissions'] - - signal('post_client_authenticated').connect(self.callback_client_authenticated) - signal('pre_message_sent').connect(self.callback_message_sent) - - # Commands - def command_say(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - - if (input == ''): - sender.send('Usage: say <message>') - return - - results = self.pre_user_say.send(None, sender=sender, input=input) - for result in results: - if (result[1] is True): - return - - sender.location.broadcast('%s says, "%s"' % (sender.display_name, input), sender) - sender.send('You say, "%s"' % (input)) - self.post_user_say.send(None, sender=sender, input=input) - - def command_pose(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - results = self.pre_user_pose.send(None, sender=sender, input=input) - for result in results: - if (result[1] is True): - return - - sender.location.broadcast('%s %s' % (sender.display_name, kwargs['input'])) - self.post_user_pose.send(None, sender=sender, input=input) - - def command_look(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - target = sender.location - name = sender.location.name - - self.pre_user_look.send(sender=sender, input=input) - - if (input != ''): - target = sender.location.find_player(name=input) - if (target is None): - target = sender.location.find_bot(name=input) - - if (target is not None): - name = target.display_name - if (type(target) is game.models.Player): - target.send('++++++++ %s is looking at you!' % (sender.display_name)) - else: - target = sender.location.find_item(name=input) - if (target is None): - target = sender.inventory.find_item(name=input) - - if (target is not None): - name = target.name - else: - sender.send('I do not see that.') - return - - sender.send('<' + name + '>') - - if (type(target) is game.models.Room): - sender.send('Obvious Exits: ') - if (len(target.exits) != 0): - for exit in target.exits: - sender.send(' %s' % (exit.name)) - else: - sender.send(' None') - - sender.send('People: ') - for player in target.players: - sender.send(' %s' % (player.display_name)) - - sender.send('Bots: ') - if (len(target.bots) != 0): - for bot in target.bots: - sender.send(' %s' % (bot.display_name)) - else: - sender.send(' None') - - sender.send('Items: ') - if (len(target.items) != 0): - for item in target.items: - sender.send(' %s' % (item.name)) - else: - sender.send(' None') - - self.pre_show_description.send(None, sender=sender, target=target) - sender.send('Description:') - sender.send(target.description) - - self.post_user_look.send(sender=sender, input=input, target=target) - - def command_move(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - - if (input == ''): - sender.send('Usage: move <name of exit>') - return - - exit = sender.location.find_exit(name=input) - if (exit is not None): - sender.send(exit.user_enter_message) - sender.location.broadcast('%s %s' % (sender.display_name, exit.room_enter_message), sender) - results = self.pre_exit_room.send(None, sender=sender, target=exit.target_id) - for result in results: - if (result[1] is True): - return - sender.set_location(exit.target_id) - sender.location.broadcast('%s %s' % (sender.display_name, exit.room_exit_message), sender) - sender.send(exit.user_exit_message) - self.command_look(sender=sender, input='') - self.post_exit_room.send(None, sender=sender, target=sender.location) - else: - sender.send('I do not see that.') - - def command_inventory(self, **kwargs): - sender = kwargs['sender'] - - sender.send('Items:') - if (len(sender.inventory.items) != 0): - for item in sender.inventory.items: - sender.send(' %s' % (item.name)) - else: - sender.send(' None') - - # Pocket dimension anybody? - if (len(sender.inventory.players) != 0): - sender.send('People: ') - for player in sender.inventory.players: - sender.send(' %s' % (player.display_name)) - - def command_take(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - - if (input == ''): - sender.send('Usage: take <object name>') - return - - item = sender.location.find_item(name=input) - if (item is not None): - results = self.pre_item_pickup.send(None, sender=sender, item=item) - for result in results: - if (result[1] is True): - return - - sender.send('Taken.') - - item.set_location(sender.inventory) - self.post_item_pickup.send(None, sender=sender, item=item) - return - - sender.send('I do not see that.') - - def command_passwd(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - - if (input == ''): - sender.send('Usage: passwd <password>') - return - - sender.set_password(input) - sender.send('Your password has been changed. Remember it well.') - - def command_drop(self, **kwargs): - sender = kwargs['sender'] - input = kwargs['input'] - - if (input == ''): - sender.send('Usage: drop <item name>') - return - - item = sender.inventory.find_item(name=input) - if (item is not None): - results = self.pre_item_drop.send(sender=sender, item=item) - for result in results: - if (result[1] is True): - return - - sender.send('You dropped a/an %s.' % (item.name )) - sender.location.broadcast('%s drops a/an %s.' % (sender.display_name, item.name) ,sender) - item.set_location(sender.location) - self.post_item_drop.send(sender=sender, item=item) - else: - sender.send('I see no such item.') - - def command_help(self, **kwargs): - sender = kwargs['sender'] - input = string.lower(kwargs['input']) - - if (input not in self.interface.commands): - sender.send('For more information, type: help <command>') - sender.send('Command listing: ') - out = '' - for command in self.interface.commands: - privilege = self.interface.commands[command]['privilege'] - if ((privilege == 1 and sender.is_admin is False) or (privilege == 2 and sender.is_sadmin is False) or (privilege == 3 and sender.is_owner is False)): - continue - out += command + ', ' - # Cheap trick to strip off the last comma (and space) but eh! - sender.send(out[:len(out)-2]) - return - else: - sender.send('From: %s' % (self.interface.commands[input]['modification'])) - sender.send('Usage: %s' % (self.interface.commands[input]['usage'])) - sender.send(self.interface.commands[input]['description']) - - def command_quit(self, **kwargs): - sender = kwargs['sender'] - sender.disconnect() - - def command_froguser(self, **kwargs): - sender = kwargs['sender'] - args = kwargs['arguments'] - if (len(args) < 1): - sender.send('Usage: frog <name>') - return - - name = args[0] - target = self.world.find_player(name=string.lower(name)) - if (target is not None): - if (target is sender): - sender.send('That is yourself!') - return - elif (target.is_sadmin ==1 or target.is_owner == 1): - sender.send('You cannot do that, they are too powerful.') - return - - item = self.world.create_item(target.display_name, target.description, sender, sender.inventory) - sender.send('User "%s" frogged. Check your inventory.' % (target.display_name)) - target.send('%s has turned you into a small plastic figurine, never to move again and discreetly places you in their inventory.' % (sender.display_name)) - sender.location.broadcast('%s has turned %s into a small plastic figurine, never to move again.' % (sender.display_name, target.display_name), sender, target) - target.delete() - else: - sender.send('User "%s" does not exist anywhere.' % (name)) - - def command_adduser(self, **kwargs): - sender = kwargs['sender'] - args = kwargs['arguments'] - if (len(args) < 2): - sender.send('Usage: adduser <name> <password>') - return - - name = args[0] - password = args[1] - - if (self.world.find_player(name=string.lower(name)) is not None): - sender.send('User already exists.') - return - - # TODO: Make this take server prefs into consideration, and also let this have a default location ... - player = self.world.create_player(name, password, game.models.server.work_factor, sender.location) - sender.send('User "%s" created.' % (name)) - self.post_user_create.send(None, creator=sender, created=player) - - def command_admin(self, **kwargs): - sender = kwargs['sender'] - args = kwargs['arguments'] - if (len(args) < 1): - sender.send('Usage: admin <name>') - return - - name = args[0] - - target = self.world.find_player(name=string.lower(name)) - if (target is not None): - if (target is sender): - sender.send('That is yourself!') - return - elif (sender.check_admin_trump(target) is False): - sender.send('You cannot do that. They are too strong.') - target.send('%s tried to take your administrator privileges away.' % (sender.display_name)) - return - - target.set_is_admin(target.is_admin is False) - if (target.is_admin == 0): - sender.send('%s is no longer an administrator.' % (target.display_name)) - target.send('%s took your adminship.' % (sender.display_name)) - return - else: - sender.send('%s is now an administrator.' % (target.display_name)) - target.send('%s gave you adminship rights.' % (sender.display_name)) - return - sender.send('Unknown user.') - - def command_sadmin(self, **kwargs): - sender = kwargs['sender'] - args = kwargs['arguments'] - if (len(args) < 1): - sender.send('Usage: sadmin <name>') - return - - name = args[0] - - target = self.world.find_player(name=string.lower(name)) - if (target is not None): - if (target is sender): - sender.send('That is yourself!') - return - elif (sender.check_admin_trump(target) is False): - sender.send('You cannot do that. They are too strong.') - target.send('%s tried to take your super administrator privileges away.' % (sender.display_name)) - return - - target.set_is_super_admin(target.is_sadmin is False) - if (target.is_sadmin is False): - sender.send('%s is no longer a super administrator.' % (target.display_name)) - target.send('%s took your super adminship.' % (sender.display_name)) - return - else: - sender.send('%s is now a super administrator.' % (target.display_name)) - target.send('%s gave you super adminship rights.' % (sender.display_name)) - return - sender.send('Unknown user.') - - def command_chown(self, **kwargs): - sender = kwargs['sender'] - args = kwargs['arguments'] - - if (len(args) < 2): - sender.send('Usage: chown <item name> <new owner>') - return - - item_name = args[0] - owner_name = args[1] - - item = sender.inventory.find_item(name=item_name) - if (item.owner_id != sender.id): - sender.send('This is not your item.') - return - - player = self.world.find_player(name=owner_name) - if (player is None): - sender.send('There is no such player.') - else: - item.set_owner(player) - sender.send('%s now owns that item.' % (player.display_name)) - player.send('%s has given you a %s.' % (sender.display_name, item.name)) - - def command_ping(self, **kwargs): - kwargs['sender'].send('Pong.') - - # Callbacks - def callback_client_authenticated(self, trigger, sender): - self.command_look(sender=sender, input='') - - def callback_message_sent(self, trigger, sender, input): - if (len(input) != 0): - if (input[0] == ':'): - self.command_pose(sender=sender, input=input[1:].lstrip()) - return True - elif (input[0] == '"'): - self.command_say(sender=sender, input=input[1:].lstrip()) - return True - - return False - -
[docs] def get_commands(self): - """ Returns a dictionary mapping the available commands in this modification. - - This is a function call merely for the purpose of being able to provide variable - output, so that if the modification has an accompanying configuration file it can - omit or include certain commands based on the configuration settings loaded in the - modification's __init__ function. - - """ - command_dict = { - 'say': - { - 'command': self.command_say, - 'description': 'Makes you say something. Only visible to the current room you\'re in.', - 'usage': 'say <arbitrary text> | "<arbitrary text>', - 'aliases': [ 'speak' ], - 'privilege': 0 - }, - - 'pose': - { - 'command': self.command_pose, - 'description': 'Used to show arbitrary action. Only visible to the current room you\'re in.', - 'usage': 'pose <arbitrary pose> | :<arbitrary pose>', - 'aliases': [ ], - 'privilege': 0 - }, - - 'look': - { - 'command': self.command_look, - 'description': 'Get your bearings. Look around in the local area to see what you can see.', - 'usage': 'look [room name | item name | player name]', - 'aliases': [ ], - 'privilege': 0 - }, - - 'move': - { - 'command': self.command_move, - 'description': 'Moves to a new location.', - 'usage': 'move <exit name>', - 'aliases': [ 'go' ], - 'privilege': 0 - }, - - 'inventory': - { - 'command': self.command_inventory, - 'description': 'View your inventory.', - 'usage': 'inventory', - 'aliases': [ ], - 'privilege': 0 - }, - - 'take': - { - 'command': self.command_take, - 'description': 'Take an item from the current room.', - 'usage': 'take <item>', - 'aliases': [ 'get' ], - 'privilege': 0 - }, - - 'passwd': - { - 'command': self.command_passwd, - 'description': 'Changes your password.', - 'usage': 'passwd <new password>', - 'aliases': [ ], - 'privilege': 0 - }, - - 'drop': - { - 'command': self.command_drop, - 'description': 'Drops an item from your inventory.', - 'usage': 'drop <item name>', - 'aliases': [ ], - 'privilege': 0 - }, - - 'help': - { - 'command': self.command_help, - 'description': 'Displays the help text.', - 'usage': 'help [command name]', - 'aliases': [ ], - 'privilege': 0 - }, - - 'quit': - { - 'command': self.command_quit, - 'description': 'Drops your connection from the server.', - 'usage': 'quit', - 'aliases': [ 'leave' ], - 'privilege': 0 - }, - - 'frog': - { - 'command': self.command_froguser, - 'description': 'Super Admin only: Deletes a user from the world -- making them an item in your inventory to with as you please.', - 'usage': 'frog <player name>', - 'aliases': [ ], - 'privilege': 2 - }, - - 'adduser': - { - 'command': self.command_adduser, - 'description': 'Creates a new player in the world.', - 'usage': 'adduser <name> <password>', - 'aliases': [ ], - 'privilege': 2 - }, - - 'admin': - { - 'command': self.command_admin, - 'description': 'Admin only: Toggles the admin status of a specified player.', - 'usage': 'admin <name>', - 'aliases': [ ], - 'privilege': 1 - }, - - 'sadmin': - { - 'command': self.command_sadmin, - 'description': 'Super Admin only: Toggles the super admin status of a specified player.', - 'usage': 'sadmin <name>', - 'aliases': [ ], - 'privilege': 2 - }, - - 'chown': - { - 'command': self.command_chown, - 'description': 'Transfers ownership of an item in your inventory or in the room to a specified player providied you are the original owner.', - 'usage': 'chown <item name> <new owner name>', - 'aliases': [ ], - 'privilege': 0 - }, - - 'ping': - { - 'command': self.command_ping, - 'description': 'Ping-Pong.', - 'usage': 'ping', - 'aliases': [ ], - 'privilege': 0 - } - } - return command_dict
-
- -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_modules/server.html b/doc/build/html/_modules/server.html deleted file mode 100644 index c2ac898..0000000 --- a/doc/build/html/_modules/server.html +++ /dev/null @@ -1,361 +0,0 @@ - - - - - - - - - - server — ScalyMUCK 3.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -

Source code for server

-"""
-	This is the main ScalyMUCK server code,
-	it performs the initialisation of various
-	systems and is the binding of everything.
-
-	Copyright (c) 2013 Robert MacGregor
-	This software is licensed under the GNU General
-	Public License version 3. Please refer to gpl.txt 
-	for more information.
-"""
-
-import sys
-import string
-import logging
-
-import bcrypt
-from blinker import signal
-from miniboa import TelnetServer
-from sqlalchemy import create_engine
-from sqlalchemy.exc import OperationalError
-from sqlalchemy.engine.reflection import Inspector
-
-import daemon
-import game.models
-from game import interface, world
-
-
[docs]class Server(daemon.Daemon): - """ - - Server class that is initialized by the main.py script to act as the MUCK server. - It performs all of the core functions of the MUCK server, which is mainly accepting - connections and performing the login sequence before giving them access to the - server and its world. - - """ - is_running = False - telnet_server = None - logger = None - connection_logger = None - world = None - interface = None - work_factor = 10 - - welcome_message_data = 'Unable to load welcome message!\n' - exit_message_data = 'Unable to load exit message!\n' - - pending_connection_list = [ ] - established_connection_list = [ ] - - post_client_connect = signal('post_client_connect') - pre_client_disconnect = signal('pre_client_disconnect') - post_client_authenticated = signal('post_client_authenticated') - world_tick = signal('world_tick') - - auth_low_argc = None - auth_invalid_combination = None - auth_connected = None - auth_replace_Connection = None - auth_connection_replaced = None - auth_connect_suggestion = None - game_client_disconnect = None - -
[docs] def __init__(self, config=None, path=None, workdir=None): - """ The server class is created and managed by the main.py script. - - When created, the server automatically initiates a Telnet server provided - by Miniboa which immediately listens for incoming connections and will query - them for login details upon connection. - - Keyword arguments: - config -- An instance of game.Settings that is to be used when loading configuration data. - path -- The data path that all permenant data should be written to. - workdir -- The current working directory of the server. This should be an absolute path to application/. - - """ - self.connection_logger = logging.getLogger('Connections') - self.logger = logging.getLogger('Server') - - # Loading all of the configuration variables - database_type = string.lower(config.get_index(index='DatabaseType', datatype=str)) - database = config.get_index(index='DatabaseName', datatype=str) - user = config.get_index(index='DatabaseUser', datatype=str) - password = config.get_index(index='DatabasePassword', datatype=str) - self.work_factor = config.get_index(index='WorkFactor', datatype=int) - if (database_type == 'sqlite'): - database_location = path + config.get_index(index='TargetDatabase', datatype=str) - else: - database_location = config.get_index(index='TargetDatabase', datatype=str) - - # Load server messages - self.auth_low_argc = config.get_index('AuthLowArgC', str) - self.auth_invalid_combination = config.get_index('AuthInvalidCombination', str) - self.auth_connected = config.get_index('AuthConnected', str) - self.auth_replace_Connection = config.get_index('AuthReplaceConnection', str) - self.auth_connection_replaced = config.get_index('AuthConnectionReplaced', str) - self.auth_connect_suggestion = config.get_index('AuthConnectSuggestion', str).replace('\\n','\n') - self.game_client_disconnect = config.get_index('GameClientDisconnect', str) - - # Loading welcome/exit messages - with open(workdir + 'config/welcome_message.txt') as f: - self.welcome_message_data = f.read() + '\n' - with open(workdir + 'config/exit_message.txt') as f: - self.exit_message_data = f.read() + '\n' - - # Connect/Create our database is required - database_exists = True - if (database_type == 'sqlite'): - try: - with open(database_location) as f: pass - except IOError as e: - self.logger.info('This appears to be your first time running the ScalyMUCK server. We must initialise your database ...') - database_exists = False - - database_engine = create_engine('sqlite:////%s' % (database_location), echo=False) - else: - url = database_type + '://' + user + ':' + password + '@' + database_location + '/' + database - try: - database_engine = create_engine(url, echo=False) - connection = database_engine.connect() - except OperationalError as e: - self.logger.error(str(e)) - self.logger.error('URL: ' + url) - self.is_running = False - return - - self.world = world.World(database_engine) - self.interface = interface.Interface(config=config, world=self.world, workdir=workdir, session=self.world.session, server=self) - game.models.Base.metadata.create_all(database_engine) - - # Check to see if our root user exists - if (database_type != 'sqlite'): - root_user = self.world.find_player(name='RaptorJesus') - if (root_user is None): - database_exists = False - - if (database_exists is False): - room = self.world.create_room('Portal Room Main') - user = self.world.create_player(name='RaptorJesus', password='ChangeThisPasswordNowPlox', workfactor=self.work_factor, location=room, admin=True, sadmin=True, owner=True) - self.logger.info('The database has been successfully initialised.') - - self.telnet_server = TelnetServer(port=config.get_index(index='ServerPort', datatype=int), - address=config.get_index(index='ServerAddress', datatype=str), - on_connect = self.on_client_connect, - on_disconnect = self.on_client_disconnect, - timeout = 0.05) - - self.logger.info('ScalyMUCK successfully initialised.') - self.is_running = True - - game.models.server = self - game.models.world = self.world -
-
[docs] def update(self): - """ The update command is called by the main.py script file. - - The update command does as it says, it causes the server to go through and - poll for data from any of the clients and processes this data differently - based on whether or not that they had actually logged in. When this function - finishes calling, a single world tick has passed. - - """ - try: - self.telnet_server.poll() - except UnicodeDecodeError: - return - - for connection in self.pending_connection_list: - if (connection.cmd_ready is True): - data = "".join(filter(lambda x: ord(x)<128, connection.get_command())) - command_data = string.split(data, ' ') - - # Try and perform the authentification process - if (len(command_data) < 3): - connection.send('%s\n' % (self.auth_low_argc)) - elif (len(command_data) >= 3 and string.lower(command_data[0]) == 'connect'): - name = string.lower(command_data[1]) - password = command_data[2] - - target_player = self.world.find_player(name=name) - if (target_player is None): - connection.send('%s\n' % (self.auth_invalid_combination)) - else: - player_hash = target_player.hash - if (player_hash == bcrypt.hashpw(password, player_hash) == player_hash): - connection.id = target_player.id - target_player.connection = connection - - # Check if our work factors differ - work_factor = int(player_hash.split('$')[2]) - if (work_factor != self.work_factor): - target_player.set_password(password) - self.logger.info('%s had their hash updated.' % (target_player.display_name)) - - self.connection_logger.info('Client %s:%u signed in as user %s.' % (connection.address, connection.port, target_player.display_name)) - self.post_client_authenticated.send(None, sender=target_player) - for player in target_player.location.players: - if (player is not target_player): - player.send('%s %s' % (target_player.display_name, self.auth_connected)) - - for player in self.established_connection_list: - if (player.id == connection.id): - player.send('%s\n' % (self.auth_replace_connection)) - player.socket_send() - player.deactivate() - player.sock.close() - connection.send('%s\n' % (self.auth_connection_replaced)) - self.established_connection_list.remove(player) - break - self.pending_connection_list.remove(connection) - self.established_connection_list.append(connection) - else: - connection.send('You have specified an invalid username/password combination.\n') - elif (len(command_data) >= 3 and string.lower(command_data[0]) != 'connect'): - connection.send('%s\n' % (self.auth_connect_suggestion)) - #connection.send('You must use the "connect" command:\n') - #connection.send('connect <username> <password>\n') - - # With already connected clients, we'll now deploy the command interface. - for connection in self.established_connection_list: - if (connection.cmd_ready): - input = "".join(filter(lambda x: ord(x)<128, connection.get_command())) - sending_player = self.world.find_player(id=connection.id) - sending_player.connection = connection - self.interface.parse_command(sender=sending_player, input=input) - - self.world_tick.send(None) -
-
[docs] def shutdown(self): - """ Shuts down the ScalyMUCK server. - - This command shuts down the ScalyMUCK server and gracefully disconnects all connected clients - by sending a message before their disconnection which currently reads: "The server has been shutdown - adruptly by the server owner." This message cannot be changed. - - """ - self.is_running = False - for connection in self.established_connection_list: - connection.send('The server has been shutdown adruptly by the server owner.\n') - connection.socket_send() -
-
[docs] def find_connection(self, id): - """ Finds a player connection by their database ID. """ - for player in self.established_connection_list: - if (player.id == id): - return player -
-
[docs] def on_client_connect(self, client): - """ This is merely a callback for Miniboa to refer to when receiving a client connection from somewhere. """ - self.connection_logger.info('Received client connection from %s:%u' % (client.address, client.port)) - client.send(self.welcome_message_data) - self.pending_connection_list.append(client) - self.post_client_connect.send(sender=client) -
-
[docs] def on_client_disconnect(self, client): - """ This is merely a callback for Miniboa to refer to when receiving a client disconnection. """ - self.pre_client_disconnect.send(sender=client) - self.connection_logger.info('Received client disconnection from %s:%u' % (client.address, client.port)) - # Iterate over anyone who had connected but did not authenticate - if (client in self.pending_connection_list): - self.pending_connection_list.remove(client) - # Otherwise run over the list of people who had authenticated - elif (client in self.established_connection_list): - player = self.world.find_player(id=client.id) - room = self.world.find_room(id=player.location_id) - room.broadcast('%s %s' % (player.display_name, self.game_client_disconnect), player) - - self.established_connection_list.remove(client)
-
- -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_modules/settings.html b/doc/build/html/_modules/settings.html deleted file mode 100644 index c7f9887..0000000 --- a/doc/build/html/_modules/settings.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - - - - - settings — ScalyMUCK 3.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -

Source code for settings

-"""
-	Basic settings loader that provides easy to use functionality to load from simple
-	configuration files that have take the following form on every line:
-	
-	* Option=Yes
-	* Number=20
-	* String=Whatever
-
-	Copyright (c) 2013 Robert MacGregor
-	This software is licensed under the GNU General
-	Public License version 3. Please refer to gpl.txt 
-	for more information.
-"""
-
-import string
-
-
[docs]class Settings: - """ The Settings loader class provides simple functionality to load from simple configuration files. """ - _settings_entries = None - _yes = ['1', 'true', 'y', 'yes', 'enable', 'toggle', 'enabled'] - -
[docs] def __init__(self, target_file): - """ Initializes an instance of the Settings loader. """ - self._settings_entries = { } - self.load(target_file) -
-
[docs] def load(self, target_file): - """ Loads a configuration file from the hard disk. """ - try: - file_handle = open(target_file, 'r') - except IOError: - return - - for line_data in file_handle: - preference_data = string.split(line_data, '=') - line_data = line_data.lstrip() - - if (len(preference_data) == 2): - data = preference_data[1] - self._settings_entries[preference_data[0]] = data[:len(data)].replace('\n','') - # TODO: Make this actually do some work to make post-fix comments work ... - elif(line_data.find('#') and line_data != ''): - continue - - file_handle.close() -
-
[docs] def get_indices(self): - """ Returns all known indices. This is a list of the indices you would use in :func:`get_index`. """ - return self._settings_entries.keys() -
-
[docs] def get_index(self, index=None, datatype=None): - """ Returns a loaded configuration setting from the Settings loader. - - Keyword arguments: - * index -- The name of the setting that is to be loaded from the file. - * datatype -- The datatype that is supposed to be used to represent this setting in the return value. - - """ - if(index in self._settings_entries): - entries = self._settings_entries - if (datatype is bool): - if (string.lower(self._settings_entries[index]) in self._yes): - return True - else: - return False - else: - return datatype(entries[index]) - else: - return None
-
- -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_modules/world.html b/doc/build/html/_modules/world.html deleted file mode 100644 index a242075..0000000 --- a/doc/build/html/_modules/world.html +++ /dev/null @@ -1,309 +0,0 @@ - - - - - - - - - - world — ScalyMUCK 3.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -

Source code for world

-""" 
-	This contains "global" functions that is to
-	be called by the various mods of the MUCK to perform actions such as creating
-	Players.
-
-	Copyright (c) 2013 Robert MacGregor
-	This software is licensed under the GNU General
-	Public License version 3. Please refer to gpl.txt 
-	for more information.
-"""
-
-from sqlalchemy.orm import sessionmaker
-from sqlalchemy.orm import scoped_session
-
-from models import Room, Player, Item, Bot
-import exception
-
-
[docs]class World(): - """ - - The "singleton" class that represents the ScalyMUCK world in memory and is to - be passed to every mod that actually manages to initialize when loaded. - - """ - engine = None - session = None - -
[docs] def __init__(self, engine): - """ Initializes an instance of the World with an SQLAlchemy engine. """ - self.engine = engine - self.session = scoped_session(sessionmaker(bind=self.engine)) -
-
[docs] def create_room(self, name, description='<Unset>', owner=0): - """ Creates a new Room if the World. - - Keyword arguments: - description -- The description that is to be used with the new Room instance. - owner -- The ID or instance of Player that is to become the owner of this Room. - - """ - room = Room(name, description, owner) - self.session.add(room) - self.session.commit() - self.session.refresh(room) - return room -
-
[docs] def find_room(self, **kwargs): - """ Locates the specified Room in the ScalyMUCK world. - - This can be a bit computionally intense if you are running a very large world. - - Keyword arguments (one or the other): - id -- The id of the requested room to return an instance of. This overrides the name if both are specified. - name -- The name of the requested room to return an instance of. - - """ - target_room = self.session.query(Room).filter_by(**kwargs).first() - return target_room -
-
[docs] def create_player(self, name=None, password=None, workfactor=None, location=None, admin=False, sadmin=False, owner=False): - """ Creates a new instance of a Player. - - Keyword arguments: - name -- The name of the new Player instance to be used. - password -- The password that is to be used for the Player. - workfactor -- The work factor # to be used when hasing the Player's password. - location -- The ID or instance of Room that the new Player is to be created at. - admin -- A boolean representing whether or not this new Player is an administrator. - sadmin -- A boolean representing whether or not this new Player is a super administrator. - owner -- A boolean representing whether or not this new Player is an owner. - - """ - if (name is None or password is None or workfactor is None or location is None): - raise exception.WorldArgumentError('All of the arguments to create_player are mandatory! (or None was passed in)') - - if (type(location) is int): - location = self.find_room(id=location) - - player_inventory = self.create_room('%s\'s Inventory' % (name)) - player = Player(name, password, workfactor, location.id, 0, admin=admin, sadmin=sadmin, owner=owner) - player.inventory_id = player_inventory.id - self.session.add(player) - - location.players.append(player) - self.session.add(location) - - self.session.add(player_inventory) - self.session.commit() - - self.session.refresh(player) - self.session.refresh(player_inventory) - - player.location = location - player.inventory = player_inventory - return player -
-
[docs] def create_bot(self, name=None, location=None): - """ Creates a new instance of a Bot. - - Keyword arguments: - name -- The name of the new Player instance to be used. - location -- The ID or instance of Room that the new Player is to be created at. - - """ - if (name is None or location is None): - raise exception.WorldArgumentError('All of the arguments to create_bot are mandatory! (or None was passed in)') - - if (type(location) is int): - location = self.find_room(id=location) - - bot = bot(name, '<Unset>', location) - self.session.add(bot) - location.bots.append(bot) - self.session.add(location) - self.session.commit() - - self.session.refresh(bot) - - bot.location = location - return bot -
-
[docs] def find_player(self, **kwargs): - """ Locates a Player inside of the ScalyMUCK world. - - This searches the entire WORLD for the specified Player so if you happen to be running a very, very - large world this search will end up getting slow and it is recommended in that case that you try and - use the Room level find_player function whenever possible. - - Keyword arguments (one or the other): - id -- The ID of the Player to locate. This overrides the name if both are specified. - name -- The name of the Player to locate. - - """ - target_player = self.session.query(Player).filter_by(**kwargs).first() - if (target_player is not None): - target_player.location = self.find_room(id=target_player.location_id) - target_player.inventory = self.find_room(id=target_player.inventory_id) - - return target_player -
-
[docs] def find_bot(self, **kwargs): - """ Locates a Bot inside of the ScalyMUCK world. - - This searches the entire WORLD for the specified Bot so if you happen to be running a very, very - large world this search will end up getting slow and it is recommended in that case that you try and - use the Room level find_player function whenever possible. - - Keyword arguments (one or the other): - id -- The ID of the Bot to locate. This overrides the name if both are specified. - - """ - target_bot = self.session.query(Bot).filter_by(**kwargs).first() - if (target_bot is not None): - target_bot.location = self.find_room(id=target_bot.location_id) - - return target_bot -
-
[docs] def get_players(self): - """ Returns a list of all Players in the ScalyMUCK world. """ - list = [ ] - results = self.session.query(Player).filter_by() - for player in results: - load_test = self.find_player(id=player.id) - if (load_test is None): - list.append(self.find_player(id=player.id)) - else: - list.append(load_test) - return list -
-
[docs] def find_item(self, **kwargs): - """ Locates an item by any specifications. - - If the ID number does not exist then None is returned. - - """ - target_item = self.session.query(Item).filter_by(**kwargs).first() - if (target_item is not None): - target_item.location = self.find_room(id=target_item.location_id) - - return target_item -
-
[docs] def create_item(self, name=None, description='<Unset>', owner=0, location=None): - """ Creates a new item in the ScalyMUCK world. - - Keyword arguments: - name -- The name of the Item that is to be used. - description -- The description of the Item that is to be used. Default: <Unset> - owner -- The ID or instance of Player that is to become the owner of this Item. - location -- The ID or instance of Room that is to become the location of this Item. - """ - if (name is None or location is None): - raise exception.WorldArgumentError('Either the name or location was not specified. (or they were None)') - - item = Item(name, description, owner) - if (type(location) is int): - item.location_id = location - item.location = self.find_room(id=location) - else: - item.location = location - item.location_id = location.id - - self.session.add(item) - self.session.commit() - self.session.refresh(item) - return item -
-
[docs] def get_rooms(self, **kwargs): - """ Returns all rooms in the database that meet the specified criterion. - - Keyword arguments: - owner -- The owner we are to filter by. If not specified, this filter is not used. - - """ - rooms = self.session.query(Room).filter_by(**kwargs) - return rooms
-
- -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_sources/exception.txt b/doc/build/html/_sources/exception.txt deleted file mode 100644 index 577d1e7..0000000 --- a/doc/build/html/_sources/exception.txt +++ /dev/null @@ -1,9 +0,0 @@ -Exception Classes -===================================== - -.. toctree:: - :maxdepth: 2 - - exceptions/modapplicationerror - exceptions/worldargumenterror - exceptions/modelargumenterror diff --git a/doc/build/html/_sources/exceptions/modapplicationerror.txt b/doc/build/html/_sources/exceptions/modapplicationerror.txt deleted file mode 100644 index fe30209..0000000 --- a/doc/build/html/_sources/exceptions/modapplicationerror.txt +++ /dev/null @@ -1,7 +0,0 @@ -Mod Error -===================================== - -.. toctree:: - :maxdepth: 2 -.. automodule:: exception - :members: ModApplicationError \ No newline at end of file diff --git a/doc/build/html/_sources/exceptions/modelargumenterror.txt b/doc/build/html/_sources/exceptions/modelargumenterror.txt deleted file mode 100644 index 16816ba..0000000 --- a/doc/build/html/_sources/exceptions/modelargumenterror.txt +++ /dev/null @@ -1,7 +0,0 @@ -Model Argument Error -===================================== - -.. toctree:: - :maxdepth: 2 -.. automodule:: exception - :members: ModelArgumentError \ No newline at end of file diff --git a/doc/build/html/_sources/exceptions/worldargumenterror.txt b/doc/build/html/_sources/exceptions/worldargumenterror.txt deleted file mode 100644 index bf0b93f..0000000 --- a/doc/build/html/_sources/exceptions/worldargumenterror.txt +++ /dev/null @@ -1,7 +0,0 @@ -World Argument Error -===================================== - -.. toctree:: - :maxdepth: 2 -.. automodule:: exception - :members: WorldArgumentError \ No newline at end of file diff --git a/doc/build/html/_sources/index.txt b/doc/build/html/_sources/index.txt deleted file mode 100644 index e62f9ac..0000000 --- a/doc/build/html/_sources/index.txt +++ /dev/null @@ -1,34 +0,0 @@ -Welcome to ScalyMUCK's documentation! -===================================== - -Found here is the programmer's documentation for any version of ScalyMUCK that falls under the 3.0.0 -major version. - -Core Functionality ----------------------- -.. toctree:: - :maxdepth: 5 - - server - interface - -Modding API ----------------------- -.. toctree:: - :maxdepth: 5 - - modloader - exception - models - world - permissions - settings - scommands - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/doc/build/html/_sources/interface.txt b/doc/build/html/_sources/interface.txt deleted file mode 100644 index ef9291a..0000000 --- a/doc/build/html/_sources/interface.txt +++ /dev/null @@ -1,9 +0,0 @@ -User Interface Class -===================================== - -.. toctree:: - :maxdepth: 2 -.. automodule:: interface - :members: - :special-members: - diff --git a/doc/build/html/_sources/model_bot.txt b/doc/build/html/_sources/model_bot.txt deleted file mode 100644 index 4e1c590..0000000 --- a/doc/build/html/_sources/model_bot.txt +++ /dev/null @@ -1,8 +0,0 @@ -Bot Model -===================================== - -.. toctree:: - :maxdepth: 2 - -.. automodule:: models - :members: Bot diff --git a/doc/build/html/_sources/model_exit.txt b/doc/build/html/_sources/model_exit.txt deleted file mode 100644 index 5b8b791..0000000 --- a/doc/build/html/_sources/model_exit.txt +++ /dev/null @@ -1,8 +0,0 @@ -Exit Model -===================================== - -.. toctree:: - :maxdepth: 2 - -.. automodule:: models - :members: Exit diff --git a/doc/build/html/_sources/model_item.txt b/doc/build/html/_sources/model_item.txt deleted file mode 100644 index 00bb596..0000000 --- a/doc/build/html/_sources/model_item.txt +++ /dev/null @@ -1,8 +0,0 @@ -Item Model -===================================== - -.. toctree:: - :maxdepth: 2 - -.. automodule:: models - :members: Item diff --git a/doc/build/html/_sources/model_player.txt b/doc/build/html/_sources/model_player.txt deleted file mode 100644 index 27c666e..0000000 --- a/doc/build/html/_sources/model_player.txt +++ /dev/null @@ -1,8 +0,0 @@ -Player Model -===================================== - -.. toctree:: - :maxdepth: 2 - -.. automodule:: models - :members: Player diff --git a/doc/build/html/_sources/model_room.txt b/doc/build/html/_sources/model_room.txt deleted file mode 100644 index e1cfc8c..0000000 --- a/doc/build/html/_sources/model_room.txt +++ /dev/null @@ -1,8 +0,0 @@ -Room Model -===================================== - -.. toctree:: - :maxdepth: 2 - -.. automodule:: models - :members: Room diff --git a/doc/build/html/_sources/models.txt b/doc/build/html/_sources/models.txt deleted file mode 100644 index 7a05538..0000000 --- a/doc/build/html/_sources/models.txt +++ /dev/null @@ -1,13 +0,0 @@ -Database Models -===================================== - -.. toctree:: - :maxdepth: 2 - - models/player - models/room - models/bot - models/item - models/exit - models/common - diff --git a/doc/build/html/_sources/models/bot.txt b/doc/build/html/_sources/models/bot.txt deleted file mode 100644 index 4e1c590..0000000 --- a/doc/build/html/_sources/models/bot.txt +++ /dev/null @@ -1,8 +0,0 @@ -Bot Model -===================================== - -.. toctree:: - :maxdepth: 2 - -.. automodule:: models - :members: Bot diff --git a/doc/build/html/_sources/models/common.txt b/doc/build/html/_sources/models/common.txt deleted file mode 100644 index 9aa7ec9..0000000 --- a/doc/build/html/_sources/models/common.txt +++ /dev/null @@ -1,8 +0,0 @@ -Common Class -===================================== - -.. toctree:: - :maxdepth: 2 - -.. automodule:: models - :members: ObjectBase diff --git a/doc/build/html/_sources/models/exit.txt b/doc/build/html/_sources/models/exit.txt deleted file mode 100644 index 9a42049..0000000 --- a/doc/build/html/_sources/models/exit.txt +++ /dev/null @@ -1,9 +0,0 @@ -Exit Model -===================================== - -.. toctree:: - :maxdepth: 2 - -.. automodule:: models - :members: Exit - :special-members: diff --git a/doc/build/html/_sources/models/item.txt b/doc/build/html/_sources/models/item.txt deleted file mode 100644 index 00bb596..0000000 --- a/doc/build/html/_sources/models/item.txt +++ /dev/null @@ -1,8 +0,0 @@ -Item Model -===================================== - -.. toctree:: - :maxdepth: 2 - -.. automodule:: models - :members: Item diff --git a/doc/build/html/_sources/models/model_bot.txt b/doc/build/html/_sources/models/model_bot.txt deleted file mode 100644 index 4e1c590..0000000 --- a/doc/build/html/_sources/models/model_bot.txt +++ /dev/null @@ -1,8 +0,0 @@ -Bot Model -===================================== - -.. toctree:: - :maxdepth: 2 - -.. automodule:: models - :members: Bot diff --git a/doc/build/html/_sources/models/model_exit.txt b/doc/build/html/_sources/models/model_exit.txt deleted file mode 100644 index 5b8b791..0000000 --- a/doc/build/html/_sources/models/model_exit.txt +++ /dev/null @@ -1,8 +0,0 @@ -Exit Model -===================================== - -.. toctree:: - :maxdepth: 2 - -.. automodule:: models - :members: Exit diff --git a/doc/build/html/_sources/models/model_item.txt b/doc/build/html/_sources/models/model_item.txt deleted file mode 100644 index 00bb596..0000000 --- a/doc/build/html/_sources/models/model_item.txt +++ /dev/null @@ -1,8 +0,0 @@ -Item Model -===================================== - -.. toctree:: - :maxdepth: 2 - -.. automodule:: models - :members: Item diff --git a/doc/build/html/_sources/models/model_player.txt b/doc/build/html/_sources/models/model_player.txt deleted file mode 100644 index 27c666e..0000000 --- a/doc/build/html/_sources/models/model_player.txt +++ /dev/null @@ -1,8 +0,0 @@ -Player Model -===================================== - -.. toctree:: - :maxdepth: 2 - -.. automodule:: models - :members: Player diff --git a/doc/build/html/_sources/models/model_room.txt b/doc/build/html/_sources/models/model_room.txt deleted file mode 100644 index e1cfc8c..0000000 --- a/doc/build/html/_sources/models/model_room.txt +++ /dev/null @@ -1,8 +0,0 @@ -Room Model -===================================== - -.. toctree:: - :maxdepth: 2 - -.. automodule:: models - :members: Room diff --git a/doc/build/html/_sources/models/player.txt b/doc/build/html/_sources/models/player.txt deleted file mode 100644 index f6e1c9e..0000000 --- a/doc/build/html/_sources/models/player.txt +++ /dev/null @@ -1,8 +0,0 @@ -Player Model -===================================== - -.. toctree:: - :maxdepth: 2 - -.. automodule:: models - :members: Player \ No newline at end of file diff --git a/doc/build/html/_sources/models/room.txt b/doc/build/html/_sources/models/room.txt deleted file mode 100644 index 58dc488..0000000 --- a/doc/build/html/_sources/models/room.txt +++ /dev/null @@ -1,8 +0,0 @@ -Room Model -===================================== - -.. toctree:: - :maxdepth: 2 - -.. automodule:: models - :members: Room \ No newline at end of file diff --git a/doc/build/html/_sources/modloader.txt b/doc/build/html/_sources/modloader.txt deleted file mode 100644 index ffc637a..0000000 --- a/doc/build/html/_sources/modloader.txt +++ /dev/null @@ -1,9 +0,0 @@ -Mod Loading Class -===================================== - -.. toctree:: - :maxdepth: 2 -.. automodule:: modloader - :members: - :special-members: - diff --git a/doc/build/html/_sources/permissions.txt b/doc/build/html/_sources/permissions.txt deleted file mode 100644 index 780f913..0000000 --- a/doc/build/html/_sources/permissions.txt +++ /dev/null @@ -1,12 +0,0 @@ -Permission Repository Class -===================================== - -.. toctree:: - :maxdepth: 2 - - permissions/allowadminoverride - permissions/allowsuperadminoverride - permissions/allowowneroverride -.. automodule:: permissions - :members: - :special-members: \ No newline at end of file diff --git a/doc/build/html/_sources/permissions/allowadminoverride.txt b/doc/build/html/_sources/permissions/allowadminoverride.txt deleted file mode 100644 index ecec34c..0000000 --- a/doc/build/html/_sources/permissions/allowadminoverride.txt +++ /dev/null @@ -1,6 +0,0 @@ -Allow Admin Permission Override -===================================== - -The allow admin permission override is merely a permission setting that you can set as the server host that dictates whether or not administrators can override other permission settings merely because of their status. It goes under the name -"AllowAdminOverride" in config/permissions.cfg. - diff --git a/doc/build/html/_sources/permissions/allowowneroverride.txt b/doc/build/html/_sources/permissions/allowowneroverride.txt deleted file mode 100644 index f2bc453..0000000 --- a/doc/build/html/_sources/permissions/allowowneroverride.txt +++ /dev/null @@ -1,5 +0,0 @@ -Allow Owner Permission Override -===================================== - -The allow owner permission override is merely a permission setting that you can set as the server host that dictates whether or not server owners can override other permission settings merely because of their status. -It goes under the name “AllowOwnerOverride” in config/permissions.cfg. \ No newline at end of file diff --git a/doc/build/html/_sources/permissions/allowsuperadminoverride.txt b/doc/build/html/_sources/permissions/allowsuperadminoverride.txt deleted file mode 100644 index e4713bf..0000000 --- a/doc/build/html/_sources/permissions/allowsuperadminoverride.txt +++ /dev/null @@ -1,5 +0,0 @@ -Allow Super Admin Permission Override -===================================== - -The allow super admin permission override is merely a permission setting that you can set as the server host that dictates whether or not super administrators can override other permission settings merely because of their status. -It goes under the name “AllowSuperAdminOverride” in config/permissions.cfg. diff --git a/doc/build/html/_sources/scommands.txt b/doc/build/html/_sources/scommands.txt deleted file mode 100644 index 42c5f66..0000000 --- a/doc/build/html/_sources/scommands.txt +++ /dev/null @@ -1,8 +0,0 @@ -ScalyMUCK Base Modification -===================================== - -.. toctree:: - :maxdepth: 2 -.. automodule:: scommands - :members: Modification - :special-members: diff --git a/doc/build/html/_sources/server.txt b/doc/build/html/_sources/server.txt deleted file mode 100644 index 2229ef0..0000000 --- a/doc/build/html/_sources/server.txt +++ /dev/null @@ -1,9 +0,0 @@ -Server Instance Class -===================================== - -.. toctree:: - :maxdepth: 2 -.. automodule:: server - :members: - :special-members: - diff --git a/doc/build/html/_sources/settings.txt b/doc/build/html/_sources/settings.txt deleted file mode 100644 index a5d19bd..0000000 --- a/doc/build/html/_sources/settings.txt +++ /dev/null @@ -1,8 +0,0 @@ -Settings File Loading Class -===================================== - -.. toctree:: - :maxdepth: 2 -.. automodule:: settings - :members: - :special-members: diff --git a/doc/build/html/_sources/world.txt b/doc/build/html/_sources/world.txt deleted file mode 100644 index 74b6098..0000000 --- a/doc/build/html/_sources/world.txt +++ /dev/null @@ -1,8 +0,0 @@ -World Class -===================================== - -.. toctree:: - :maxdepth: 2 -.. automodule:: world - :members: - :special-members: diff --git a/doc/build/html/_static/ajax-loader.gif b/doc/build/html/_static/ajax-loader.gif deleted file mode 100644 index 61faf8cab23993bd3e1560bff0668bd628642330..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 673 zcmZ?wbhEHb6krfw_{6~Q|Nno%(3)e{?)x>&1u}A`t?OF7Z|1gRivOgXi&7IyQd1Pl zGfOfQ60;I3a`F>X^fL3(@);C=vM_KlFfb_o=k{|A33hf2a5d61U}gjg=>Rd%XaNQW zW@Cw{|b%Y*pl8F?4B9 zlo4Fz*0kZGJabY|>}Okf0}CCg{u4`zEPY^pV?j2@h+|igy0+Kz6p;@SpM4s6)XEMg z#3Y4GX>Hjlml5ftdH$4x0JGdn8~MX(U~_^d!Hi)=HU{V%g+mi8#UGbE-*ao8f#h+S z2a0-5+vc7MU$e-NhmBjLIC1v|)9+Im8x1yacJ7{^tLX(ZhYi^rpmXm0`@ku9b53aN zEXH@Y3JaztblgpxbJt{AtE1ad1Ca>{v$rwwvK(>{m~Gf_=-Ro7Fk{#;i~+{{>QtvI yb2P8Zac~?~=sRA>$6{!(^3;ZP0TPFR(G_-UDU(8Jl0?(IXu$~#4A!880|o%~Al1tN diff --git a/doc/build/html/_static/basic.css b/doc/build/html/_static/basic.css deleted file mode 100644 index 43e8baf..0000000 --- a/doc/build/html/_static/basic.css +++ /dev/null @@ -1,540 +0,0 @@ -/* - * basic.css - * ~~~~~~~~~ - * - * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/* -- main layout ----------------------------------------------------------- */ - -div.clearer { - clear: both; -} - -/* -- relbar ---------------------------------------------------------------- */ - -div.related { - width: 100%; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -/* -- sidebar --------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 230px; - margin-left: -100%; - font-size: 90%; -} - -div.sphinxsidebar ul { - list-style: none; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar #searchbox input[type="text"] { - width: 170px; -} - -div.sphinxsidebar #searchbox input[type="submit"] { - width: 30px; -} - -img { - border: 0; -} - -/* -- search page ----------------------------------------------------------- */ - -ul.search { - margin: 10px 0 0 20px; - padding: 0; -} - -ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li div.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* -- index page ------------------------------------------------------------ */ - -table.contentstable { - width: 90%; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* -- general index --------------------------------------------------------- */ - -table.indextable { - width: 100%; -} - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable dl, table.indextable dd { - margin-top: 0; - margin-bottom: 0; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -div.modindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -div.genindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -/* -- general body styles --------------------------------------------------- */ - -a.headerlink { - visibility: hidden; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink { - visibility: visible; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -.field-list ul { - padding-left: 1em; -} - -.first { - margin-top: 0 !important; -} - -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -img.align-left, .figure.align-left, object.align-left { - clear: left; - float: left; - margin-right: 1em; -} - -img.align-right, .figure.align-right, object.align-right { - clear: right; - float: right; - margin-left: 1em; -} - -img.align-center, .figure.align-center, object.align-center { - display: block; - margin-left: auto; - margin-right: auto; -} - -.align-left { - text-align: left; -} - -.align-center { - text-align: center; -} - -.align-right { - text-align: right; -} - -/* -- sidebars -------------------------------------------------------------- */ - -div.sidebar { - margin: 0 0 0.5em 1em; - border: 1px solid #ddb; - padding: 7px 7px 0 7px; - background-color: #ffe; - width: 40%; - float: right; -} - -p.sidebar-title { - font-weight: bold; -} - -/* -- topics ---------------------------------------------------------------- */ - -div.topic { - border: 1px solid #ccc; - padding: 7px 7px 0 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* -- admonitions ----------------------------------------------------------- */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -div.admonition dl { - margin-bottom: 0; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -/* -- tables ---------------------------------------------------------------- */ - -table.docutils { - border: 0; - border-collapse: collapse; -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 5px; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -table.field-list td, table.field-list th { - border: 0 !important; -} - -table.footnote td, table.footnote th { - border: 0 !important; -} - -th { - text-align: left; - padding-right: 5px; -} - -table.citation { - border-left: solid 1px gray; - margin-left: 1px; -} - -table.citation td { - border-bottom: none; -} - -/* -- other body styles ----------------------------------------------------- */ - -ol.arabic { - list-style: decimal; -} - -ol.loweralpha { - list-style: lower-alpha; -} - -ol.upperalpha { - list-style: upper-alpha; -} - -ol.lowerroman { - list-style: lower-roman; -} - -ol.upperroman { - list-style: upper-roman; -} - -dl { - margin-bottom: 15px; -} - -dd p { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -dt:target, .highlighted { - background-color: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -.refcount { - color: #060; -} - -.optional { - font-size: 1.3em; -} - -.versionmodified { - font-style: italic; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -.footnote:target { - background-color: #ffa; -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} - -.guilabel, .menuselection { - font-family: sans-serif; -} - -.accelerator { - text-decoration: underline; -} - -.classifier { - font-style: oblique; -} - -abbr, acronym { - border-bottom: dotted 1px; - cursor: help; -} - -/* -- code displays --------------------------------------------------------- */ - -pre { - overflow: auto; - overflow-y: hidden; /* fixes display issues on Chrome browsers */ -} - -td.linenos pre { - padding: 5px 0px; - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - margin-left: 0.5em; -} - -table.highlighttable td { - padding: 0 0.5em 0 0.5em; -} - -tt.descname { - background-color: transparent; - font-weight: bold; - font-size: 1.2em; -} - -tt.descclassname { - background-color: transparent; -} - -tt.xref, a tt { - background-color: transparent; - font-weight: bold; -} - -h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { - background-color: transparent; -} - -.viewcode-link { - float: right; -} - -.viewcode-back { - float: right; - font-family: sans-serif; -} - -div.viewcode-block:target { - margin: -1px -10px; - padding: 0 10px; -} - -/* -- math display ---------------------------------------------------------- */ - -img.math { - vertical-align: middle; -} - -div.body div.math p { - text-align: center; -} - -span.eqno { - float: right; -} - -/* -- printout stylesheet --------------------------------------------------- */ - -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0 !important; - width: 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - #top-link { - display: none; - } -} \ No newline at end of file diff --git a/doc/build/html/_static/comment-bright.png b/doc/build/html/_static/comment-bright.png deleted file mode 100644 index 551517b8c83b76f734ff791f847829a760ad1903..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3500 zcmV;d4O8-oP)Oz@Z0f2-7z;ux~O9+4z06=<WDR*FRcSTFz- zW=q650N5=6FiBTtNC2?60Km==3$g$R3;-}uh=nNt1bYBr$Ri_o0EC$U6h`t_Jn<{8 z5a%iY0C<_QJh>z}MS)ugEpZ1|S1ukX&Pf+56gFW3VVXcL!g-k)GJ!M?;PcD?0HBc- z5#WRK{dmp}uFlRjj{U%*%WZ25jX z{P*?XzTzZ-GF^d31o+^>%=Ap99M6&ogks$0k4OBs3;+Bb(;~!4V!2o<6ys46agIcq zjPo+3B8fthDa9qy|77CdEc*jK-!%ZRYCZvbku9iQV*~a}ClFY4z~c7+0P?$U!PF=S z1Au6Q;m>#f??3%Vpd|o+W=WE9003S@Bra6Svp>fO002awfhw>;8}z{#EWidF!3EsG z3;bXU&9EIRU@z1_9W=mEXoiz;4lcq~xDGvV5BgyU zp1~-*fe8db$Osc*A=-!mVv1NJjtCc-h4>-CNCXm#Bp}I%6j35eku^v$Qi@a{RY)E3 zJ#qp$hg?Rwkvqr$GJ^buyhkyVfwECO)C{#lxu`c9ghrwZ&}4KmnvWKso6vH!8a<3Q zq36)6Xb;+tK10Vaz~~qUGsJ8#F2=(`u{bOVlVi)VBCHIn#u~6ztOL7=^<&SmcLWlF zMZgI*1b0FpVIDz9SWH+>*hr`#93(Um+6gxa1B6k+CnA%mOSC4s5&6UzVlpv@SV$}* z))J2sFA#f(L&P^E5{W}HC%KRUNwK6<(h|}}(r!{C=`5+6G)NjFlgZj-YqAG9lq?`C z$c5yc>d>VnA`E_*3F2Qp##d8RZb=H01_mm@+|Cqnc9PsG(F5HIG_C zt)aG3uTh7n6Et<2In9F>NlT@zqLtGcXcuVrX|L#Xx)I%#9!{6gSJKPrN9dR61N3(c z4Tcqi$B1Vr8Jidf7-t!G7_XR2rWwr)$3XQ?}=hpK0&Z&W{| zep&sA23f;Q!%st`QJ}G3cbou<7-yIK2z4nfCCCtN2-XOGSWo##{8Q{ATurxr~;I`ytDs%xbip}RzP zziy}Qn4Z2~fSycmr`~zJ=lUFdFa1>gZThG6M+{g7vkW8#+YHVaJjFF}Z#*3@$J_By zLtVo_L#1JrVVB{Ak-5=4qt!-@Mh}c>#$4kh<88)m#-k<%CLtzEP3leVno>={htGUuD;o7bD)w_sX$S}eAxwzy?UvgBH(S?;#HZiQMoS*2K2 zT3xe7t(~nU*1N5{rxB;QPLocnp4Ml>u<^FZwyC!nu;thW+pe~4wtZn|Vi#w(#jeBd zlf9FDx_yoPJqHbk*$%56S{;6Kv~mM9!g3B(KJ}#RZ#@)!hR|78Dq|Iq-afF%KE1Brn_fm;Im z_u$xr8UFki1L{Ox>G0o)(&RAZ;=|I=wN2l97;cLaHH6leTB-XXa*h%dBOEvi`+x zi?=Txl?TadvyiL>SuF~-LZ;|cS}4~l2eM~nS7yJ>iOM;atDY;(?aZ^v+mJV$@1Ote z62cPUlD4IWOIIx&SmwQ~YB{nzae3Pc;}r!fhE@iwJh+OsDs9zItL;~pu715HdQEGA zUct(O!LkCy1<%NCg+}G`0PgpNm-?d@-hMgNe6^V+j6x$b<6@S<$+<4_1hi}Ti zncS4LsjI}fWY1>OX6feMEuLErma3QLmkw?X+1j)X-&VBk_4Y;EFPF_I+q;9dL%E~B zJh;4Nr^(LEJ3myURP{Rblsw%57T)g973R8o)DE9*xN#~;4_o$q%o z4K@u`jhx2fBXC4{U8Qn{*%*B$Ge=nny$HAYq{=vy|sI0 z_vss+H_qMky?OB#|JK!>IX&II^LlUh#rO5!7TtbwC;iULyV-Xq?ybB}ykGP{?LpZ? z-G|jbTmIbG@7#ZCz;~eY(cDM(28Dyq{*m>M4?_iynUBkc4TkHUI6gT!;y-fz>HMcd z&t%Ugo)`Y2{>!cx7B7DI)$7;J(U{Spm-3gBzioV_{p!H$8L!*M!p0uH$#^p{Ui4P` z?ZJ24cOCDe-w#jZd?0@)|7iKK^;6KN`;!@ylm7$*nDhK&GcDTy000JJOGiWi{{a60 z|De66lK=n!32;bRa{vGf6951U69E94oEQKA00(qQO+^RV2niQ93PPz|JOBU!-bqA3 zR5;6pl1pe^WfX zkSdl!omi0~*ntl;2q{jA^;J@WT8O!=A(Gck8fa>hn{#u{`Tyg)!KXI6l>4dj==iVKK6+%4zaRizy(5eryC3d2 z+5Y_D$4}k5v2=Siw{=O)SWY2HJwR3xX1*M*9G^XQ*TCNXF$Vj(kbMJXK0DaS_Sa^1 z?CEa!cFWDhcwxy%a?i@DN|G6-M#uuWU>lss@I>;$xmQ|`u3f;MQ|pYuHxxvMeq4TW;>|7Z2*AsqT=`-1O~nTm6O&pNEK?^cf9CX= zkq5|qAoE7un3V z^yy=@%6zqN^x`#qW+;e7j>th{6GV}sf*}g7{(R#T)yg-AZh0C&U;WA`AL$qz8()5^ zGFi2`g&L7!c?x+A2oOaG0c*Bg&YZt8cJ{jq_W{uTdA-<;`@iP$$=$H?gYIYc_q^*$ z#k(Key`d40R3?+GmgK8hHJcwiQ~r4By@w9*PuzR>x3#(F?YW_W5pPc(t(@-Y{psOt zz2!UE_5S)bLF)Oz@Z0f2-7z;ux~O9+4z06=<WDR*FRcSTFz- zW=q650N5=6FiBTtNC2?60Km==3$g$R3;-}uh=nNt1bYBr$Ri_o0EC$U6h`t_Jn<{8 z5a%iY0C<_QJh>z}MS)ugEpZ1|S1ukX&Pf+56gFW3VVXcL!g-k)GJ!M?;PcD?0HBc- z5#WRK{dmp}uFlRjj{U%*%WZ25jX z{P*?XzTzZ-GF^d31o+^>%=Ap99M6&ogks$0k4OBs3;+Bb(;~!4V!2o<6ys46agIcq zjPo+3B8fthDa9qy|77CdEc*jK-!%ZRYCZvbku9iQV*~a}ClFY4z~c7+0P?$U!PF=S z1Au6Q;m>#f??3%Vpd|o+W=WE9003S@Bra6Svp>fO002awfhw>;8}z{#EWidF!3EsG z3;bXU&9EIRU@z1_9W=mEXoiz;4lcq~xDGvV5BgyU zp1~-*fe8db$Osc*A=-!mVv1NJjtCc-h4>-CNCXm#Bp}I%6j35eku^v$Qi@a{RY)E3 zJ#qp$hg?Rwkvqr$GJ^buyhkyVfwECO)C{#lxu`c9ghrwZ&}4KmnvWKso6vH!8a<3Q zq36)6Xb;+tK10Vaz~~qUGsJ8#F2=(`u{bOVlVi)VBCHIn#u~6ztOL7=^<&SmcLWlF zMZgI*1b0FpVIDz9SWH+>*hr`#93(Um+6gxa1B6k+CnA%mOSC4s5&6UzVlpv@SV$}* z))J2sFA#f(L&P^E5{W}HC%KRUNwK6<(h|}}(r!{C=`5+6G)NjFlgZj-YqAG9lq?`C z$c5yc>d>VnA`E_*3F2Qp##d8RZb=H01_mm@+|Cqnc9PsG(F5HIG_C zt)aG3uTh7n6Et<2In9F>NlT@zqLtGcXcuVrX|L#Xx)I%#9!{6gSJKPrN9dR61N3(c z4Tcqi$B1Vr8Jidf7-t!G7_XR2rWwr)$3XQ?}=hpK0&Z&W{| zep&sA23f;Q!%st`QJ}G3cbou<7-yIK2z4nfCCCtN2-XOGSWo##{8Q{ATurxr~;I`ytDs%xbip}RzP zziy}Qn4Z2~fSycmr`~zJ=lUFdFa1>gZThG6M+{g7vkW8#+YHVaJjFF}Z#*3@$J_By zLtVo_L#1JrVVB{Ak-5=4qt!-@Mh}c>#$4kh<88)m#-k<%CLtzEP3leVno>={htGUuD;o7bD)w_sX$S}eAxwzy?UvgBH(S?;#HZiQMoS*2K2 zT3xe7t(~nU*1N5{rxB;QPLocnp4Ml>u<^FZwyC!nu;thW+pe~4wtZn|Vi#w(#jeBd zlf9FDx_yoPJqHbk*$%56S{;6Kv~mM9!g3B(KJ}#RZ#@)!hR|78Dq|Iq-afF%KE1Brn_fm;Im z_u$xr8UFki1L{Ox>G0o)(&RAZ;=|I=wN2l97;cLaHH6leTB-XXa*h%dBOEvi`+x zi?=Txl?TadvyiL>SuF~-LZ;|cS}4~l2eM~nS7yJ>iOM;atDY;(?aZ^v+mJV$@1Ote z62cPUlD4IWOIIx&SmwQ~YB{nzae3Pc;}r!fhE@iwJh+OsDs9zItL;~pu715HdQEGA zUct(O!LkCy1<%NCg+}G`0PgpNm-?d@-hMgNe6^V+j6x$b<6@S<$+<4_1hi}Ti zncS4LsjI}fWY1>OX6feMEuLErma3QLmkw?X+1j)X-&VBk_4Y;EFPF_I+q;9dL%E~B zJh;4Nr^(LEJ3myURP{Rblsw%57T)g973R8o)DE9*xN#~;4_o$q%o z4K@u`jhx2fBXC4{U8Qn{*%*B$Ge=nny$HAYq{=vy|sI0 z_vss+H_qMky?OB#|JK!>IX&II^LlUh#rO5!7TtbwC;iULyV-Xq?ybB}ykGP{?LpZ? z-G|jbTmIbG@7#ZCz;~eY(cDM(28Dyq{*m>M4?_iynUBkc4TkHUI6gT!;y-fz>HMcd z&t%Ugo)`Y2{>!cx7B7DI)$7;J(U{Spm-3gBzioV_{p!H$8L!*M!p0uH$#^p{Ui4P` z?ZJ24cOCDe-w#jZd?0@)|7iKK^;6KN`;!@ylm7$*nDhK&GcDTy000JJOGiWi{{a60 z|De66lK=n!32;bRa{vGf6951U69E94oEQKA00(qQO+^RV2oe()A>y0J-2easEJ;K` zR5;6Jl3z%jbr{D#&+mQTbB>-f&3W<<%ayjKi&ZjBc2N<@)`~{dMXWB0(ajbV85_gJ zf(EU`iek}4Bt%55ix|sVMm1u8KvB#hnmU~_r<Ogd(A5vg_omvd-#L!=(BMVklxVqhdT zofSj`QA^|)G*lu58>#vhvA)%0Or&dIsb%b)st*LV8`ANnOipDbh%_*c7`d6# z21*z~Xd?ovgf>zq(o0?Et~9ti+pljZC~#_KvJhA>u91WRaq|uqBBKP6V0?p-NL59w zrK0w($_m#SDPQ!Z$nhd^JO|f+7k5xca94d2OLJ&sSxlB7F%NtrF@@O7WWlkHSDtor zzD?u;b&KN$*MnHx;JDy9P~G<{4}9__s&MATBV4R+MuA8TjlZ3ye&qZMCUe8ihBnHI zhMSu zSERHwrmBb$SWVr+)Yk2k^FgTMR6mP;@FY2{}BeV|SUo=mNk<-XSOHNErw>s{^rR-bu$@aN7= zj~-qXcS2!BA*(Q**BOOl{FggkyHdCJi_Fy>?_K+G+DYwIn8`29DYPg&s4$}7D`fv? zuyJ2sMfJX(I^yrf6u!(~9anf(AqAk&ke}uL0SIb-H!SaDQvd(}07*qoM6N<$g1Ha7 A2LJ#7 diff --git a/doc/build/html/_static/comment.png b/doc/build/html/_static/comment.png deleted file mode 100644 index 92feb52b8824c6b0f59b658b1196c61de9162a95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3445 zcmV-*4T|!KP)Oz@Z0f2-7z;ux~O9+4z06=<WDR*FRcSTFz- zW=q650N5=6FiBTtNC2?60Km==3$g$R3;-}uh=nNt1bYBr$Ri_o0EC$U6h`t_Jn<{8 z5a%iY0C<_QJh>z}MS)ugEpZ1|S1ukX&Pf+56gFW3VVXcL!g-k)GJ!M?;PcD?0HBc- z5#WRK{dmp}uFlRjj{U%*%WZ25jX z{P*?XzTzZ-GF^d31o+^>%=Ap99M6&ogks$0k4OBs3;+Bb(;~!4V!2o<6ys46agIcq zjPo+3B8fthDa9qy|77CdEc*jK-!%ZRYCZvbku9iQV*~a}ClFY4z~c7+0P?$U!PF=S z1Au6Q;m>#f??3%Vpd|o+W=WE9003S@Bra6Svp>fO002awfhw>;8}z{#EWidF!3EsG z3;bXU&9EIRU@z1_9W=mEXoiz;4lcq~xDGvV5BgyU zp1~-*fe8db$Osc*A=-!mVv1NJjtCc-h4>-CNCXm#Bp}I%6j35eku^v$Qi@a{RY)E3 zJ#qp$hg?Rwkvqr$GJ^buyhkyVfwECO)C{#lxu`c9ghrwZ&}4KmnvWKso6vH!8a<3Q zq36)6Xb;+tK10Vaz~~qUGsJ8#F2=(`u{bOVlVi)VBCHIn#u~6ztOL7=^<&SmcLWlF zMZgI*1b0FpVIDz9SWH+>*hr`#93(Um+6gxa1B6k+CnA%mOSC4s5&6UzVlpv@SV$}* z))J2sFA#f(L&P^E5{W}HC%KRUNwK6<(h|}}(r!{C=`5+6G)NjFlgZj-YqAG9lq?`C z$c5yc>d>VnA`E_*3F2Qp##d8RZb=H01_mm@+|Cqnc9PsG(F5HIG_C zt)aG3uTh7n6Et<2In9F>NlT@zqLtGcXcuVrX|L#Xx)I%#9!{6gSJKPrN9dR61N3(c z4Tcqi$B1Vr8Jidf7-t!G7_XR2rWwr)$3XQ?}=hpK0&Z&W{| zep&sA23f;Q!%st`QJ}G3cbou<7-yIK2z4nfCCCtN2-XOGSWo##{8Q{ATurxr~;I`ytDs%xbip}RzP zziy}Qn4Z2~fSycmr`~zJ=lUFdFa1>gZThG6M+{g7vkW8#+YHVaJjFF}Z#*3@$J_By zLtVo_L#1JrVVB{Ak-5=4qt!-@Mh}c>#$4kh<88)m#-k<%CLtzEP3leVno>={htGUuD;o7bD)w_sX$S}eAxwzy?UvgBH(S?;#HZiQMoS*2K2 zT3xe7t(~nU*1N5{rxB;QPLocnp4Ml>u<^FZwyC!nu;thW+pe~4wtZn|Vi#w(#jeBd zlf9FDx_yoPJqHbk*$%56S{;6Kv~mM9!g3B(KJ}#RZ#@)!hR|78Dq|Iq-afF%KE1Brn_fm;Im z_u$xr8UFki1L{Ox>G0o)(&RAZ;=|I=wN2l97;cLaHH6leTB-XXa*h%dBOEvi`+x zi?=Txl?TadvyiL>SuF~-LZ;|cS}4~l2eM~nS7yJ>iOM;atDY;(?aZ^v+mJV$@1Ote z62cPUlD4IWOIIx&SmwQ~YB{nzae3Pc;}r!fhE@iwJh+OsDs9zItL;~pu715HdQEGA zUct(O!LkCy1<%NCg+}G`0PgpNm-?d@-hMgNe6^V+j6x$b<6@S<$+<4_1hi}Ti zncS4LsjI}fWY1>OX6feMEuLErma3QLmkw?X+1j)X-&VBk_4Y;EFPF_I+q;9dL%E~B zJh;4Nr^(LEJ3myURP{Rblsw%57T)g973R8o)DE9*xN#~;4_o$q%o z4K@u`jhx2fBXC4{U8Qn{*%*B$Ge=nny$HAYq{=vy|sI0 z_vss+H_qMky?OB#|JK!>IX&II^LlUh#rO5!7TtbwC;iULyV-Xq?ybB}ykGP{?LpZ? z-G|jbTmIbG@7#ZCz;~eY(cDM(28Dyq{*m>M4?_iynUBkc4TkHUI6gT!;y-fz>HMcd z&t%Ugo)`Y2{>!cx7B7DI)$7;J(U{Spm-3gBzioV_{p!H$8L!*M!p0uH$#^p{Ui4P` z?ZJ24cOCDe-w#jZd?0@)|7iKK^;6KN`;!@ylm7$*nDhK&GcDTy000JJOGiWi{{a60 z|De66lK=n!32;bRa{vGf6951U69E94oEQKA00(qQO+^RV2nzr)JMUJvzW@LNr%6OX zR5;6Zk;`k`RTRfR-*ac2G}PGmXsUu>6ce?Lsn$m^3Q`48f|TwQ+_-Qh=t8Ra7nE)y zf@08(pjZ@22^EVjG*%30TJRMkBUC$WqZ73uoiv&J=APqX;!v%AH}`Vx`999MVjXwy z{f1-vh8P<=plv&cZ>p5jjX~Vt&W0e)wpw1RFRuRdDkwlKb01tp5 zP=trFN0gH^|L4jJkB{6sCV;Q!ewpg-D&4cza%GQ*b>R*=34#dW;ek`FEiB(vnw+U# zpOX5UMJBhIN&;D1!yQoIAySC!9zqJmmfoJqmQp}p&h*HTfMh~u9rKic2oz3sNM^#F zBIq*MRLbsMt%y{EHj8}LeqUUvoxf0=kqji62>ne+U`d#%J)abyK&Y`=eD%oA!36<)baZyK zXJh5im6umkS|_CSGXips$nI)oBHXojzBzyY_M5K*uvb0_9viuBVyV%5VtJ*Am1ag# zczbv4B?u8j68iOz<+)nDu^oWnL+$_G{PZOCcOGQ?!1VCefves~rfpaEZs-PdVYMiV z98ElaJ2}7f;htSXFY#Zv?__sQeckE^HV{ItO=)2hMQs=(_ Xn!ZpXD%P(H00000NkvXXu0mjf= 0 && !jQuery(node.parentNode).hasClass(className)) { - var span = document.createElement("span"); - span.className = className; - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this); - }); - } - } - return this.each(function() { - highlight(this); - }); -}; - -/** - * Small JavaScript module for the documentation. - */ -var Documentation = { - - init : function() { - this.fixFirefoxAnchorBug(); - this.highlightSearchWords(); - this.initIndexTable(); - }, - - /** - * i18n support - */ - TRANSLATIONS : {}, - PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, - LOCALE : 'unknown', - - // gettext and ngettext don't access this so that the functions - // can safely bound to a different name (_ = Documentation.gettext) - gettext : function(string) { - var translated = Documentation.TRANSLATIONS[string]; - if (typeof translated == 'undefined') - return string; - return (typeof translated == 'string') ? translated : translated[0]; - }, - - ngettext : function(singular, plural, n) { - var translated = Documentation.TRANSLATIONS[singular]; - if (typeof translated == 'undefined') - return (n == 1) ? singular : plural; - return translated[Documentation.PLURALEXPR(n)]; - }, - - addTranslations : function(catalog) { - for (var key in catalog.messages) - this.TRANSLATIONS[key] = catalog.messages[key]; - this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); - this.LOCALE = catalog.locale; - }, - - /** - * add context elements like header anchor links - */ - addContextElements : function() { - $('div[id] > :header:first').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this headline')). - appendTo(this); - }); - $('dt[id]').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this definition')). - appendTo(this); - }); - }, - - /** - * workaround a firefox stupidity - */ - fixFirefoxAnchorBug : function() { - if (document.location.hash && $.browser.mozilla) - window.setTimeout(function() { - document.location.href += ''; - }, 10); - }, - - /** - * highlight the search words provided in the url in the text - */ - highlightSearchWords : function() { - var params = $.getQueryParameters(); - var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; - if (terms.length) { - var body = $('div.body'); - window.setTimeout(function() { - $.each(terms, function() { - body.highlightText(this.toLowerCase(), 'highlighted'); - }); - }, 10); - $('') - .appendTo($('#searchbox')); - } - }, - - /** - * init the domain index toggle buttons - */ - initIndexTable : function() { - var togglers = $('img.toggler').click(function() { - var src = $(this).attr('src'); - var idnum = $(this).attr('id').substr(7); - $('tr.cg-' + idnum).toggle(); - if (src.substr(-9) == 'minus.png') - $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); - else - $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); - }).css('display', ''); - if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { - togglers.click(); - } - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords : function() { - $('#searchbox .highlight-link').fadeOut(300); - $('span.highlighted').removeClass('highlighted'); - }, - - /** - * make the url absolute - */ - makeURL : function(relativeURL) { - return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; - }, - - /** - * get the current relative url - */ - getCurrentURL : function() { - var path = document.location.pathname; - var parts = path.split(/\//); - $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { - if (this == '..') - parts.pop(); - }); - var url = parts.join('/'); - return path.substring(url.lastIndexOf('/') + 1, path.length - 1); - } -}; - -// quick alias for translations -_ = Documentation.gettext; - -$(document).ready(function() { - Documentation.init(); -}); diff --git a/doc/build/html/_static/down-pressed.png b/doc/build/html/_static/down-pressed.png deleted file mode 100644 index 6f7ad782782e4f8e39b0c6e15c7344700cdd2527..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6U4S$Y z{B+)352QE?JR*yM+OLB!qm#z$3ZNi+iKnkC`z>}Z23@f-Ava~9&<9T!#}JFtXD=!G zGdl{fK6ro2OGiOl+hKvH6i=D3%%Y^j`yIkRn!8O>@bG)IQR0{Kf+mxNd=_WScA8u_ z3;8(7x2){m9`nt+U(Nab&1G)!{`SPVpDX$w8McLTzAJ39wprG3p4XLq$06M`%}2Yk zRPPsbES*dnYm1wkGL;iioAUB*Or2kz6(-M_r_#Me-`{mj$Z%( diff --git a/doc/build/html/_static/down.png b/doc/build/html/_static/down.png deleted file mode 100644 index 3003a88770de3977d47a2ba69893436a2860f9e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 363 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6U4S$Y z{B+)352QE?JR*yM+OLB!qm#z$3ZNi+iKnkC`z>}xaV3tUZ$qnrLa#kt978NlpS`ru z&)HFc^}^>{UOEce+71h5nn>6&w6A!ieNbu1wh)UGh{8~et^#oZ1# z>T7oM=FZ~xXWnTo{qnXm$ZLOlqGswI_m2{XwVK)IJmBjW{J3-B3x@C=M{ShWt#fYS9M?R;8K$~YwlIqwf>VA7q=YKcwf2DS4Zj5inDKXXB1zl=(YO3ST6~rDq)&z z*o>z)=hxrfG-cDBW0G$!?6{M<$@{_4{m1o%Ub!naEtn|@^frU1tDnm{r-UW|!^@B8 diff --git a/doc/build/html/_static/file.png b/doc/build/html/_static/file.png deleted file mode 100644 index d18082e397e7e54f20721af768c4c2983258f1b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 392 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmP$HyOL$D9)yc9|lc|nKf<9@eUiWd>3GuTC!a5vdfWYEazjncPj5ZQX%+1 zt8B*4=d)!cdDz4wr^#OMYfqGz$1LDFF>|#>*O?AGil(WEs?wLLy{Gj2J_@opDm%`dlax3yA*@*N$G&*ukFv>P8+2CBWO(qz zD0k1@kN>hhb1_6`&wrCswzINE(evt-5C1B^STi2@PmdKI;Vst0PQB6!2kdN diff --git a/doc/build/html/_static/jquery.js b/doc/build/html/_static/jquery.js deleted file mode 100644 index 7c24308..0000000 --- a/doc/build/html/_static/jquery.js +++ /dev/null @@ -1,154 +0,0 @@ -/*! - * jQuery JavaScript Library v1.4.2 - * http://jquery.com/ - * - * Copyright 2010, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * Copyright 2010, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Sat Feb 13 22:33:48 2010 -0500 - */ -(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/, -Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&& -(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this, -a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b=== -"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this, -function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
a"; -var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected, -parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent= -false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n= -s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true, -applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando]; -else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this, -a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b=== -w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i, -cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected= -c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); -a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g, -function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split("."); -k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a), -C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B=0){a.type= -e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&& -f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive; -if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data", -e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a, -"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a, -d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, -e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift(); -t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D|| -g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, -CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, -g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, -text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, -setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= -h[3];l=0;for(m=h.length;l=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== -"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, -h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& -q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=""; -if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="

";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); -(function(){var g=s.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: -function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= -{},i;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== -"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", -d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? -a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== -1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"},F={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= -c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, -wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, -prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, -this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); -return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, -""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); -return this}else{e=0;for(var j=d.length;e0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", -""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===""&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= -c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? -c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= -function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= -Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, -"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= -a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= -a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=//gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!== -"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("
").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this}, -serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), -function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href, -global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&& -e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)? -"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache=== -false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B= -false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since", -c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E|| -d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x); -g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status=== -1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b=== -"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional; -if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration=== -"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]|| -c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start; -this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now= -this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem, -e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b
"; -a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b); -c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a, -d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top- -f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset": -"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in -e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window); diff --git a/doc/build/html/_static/minus.png b/doc/build/html/_static/minus.png deleted file mode 100644 index da1c5620d10c047525a467a425abe9ff5269cfc2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1SHkYJtzcHoCO|{#XvD(5N2eUHAey{$X?>< z>&kweokM_|(Po{+Q=kw>iEBiObAE1aYF-J$w=>iB1I2R$WLpMkF=>bh=@O1TaS?83{1OVknK< z>&kweokM`jkU7Va11Q8%;u=xnoS&PUnpeW`?aZ|OK(QcC7sn8Z%gHvy&v=;Q4jejg zV8NnAO`-4Z@2~&zopr02WF_WB>pF diff --git a/doc/build/html/_static/pygments.css b/doc/build/html/_static/pygments.css deleted file mode 100644 index d79caa1..0000000 --- a/doc/build/html/_static/pygments.css +++ /dev/null @@ -1,62 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight { background: #eeffcc; } -.highlight .c { color: #408090; font-style: italic } /* Comment */ -.highlight .err { border: 1px solid #FF0000 } /* Error */ -.highlight .k { color: #007020; font-weight: bold } /* Keyword */ -.highlight .o { color: #666666 } /* Operator */ -.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #007020 } /* Comment.Preproc */ -.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ -.highlight .gd { color: #A00000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #FF0000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #00A000 } /* Generic.Inserted */ -.highlight .go { color: #333333 } /* Generic.Output */ -.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #0044DD } /* Generic.Traceback */ -.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #007020 } /* Keyword.Pseudo */ -.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #902000 } /* Keyword.Type */ -.highlight .m { color: #208050 } /* Literal.Number */ -.highlight .s { color: #4070a0 } /* Literal.String */ -.highlight .na { color: #4070a0 } /* Name.Attribute */ -.highlight .nb { color: #007020 } /* Name.Builtin */ -.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ -.highlight .no { color: #60add5 } /* Name.Constant */ -.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ -.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ -.highlight .ne { color: #007020 } /* Name.Exception */ -.highlight .nf { color: #06287e } /* Name.Function */ -.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ -.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ -.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #bb60d5 } /* Name.Variable */ -.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #208050 } /* Literal.Number.Float */ -.highlight .mh { color: #208050 } /* Literal.Number.Hex */ -.highlight .mi { color: #208050 } /* Literal.Number.Integer */ -.highlight .mo { color: #208050 } /* Literal.Number.Oct */ -.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ -.highlight .sc { color: #4070a0 } /* Literal.String.Char */ -.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ -.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ -.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ -.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ -.highlight .sx { color: #c65d09 } /* Literal.String.Other */ -.highlight .sr { color: #235388 } /* Literal.String.Regex */ -.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ -.highlight .ss { color: #517918 } /* Literal.String.Symbol */ -.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ -.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ -.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ -.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/doc/build/html/_static/searchtools.js b/doc/build/html/_static/searchtools.js deleted file mode 100644 index 663be4c..0000000 --- a/doc/build/html/_static/searchtools.js +++ /dev/null @@ -1,560 +0,0 @@ -/* - * searchtools.js_t - * ~~~~~~~~~~~~~~~~ - * - * Sphinx JavaScript utilties for the full-text search. - * - * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/** - * helper function to return a node containing the - * search summary for a given text. keywords is a list - * of stemmed words, hlwords is the list of normal, unstemmed - * words. the first one is used to find the occurance, the - * latter for highlighting it. - */ - -jQuery.makeSearchSummary = function(text, keywords, hlwords) { - var textLower = text.toLowerCase(); - var start = 0; - $.each(keywords, function() { - var i = textLower.indexOf(this.toLowerCase()); - if (i > -1) - start = i; - }); - start = Math.max(start - 120, 0); - var excerpt = ((start > 0) ? '...' : '') + - $.trim(text.substr(start, 240)) + - ((start + 240 - text.length) ? '...' : ''); - var rv = $('
').text(excerpt); - $.each(hlwords, function() { - rv = rv.highlightText(this, 'highlighted'); - }); - return rv; -} - - -/** - * Porter Stemmer - */ -var Stemmer = function() { - - var step2list = { - ational: 'ate', - tional: 'tion', - enci: 'ence', - anci: 'ance', - izer: 'ize', - bli: 'ble', - alli: 'al', - entli: 'ent', - eli: 'e', - ousli: 'ous', - ization: 'ize', - ation: 'ate', - ator: 'ate', - alism: 'al', - iveness: 'ive', - fulness: 'ful', - ousness: 'ous', - aliti: 'al', - iviti: 'ive', - biliti: 'ble', - logi: 'log' - }; - - var step3list = { - icate: 'ic', - ative: '', - alize: 'al', - iciti: 'ic', - ical: 'ic', - ful: '', - ness: '' - }; - - var c = "[^aeiou]"; // consonant - var v = "[aeiouy]"; // vowel - var C = c + "[^aeiouy]*"; // consonant sequence - var V = v + "[aeiou]*"; // vowel sequence - - var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 - var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 - var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 - var s_v = "^(" + C + ")?" + v; // vowel in stem - - this.stemWord = function (w) { - var stem; - var suffix; - var firstch; - var origword = w; - - if (w.length < 3) - return w; - - var re; - var re2; - var re3; - var re4; - - firstch = w.substr(0,1); - if (firstch == "y") - w = firstch.toUpperCase() + w.substr(1); - - // Step 1a - re = /^(.+?)(ss|i)es$/; - re2 = /^(.+?)([^s])s$/; - - if (re.test(w)) - w = w.replace(re,"$1$2"); - else if (re2.test(w)) - w = w.replace(re2,"$1$2"); - - // Step 1b - re = /^(.+?)eed$/; - re2 = /^(.+?)(ed|ing)$/; - if (re.test(w)) { - var fp = re.exec(w); - re = new RegExp(mgr0); - if (re.test(fp[1])) { - re = /.$/; - w = w.replace(re,""); - } - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1]; - re2 = new RegExp(s_v); - if (re2.test(stem)) { - w = stem; - re2 = /(at|bl|iz)$/; - re3 = new RegExp("([^aeiouylsz])\\1$"); - re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re2.test(w)) - w = w + "e"; - else if (re3.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - else if (re4.test(w)) - w = w + "e"; - } - } - - // Step 1c - re = /^(.+?)y$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(s_v); - if (re.test(stem)) - w = stem + "i"; - } - - // Step 2 - re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step2list[suffix]; - } - - // Step 3 - re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step3list[suffix]; - } - - // Step 4 - re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; - re2 = /^(.+?)(s|t)(ion)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - if (re.test(stem)) - w = stem; - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1] + fp[2]; - re2 = new RegExp(mgr1); - if (re2.test(stem)) - w = stem; - } - - // Step 5 - re = /^(.+?)e$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - re2 = new RegExp(meq1); - re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) - w = stem; - } - re = /ll$/; - re2 = new RegExp(mgr1); - if (re.test(w) && re2.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - - // and turn initial Y back to y - if (firstch == "y") - w = firstch.toLowerCase() + w.substr(1); - return w; - } -} - - -/** - * Search Module - */ -var Search = { - - _index : null, - _queued_query : null, - _pulse_status : -1, - - init : function() { - var params = $.getQueryParameters(); - if (params.q) { - var query = params.q[0]; - $('input[name="q"]')[0].value = query; - this.performSearch(query); - } - }, - - loadIndex : function(url) { - $.ajax({type: "GET", url: url, data: null, success: null, - dataType: "script", cache: true}); - }, - - setIndex : function(index) { - var q; - this._index = index; - if ((q = this._queued_query) !== null) { - this._queued_query = null; - Search.query(q); - } - }, - - hasIndex : function() { - return this._index !== null; - }, - - deferQuery : function(query) { - this._queued_query = query; - }, - - stopPulse : function() { - this._pulse_status = 0; - }, - - startPulse : function() { - if (this._pulse_status >= 0) - return; - function pulse() { - Search._pulse_status = (Search._pulse_status + 1) % 4; - var dotString = ''; - for (var i = 0; i < Search._pulse_status; i++) - dotString += '.'; - Search.dots.text(dotString); - if (Search._pulse_status > -1) - window.setTimeout(pulse, 500); - }; - pulse(); - }, - - /** - * perform a search for something - */ - performSearch : function(query) { - // create the required interface elements - this.out = $('#search-results'); - this.title = $('

' + _('Searching') + '

').appendTo(this.out); - this.dots = $('').appendTo(this.title); - this.status = $('

').appendTo(this.out); - this.output = $('