diff --git a/src/commands/default/comms.py b/src/commands/default/comms.py index e9f969a2f0..9099637605 100644 --- a/src/commands/default/comms.py +++ b/src/commands/default/comms.py @@ -12,7 +12,7 @@ from src.comms.models import Channel, Msg, PlayerChannelConnection, ExternalChan from src.comms import irc, imc2, rss from src.comms.channelhandler import CHANNELHANDLER from src.utils import create, utils -from src.commands.default.muxcommand import MuxCommand +from src.commands.default.muxcommand import MuxCommand, MuxCommandOOC # limit symbol import for API __all__ = ("CommCommand", "CmdAddCom", "CmdDelCom", "CmdAllCom", @@ -624,7 +624,7 @@ class CmdCdesc(MuxCommand): channel.save() caller.msg("Description of channel '%s' set to '%s'." % (channel.key, self.rhs)) -class CmdPage(MuxCommand): +class CmdPage(MuxCommandOOC): """ page - send private message @@ -647,18 +647,17 @@ class CmdPage(MuxCommand): help_category = "Comms" def func(self): - "Implement function using the Msg methods" + # this is a MuxCommandOOC, which means caller will be a Player. caller = self.caller - player = caller + character = self.character # get the messages we've sent - messages_we_sent = list(Msg.objects.get_messages_by_sender(player)) - pages_we_sent = [msg for msg in messages_we_sent - if msg.receivers] + messages_we_sent = list(Msg.objects.get_messages_by_sender(caller)) + pages_we_sent = [msg for msg in messages_we_sent if msg.receivers] # get last messages we've got - pages_we_got = list(Msg.objects.get_messages_by_receiver(player)) + pages_we_got = list(Msg.objects.get_messages_by_receiver(caller)) if 'last' in self.switches: if pages_we_sent: @@ -718,9 +717,7 @@ class CmdPage(MuxCommand): recobjs = [] for receiver in set(receivers): if isinstance(receiver, basestring): - pobj = caller.search("*%s" % (receiver.lstrip('*')), global_search=True) - if not pobj: - return + pobj = caller.search(receiver) elif hasattr(receiver, 'character'): pobj = receiver.character else: @@ -739,7 +736,7 @@ class CmdPage(MuxCommand): message = "%s %s" % (caller.key, message.strip(':').strip()) # create the persistent message object - msg = create.create_message(player, message, + msg = create.create_message(caller, message, receivers=recobjs) # tell the players they got a message. diff --git a/src/commands/default/general.py b/src/commands/default/general.py index 1567f2f26c..79f874bb82 100644 --- a/src/commands/default/general.py +++ b/src/commands/default/general.py @@ -5,9 +5,9 @@ now. import time from django.conf import settings from src.server.sessionhandler import SESSIONS -from src.utils import utils +from src.utils import utils, search from src.objects.models import ObjectNick as Nick -from src.commands.default.muxcommand import MuxCommand +from src.commands.default.muxcommand import MuxCommand, MuxCommandOOC # limit symbol import for API __all__ = ("CmdHome", "CmdLook", "CmdPassword", "CmdNick", @@ -611,7 +611,7 @@ class CmdAccess(MuxCommand): # OOC commands -class CmdOOCLook(CmdLook): +class CmdOOCLook(MuxCommandOOC, CmdLook): """ ooc look @@ -633,14 +633,6 @@ class CmdOOCLook(CmdLook): def func(self): "implement the ooc look command" - self.character = None - if utils.inherits_from(self.caller, "src.objects.objects.Object"): - # An object of some type is calling. Convert to player. - #print self.caller, self.caller.__class__ - self.character = self.caller - if hasattr(self.caller, "player"): - self.caller = self.caller.player - if not self.character: string = "You are out-of-character (OOC). " string += "Use {w@ic{n to get back to the game, {whelp{n for more info." @@ -649,7 +641,7 @@ class CmdOOCLook(CmdLook): self.caller = self.character # we have to put this back for normal look to work. super(CmdOOCLook, self).func() -class CmdIC(MuxCommand): +class CmdIC(MuxCommandOOC): """ Switch control to an object @@ -674,8 +666,7 @@ class CmdIC(MuxCommand): Simple puppet method """ caller = self.caller - if utils.inherits_from(caller, "src.objects.objects.Object"): - caller = caller.player + old_character = self.character new_character = None if not self.args: @@ -685,10 +676,12 @@ class CmdIC(MuxCommand): return if not new_character: # search for a matching character - new_character = caller.search(self.args, global_search=True) - if not new_character: - # the search method handles error messages etc. - return + new_character = search.objects(self.args, caller, global_search=True, single_result=True) + if new_character: + new_character = new_character[0] + else: + # the search method handles error messages etc. + return if new_character.player: if new_character.player == caller: caller.msg("{RYou already are {c%s{n." % new_character.name) @@ -698,13 +691,9 @@ class CmdIC(MuxCommand): if not new_character.access(caller, "puppet"): caller.msg("{rYou may not become %s.{n" % new_character.name) return - old_char = None - if caller.character: - # save the old character. We only assign this to last_puppet if swap is successful. - old_char = caller.character if caller.swap_character(new_character): new_character.msg("\n{gYou become {c%s{n.\n" % new_character.name) - caller.db.last_puppet = old_char + caller.db.last_puppet = old_character if not new_character.location: # this might be due to being hidden away at logout; check loc = new_character.db.prelogout_location @@ -718,7 +707,7 @@ class CmdIC(MuxCommand): else: caller.msg("{rYou cannot become {C%s{n." % new_character.name) -class CmdOOC(MuxCommand): +class CmdOOC(MuxCommandOOC): """ @ooc - go ooc diff --git a/src/commands/default/muxcommand.py b/src/commands/default/muxcommand.py index 0dd7a860b2..7057016113 100644 --- a/src/commands/default/muxcommand.py +++ b/src/commands/default/muxcommand.py @@ -1,5 +1,6 @@ """ -The command template for the default MUX-style command set +The command template for the default MUX-style command set. There +is also an OOC version that makes sure caller is a Player object. """ from src.utils import utils @@ -11,7 +12,7 @@ __all__ = ("MuxCommand",) class MuxCommand(Command): """ This sets up the basis for a MUX command. The idea - is that most other Mux-related commands should just + is tkhat most other Mux-related commands should just inherit from this and don't have to implement much parsing of their own unless they do something particularly advanced. @@ -161,3 +162,33 @@ class MuxCommand(Command): string += "rhs, comma separated (self.rhslist): {w%s{n\n" % self.rhslist string += "-" * 50 self.caller.msg(string) + +class MuxCommandOOC(MuxCommand): + """ + This is an OOC version of the MuxCommand. Since OOC commands sit + on Players rather than on Characters/Objects, we need to check + this in the parser. + + OOC commands are strictly speaking also available when IC, it's + just that they are applied with a lower priority and are always + available, also when disconnected from a character (i.e. "ooc"). + + This class makes sure that caller is always a Player object, while + creating a new property "character" that is set only if a + character is actually attached to the Player. + """ + def parse(self): + """ + We run the parent parser as usual, then fix the result + """ + super(MuxCommandOOC, self).parse() + + if utils.inherits_from(self.caller, "src.objects.objects.Object"): + # caller is an Object/Character + self.character = self.caller + self.caller = self.caller.player + elif hasattr(self.caller, "character"): + # caller was already a Player + self.character = self.caller.character + else: + self.character = None diff --git a/src/commands/default/tests.py b/src/commands/default/tests.py index d7608d62c7..de04c11df8 100644 --- a/src/commands/default/tests.py +++ b/src/commands/default/tests.py @@ -33,10 +33,11 @@ from src.objects.models import ObjectDB # ------------------------------------------------------------ # print all feedback from test commands (can become very verbose!) -VERBOSE = False - +VERBOSE = True +NOMANGLE = True # mangle command input for extra testing def cleanup(): + print "cleaning test database ..." User.objects.all().delete() PlayerDB.objects.all().delete() ObjectDB.objects.all().delete() @@ -45,6 +46,7 @@ def cleanup(): PlayerChannelConnection.objects.all().delete() ExternalChannelConnection.objects.all().delete() ServerConfig.objects.all().delete() +cleanup() class FakeSessionHandler(sessionhandler.ServerSessionHandler): """ @@ -82,6 +84,7 @@ class FakeSession(serversession.ServerSession): def lineReceived(self, raw_string): pass def msg(self, message, data=None): + global VERBOSE if message.startswith("Traceback (most recent call last):"): #retval = "Traceback last line: %s" % message.split('\n')[-4:] raise AssertionError(message) @@ -103,6 +106,7 @@ class FakeSession(serversession.ServerSession): raise AssertionError(retval) if VERBOSE: print message +# setting up objects class CommandTest(TestCase): """ @@ -111,53 +115,77 @@ class CommandTest(TestCase): Inherit new tests from this. """ - - NOMANGLE = True # mangle command input for extra testing + print "creating command testing objects ..." + ROOM1 = create.create_object(settings.BASE_ROOM_TYPECLASS, key="room1") + ROOM2 = create.create_object(settings.BASE_ROOM_TYPECLASS, key="room2") + # create a faux player/character for testing. + CHAR1 = create.create_player("TestChar", "testplayer@test.com", "testpassword", character_location=ROOM1) + CHAR1.player.user.is_superuser = True + CHAR1.lock_storage = "" + CHAR1.locks = LockHandler(CHAR1) + CHAR1.ndb.return_string = None + sess = FakeSession() + sess.connectionMade() + sess.session_login(CHAR1.player) + # create second player + CHAR2 = create.create_player("TestChar2", "testplayer2@test.com", "testpassword2", character_location=ROOM1) + CHAR2.player.user.is_superuser = False + CHAR2.lock_storage = "" + CHAR2.locks = LockHandler(CHAR2) + CHAR2.ndb.return_string = None + sess2 = FakeSession() + sess2.connectionMade() + sess2.session_login(CHAR2.player) + # A non-player-controlled character + CHAR3 = create.create_object(settings.BASE_CHARACTER_TYPECLASS, key="TestChar3", location=ROOM1) + # create some objects + OBJ1 = create.create_object(settings.BASE_OBJECT_TYPECLASS, key="obj1", location=ROOM1) + OBJ2 = create.create_object(settings.BASE_OBJECT_TYPECLASS, key="obj2", location=ROOM1) + EXIT1 = create.create_object(settings.BASE_EXIT_TYPECLASS, key="exit1", location=ROOM1) + EXIT2 = create.create_object(settings.BASE_EXIT_TYPECLASS, key="exit2", location=ROOM2) def setUp(self): "sets up the testing environment" #ServerConfig.objects.conf("default_home", 2) - self.addCleanup(cleanup) + self.room1 = self.ROOM1 + self.room2 = self.ROOM2 + self.char1 = self.CHAR1 + self.char2 = self.CHAR2 + self.char3 = self.CHAR3 + self.obj1 = self.OBJ1 + self.obj2 = self.OBJ2 + self.exit1 = self.EXIT1 + self.exit2 = self.EXIT2 - self.room1 = create.create_object(settings.BASE_ROOM_TYPECLASS, key="room1") - self.room2 = create.create_object(settings.BASE_ROOM_TYPECLASS, key="room2") - # create a faux player/character for testing. - self.char1 = create.create_player("TestChar", "testplayer@test.com", "testpassword", character_location=self.room1) - self.char1.player.user.is_superuser = True - self.char1.lock_storage = "" - self.char1.locks = LockHandler(self.char1) - self.char1.ndb.return_string = None - sess = FakeSession() - sess.connectionMade() - sess.session_login(self.char1.player) - # create second player - self.char2 = create.create_player("TestChar2", "testplayer2@test.com", "testpassword2", character_location=self.room1) - self.char2.player.user.is_superuser = False - self.char2.lock_storage = "" - self.char2.locks = LockHandler(self.char2) - self.char2.ndb.return_string = None - sess2 = FakeSession() - sess2.connectionMade() - sess2.session_login(self.char2.player) - # A non-player-controlled character - self.char3 = create.create_object(settings.BASE_CHARACTER_TYPECLASS, key="TestChar3", location=self.room1) - # create some objects - self.obj1 = create.create_object(settings.BASE_OBJECT_TYPECLASS, key="obj1", location=self.room1) - self.obj2 = create.create_object(settings.BASE_OBJECT_TYPECLASS, key="obj2", location=self.room1) - self.exit1 = create.create_object(settings.BASE_EXIT_TYPECLASS, key="exit1", location=self.room1) - self.exit2 = create.create_object(settings.BASE_EXIT_TYPECLASS, key="exit2", location=self.room2) - - def tearDown(self): - "Cleans up testing environment after test has run." - User.objects.all().delete() - PlayerDB.objects.all().delete() - ObjectDB.objects.all().delete() - Channel.objects.all().delete() - Msg.objects.all().delete() - PlayerChannelConnection.objects.all().delete() - ExternalChannelConnection.objects.all().delete() - ServerConfig.objects.all().delete() + #self.addCleanup(cleanup) + #self.room1 = create.create_object(settings.BASE_ROOM_TYPECLASS, key="room1") + #self.room2 = create.create_object(settings.BASE_ROOM_TYPECLASS, key="room2") + ## create a faux player/character for testing. + #self.char1 = create.create_player("TestChar", "testplayer@test.com", "testpassword", character_location=self.room1) + #self.char1.player.user.is_superuser = True + #self.char1.lock_storage = "" + #self.char1.locks = LockHandler(self.char1) + #self.char1.ndb.return_string = None + #sess = FakeSession() + #sess.connectionMade() + #sess.session_login(self.char1.player) + ## create second player + #self.char2 = create.create_player("TestChar2", "testplayer2@test.com", "testpassword2", character_location=self.room1) + #self.char2.player.user.is_superuser = False + #self.char2.lock_storage = "" + #self.char2.locks = LockHandler(self.char2) + #self.char2.ndb.return_string = None + #sess2 = FakeSession() + #sess2.connectionMade() + #sess2.session_login(self.char2.player) + ## A non-player-controlled character + #self.char3 = create.create_object(settings.BASE_CHARACTER_TYPECLASS, key="TestChar3", location=self.room1) + ## create some objects + #self.obj1 = create.create_object(settings.BASE_OBJECT_TYPECLASS, key="obj1", location=self.room1) + #self.obj2 = create.create_object(settings.BASE_OBJECT_TYPECLASS, key="obj2", location=self.room1) + #self.exit1 = create.create_object(settings.BASE_EXIT_TYPECLASS, key="exit1", location=self.room1) + #self.exit2 = create.create_object(settings.BASE_EXIT_TYPECLASS, key="exit2", location=self.room2) def get_cmd(self, cmd_class, argument_string=""): """ @@ -178,7 +206,7 @@ class CommandTest(TestCase): This also mangles the input in various ways to test if the command will be fooled. """ - if not nomangle and not VERBOSE and not self.NOMANGLE: + if not nomangle and not VERBOSE and not NOMANGLE: # only mangle if not VERBOSE, to make fewer return lines test1 = re.sub(r'\s', '', raw_string) # remove all whitespace inside it test2 = "%s/åäö öäö;-:$£@*~^' 'test" % raw_string # inserting weird characters in call @@ -202,7 +230,6 @@ class BuildTest(CommandTest): NOMANGLE = True - #------------------------------------------------------------ # Default set Command testing #------------------------------------------------------------ @@ -352,7 +379,7 @@ class TestSet(BuildTest): class TestCpAttr(BuildTest): def test_call(self): self.execute_cmd("@set obj1/test = value") - self.execute_cmd("@set me/test2 = value2") + self.execute_cmd("@set obj2/test2 = value2") self.execute_cmd("@cpattr obj1/test = obj2/test") self.execute_cmd("@cpattr test2 = obj2") self.assertEqual(self.obj2.db.test, u"value") @@ -408,7 +435,7 @@ class TestCmdSets(BuildTest): def test_call(self): self.execute_cmd("@cmdsets") self.execute_cmd("@cmdsets obj1") -class TestDesc(BuildTest): +class TestName(BuildTest): def test_call(self): self.execute_cmd("@name obj1 = Test object", "Object's name changed to 'Test object'.") self.assertEqual(self.obj1.key, u"Test object") diff --git a/src/objects/manager.py b/src/objects/manager.py index 45e10a8445..f88b4a31f7 100644 --- a/src/objects/manager.py +++ b/src/objects/manager.py @@ -203,7 +203,7 @@ class ObjectManager(TypedObjectManager): @returns_typeclass_list def object_search(self, ostring, caller=None, global_search=False, - attribute_name=None, location=None): + attribute_name=None, location=None, single_result=False): """ Search as an object and return results. The result is always an Object. If * is appended (player search, a Character controlled by this Player @@ -313,7 +313,7 @@ class ObjectManager(TypedObjectManager): matches = [matches[match_number]] except IndexError: pass - # This is always a list. + # We always have a (possibly empty) list at this point. return matches # diff --git a/src/objects/models.py b/src/objects/models.py index 610e4bff23..e3e7316c83 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -28,7 +28,7 @@ from src.commands.cmdsethandler import CmdSetHandler from src.commands import cmdhandler from src.scripts.scripthandler import ScriptHandler from src.utils import logger -from src.utils.utils import make_iter, to_unicode, variable_from_module +from src.utils.utils import make_iter, to_unicode, variable_from_module, inherits_from #__all__ = ("ObjAttribute", "Alias", "ObjectNick", "ObjectDB") @@ -245,7 +245,7 @@ class ObjectDB(TypedObject): #@player.setter def __player_set(self, player): "Setter. Allows for self.player = value" - if isinstance(player, TypeClass): + if inherits_from(player, TypeClass): player = player.dbobj _set_cache(self, "player", player) #@player.deleter diff --git a/src/players/manager.py b/src/players/manager.py index d2be0dabd4..4cc88b7e01 100644 --- a/src/players/manager.py +++ b/src/players/manager.py @@ -4,7 +4,6 @@ The managers for the custom Player object and permissions. import datetime from functools import update_wrapper -from django.db import models from django.contrib.auth.models import User from src.typeclasses.managers import returns_typeclass_list, returns_typeclass, TypedObjectManager from src.utils import logger @@ -181,6 +180,7 @@ class PlayerManager(TypedObjectManager): return matches return self.filter(user__username__iexact=ostring) + def swap_character(self, player, new_character, delete_old_character=False): """ This disconnects a player from the current character (if any) and connects @@ -201,9 +201,10 @@ class PlayerManager(TypedObjectManager): new_character.player = player except Exception: # recover old setup - old_character.player = player - player.character = old_character + if old_character: + old_character.player = player + player.character = old_character return False - if delete_old_character: + if old_character and delete_old_character: old_character.delete() return True diff --git a/src/players/models.py b/src/players/models.py index 59f7b47598..25b0f6ca99 100644 --- a/src/players/models.py +++ b/src/players/models.py @@ -50,9 +50,11 @@ from src.typeclasses.models import _get_cache, _set_cache, _del_cache from src.server.sessionhandler import SESSIONS from src.players import manager from src.typeclasses.models import Attribute, TypedObject, TypeNick, TypeNickHandler -from src.utils import logger, utils +from src.typeclasses.typeclass import TypeClass from src.commands.cmdsethandler import CmdSetHandler from src.commands import cmdhandler +from src.utils import logger, utils +from src.utils.utils import inherits_from __all__ = ("PlayerAttribute", "PlayerNick", "PlayerDB") @@ -217,9 +219,11 @@ class PlayerDB(TypedObject): "Getter. Allows for value = self.character" return _get_cache(self, "obj") #@character.setter - def character_set(self, value): + def character_set(self, character): "Setter. Allows for self.character = value" - _set_cache(self, "obj", value) + if inherits_from(character, TypeClass): + character = character.dbobj + _set_cache(self, "obj", character) #@character.deleter def character_del(self): "Deleter. Allows for del self.character" @@ -382,25 +386,17 @@ class PlayerDB(TypedObject): break return cmdhandler.cmdhandler(self.typeclass, raw_string) - def search(self, ostring, global_search=False, attribute_name=None, use_nicks=False, - location=None, ignore_errors=False, player=False): - """ - A shell method mimicking the ObjectDB equivalent, for easy inclusion from - commands regardless of if the command is run by a Player or an Object. + def search(self, ostring, return_character=False): """ + This is similar to the ObjectDB search method but will search for Players only. Errors + will be echoed, and None returned if no Player is found. - if self.character: - # run the normal search - return self.character.search(ostring, global_search=global_search, attribute_name=attribute_name, - use_nicks=use_nicks, location=location, - ignore_errors=ignore_errors, player=player) - if player: - # seach for players - matches = self.__class__.objects.player_search(ostring) - else: - # more limited player-only search. Still returns an Object. - ObjectDB = ContentType.objects.get(app_label="objects", model="objectdb").model_class() - matches = ObjectDB.objects.object_search(ostring, caller=self, global_search=global_search) - # deal with results - matches = _AT_SEARCH_RESULT(self, ostring, matches, global_search=global_search) + return_character - will try to return the character the player controls instead of + the Player object itself. If no Character exists (since Player is + OOC), None will be returned. + """ + matches = self.__class__.objects.player_search(ostring) + matches = _AT_SEARCH_RESULT(self, ostring, matches, global_search=True) + if matches and return_character and hasattr(matches, "character"): + return matches.character return matches diff --git a/src/players/player.py b/src/players/player.py index 0a6cc72644..9599d4d16e 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -129,16 +129,14 @@ class Player(TypeClass): This return is not used at all by Evennia by default, but might be useful for coders intending to implement some sort of nested command structure. """ - self.dbobj.execute_cmd(raw_string) + return self.dbobj.execute_cmd(raw_string) - def search(self, ostring, global_search=False, attribute_name=None, use_nicks=False, - location=None, ignore_errors=False, player=False): + def search(self, ostring, return_character=False): """ This method mimicks object.search if self.character is set. Otherwise only other Players can be searched with this method. """ - self.dbobj.search(ostring, global_search=global_search, attribute_name=attribute_name, use_nicks=use_nicks, - location=location, ignore_errors=ignore_errors, player=player) + return self.dbobj.search(ostring, return_character=return_character) def is_typeclass(self, typeclass, exact=False): """ diff --git a/src/server/migrations/0001_rename_config_table_to_server_table.py b/src/server/migrations/0001_rename_config_table_to_server_table.py index d4077e7d09..afa461a04a 100644 --- a/src/server/migrations/0001_rename_config_table_to_server_table.py +++ b/src/server/migrations/0001_rename_config_table_to_server_table.py @@ -6,11 +6,11 @@ from django.db import models, utils import pickle class Migration(SchemaMigration): - - no_dry_run = True + + no_dry_run = True def forwards(self, orm): try: - db.rename_table("config_configvalue", "server_serverconfig") + db.rename_table("config_configvalue", "server_serverconfig") for conf in orm.ServerConfig.objects.all(): conf.db_value = pickle.dumps(conf.db_value) conf.save() @@ -18,14 +18,14 @@ class Migration(SchemaMigration): # this will happen if we start db from scratch (the config # app will then already be gone and no data is to be transferred) # So instead of renaming the old we instead have to manually create the new model. - # Adding model 'ServerConfig' + # Adding model 'ServerConfig' db.create_table('server_serverconfig', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('db_key', self.gf('django.db.models.fields.CharField')(unique=True, max_length=64)), ('db_value', self.gf('django.db.models.fields.TextField')(blank=True)), )) db.send_create_signal('server', ['ServerConfig']) - + def backwards(self, orm): raise RuntimeError("This migration cannot be reversed.") diff --git a/src/server/msdp.py b/src/server/msdp.py index a185e25898..6d9f24d938 100644 --- a/src/server/msdp.py +++ b/src/server/msdp.py @@ -15,7 +15,12 @@ from django.conf import settings from src.utils.utils import make_iter, mod_import from src.utils import logger +# custom functions OOC_MODULE = mod_import(settings.OOB_FUNC_MODULE) +OOC_FUNCS = dict((key.upper(), var) for key, var in OOC_MODULE if not key.startswith('__') and callable(var)) + +# MSDP commands supported by Evennia +MSDP_COMMANDS = ("LIST", "REPORT", "RESET", "SEND", "UNREPORT") # MSDP-relevant telnet cmd/opt-codes MSDP = chr(69) @@ -99,46 +104,75 @@ class Msdp(object): msdp_string = MSDP_VAR + cmdname + MSDP_VAL + data return msdp_string - # MSDP commands supported by Evennia - MSDP_COMMANDS = {"LIST": "msdp_list", - "REPORT":"mspd_report", - "RESET":"mspd_reset", - "SEND":"mspd_send", - "UNREPORT":"mspd_unreport"} def msdp_to_func(self, data): """ Handle a client's requested negotiation, converting - it into a function mapping. + it into a function mapping - either one of the MSDP + default functions (LIST, SEND etc) or a custom one + in OOC_FUNCS dictionary. command names are case-insensitive. + + varname, var --> mapped to function varname(var) + arrayname, array --> mapped to function arrayname(*array) + tablename, table --> mapped to function tablename(**table) + + Note: Combinations of args/kwargs to one function is not supported + in this implementation (it complicates the code for limited + gain - arrayname(*array) is usually as complex as anyone should + ever need to go anyway (I hope!). - This does not support receiving nested tables from the client - (and there is no real reason why it should). """ tables = {} arrays = {} variables = {} for table in regex_table.findall(data): - tables[table[0]] = dict(regex_varval(table[1])) + tables[table[0].upper()] = dict(regex_varval(table[1])) for array in regex_array.findall(data): - arrays[array[0]] = dict(regex_varval(array[1])) - variables = dict(regex_varval(regex_array.sub("", regex_table.sub("", data)))) - ret = "" - if "LIST" in variables: - ret = self.msdp_cmd_list(variables["LIST"]) - if "REPORT" in variables: - ret = self.msdp_cmd_report(*(variables["REPORT"],)) - if "REPORT" in arrays: - ret = self.msdp_cmd_report(*arrays["REPORT"]) - if "RESET" in variables: - ret = self.msdp_cmd_reset(*(variables["RESET"],)) - if "RESET" in arrays: - ret = self.msdp_cmd_reset(*arrays["RESET"]) - if "SEND" in variables: - ret = self.msdp_cmd_send((*variables["SEND"],)) - if "SEND" in arrays: - ret = self.msdp_cmd_send(*arrays["SEND"]) + arrays[array[0].upper()] = dict(regex_varval(array[1])) + variables = dict((key.upper(), val) for key, val in regex_varval(regex_array.sub("", regex_table.sub("", data)))) + ret = "" + # default MSDP functions + if "LIST" in variables: + ret += self.func_to_msdp("LIST", self.msdp_cmd_list(variables["LIST"])) + del variables["LIST"] + if "REPORT" in variables: + ret += self.func_to_msdp("REPORT", self.msdp_cmd_report(*(variables["REPORT"],))) + del variables["REPORT"] + if "REPORT" in arrays: + ret += self.func_to_msdp("REPORT", self.msdp_cmd_report(*arrays["REPORT"])) + del arrays["REPORT"] + if "RESET" in variables: + ret += self.func_to_msdp("RESET", self.msdp_cmd_reset(*(variables["RESET"],))) + del variables["RESET"] + if "RESET" in arrays: + ret += self.func_to_msdp("RESET", self.msdp_cmd_reset(*arrays["RESET"])) + del arrays["RESET"] + if "SEND" in variables: + ret += self.func_to_msdp("SEND", self.msdp_cmd_send((*variables["SEND"],))) + del variables["SEND"] + if "SEND" in arrays: + ret += self.func_to_msdp("SEND",self.msdp_cmd_send(*arrays["SEND"])) + del arrays["SEND"] + + # if there are anything left we look for a custom function + for varname, var in variables.items(): + # a simple function + argument + ooc_func = OOC_FUNCS.get(varname.upper()) + if ooc_func: + ret += self.func_to_msdp(varname, ooc_func(var)) + for arrayname, array in arrays.items(): + # we assume the array are multiple arguments to the function + ooc_func = OOC_FUNCS.get(arrayname.upper()) + if ooc_func: + ret += self.func_to_msdp(arrayname, ooc_func(*array)) + for tablename, table in tables.items(): + # we assume tables are keyword arguments to the function + ooc_func = OOC_FUNCS.get(arrayname.upper()) + if ooc_func: + ret += self.func_to_msdp(tablename, ooc_func(**table)) + return ret # MSDP Commands # Some given MSDP (varname, value) pairs can also be treated as command + argument. @@ -150,10 +184,9 @@ class Msdp(object): The List command allows for retrieving various info about the server/client """ if arg == 'COMMANDS': - return self.func_to_msdp(arg, self.MSDP_COMMANDS.keys()) + return self.func_to_msdp(arg, MSDP_COMMANDS)) elif arg == 'LISTS': - return self.func_to_msdp(arg, ("COMMANDS", "LISTS", - "CONFIGURABLE_VARIABLES", + return self.func_to_msdp(arg, ("COMMANDS", "LISTS", "CONFIGURABLE_VARIABLES", "REPORTED_VARIABLES", "SENDABLE_VARIABLES")) elif arg == 'CONFIGURABLE_VARIABLES': return self.func_to_msdp(arg, ("CLIENT_NAME", "CLIENT_VERSION", "PLUGIN_ID")) @@ -210,7 +243,6 @@ class Msdp(object): return ret - # MSDP_MAP is a standard suggestions for making it easy to create generic guis. # this maps MSDP command names to Evennia commands found in OOB_FUNC_MODULE. It # is up to these commands to return data on proper form. diff --git a/src/settings_default.py b/src/settings_default.py index 77a1e912f3..3c23f6ceda 100644 --- a/src/settings_default.py +++ b/src/settings_default.py @@ -28,6 +28,13 @@ TELNET_ENABLED = True TELNET_PORTS = [4000] # Interface addresses to listen to. If 0.0.0.0, listen to all. TELNET_INTERFACES = ['0.0.0.0'] +# OOB (out-of-band) telnet communication allows Evennia to communicate +# special commands and data with enabled Telnet clients. This is used +# to create custom client interfaces over a telnet connection. To make +# full use of OOB, you need to prepare functions to handle the data +# server-side (see OOB_FUNC_MODULE). TELNET_ENABLED is required for this +# to work. +TELNET_OOB_ENABLED = False # OBS - currently not fully implemented - do not use! # Start the evennia django+twisted webserver so you can # browse the evennia website and the admin interface # (Obs - further web configuration can be found below @@ -108,6 +115,7 @@ AMP_PORT = 5000 # memory. So every now and then Evennia checks the size of this cache and resets # it if it's too big. This variable sets the maximum size (in MB). ATTRIBUTE_CACHE_MAXSIZE = 100 +# OOB (Out-of-band ###################################################################### # Evennia Database config @@ -146,7 +154,8 @@ DATABASE_PORT = '' # Evennia pluggable modules ###################################################################### -# An alternate command parser module to use +# The command parser module to use. See the default module for which +# functions it must implement. COMMAND_PARSER = "src.commands.cmdparser.cmdparser" # The handler that outputs errors when searching # objects using object.search(). @@ -159,29 +168,34 @@ SEARCH_AT_MULTIMATCH_INPUT = "src.commands.cmdparser.at_multimatch_input" # This module should contain one or more variables # with strings defining the look of the screen. CONNECTION_SCREEN_MODULE = "src.commands.connection_screen" -# An option al module that, if existing, must hold a function +# An optional module that, if existing, must hold a function # named at_initial_setup(). This hook method can be used to customize # the server's initial setup sequence (the very first startup of the system). # The check will fail quietly if module doesn't exist or fails to load. AT_INITIAL_SETUP_HOOK_MODULE = "" -# Module holding at_server_start(), at_server_reload() and +# Module containing your custom at_server_start(), at_server_reload() and # at_server_stop() methods. These methods will be called every time -# the server starts, reloads and resets/stops. +# the server starts, reloads and resets/stops respectively. AT_SERVER_STARTSTOP_MODULE = "" -# Module holding server-side functions for out-of-band protocols to call. -OOB_FUNC_MODULE = "" -# Module holding MSSP meta data +# Module holding MSSP meta data. This is used by MUD-crawlers to determine +# what type of game you are running, how many players you have etc. MSSP_META_MODULE = "" +# Module holding server-side custom functions for out-of-band protocols to call. +# Note that OOB_ENABLED must be True for this to be used. +OOB_FUNC_MODULE = "" # Not yet available in Evennia - do not use! ###################################################################### # Default command sets ###################################################################### # Note that with the exception of the unloggedin set (which is not -# stored anywhere), changing these paths will only affect NEW created -# characters, not those already in play. So if you plan to change -# this, it's recommended you do it on a pristine setup only. To -# dynamically add new commands to a running server, extend/overload -# these existing sets instead. +# stored anywhere in the databse), changing these paths will only affect NEW created +# characters/objects, not those already in play. So if you plan to change +# this, it's recommended you do it before having created a lot of objects +# (or simply reset the database after the change for simplicity). Remember +# that you should never edit things in src/. Instead copy out the examples +# in game/gamesrc/commands/examples up one level and re-point these settings +# to point to these copies instead - these you can then change as you please +# (or copy/paste from the default modules in src/ if you prefer). # Command set used before player has logged in CMDSET_UNLOGGEDIN = "src.commands.default.cmdset_unloggedin.UnloggedinCmdSet"