From 596efe4c725eb00ff53c42df4a33c72165518768 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 20 Dec 2016 22:11:58 +0100 Subject: [PATCH 01/63] Add prefix-ignorer. This will identify allow users to target a command using both cmd, @cmd, +cmd etc. If there are two different commands @cmd and cmd, entering @cmd will still explicitly target the former. Single-character commands consisting of only an ignore-character will not be ignored. Configure and turn off using settings.CMD_PREFIX_IGNORE. --- evennia/commands/cmdparser.py | 75 ++++++++++++++++++++++++----------- evennia/settings_default.py | 7 ++++ 2 files changed, 58 insertions(+), 24 deletions(-) diff --git a/evennia/commands/cmdparser.py b/evennia/commands/cmdparser.py index db210b6013..0a2a27164d 100644 --- a/evennia/commands/cmdparser.py +++ b/evennia/commands/cmdparser.py @@ -12,6 +12,7 @@ from django.conf import settings from evennia.utils.logger import log_trace _MULTIMATCH_REGEX = re.compile(settings.SEARCH_MULTIMATCH_REGEX, re.I + re.U) +_CMD_IGNORE_PREFIXES = settings.CMD_IGNORE_PREFIXES def cmdparser(raw_string, cmdset, caller, match_index=None): """ @@ -69,37 +70,64 @@ def cmdparser(raw_string, cmdset, caller, match_index=None): args = string[cmdlen:] return (cmdname, args, cmdobj, cmdlen, mratio) + def build_matches(raw_string, include_prefixes=False): + l_raw_string = raw_string.lower() + matches = [] + try: + if include_prefixes: + for cmd in cmdset: + matches.extend([create_match(cmdname, raw_string, cmd) + for cmdname in [cmd.key] + cmd.aliases + if cmdname and l_raw_string.startswith(cmdname.lower()) + and (not cmd.arg_regex or + cmd.arg_regex.match(l_raw_string[len(cmdname):]))]) + else: + for cmd in cmdset: + for cmdname in [cmd.key] + cmd.aliases: + cmdname = cmdname.lstrip(_CMD_IGNORE_PREFIXES) if len(cmdname) > 1 else cmdname + if cmdname and l_raw_string.startswith(cmdname.lower()) and \ + (not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname):])): + matches.append(create_match(cmdname, raw_string, cmd)) + except Exception: + log_trace("cmdhandler error. raw_input:%s" % raw_string) + return matches + + def try_num_prefixes(raw_string): + if not matches: + # no matches found + num_ref_match = _MULTIMATCH_REGEX.match(raw_string) + if num_ref_match: + # the user might be trying to identify the command + # with a #num-command style syntax. We expect the regex to + # contain the groups "number" and "name". + mindex, new_raw_string = num_ref_match.group("number"), num_ref_match.group("name") + return mindex, new_raw_string + return None, None + if not raw_string: return [] - matches = [] - - # match everything that begins with a matching cmdname. - l_raw_string = raw_string.lower() - for cmd in cmdset: - try: - matches.extend([create_match(cmdname, raw_string, cmd) - for cmdname in [cmd.key] + cmd.aliases - if cmdname and l_raw_string.startswith(cmdname.lower()) - and (not cmd.arg_regex or - cmd.arg_regex.match(l_raw_string[len(cmdname):]))]) - except Exception: - log_trace("cmdhandler error. raw_input:%s" % raw_string) - + # find mathces + matches = build_matches(raw_string, include_prefixes=True) if not matches: - # no matches found - num_ref_match = _MULTIMATCH_REGEX.match(raw_string) - if num_ref_match: - # the user might be trying to identify the command - # with a #num-command style syntax. We expect the regex to - # contain the groups "number" and "name". - mindex, new_raw_string = num_ref_match.group("number"), num_ref_match.group("name") - return cmdparser(new_raw_string, cmdset, - caller, match_index=int(mindex)) + # try to match a number 1-cmdname, 2-cmdname etc + mindex, new_raw_string = try_num_prefixes(raw_string) + if mindex is not None: + return cmdparser(new_raw_string, cmdset, caller, match_index=int(mindex)) + elif _CMD_IGNORE_PREFIXES: + # still no match. Try to strip prefixes + raw_string = raw_string.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_string) > 1 else raw_string + matches = build_matches(raw_string, include_prefixes=False) + if not matches: + # try to match a number 1-cmdname, 2-cmdname etc + mindex, new_raw_string = try_num_prefixes(raw_string) + if mindex is not None: + return cmdparser(new_raw_string, cmdset, caller, match_index=int(mindex)) # only select command matches we are actually allowed to call. matches = [match for match in matches if match[2].access(caller, 'cmd')] + # try to bring the number of matches down to 1 if len(matches) > 1: # See if it helps to analyze the match with preserved case but only if # it leaves at least one match. @@ -129,4 +157,3 @@ def cmdparser(raw_string, cmdset, caller, match_index=None): # no matter what we have at this point, we have to return it. return matches - diff --git a/evennia/settings_default.py b/evennia/settings_default.py index 233e8e42a7..743e345670 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -279,6 +279,13 @@ SEARCH_MULTIMATCH_TEMPLATE = " {number}-{name}{aliases}{info}\n" # both for command- and object-searches. This allows full control # over the error output (it uses SEARCH_MULTIMATCH_TEMPLATE by default). SEARCH_AT_RESULT = "evennia.utils.utils.at_search_result" +# Single characters to ignore at the beginning of a command. When set, e.g. +# cmd, @cmd and +cmd will all find a command "cmd" or one named "@cmd". If +# you have defined two different commands cmd and @cmd you can still enter +# @cmd to exactly target the second one. Single-character commands consisting +# of only a prefix character will not be stripped. Set to the empty +# string ("") to turn off prefix ignore. +CMD_IGNORE_PREFIXES = "@&/+" # The module holding text strings for the connection screen. # This module should contain one or more variables # with strings defining the look of the screen. From fd3d6aee9a12d37681cd5ec4e8f730149b632962 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 20 Dec 2016 22:58:13 +0100 Subject: [PATCH 02/63] Clean up default command names and aliases to avoid multimatches with new prefix-ignorer. Chane desc->setdesc, alias->nick, @home->@sethome. --- evennia/commands/default/building.py | 10 ++++------ evennia/commands/default/comms.py | 2 +- evennia/commands/default/general.py | 6 +++--- evennia/commands/default/help.py | 3 +-- evennia/commands/default/player.py | 2 -- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 9702ee8560..6ea81ead03 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -1033,8 +1033,7 @@ class CmdSetHome(CmdLink): If no location is given, just view the object's home location. """ - key = "@home" - aliases = "@sethome" + key = "@sethome" locks = "cmd:perm(@home) or perm(Builders)" help_category = "Building" @@ -1751,7 +1750,7 @@ class CmdLock(ObjManipCommand): 'get:id(25);delete:perm(Builders)' """ key = "@lock" - aliases = ["@locks", "lock", "locks"] + aliases = ["@locks"] locks = "cmd: perm(locks) or perm(Builders)" help_category = "Building" @@ -1844,7 +1843,7 @@ class CmdExamine(ObjManipCommand): """ key = "@examine" - aliases = ["@ex","ex", "exam", "examine"] + aliases = ["@ex","exam"] locks = "cmd:perm(examine) or perm(Builders)" help_category = "Building" arg_regex = r"(/\w+?(\s|$))|\s|$" @@ -2113,7 +2112,7 @@ class CmdFind(COMMAND_DEFAULT_CLASS): """ key = "@find" - aliases = "find, @search, search, @locate, locate" + aliases = "@search, @locate" locks = "cmd:perm(find) or perm(Builders)" help_category = "Building" @@ -2585,7 +2584,6 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS): """ key = "@spawn" - aliases = ["spawn"] locks = "cmd:perm(spawn) or perm(Builders)" help_category = "Building" diff --git a/evennia/commands/default/comms.py b/evennia/commands/default/comms.py index e2945c700f..ed69475635 100644 --- a/evennia/commands/default/comms.py +++ b/evennia/commands/default/comms.py @@ -268,7 +268,7 @@ class CmdChannels(COMMAND_DEFAULT_CLASS): Use addcom/delcom to join and leave channels """ key = "@channels" - aliases = ["@clist", "channels", "comlist", "chanlist", "channellist", "all channels"] + aliases = ["@clist", "comlist", "chanlist", "channellist", "all channels"] help_category = "Comms" locks = "cmd: not pperm(channel_banned)" diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index e386fbe927..3bc4337ecf 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -112,7 +112,7 @@ class CmdNick(COMMAND_DEFAULT_CLASS): """ key = "nick" - aliases = ["nickname", "nicks", "@nick", "@nicks", "alias"] + aliases = ["nickname", "nicks", "alias"] locks = "cmd:all()" def func(self): @@ -361,13 +361,13 @@ class CmdDesc(COMMAND_DEFAULT_CLASS): describe yourself Usage: - desc + setdesc Add a description to yourself. This will be visible to people when they look at you. """ - key = "desc" + key = "setdesc" locks = "cmd:all()" arg_regex = r"\s|$" diff --git a/evennia/commands/default/help.py b/evennia/commands/default/help.py index e2113be688..d15228992c 100644 --- a/evennia/commands/default/help.py +++ b/evennia/commands/default/help.py @@ -292,8 +292,7 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS): is to let everyone read the help file. """ - key = "@help" - aliases = "@sethelp" + key = "@sethelp" locks = "cmd:perm(PlayerHelpers)" help_category = "Building" diff --git a/evennia/commands/default/player.py b/evennia/commands/default/player.py index 03680efc04..ae95388a8e 100644 --- a/evennia/commands/default/player.py +++ b/evennia/commands/default/player.py @@ -648,7 +648,6 @@ class CmdQuit(COMMAND_DEFAULT_CLASS): game. Use the /all switch to disconnect from all sessions. """ key = "@quit" - aliases = "quit" locks = "cmd:all()" # this is used by the parent @@ -689,7 +688,6 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS): color - if not you will see rubbish appear. """ key = "@color" - aliases = "color" locks = "cmd:all()" help_category = "General" From dffbf9f8d6b0f9a7063742516475e8847e66c52a Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 17 Feb 2017 00:46:00 +0100 Subject: [PATCH 03/63] Rework prefix-ignorer to retain a reference to the unstripped cmdname. Currently unworking. --- evennia/commands/cmdhandler.py | 18 +++++++++------ evennia/commands/cmdparser.py | 40 ++++++++++++++++++++-------------- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index 2719e3602b..85f52c13dd 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -428,7 +428,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess """ @inlineCallbacks - def _run_command(cmd, cmdname, args, raw_string): + def _run_command(cmd, cmdname, raw_cmdname, args): """ Helper function: This initializes and runs the Command instance once the parser has identified it as either a normal @@ -437,8 +437,10 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess Args: cmd (Command): Command object cmdname (str): Name of command - args (str): Extra text entered after the identified command - raw_string (str): Full input string, only used for debugging. + raw_cmdname (str): Name of Command, unaffected by eventual + prefix-stripping (if no prefix-stripping, this is the same + as cmdname). + args (str): extra text entered after the identified command Returns: deferred (Deferred): this will fire with the return of the @@ -452,7 +454,9 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess try: # Assign useful variables to the instance cmd.caller = caller - cmd.cmdstring = cmdname + cmd.cmdname = cmdname + cmd.raw_cmdname = raw_cmdname + cmd.cmdstring = cmdname # deprecated cmd.args = args cmd.cmdset = cmdset cmd.session = session @@ -575,7 +579,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess if len(matches) == 1: # We have a unique command match. But it may still be invalid. match = matches[0] - cmdname, args, cmd = match[0], match[1], match[2] + cmdname, args, cmd, raw_cmdname = match[0], match[1], match[2], match[5] if not matches: # No commands match our entered command @@ -608,7 +612,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess raise ExecSystemCommand(cmd, sysarg) # A normal command. - ret = yield _run_command(cmd, cmdname, args, raw_string) + ret = yield _run_command(cmd, cmdname, raw_cmdname, args) returnValue(ret) except ErrorReported as exc: @@ -623,7 +627,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess sysarg = exc.sysarg if syscmd: - ret = yield _run_command(syscmd, syscmd.key, sysarg, raw_string) + ret = yield _run_command(syscmd, syscmd.key, syscmd, sysarg) returnValue(ret) elif sysarg: # return system arg diff --git a/evennia/commands/cmdparser.py b/evennia/commands/cmdparser.py index 0a2a27164d..22de701576 100644 --- a/evennia/commands/cmdparser.py +++ b/evennia/commands/cmdparser.py @@ -47,7 +47,7 @@ def cmdparser(raw_string, cmdset, caller, match_index=None): """ - def create_match(cmdname, string, cmdobj): + def create_match(cmdname, string, cmdobj, raw_cmdname): """ Builds a command match by splitting the incoming string and evaluating the quality of the match. @@ -56,38 +56,44 @@ def cmdparser(raw_string, cmdset, caller, match_index=None): cmdname (str): Name of command to check for. string (str): The string to match against. cmdobj (str): The full Command instance. + raw_cmdname (str, optional): If CMD_IGNORE_PREFIX is set and the cmdname starts with + one of the prefixes to ignore, this contains the raw, unstripped cmdname, + otherwise it is None. Returns: - match (tuple): This is on the form (cmdname, args, cmdobj, cmdlen, mratio), where + match (tuple): This is on the form (cmdname, args, cmdobj, cmdlen, mratio, raw_cmdname), where `cmdname` is the command's name and `args` the rest of the incoming string, without said command name. `cmdobj` is the Command instance, the cmdlen is the same as len(cmdname) and mratio is a measure of how big a part of the - full input string the cmdname takes up - an exact match would be 1.0. + full input string the cmdname takes up - an exact match would be 1.0. Finally, + the `raw_cmdname` is the cmdname unmodified by eventual prefix-stripping. """ cmdlen, strlen = len(unicode(cmdname)), len(unicode(string)) mratio = 1 - (strlen - cmdlen) / (1.0 * strlen) args = string[cmdlen:] - return (cmdname, args, cmdobj, cmdlen, mratio) + return (cmdname, args, cmdobj, cmdlen, mratio, raw_cmdname) def build_matches(raw_string, include_prefixes=False): l_raw_string = raw_string.lower() matches = [] try: if include_prefixes: + # use the cmdname as-is for cmd in cmdset: - matches.extend([create_match(cmdname, raw_string, cmd) + matches.extend([create_match(cmdname, raw_string, cmd, cmdname) for cmdname in [cmd.key] + cmd.aliases if cmdname and l_raw_string.startswith(cmdname.lower()) and (not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname):]))]) else: + # strip prefixes set in settings for cmd in cmdset: - for cmdname in [cmd.key] + cmd.aliases: - cmdname = cmdname.lstrip(_CMD_IGNORE_PREFIXES) if len(cmdname) > 1 else cmdname + for raw_cmdname in [cmd.key] + cmd.aliases: + cmdname = raw_cmdname.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_cmdname) > 1 else raw_cmdname if cmdname and l_raw_string.startswith(cmdname.lower()) and \ (not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname):])): - matches.append(create_match(cmdname, raw_string, cmd)) + matches.append(create_match(cmdname, raw_string, cmd, raw_cmdname)) except Exception: log_trace("cmdhandler error. raw_input:%s" % raw_string) return matches @@ -107,7 +113,7 @@ def cmdparser(raw_string, cmdset, caller, match_index=None): if not raw_string: return [] - # find mathces + # find mathces, first using the full name matches = build_matches(raw_string, include_prefixes=True) if not matches: # try to match a number 1-cmdname, 2-cmdname etc @@ -116,13 +122,15 @@ def cmdparser(raw_string, cmdset, caller, match_index=None): return cmdparser(new_raw_string, cmdset, caller, match_index=int(mindex)) elif _CMD_IGNORE_PREFIXES: # still no match. Try to strip prefixes - raw_string = raw_string.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_string) > 1 else raw_string - matches = build_matches(raw_string, include_prefixes=False) - if not matches: - # try to match a number 1-cmdname, 2-cmdname etc - mindex, new_raw_string = try_num_prefixes(raw_string) - if mindex is not None: - return cmdparser(new_raw_string, cmdset, caller, match_index=int(mindex)) + new_raw_string = raw_string.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_string) > 1 else raw_string + if len(new_raw_string) < len(raw_string): + raw_string = new_raw_string + matches = build_matches(raw_string, include_prefixes=False) + if not matches: + # try to match a number 1-cmdname, 2-cmdname etc + mindex, new_raw_string = try_num_prefixes(raw_string) + if mindex is not None: + return cmdparser(new_raw_string, cmdset, caller, match_index=int(mindex)) # only select command matches we are actually allowed to call. matches = [match for match in matches if match[2].access(caller, 'cmd')] From d4df0948e3230b356874e427b764b074d6d86be0 Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 17 Feb 2017 08:39:14 +0100 Subject: [PATCH 04/63] Change @ desc to @ setdesc to avoid confusion with desc command. --- evennia/commands/default/building.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 6ea81ead03..b098aebd5f 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -20,7 +20,7 @@ COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) # limit symbol import for API __all__ = ("ObjManipCommand", "CmdSetObjAlias", "CmdCopy", "CmdCpAttr", "CmdMvAttr", "CmdCreate", - "CmdDesc", "CmdDestroy", "CmdDig", "CmdTunnel", "CmdLink", + "CmdSetDesc", "CmdDestroy", "CmdDig", "CmdTunnel", "CmdLink", "CmdUnLink", "CmdSetHome", "CmdListCmdSets", "CmdName", "CmdOpen", "CmdSetAttribute", "CmdTypeclass", "CmdWipe", "CmdLock", "CmdExamine", "CmdFind", "CmdTeleport", @@ -533,12 +533,12 @@ def _desc_quit(caller): caller.attributes.remove("evmenu_target") caller.msg("Exited editor.") -class CmdDesc(COMMAND_DEFAULT_CLASS): +class CmdSetDesc(COMMAND_DEFAULT_CLASS): """ - describe an object + describe an object or the current room. Usage: - @desc [ =] + @setdesc [ =] Switches: edit - Open up a line editor for more advanced editing. @@ -546,7 +546,7 @@ class CmdDesc(COMMAND_DEFAULT_CLASS): Sets the "desc" attribute on an object. If an object is not given, describe the current room. """ - key = "@desc" + key = "@setdesc" aliases = "@describe" locks = "cmd:perm(desc) or perm(Builders)" help_category = "Building" From 1fbb4c4358adc06e27230732472dffb6a62b3994 Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 17 Feb 2017 08:40:32 +0100 Subject: [PATCH 05/63] Add functioning prefix-ignorer and cleanup of code. --- evennia/commands/cmdhandler.py | 2 ++ evennia/commands/cmdparser.py | 13 +++---------- evennia/commands/default/cmdset_character.py | 2 +- evennia/commands/default/general.py | 2 +- evennia/commands/default/tests.py | 2 +- evennia/contrib/extended_room.py | 2 +- evennia/settings_default.py | 2 +- 7 files changed, 10 insertions(+), 15 deletions(-) diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index 85f52c13dd..36a73078bd 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -549,6 +549,8 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess if not cmdset: # this is bad and shouldn't happen. raise NoCmdSets + # store the completely unmodified raw string - including + # whitespace and eventual prefixes-to-be-stripped. unformatted_raw_string = raw_string raw_string = raw_string.strip() if not raw_string: diff --git a/evennia/commands/cmdparser.py b/evennia/commands/cmdparser.py index 22de701576..6cb9ee0143 100644 --- a/evennia/commands/cmdparser.py +++ b/evennia/commands/cmdparser.py @@ -120,17 +120,10 @@ def cmdparser(raw_string, cmdset, caller, match_index=None): mindex, new_raw_string = try_num_prefixes(raw_string) if mindex is not None: return cmdparser(new_raw_string, cmdset, caller, match_index=int(mindex)) - elif _CMD_IGNORE_PREFIXES: + if _CMD_IGNORE_PREFIXES: # still no match. Try to strip prefixes - new_raw_string = raw_string.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_string) > 1 else raw_string - if len(new_raw_string) < len(raw_string): - raw_string = new_raw_string - matches = build_matches(raw_string, include_prefixes=False) - if not matches: - # try to match a number 1-cmdname, 2-cmdname etc - mindex, new_raw_string = try_num_prefixes(raw_string) - if mindex is not None: - return cmdparser(new_raw_string, cmdset, caller, match_index=int(mindex)) + raw_string = raw_string.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_string) > 1 else raw_string + matches = build_matches(raw_string, include_prefixes=False) # only select command matches we are actually allowed to call. matches = [match for match in matches if match[2].access(caller, 'cmd')] diff --git a/evennia/commands/default/cmdset_character.py b/evennia/commands/default/cmdset_character.py index a8216db73b..f695cc6762 100644 --- a/evennia/commands/default/cmdset_character.py +++ b/evennia/commands/default/cmdset_character.py @@ -63,7 +63,7 @@ class CharacterCmdSet(CmdSet): self.add(building.CmdWipe()) self.add(building.CmdSetAttribute()) self.add(building.CmdName()) - self.add(building.CmdDesc()) + self.add(building.CmdSetDesc()) self.add(building.CmdCpAttr()) self.add(building.CmdMvAttr()) self.add(building.CmdCopy()) diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index 3bc4337ecf..a4afbf961e 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -367,7 +367,7 @@ class CmdDesc(COMMAND_DEFAULT_CLASS): will be visible to people when they look at you. """ - key = "setdesc" + key = "desc" locks = "cmd:all()" arg_regex = r"\s|$" diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index c61304710a..ee0409e06d 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -229,7 +229,7 @@ class TestBuilding(CommandTest): self.call(building.CmdName(), "Obj2=Obj3", "Object's name changed to 'Obj3'.") def test_desc(self): - self.call(building.CmdDesc(), "Obj2=TestDesc", "The description was set on Obj2(#5).") + self.call(building.CmdSetDesc(), "Obj2=TestDesc", "The description was set on Obj2(#5).") def test_wipe(self): self.call(building.CmdDestroy(), "Obj", "Obj was destroyed.") diff --git a/evennia/contrib/extended_room.py b/evennia/contrib/extended_room.py index 6bc8ccecbc..791babf73e 100644 --- a/evennia/contrib/extended_room.py +++ b/evennia/contrib/extended_room.py @@ -375,7 +375,7 @@ class CmdExtendedDesc(default_cmds.CmdDesc): "Define extended command" caller = self.caller location = caller.location - if self.cmdstring == '@detail': + if self.cmdname == 'detail': # switch to detailing mode. This operates only on current location if not location: caller.msg("No location to detail!") diff --git a/evennia/settings_default.py b/evennia/settings_default.py index 743e345670..39a6165228 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -280,7 +280,7 @@ SEARCH_MULTIMATCH_TEMPLATE = " {number}-{name}{aliases}{info}\n" # over the error output (it uses SEARCH_MULTIMATCH_TEMPLATE by default). SEARCH_AT_RESULT = "evennia.utils.utils.at_search_result" # Single characters to ignore at the beginning of a command. When set, e.g. -# cmd, @cmd and +cmd will all find a command "cmd" or one named "@cmd". If +# cmd, @cmd and +cmd will all find a command "cmd" or one named "@cmd" etc. If # you have defined two different commands cmd and @cmd you can still enter # @cmd to exactly target the second one. Single-character commands consisting # of only a prefix character will not be stripped. Set to the empty From 22e57db4a7f58f2576a47878bf2853505cd8fc2a Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 17 Feb 2017 09:32:15 +0100 Subject: [PATCH 06/63] Fix errors in testing. --- evennia/commands/default/building.py | 4 ++-- evennia/commands/default/cmdset_character.py | 4 ++-- evennia/commands/default/general.py | 6 +++--- evennia/commands/default/tests.py | 6 ++++-- evennia/contrib/tests.py | 10 +++++----- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index b098aebd5f..28ee1a033a 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -20,7 +20,7 @@ COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) # limit symbol import for API __all__ = ("ObjManipCommand", "CmdSetObjAlias", "CmdCopy", "CmdCpAttr", "CmdMvAttr", "CmdCreate", - "CmdSetDesc", "CmdDestroy", "CmdDig", "CmdTunnel", "CmdLink", + "CmdDesc", "CmdDestroy", "CmdDig", "CmdTunnel", "CmdLink", "CmdUnLink", "CmdSetHome", "CmdListCmdSets", "CmdName", "CmdOpen", "CmdSetAttribute", "CmdTypeclass", "CmdWipe", "CmdLock", "CmdExamine", "CmdFind", "CmdTeleport", @@ -533,7 +533,7 @@ def _desc_quit(caller): caller.attributes.remove("evmenu_target") caller.msg("Exited editor.") -class CmdSetDesc(COMMAND_DEFAULT_CLASS): +class CmdDesc(COMMAND_DEFAULT_CLASS): """ describe an object or the current room. diff --git a/evennia/commands/default/cmdset_character.py b/evennia/commands/default/cmdset_character.py index f695cc6762..85fb163e50 100644 --- a/evennia/commands/default/cmdset_character.py +++ b/evennia/commands/default/cmdset_character.py @@ -25,7 +25,7 @@ class CharacterCmdSet(CmdSet): self.add(general.CmdInventory()) self.add(general.CmdPose()) self.add(general.CmdNick()) - self.add(general.CmdDesc()) + self.add(general.CmdSetDesc()) self.add(general.CmdGet()) self.add(general.CmdDrop()) self.add(general.CmdGive()) @@ -63,7 +63,7 @@ class CharacterCmdSet(CmdSet): self.add(building.CmdWipe()) self.add(building.CmdSetAttribute()) self.add(building.CmdName()) - self.add(building.CmdSetDesc()) + self.add(building.CmdDesc()) self.add(building.CmdCpAttr()) self.add(building.CmdMvAttr()) self.add(building.CmdCopy()) diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index a4afbf961e..8b78e97a0f 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -9,7 +9,7 @@ COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS) # limit symbol import for API __all__ = ("CmdHome", "CmdLook", "CmdNick", - "CmdInventory", "CmdGet", "CmdDrop", "CmdGive", + "CmdInventory", "CmdSetDesc", "CmdGet", "CmdDrop", "CmdGive", "CmdSay", "CmdPose", "CmdAccess") @@ -356,7 +356,7 @@ class CmdGive(COMMAND_DEFAULT_CLASS): target.msg("%s gives you %s." % (caller.key, to_give.key)) -class CmdDesc(COMMAND_DEFAULT_CLASS): +class CmdSetDesc(COMMAND_DEFAULT_CLASS): """ describe yourself @@ -367,7 +367,7 @@ class CmdDesc(COMMAND_DEFAULT_CLASS): will be visible to people when they look at you. """ - key = "desc" + key = "setdesc" locks = "cmd:all()" arg_regex = r"\s|$" diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index ee0409e06d..4ade106ffe 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -52,7 +52,9 @@ class CommandTest(EvenniaTest): caller = caller if caller else self.char1 receiver = receiver if receiver else caller cmdobj.caller = caller - cmdobj.cmdstring = cmdstring if cmdstring else cmdobj.key + cmdobj.cmdname = cmdstring if cmdstring else cmdobj.key + cmdobj.raw_cmdname = cmdobj.cmdname + cmdobj.cmdstring = cmdobj.cmdname # deprecated cmdobj.args = args cmdobj.cmdset = cmdset cmdobj.session = SESSIONS.session_from_sessid(1) @@ -229,7 +231,7 @@ class TestBuilding(CommandTest): self.call(building.CmdName(), "Obj2=Obj3", "Object's name changed to 'Obj3'.") def test_desc(self): - self.call(building.CmdSetDesc(), "Obj2=TestDesc", "The description was set on Obj2(#5).") + self.call(building.CmdDesc(), "Obj2=TestDesc", "The description was set on Obj2(#5).") def test_wipe(self): self.call(building.CmdDestroy(), "Obj", "Obj was destroyed.") diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 1627ad407f..bf5ff1af0d 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -200,12 +200,12 @@ class TestExtendedRoom(CommandTest): self.call(extended_room.CmdExtendedLook(), "nonexistent", "Could not find 'nonexistent'.") def test_cmdextendeddesc(self): - self.call(extended_room.CmdExtendedDesc(), "", "Details on Room", cmdstring="@detail") + self.call(extended_room.CmdExtendedDesc(), "", "Details on Room", cmdstring="detail") self.call(extended_room.CmdExtendedDesc(), "thingie = newdetail with spaces", - "Set Detail thingie to 'newdetail with spaces'.", cmdstring="@detail") - self.call(extended_room.CmdExtendedDesc(), "thingie", "Detail 'thingie' on Room:\n", cmdstring="@detail") - self.call(extended_room.CmdExtendedDesc(), "/del thingie", "Detail thingie deleted, if it existed.", cmdstring="@detail") - self.call(extended_room.CmdExtendedDesc(), "thingie", "Detail 'thingie' not found.", cmdstring="@detail") + "Set Detail thingie to 'newdetail with spaces'.", cmdstring="detail") + self.call(extended_room.CmdExtendedDesc(), "thingie", "Detail 'thingie' on Room:\n", cmdstring="detail") + self.call(extended_room.CmdExtendedDesc(), "/del thingie", "Detail thingie deleted, if it existed.", cmdstring="detail") + self.call(extended_room.CmdExtendedDesc(), "thingie", "Detail 'thingie' not found.", cmdstring="detail") self.call(extended_room.CmdExtendedDesc(), "", "Descriptions on Room:") def test_cmdgametime(self): From ccfcf37e33c69aa3f69475ab1144277d3ead7b25 Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 17 Feb 2017 21:18:15 +0100 Subject: [PATCH 07/63] Convert EvMenu to use overridable methods instead of plugin functions. Implements #1205. --- evennia/utils/evmenu.py | 356 +++++++++++++++++++--------------------- evennia/utils/tests.py | 14 ++ 2 files changed, 179 insertions(+), 191 deletions(-) diff --git a/evennia/utils/evmenu.py b/evennia/utils/evmenu.py index 987a8ba184..b96a6b9e9e 100644 --- a/evennia/utils/evmenu.py +++ b/evennia/utils/evmenu.py @@ -227,16 +227,13 @@ class CmdEvMenuNode(Command): # this will re-start a completely new evmenu call. saved_options = caller.attributes.get("_menutree_saved") if saved_options: - startnode_tuple = caller.attributes.get("_menutree_saved_startnode") - try: - startnode, startnode_input = startnode_tuple - except ValueError: # old form of startnode stor - startnode, startnode_input = startnode_tuple, "" + startnode, startnode_input = caller.attributes.get("_menutree_saved_startnode") if startnode: - saved_options[1]["startnode"] = startnode - saved_options[1]["startnode_input"] = startnode_input + saved_options[2]["startnode"] = startnode + saved_options[2]["startnode_input"] = startnode_input + MenuClass = saved_options[0] # this will create a completely new menu call - EvMenu(caller, *saved_options[0], **saved_options[1]) + MenuClass(caller, *saved_options[1], **saved_options[2]) return True return None @@ -264,7 +261,7 @@ class CmdEvMenuNode(Command): # can be either Player, Object or Session (in the latter case this info will be superfluous). caller.ndb._menutree._session = self.session # we have a menu, use it. - menu._input_parser(menu, self.raw_string, caller) + menu.parse_input(self.raw_string) class EvMenuCmdSet(CmdSet): @@ -286,130 +283,6 @@ class EvMenuCmdSet(CmdSet): self.add(CmdEvMenuNode()) -# These are default node formatters -def dedent_strip_nodetext_formatter(nodetext, has_options, caller=None): - """ - Simple dedent formatter that also strips text - """ - return dedent(nodetext).strip() - - -def dedent_nodetext_formatter(nodetext, has_options, caller=None): - """ - Just dedent text. - """ - return dedent(nodetext) - - -def evtable_options_formatter(optionlist, caller=None): - """ - Formats the option list display. - """ - if not optionlist: - return "" - - # column separation distance - colsep = 4 - - nlist = len(optionlist) - - # get the widest option line in the table. - table_width_max = -1 - table = [] - for key, desc in optionlist: - if not (key or desc): - continue - table_width_max = max(table_width_max, - max(m_len(p) for p in key.split("\n")) + - max(m_len(p) for p in desc.split("\n")) + colsep) - raw_key = strip_ansi(key) - if raw_key != key: - # already decorations in key definition - table.append(" |lc%s|lt%s|le: %s" % (raw_key, key, desc)) - else: - # add a default white color to key - table.append(" |lc%s|lt|w%s|n|le: %s" % (raw_key, raw_key, desc)) - - ncols = (_MAX_TEXT_WIDTH // table_width_max) + 1 # number of ncols - - # get the amount of rows needed (start with 4 rows) - nrows = 4 - while nrows * ncols < nlist: - nrows += 1 - ncols = nlist // nrows # number of full columns - nlastcol = nlist % nrows # number of elements in last column - - # get the final column count - ncols = ncols + 1 if nlastcol > 0 else ncols - if ncols > 1: - # only extend if longer than one column - table.extend([" " for i in range(nrows - nlastcol)]) - - # build the actual table grid - table = [table[icol * nrows : (icol * nrows) + nrows] for icol in range(0, ncols)] - - # adjust the width of each column - for icol in range(len(table)): - col_width = max(max(m_len(p) for p in part.split("\n")) for part in table[icol]) + colsep - table[icol] = [pad(part, width=col_width + colsep, align="l") for part in table[icol]] - - # format the table into columns - return unicode(EvTable(table=table, border="none")) - - -def underline_node_formatter(nodetext, optionstext, caller=None): - """ - Draws a node with underlines '_____' around it. - """ - nodetext_width_max = max(m_len(line) for line in nodetext.split("\n")) - options_width_max = max(m_len(line) for line in optionstext.split("\n")) - total_width = max(options_width_max, nodetext_width_max) - separator1 = "_" * total_width + "\n\n" if nodetext_width_max else "" - separator2 = "\n" + "_" * total_width + "\n\n" if total_width else "" - return separator1 + "|n" + nodetext + "|n" + separator2 + "|n" + optionstext - - -def null_node_formatter(nodetext, optionstext, caller=None): - """ - A minimalistic node formatter, no lines or frames. - """ - return nodetext + "\n\n" + optionstext - - -def evtable_parse_input(menuobject, raw_string, caller): - """ - Processes the user's node inputs. - - Args: - menuobject (EvMenu): The EvMenu instance - raw_string (str): The incoming raw_string from the menu - command. - caller (Object, Player or Session): The entity using - the menu. - """ - cmd = raw_string.strip().lower() - - if cmd in menuobject.options: - # this will take precedence over the default commands - # below - goto, callback = menuobject.options[cmd] - menuobject.callback_goto(callback, goto, raw_string) - elif menuobject.auto_look and cmd in ("look", "l"): - menuobject.display_nodetext() - elif menuobject.auto_help and cmd in ("help", "h"): - menuobject.display_helptext() - elif menuobject.auto_quit and cmd in ("quit", "q", "exit"): - menuobject.close_menu() - elif menuobject.default: - goto, callback = menuobject.default - menuobject.callback_goto(callback, goto, raw_string) - else: - caller.msg(_HELP_NO_OPTION_MATCH, session=menuobject._session) - - if not (menuobject.options or menuobject.default): - # no options - we are at the end of the menu. - menuobject.close_menu() - #------------------------------------------------------------ # # Menu main class @@ -426,10 +299,6 @@ class EvMenu(object): cmdset_mergetype="Replace", cmdset_priority=1, auto_quit=True, auto_look=True, auto_help=True, cmd_on_exit="look", - nodetext_formatter=dedent_strip_nodetext_formatter, - options_formatter=evtable_options_formatter, - node_formatter=underline_node_formatter, - input_parser=evtable_parse_input, persistent=False, startnode_input="", session=None, **kwargs): """ @@ -476,38 +345,6 @@ class EvMenu(object): The callback function takes two parameters, the caller then the EvMenu object. This is called after cleanup is complete. Set to None to not call any command. - nodetext_formatter (callable, optional): This callable should be on - the form `function(nodetext, has_options, caller=None)`, where `nodetext` is the - node text string and `has_options` a boolean specifying if there - are options associated with this node. It must return a formatted - string. `caller` is optionally a reference to the user of the menu. - `caller` is optionally a reference to the user of the menu. - options_formatter (callable, optional): This callable should be on - the form `function(optionlist, caller=None)`, where ` optionlist is a list - of option dictionaries, like - [{"key":..., "desc",..., "goto": ..., "exec",...}, ...] - Each dictionary describes each possible option. Note that this - will also be called if there are no options, and so should be - able to handle an empty list. This should - be formatted into an options list and returned as a string, - including the required separator to use between the node text - and the options. If not given the default EvMenu style will be used. - `caller` is optionally a reference to the user of the menu. - node_formatter (callable, optional): This callable should be on the - form `func(nodetext, optionstext, caller=None)` where the arguments are strings - representing the node text and options respectively (possibly prepared - by `nodetext_formatter`/`options_formatter` or by the default styles). - It should return a string representing the final look of the node. This - can e.g. be used to create line separators that take into account the - dynamic width of the parts. `caller` is optionally a reference to the - user of the menu. - input_parser (callable, optional): This callable is responsible for parsing the - options dict from a node and has the form `func(menuobject, raw_string, caller)`, - where menuobject is the active `EvMenu` instance, `input_string` is the - incoming text from the caller and `caller` is the user of the menu. - It should use the helper method of the menuobject to goto new nodes, show - help texts etc. See the default `evtable_parse_input` function for help - with parsing. persistent (bool, optional): Make the Menu persistent (i.e. it will survive a reload. This will make the Menu cmdset persistent. Use with caution - if your menu is buggy you may end up in a state @@ -548,11 +385,6 @@ class EvMenu(object): """ self._startnode = startnode self._menutree = self._parse_menudata(menudata) - - self._nodetext_formatter = nodetext_formatter - self._options_formatter = options_formatter - self._node_formatter = node_formatter - self._input_parser = input_parser self._persistent = persistent if startnode not in self._menutree: @@ -561,6 +393,8 @@ class EvMenu(object): # public variables made available to the command self.caller = caller + + # track EvMenu kwargs self.auto_quit = auto_quit self.auto_look = auto_look self.auto_help = auto_help @@ -573,15 +407,16 @@ class EvMenu(object): self.cmd_on_exit = cmd_on_exit else: self.cmd_on_exit = None + # current menu state self.default = None self.nodetext = None self.helptext = None self.options = None # assign kwargs as initialization vars on ourselves. - if set(("_startnode", "_menutree", "_nodetext_formatter", "_options_formatter", - "node_formatter", "_input_parser", "_peristent", "cmd_on_exit", "default", - "nodetext", "helptext", "options")).intersection(set(kwargs.keys())): + if set(("_startnode", "_menutree", "_session", "_persistent", + "cmd_on_exit", "default", "nodetext", "helptext", + "options", "cmdset_mergetype", "auto_quit")).intersection(set(kwargs.keys())): raise RuntimeError("One or more of the EvMenu `**kwargs` is reserved by EvMenu for internal use.") for key, val in kwargs.iteritems(): setattr(self, key, val) @@ -591,17 +426,17 @@ class EvMenu(object): if persistent: # save the menu to the database + calldict = {"startnode": startnode, + "cmdset_mergetype": cmdset_mergetype, + "cmdset_priority": cmdset_priority, + "auto_quit": auto_quit, + "auto_look": auto_look, + "auto_help": auto_help, + "cmd_on_exit": cmd_on_exit, + "persistent": persistent} + calldict.update(kwargs) try: - caller.attributes.add("_menutree_saved", - ((menudata, ), - {"startnode": startnode, - "cmdset_mergetype": cmdset_mergetype, - "cmdset_priority": cmdset_priority, - "auto_quit": auto_quit, "auto_look": auto_look, "auto_help": auto_help, - "cmd_on_exit": cmd_on_exit, - "nodetext_formatter": nodetext_formatter, "options_formatter": options_formatter, - "node_formatter": node_formatter, "input_parser": input_parser, - "persistent": persistent,})) + caller.attributes.add("_menutree_saved", ( self.__class__, (menudata, ), calldict )) caller.attributes.add("_menutree_saved_startnode", (startnode, startnode_input)) except Exception as err: caller.msg(_ERROR_PERSISTENT_SAVING.format(error=err), session=self._session) @@ -662,13 +497,13 @@ class EvMenu(object): """ # handle the node text - nodetext = self._nodetext_formatter(nodetext, len(optionlist), self.caller) + nodetext = self.nodetext_formatter(nodetext) # handle the options - optionstext = self._options_formatter(optionlist, self.caller) + optionstext = self.options_formatter(optionlist) # format the entire node - return self._node_formatter(nodetext, optionstext, self.caller) + return self.node_formatter(nodetext, optionstext) def _execute_node(self, nodename, raw_string): @@ -774,7 +609,10 @@ class EvMenu(object): try: # execute the node ret = self._execute_node(nodename, raw_string) - except EvMenuError: + except EvMenuError as err: + errmsg = "Error in exec '%s' (input: '%s'): %s" % (nodename, raw_string, err) + self.caller.msg("|r%s|n" % errmsg) + logger.log_trace(errmsg) return if isinstance(ret, basestring): # only return a value if a string (a goto target), ignore all other returns @@ -871,6 +709,142 @@ class EvMenu(object): if self.cmd_on_exit is not None: self.cmd_on_exit(self.caller, self) + def parse_input(self, raw_string): + """ + Parses the incoming string from the menu user. + + Args: + raw_string (str): The incoming, unmodified string + from the user. + Notes: + This method is expected to parse input and use the result + to relay execution to the relevant methods of the menu. It + should also report errors directly to the user. + + """ + cmd = raw_string.strip().lower() + + if cmd in self.options: + # this will take precedence over the default commands + # below + goto, callback = self.options[cmd] + self.callback_goto(callback, goto, raw_string) + elif self.auto_look and cmd in ("look", "l"): + self.display_nodetext() + elif self.auto_help and cmd in ("help", "h"): + self.display_helptext() + elif self.auto_quit and cmd in ("quit", "q", "exit"): + self.close_menu() + elif self.default: + goto, callback = self.default + self.callback_goto(callback, goto, raw_string) + else: + self.caller.msg(_HELP_NO_OPTION_MATCH, session=self._session) + + if not (self.options or self.default): + # no options - we are at the end of the menu. + self.close_menu() + + # formatters - override in a child class + + def nodetext_formatter(self, nodetext): + """ + Format the node text itself. + + Args: + nodetext (str): The full node text (the text describing the node). + + Returns: + nodetext (str): The formatted node text. + + """ + return dedent(nodetext).strip() + + def options_formatter(self, optionlist): + """ + Formats the option block. + + Args: + optionlist (list): List of (key, description) tuples for every + option related to this node. + caller (Object, Player or None, optional): The caller of the node. + + Returns: + options (str): The formatted option display. + + """ + if not optionlist: + return "" + + # column separation distance + colsep = 4 + + nlist = len(optionlist) + + # get the widest option line in the table. + table_width_max = -1 + table = [] + for key, desc in optionlist: + if not (key or desc): + continue + table_width_max = max(table_width_max, + max(m_len(p) for p in key.split("\n")) + + max(m_len(p) for p in desc.split("\n")) + colsep) + raw_key = strip_ansi(key) + if raw_key != key: + # already decorations in key definition + table.append(" |lc%s|lt%s|le: %s" % (raw_key, key, desc)) + else: + # add a default white color to key + table.append(" |lc%s|lt|w%s|n|le: %s" % (raw_key, raw_key, desc)) + + ncols = (_MAX_TEXT_WIDTH // table_width_max) + 1 # number of ncols + + # get the amount of rows needed (start with 4 rows) + nrows = 4 + while nrows * ncols < nlist: + nrows += 1 + ncols = nlist // nrows # number of full columns + nlastcol = nlist % nrows # number of elements in last column + + # get the final column count + ncols = ncols + 1 if nlastcol > 0 else ncols + if ncols > 1: + # only extend if longer than one column + table.extend([" " for i in range(nrows - nlastcol)]) + + # build the actual table grid + table = [table[icol * nrows : (icol * nrows) + nrows] for icol in range(0, ncols)] + + # adjust the width of each column + for icol in range(len(table)): + col_width = max(max(m_len(p) for p in part.split("\n")) for part in table[icol]) + colsep + table[icol] = [pad(part, width=col_width + colsep, align="l") for part in table[icol]] + + # format the table into columns + return unicode(EvTable(table=table, border="none")) + + def node_formatter(self, nodetext, optionstext): + """ + Formats the entirety of the node. + + Args: + nodetext (str): The node text as returned by `self.nodetext_formatter`. + optionstext (str): The options display as returned by `self.options_formatter`. + caller (Object, Player or None, optional): The caller of the node. + + Returns: + node (str): The formatted node to display. + + """ + nodetext_width_max = max(m_len(line) for line in nodetext.split("\n")) + options_width_max = max(m_len(line) for line in optionstext.split("\n")) + total_width = max(options_width_max, nodetext_width_max) + separator1 = "_" * total_width + "\n\n" if nodetext_width_max else "" + separator2 = "\n" + "_" * total_width + "\n\n" if total_width else "" + return separator1 + "|n" + nodetext + "|n" + separator2 + "|n" + optionstext + + # ------------------------------------------------------------------------------------------------- # diff --git a/evennia/utils/tests.py b/evennia/utils/tests.py index 247c8ad0b3..f7fd203e2c 100644 --- a/evennia/utils/tests.py +++ b/evennia/utils/tests.py @@ -321,6 +321,20 @@ class TestTextToHTMLparser(TestCase): self.assertEqual(self.parser.convert_urls('http://example.com/'), 'http://example.com/') +from evennia.utils import evmenu +from mock import Mock +class TestEvMenu(TestCase): + "Run the EvMenu test." + def setUp(self): + self.caller = Mock() + self.caller.msg = Mock() + self.menu = evmenu.EvMenu(self.caller, "evennia.utils.evmenu", startnode="test_start_node", + persistent=True, cmdset_mergetype="Replace", testval="val", testval2="val2") + + def test_kwargsave(self): + self.assertTrue(hasattr(self.menu, "testval")) + self.assertTrue(hasattr(self.menu, "testval2")) + from evennia.utils import inlinefuncs From edc092bfc4df14f86d329b1e5deb3bc11bd9dc96 Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 17 Feb 2017 21:42:26 +0100 Subject: [PATCH 08/63] Run migrations! Add support for using Scripts as senders/receivers of Msg entities. Implements #1179. --- .../migrations/0011_auto_20170217_2039.py | 26 +++++++++++++++ evennia/comms/models.py | 32 +++++++++++++++++-- 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 evennia/comms/migrations/0011_auto_20170217_2039.py diff --git a/evennia/comms/migrations/0011_auto_20170217_2039.py b/evennia/comms/migrations/0011_auto_20170217_2039.py new file mode 100644 index 0000000000..b13e6dcec0 --- /dev/null +++ b/evennia/comms/migrations/0011_auto_20170217_2039.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2017-02-17 20:39 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('scripts', '0007_auto_20150403_2339'), + ('comms', '0010_auto_20161206_1912'), + ] + + operations = [ + migrations.AddField( + model_name='msg', + name='db_receivers_scripts', + field=models.ManyToManyField(blank=True, help_text=b'script_receivers', null=True, related_name='receiver_script_set', to='scripts.ScriptDB'), + ), + migrations.AddField( + model_name='msg', + name='db_sender_scripts', + field=models.ManyToManyField(blank=True, db_index=True, null=True, related_name='sender_script_set', to='scripts.ScriptDB', verbose_name=b'sender(script)'), + ), + ] diff --git a/evennia/comms/models.py b/evennia/comms/models.py index ce5f3d739c..2a2c59ed87 100644 --- a/evennia/comms/models.py +++ b/evennia/comms/models.py @@ -55,9 +55,11 @@ class Msg(SharedMemoryModel): - db_sender_players: Player senders - db_sender_objects: Object senders + - db_sender_scripts: Script senders - db_sender_external: External senders (defined as string names) - db_receivers_players: Receiving players - db_receivers_objects: Receiving objects + - db_receivers_scripts: Receiveing scripts - db_receivers_channels: Receiving channels - db_header: Header text - db_message: The actual message text @@ -82,6 +84,8 @@ class Msg(SharedMemoryModel): null=True, blank=True, verbose_name='sender(player)', db_index=True) db_sender_objects = models.ManyToManyField("objects.ObjectDB", related_name='sender_object_set', null=True, blank=True, verbose_name='sender(object)', db_index=True) + db_sender_scripts = models.ManyToManyField("scripts.ScriptDB", related_name='sender_script_set', + null=True, blank=True, verbose_name='sender(script)', db_index=True) db_sender_external = models.CharField('external sender', max_length=255, null=True, blank=True, db_index=True, help_text="identifier for external sender, for example a sender over an " "IRC connection (i.e. someone who doesn't have an exixtence in-game).") @@ -92,6 +96,8 @@ class Msg(SharedMemoryModel): null=True, blank=True, help_text="player receivers") db_receivers_objects = models.ManyToManyField('objects.ObjectDB', related_name='receiver_object_set', null=True, blank=True, help_text="object receivers") + db_receivers_scripts = models.ManyToManyField('scripts.ScriptDB', related_name='receiver_script_set', + null=True, blank=True, help_text="script_receivers") db_receivers_channels = models.ManyToManyField("ChannelDB", related_name='channel_set', null=True, blank=True, help_text="channel recievers") @@ -148,6 +154,7 @@ class Msg(SharedMemoryModel): "Getter. Allows for value = self.sender" return list(self.db_sender_players.all()) + \ list(self.db_sender_objects.all()) + \ + list(self.db_sender_scripts.all()) + \ self.extra_senders #@sender.setter @@ -168,12 +175,15 @@ class Msg(SharedMemoryModel): self.db_sender_objects.add(sender) elif clsname == "PlayerDB": self.db_sender_players.add(sender) + elif clsname == "ScriptDB": + self.db_sender_scripts.add(sender) #@sender.deleter def __senders_del(self): "Deleter. Clears all senders" self.db_sender_players.clear() self.db_sender_objects.clear() + self.db_sender_scripts.clear() self.db_sender_external = "" self.extra_senders = [] self.save() @@ -200,15 +210,20 @@ class Msg(SharedMemoryModel): self.db_sender_objects.remove(sender) elif clsname == "PlayerDB": self.db_sender_players.remove(sender) + elif clsname == "ScriptDB": + self.db_sender_players.remove(sender) # receivers property #@property def __receivers_get(self): """ Getter. Allows for value = self.receivers. - Returns three lists of receivers: players, objects and channels. + Returns four lists of receivers: players, objects, scripts and channels. """ - return list(self.db_receivers_players.all()) + list(self.db_receivers_objects.all()) + return list(self.db_receivers_players.all()) + \ + list(self.db_receivers_objects.all()) + \ + list(self.db_receivers_scripts.all()) + \ + list(self.db_receivers_channels.all()) #@receivers.setter def __receivers_set(self, receivers): @@ -226,12 +241,19 @@ class Msg(SharedMemoryModel): self.db_receivers_objects.add(receiver) elif clsname == "PlayerDB": self.db_receivers_players.add(receiver) + elif clsname == "ScriptDB": + self.db_receivers_scripts.add(receiver) + elif clsname == "ChannelDB": + self.db_receivers_channels.add(receiver) + #@receivers.deleter def __receivers_del(self): "Deleter. Clears all receivers" self.db_receivers_players.clear() self.db_receivers_objects.clear() + self.db_receivers_scripts.clear() + self.db_receivers_channels.clear() self.save() receivers = property(__receivers_get, __receivers_set, __receivers_del) @@ -240,7 +262,7 @@ class Msg(SharedMemoryModel): Remove a single receiver or a list of receivers. Args: - receivers (Player, Object, Channel or list): Receiver to remove. + receivers (Player, Object, Script, Channel or list): Receiver to remove. """ for receiver in make_iter(receivers): @@ -253,6 +275,10 @@ class Msg(SharedMemoryModel): self.db_receivers_objects.remove(receiver) elif clsname == "PlayerDB": self.db_receivers_players.remove(receiver) + elif clsname == "ScriptDB": + self.db_receivers_scripts.remove(receiver) + elif clsname == "ChannelDB": + self.db_receivers_channels.remove(receiver) # channels property #@property From 0bd47f0c52d1456d22b9d5bc87641c1062974bed Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 17 Feb 2017 23:25:00 +0100 Subject: [PATCH 09/63] Change permission strings so Immortals->Developer, Wizards->Admin, Builders->Builder, PlayerHelper->Helper, Players->Player, Guests->Guest. Made perm() and pperm() lock function accept both the plural and singular form (so both Admin and Admins work). Still lacking a data migration for updating an existing database to the new setup. --- evennia/commands/default/admin.py | 16 +++--- evennia/commands/default/batchprocess.py | 2 +- evennia/commands/default/building.py | 66 ++++++++++++------------ evennia/commands/default/comms.py | 12 ++--- evennia/commands/default/general.py | 2 +- evennia/commands/default/help.py | 2 +- evennia/commands/default/player.py | 14 ++--- evennia/commands/default/system.py | 22 ++++---- evennia/commands/default/tests.py | 2 +- evennia/commands/default/unloggedin.py | 6 +-- evennia/contrib/chargen.py | 2 +- evennia/contrib/email_login.py | 4 +- evennia/contrib/rpsystem.py | 2 +- evennia/contrib/tutorial_world/rooms.py | 2 +- evennia/contrib/wilderness.py | 2 +- evennia/locks/lockfuncs.py | 3 +- evennia/locks/lockhandler.py | 20 +++---- evennia/locks/tests.py | 10 ++-- evennia/objects/objects.py | 14 ++--- evennia/players/bots.py | 2 +- evennia/players/players.py | 4 +- evennia/server/initial_setup.py | 8 +-- evennia/settings_default.py | 20 +++---- 23 files changed, 119 insertions(+), 118 deletions(-) diff --git a/evennia/commands/default/admin.py b/evennia/commands/default/admin.py index a8b218962d..3169618d10 100644 --- a/evennia/commands/default/admin.py +++ b/evennia/commands/default/admin.py @@ -36,7 +36,7 @@ class CmdBoot(COMMAND_DEFAULT_CLASS): """ key = "@boot" - locks = "cmd:perm(boot) or perm(Wizards)" + locks = "cmd:perm(boot) or perm(Admin)" help_category = "Admin" def func(self): @@ -152,7 +152,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS): """ key = "@ban" aliases = ["@bans"] - locks = "cmd:perm(ban) or perm(Immortals)" + locks = "cmd:perm(ban) or perm(Developer)" help_category = "Admin" def func(self): @@ -219,7 +219,7 @@ class CmdUnban(COMMAND_DEFAULT_CLASS): """ key = "@unban" - locks = "cmd:perm(unban) or perm(Immortals)" + locks = "cmd:perm(unban) or perm(Developer)" help_category = "Admin" def func(self): @@ -266,7 +266,7 @@ class CmdDelPlayer(COMMAND_DEFAULT_CLASS): """ key = "@delplayer" - locks = "cmd:perm(delplayer) or perm(Immortals)" + locks = "cmd:perm(delplayer) or perm(Developer)" help_category = "Admin" def func(self): @@ -342,7 +342,7 @@ class CmdEmit(COMMAND_DEFAULT_CLASS): """ key = "@emit" aliases = ["@pemit", "@remit"] - locks = "cmd:perm(emit) or perm(Builders)" + locks = "cmd:perm(emit) or perm(Builder)" help_category = "Admin" def func(self): @@ -410,7 +410,7 @@ class CmdNewPassword(COMMAND_DEFAULT_CLASS): """ key = "@userpassword" - locks = "cmd:perm(newpassword) or perm(Wizards)" + locks = "cmd:perm(newpassword) or perm(Admin)" help_category = "Admin" def func(self): @@ -451,7 +451,7 @@ class CmdPerm(COMMAND_DEFAULT_CLASS): """ key = "@perm" aliases = "@setperm" - locks = "cmd:perm(perm) or perm(Immortals)" + locks = "cmd:perm(perm) or perm(Developer)" help_category = "Admin" def func(self): @@ -546,7 +546,7 @@ class CmdWall(COMMAND_DEFAULT_CLASS): Announces a message to all connected players. """ key = "@wall" - locks = "cmd:perm(wall) or perm(Wizards)" + locks = "cmd:perm(wall) or perm(Admin)" help_category = "Admin" def func(self): diff --git a/evennia/commands/default/batchprocess.py b/evennia/commands/default/batchprocess.py index cefeba6baa..52740ccb37 100644 --- a/evennia/commands/default/batchprocess.py +++ b/evennia/commands/default/batchprocess.py @@ -239,7 +239,7 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS): """ key = "@batchcommands" aliases = ["@batchcommand", "@batchcmd"] - locks = "cmd:perm(batchcommands) or superuser()" + locks = "cmd:perm(batchcommands) or perm(Developer)" help_category = "Building" def func(self): diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 28ee1a033a..a020b21483 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -117,7 +117,7 @@ class CmdSetObjAlias(COMMAND_DEFAULT_CLASS): key = "@alias" aliases = "@setobjalias" - locks = "cmd:perm(setobjalias) or perm(Builders)" + locks = "cmd:perm(setobjalias) or perm(Builder)" help_category = "Building" def func(self): @@ -196,7 +196,7 @@ class CmdCopy(ObjManipCommand): """ key = "@copy" - locks = "cmd:perm(copy) or perm(Builders)" + locks = "cmd:perm(copy) or perm(Builder)" help_category = "Building" def func(self): @@ -276,7 +276,7 @@ class CmdCpAttr(ObjManipCommand): If you don't supply a source object, yourself is used. """ key = "@cpattr" - locks = "cmd:perm(cpattr) or perm(Builders)" + locks = "cmd:perm(cpattr) or perm(Builder)" help_category = "Building" def check_from_attr(self, obj, attr, clear=False): @@ -418,7 +418,7 @@ class CmdMvAttr(ObjManipCommand): object. If you don't supply a source object, yourself is used. """ key = "@mvattr" - locks = "cmd:perm(mvattr) or perm(Builders)" + locks = "cmd:perm(mvattr) or perm(Builder)" help_category = "Building" def func(self): @@ -466,12 +466,12 @@ class CmdCreate(ObjManipCommand): """ key = "@create" - locks = "cmd:perm(create) or perm(Builders)" + locks = "cmd:perm(create) or perm(Builder)" help_category = "Building" # lockstring of newly created objects, for easy overloading. # Will be formatted with the {id} of the creating object. - new_obj_lockstring = "control:id({id}) or perm(Wizards);delete:id({id}) or perm(Wizards)" + new_obj_lockstring = "control:id({id}) or perm(Admin);delete:id({id}) or perm(Admin)" def func(self): """ @@ -548,7 +548,7 @@ class CmdDesc(COMMAND_DEFAULT_CLASS): """ key = "@setdesc" aliases = "@describe" - locks = "cmd:perm(desc) or perm(Builders)" + locks = "cmd:perm(desc) or perm(Builder)" help_category = "Building" def edit_handler(self): @@ -616,7 +616,7 @@ class CmdDestroy(COMMAND_DEFAULT_CLASS): key = "@destroy" aliases = ["@delete", "@del"] - locks = "cmd:perm(destroy) or perm(Builders)" + locks = "cmd:perm(destroy) or perm(Builder)" help_category = "Building" def func(self): @@ -701,14 +701,14 @@ class CmdDig(ObjManipCommand): would be 'north;no;n'. """ key = "@dig" - locks = "cmd:perm(dig) or perm(Builders)" + locks = "cmd:perm(dig) or perm(Builder)" help_category = "Building" # lockstring of newly created rooms, for easy overloading. # Will be formatted with the {id} of the creating object. - new_room_lockstring = "control:id({id}) or perm(Wizards); " \ - "delete:id({id}) or perm(Wizards); " \ - "edit:id({id}) or perm(Wizards)" + new_room_lockstring = "control:id({id}) or perm(Admin); " \ + "delete:id({id}) or perm(Admin); " \ + "edit:id({id}) or perm(Admin)" def func(self): "Do the digging. Inherits variables from ObjManipCommand.parse()" @@ -845,7 +845,7 @@ class CmdTunnel(COMMAND_DEFAULT_CLASS): key = "@tunnel" aliases = ["@tun"] - locks = "cmd: perm(tunnel) or perm(Builders)" + locks = "cmd: perm(tunnel) or perm(Builder)" help_category = "Building" # store the direction, full name and its opposite @@ -918,7 +918,7 @@ class CmdLink(COMMAND_DEFAULT_CLASS): """ key = "@link" - locks = "cmd:perm(link) or perm(Builders)" + locks = "cmd:perm(link) or perm(Builder)" help_category = "Building" def func(self): @@ -996,7 +996,7 @@ class CmdUnLink(CmdLink): # this is just a child of CmdLink key = "@unlink" - locks = "cmd:perm(unlink) or perm(Builders)" + locks = "cmd:perm(unlink) or perm(Builder)" help_key = "Building" def func(self): @@ -1034,7 +1034,7 @@ class CmdSetHome(CmdLink): """ key = "@sethome" - locks = "cmd:perm(@home) or perm(Builders)" + locks = "cmd:perm(@home) or perm(Builder)" help_category = "Building" def func(self): @@ -1081,7 +1081,7 @@ class CmdListCmdSets(COMMAND_DEFAULT_CLASS): """ key = "@cmdsets" aliases = "@listcmsets" - locks = "cmd:perm(listcmdsets) or perm(Builders)" + locks = "cmd:perm(listcmdsets) or perm(Builder)" help_category = "Building" def func(self): @@ -1112,7 +1112,7 @@ class CmdName(ObjManipCommand): key = "@name" aliases = ["@rename"] - locks = "cmd:perm(rename) or perm(Builders)" + locks = "cmd:perm(rename) or perm(Builder)" help_category = "Building" def func(self): @@ -1188,7 +1188,7 @@ class CmdOpen(ObjManipCommand): """ key = "@open" - locks = "cmd:perm(open) or perm(Builders)" + locks = "cmd:perm(open) or perm(Builder)" help_category = "Building" # a custom member method to chug out exits and do checks @@ -1410,7 +1410,7 @@ class CmdSetAttribute(ObjManipCommand): """ key = "@set" - locks = "cmd:perm(set) or perm(Builders)" + locks = "cmd:perm(set) or perm(Builder)" help_category = "Building" def check_obj(self, obj): @@ -1592,7 +1592,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS): key = "@typeclass" aliases = ["@type", "@parent", "@swap", "@update"] - locks = "cmd:perm(typeclass) or perm(Builders)" + locks = "cmd:perm(typeclass) or perm(Builder)" help_category = "Building" def func(self): @@ -1682,7 +1682,7 @@ class CmdWipe(ObjManipCommand): matching the given attribute-wildcard search string. """ key = "@wipe" - locks = "cmd:perm(wipe) or perm(Builders)" + locks = "cmd:perm(wipe) or perm(Builder)" help_category = "Building" def func(self): @@ -1740,18 +1740,18 @@ class CmdLock(ObjManipCommand): Separator expressions need not be capitalized. For example: - 'get: id(25) or perm(Wizards)' + 'get: id(25) or perm(Admin)' The 'get' access_type is checked by the get command and will an object locked with this string will only be possible to - pick up by Wizards or by object with id 25. + pick up by Admins or by object with id=25. You can add several access_types after oneanother by separating them by ';', i.e: - 'get:id(25);delete:perm(Builders)' + 'get:id(25);delete:perm(Builder)' """ key = "@lock" aliases = ["@locks"] - locks = "cmd: perm(locks) or perm(Builders)" + locks = "cmd: perm(locks) or perm(Builder)" help_category = "Building" def func(self): @@ -1844,7 +1844,7 @@ class CmdExamine(ObjManipCommand): """ key = "@examine" aliases = ["@ex","exam"] - locks = "cmd:perm(examine) or perm(Builders)" + locks = "cmd:perm(examine) or perm(Builder)" help_category = "Building" arg_regex = r"(/\w+?(\s|$))|\s|$" @@ -2113,7 +2113,7 @@ class CmdFind(COMMAND_DEFAULT_CLASS): key = "@find" aliases = "@search, @locate" - locks = "cmd:perm(find) or perm(Builders)" + locks = "cmd:perm(find) or perm(Builder)" help_category = "Building" def func(self): @@ -2244,7 +2244,7 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS): is teleported to the target location. """ key = "@tel" aliases = "@teleport" - locks = "cmd:perm(teleport) or perm(Builders)" + locks = "cmd:perm(teleport) or perm(Builder)" help_category = "Building" def func(self): @@ -2343,7 +2343,7 @@ class CmdScript(COMMAND_DEFAULT_CLASS): key = "@script" aliases = "@addscript" - locks = "cmd:perm(script) or perm(Builders)" + locks = "cmd:perm(script) or perm(Builder)" help_category = "Building" def func(self): @@ -2444,7 +2444,7 @@ class CmdTag(COMMAND_DEFAULT_CLASS): key = "@tag" aliases = ["@tags"] - locks = "cmd:perm(tag) or perm(Builders)" + locks = "cmd:perm(tag) or perm(Builder)" help_category = "Building" arg_regex = r"(/\w+?(\s|$))|\s|$" @@ -2584,7 +2584,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS): """ key = "@spawn" - locks = "cmd:perm(spawn) or perm(Builders)" + locks = "cmd:perm(spawn) or perm(Builder)" help_category = "Building" def func(self): @@ -2626,7 +2626,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS): elif isinstance(prototype, dict): # we got the prototype on the command line. We must make sure to not allow # the 'exec' key unless we are immortals or higher. - if "exec" in prototype and not self.caller.check_permstring("Immortals"): + if "exec" in prototype and not self.caller.check_permstring("Developer"): self.caller.msg("Spawn aborted: You don't have access to use the 'exec' prototype key.") return else: diff --git a/evennia/commands/default/comms.py b/evennia/commands/default/comms.py index ed69475635..1b0f96f52f 100644 --- a/evennia/commands/default/comms.py +++ b/evennia/commands/default/comms.py @@ -453,7 +453,7 @@ class CmdCemit(COMMAND_DEFAULT_CLASS): key = "@cemit" aliases = ["@cmsg"] - locks = "cmd: not pperm(channel_banned) and pperm(Players)" + locks = "cmd: not pperm(channel_banned) and pperm(Player)" help_category = "Comms" # this is used by the COMMAND_DEFAULT_CLASS parent @@ -530,7 +530,7 @@ class CmdChannelCreate(COMMAND_DEFAULT_CLASS): key = "@ccreate" aliases = "channelcreate" - locks = "cmd:not pperm(channel_banned) and pperm(Players)" + locks = "cmd:not pperm(channel_banned) and pperm(Player)" help_category = "Comms" # this is used by the COMMAND_DEFAULT_CLASS parent @@ -850,7 +850,7 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS): """ key = "@irc2chan" - locks = "cmd:serversetting(IRC_ENABLED) and pperm(Immortals)" + locks = "cmd:serversetting(IRC_ENABLED) and pperm(Developer)" help_category = "Comms" def func(self): @@ -943,7 +943,7 @@ class CmdIRCStatus(COMMAND_DEFAULT_CLASS): """ key = "@ircstatus" - locks = "cmd:serversetting(IRC_ENABLED) and perm(ircstatus) or perm(Builders))" + locks = "cmd:serversetting(IRC_ENABLED) and perm(ircstatus) or perm(Builder))" help_category = "Comms" def func(self): @@ -981,7 +981,7 @@ class CmdIRCStatus(COMMAND_DEFAULT_CLASS): # an asynchronous call. self.caller.msg("Requesting nicklist from %s (%s:%s)." % (channel, network, port)) ircbot.get_nicklist(self.caller) - elif self.caller.locks.check_lockstring(self.caller, "dummy:perm(ircstatus) or perm(Immortals)"): + elif self.caller.locks.check_lockstring(self.caller, "dummy:perm(ircstatus) or perm(Developer)"): # reboot the client self.caller.msg("Forcing a disconnect + reconnect of %s." % chtext) ircbot.reconnect() @@ -1016,7 +1016,7 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS): """ key = "@rss2chan" - locks = "cmd:serversetting(RSS_ENABLED) and pperm(Immortals)" + locks = "cmd:serversetting(RSS_ENABLED) and pperm(Developer)" help_category = "Comms" def func(self): diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index 8b78e97a0f..cfcccc7612 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -24,7 +24,7 @@ class CmdHome(COMMAND_DEFAULT_CLASS): """ key = "home" - locks = "cmd:perm(home) or perm(Builders)" + locks = "cmd:perm(home) or perm(Builder)" arg_regex = r"$" def func(self): diff --git a/evennia/commands/default/help.py b/evennia/commands/default/help.py index d15228992c..8bfe55e834 100644 --- a/evennia/commands/default/help.py +++ b/evennia/commands/default/help.py @@ -293,7 +293,7 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS): """ key = "@sethelp" - locks = "cmd:perm(PlayerHelpers)" + locks = "cmd:perm(Helper)" help_category = "Building" def func(self): diff --git a/evennia/commands/default/player.py b/evennia/commands/default/player.py index ae95388a8e..07d43cdf7c 100644 --- a/evennia/commands/default/player.py +++ b/evennia/commands/default/player.py @@ -121,7 +121,7 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS): if you want. """ key = "@charcreate" - locks = "cmd:pperm(Players)" + locks = "cmd:pperm(Player)" help_category = "General" # this is used by the parent @@ -164,7 +164,7 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS): home=default_home, permissions=permissions) # only allow creator (and immortals) to puppet this char - new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" % + new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" % (new_character.id, player.id)) player.db._playable_characters.append(new_character) if desc: @@ -184,7 +184,7 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS): Permanently deletes one of your characters. """ key = "@chardelete" - locks = "cmd:pperm(Players)" + locks = "cmd:pperm(Player)" help_category = "General" def func(self): @@ -298,7 +298,7 @@ class CmdOOC(MuxPlayerLookCommand): """ key = "@ooc" - locks = "cmd:pperm(Players)" + locks = "cmd:pperm(Player)" aliases = "@unpuppet" help_category = "General" @@ -403,7 +403,7 @@ class CmdWho(COMMAND_DEFAULT_CLASS): if self.cmdstring == "doing": show_session_data = False else: - show_session_data = player.check_permstring("Immortals") or player.check_permstring("Wizards") + show_session_data = player.check_permstring("Developer") or player.check_permstring("Wizards") nplayers = (SESSIONS.player_count()) if show_session_data: @@ -610,7 +610,7 @@ class CmdPassword(COMMAND_DEFAULT_CLASS): Changes your password. Make sure to pick a safe one. """ key = "@password" - locks = "cmd:pperm(Players)" + locks = "cmd:pperm(Player)" # this is used by the parent player_caller = True @@ -793,7 +793,7 @@ class CmdQuell(COMMAND_DEFAULT_CLASS): key = "@quell" aliases = ["@unquell"] - locks = "cmd:pperm(Players)" + locks = "cmd:pperm(Player)" help_category = "General" # this is used by the parent diff --git a/evennia/commands/default/system.py b/evennia/commands/default/system.py index 429308ce26..c07d20b697 100644 --- a/evennia/commands/default/system.py +++ b/evennia/commands/default/system.py @@ -47,7 +47,7 @@ class CmdReload(COMMAND_DEFAULT_CLASS): @reset to purge) and at_reload() hooks will be called. """ key = "@reload" - locks = "cmd:perm(reload) or perm(Immortals)" + locks = "cmd:perm(reload) or perm(Developer)" help_category = "System" def func(self): @@ -83,7 +83,7 @@ class CmdReset(COMMAND_DEFAULT_CLASS): """ key = "@reset" aliases = ['@reboot'] - locks = "cmd:perm(reload) or perm(Immortals)" + locks = "cmd:perm(reload) or perm(Developer)" help_category = "System" def func(self): @@ -105,7 +105,7 @@ class CmdShutdown(COMMAND_DEFAULT_CLASS): Gracefully shut down both Server and Portal. """ key = "@shutdown" - locks = "cmd:perm(shutdown) or perm(Immortals)" + locks = "cmd:perm(shutdown) or perm(Developer)" help_category = "System" def func(self): @@ -240,7 +240,7 @@ class CmdPy(COMMAND_DEFAULT_CLASS): """ key = "@py" aliases = ["!"] - locks = "cmd:perm(py) or perm(Immortals)" + locks = "cmd:perm(py) or perm(Developer)" help_category = "System" def func(self): @@ -322,7 +322,7 @@ class CmdScripts(COMMAND_DEFAULT_CLASS): """ key = "@scripts" aliases = ["@globalscript", "@listscripts"] - locks = "cmd:perm(listscripts) or perm(Wizards)" + locks = "cmd:perm(listscripts) or perm(Admin)" help_category = "System" def func(self): @@ -405,7 +405,7 @@ class CmdObjects(COMMAND_DEFAULT_CLASS): """ key = "@objects" aliases = ["@listobjects", "@listobjs", '@stats', '@db'] - locks = "cmd:perm(listobjects) or perm(Builders)" + locks = "cmd:perm(listobjects) or perm(Builder)" help_category = "System" def func(self): @@ -469,7 +469,7 @@ class CmdPlayers(COMMAND_DEFAULT_CLASS): """ key = "@players" aliases = ["@listplayers"] - locks = "cmd:perm(listplayers) or perm(Wizards)" + locks = "cmd:perm(listplayers) or perm(Admin)" help_category = "System" def func(self): @@ -521,7 +521,7 @@ class CmdService(COMMAND_DEFAULT_CLASS): key = "@service" aliases = ["@services"] - locks = "cmd:perm(service) or perm(Immortals)" + locks = "cmd:perm(service) or perm(Developer)" help_category = "System" def func(self): @@ -645,7 +645,7 @@ class CmdTime(COMMAND_DEFAULT_CLASS): """ key = "@time" aliases = "@uptime" - locks = "cmd:perm(time) or perm(Players)" + locks = "cmd:perm(time) or perm(Player)" help_category = "System" def func(self): @@ -703,7 +703,7 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS): """ key = "@server" aliases = ["@serverload", "@serverprocess"] - locks = "cmd:perm(list) or perm(Immortals)" + locks = "cmd:perm(list) or perm(Developer)" help_category = "System" def func(self): @@ -821,7 +821,7 @@ class CmdTickers(COMMAND_DEFAULT_CLASS): """ key = "@tickers" help_category = "System" - locks = "cmd:perm(tickers) or perm(Builders)" + locks = "cmd:perm(tickers) or perm(Builder)" def func(self): from evennia import TICKER_HANDLER diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index 4ade106ffe..2bb227873a 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -258,7 +258,7 @@ class TestBuilding(CommandTest): "Obj changed typeclass from evennia.objects.objects.DefaultObject to evennia.objects.objects.DefaultExit.") def test_lock(self): - self.call(building.CmdLock(), "Obj = test:perm(Immortals)", "Added lock 'test:perm(Immortals)' to Obj.") + self.call(building.CmdLock(), "Obj = test:perm(Developer)", "Added lock 'test:perm(Developer)' to Obj.") def test_find(self): self.call(building.CmdFind(), "Room2", "One Match") diff --git a/evennia/commands/default/unloggedin.py b/evennia/commands/default/unloggedin.py index ae8f3b0c0f..665186fafa 100644 --- a/evennia/commands/default/unloggedin.py +++ b/evennia/commands/default/unloggedin.py @@ -12,7 +12,7 @@ from evennia.objects.models import ObjectDB from evennia.server.models import ServerConfig from evennia.comms.models import ChannelDB -from evennia.utils import create, logger, utils, ansi +from evennia.utils import create, logger, utils from evennia.commands.cmdhandler import CMD_LOGINSTART COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS) @@ -560,8 +560,8 @@ def _create_character(session, new_player, typeclass, home, permissions): # set playable character list new_player.db._playable_characters.append(new_character) - # allow only the character itself and the player to puppet this character (and Immortals). - new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" % + # allow only the character itself and the player to puppet this character (and Developers). + new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" % (new_character.id, new_player.id)) # If no description is set, set a default description diff --git a/evennia/contrib/chargen.py b/evennia/contrib/chargen.py index 55639e4e90..b1087af806 100644 --- a/evennia/contrib/chargen.py +++ b/evennia/contrib/chargen.py @@ -169,7 +169,7 @@ class CmdOOCCharacterCreate(Command): self.caller.msg("{rThe Character couldn't be created. This is a bug. Please contact an admin.") return # make sure to lock the character to only be puppeted by this player - new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" % + new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" % (new_character.id, self.caller.id)) # save dbref diff --git a/evennia/contrib/email_login.py b/evennia/contrib/email_login.py index 6ec544f664..7b991ba351 100644 --- a/evennia/contrib/email_login.py +++ b/evennia/contrib/email_login.py @@ -235,8 +235,8 @@ its and @/./+/-/_ only.") # this echoes the restrictions made by django's auth m # set playable character list new_player.db._playable_characters.append(new_character) - # allow only the character itself and the player to puppet this character (and Immortals). - new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" % + # allow only the character itself and the player to puppet this character (and Admin). + new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" % (new_character.id, new_player.id)) # If no description is set, set a default description diff --git a/evennia/contrib/rpsystem.py b/evennia/contrib/rpsystem.py index 73a682eb95..7f453d67be 100644 --- a/evennia/contrib/rpsystem.py +++ b/evennia/contrib/rpsystem.py @@ -1256,7 +1256,7 @@ class ContribRPObject(DefaultObject): candidates.append(self) # the sdesc-related substitution - is_builder = self.locks.check_lockstring(self, "perm(Builders)") + is_builder = self.locks.check_lockstring(self, "perm(Builder)") use_dbref = is_builder if use_dbref is None else use_dbref search_obj = lambda string: ObjectDB.objects.object_search(string, attribute_name=attribute_name, diff --git a/evennia/contrib/tutorial_world/rooms.py b/evennia/contrib/tutorial_world/rooms.py index 5310dfa106..176bfa7994 100644 --- a/evennia/contrib/tutorial_world/rooms.py +++ b/evennia/contrib/tutorial_world/rooms.py @@ -98,7 +98,7 @@ class CmdTutorialSetDetail(default_cmds.MuxCommand): multiple aliases to the detail all at once. """ key = "@detail" - locks = "cmd:perm(Builders)" + locks = "cmd:perm(Builder)" help_category = "TutorialWorld" def func(self): diff --git a/evennia/contrib/wilderness.py b/evennia/contrib/wilderness.py index 1cb4052601..9e01a43211 100644 --- a/evennia/contrib/wilderness.py +++ b/evennia/contrib/wilderness.py @@ -603,7 +603,7 @@ class WildernessRoom(DefaultRoom): searching, and is expected to produce something useful for builders. """ - if self.locks.check_lockstring(looker, "perm(Builders)"): + if self.locks.check_lockstring(looker, "perm(Builder)"): name = "{}(#{})".format(self.location_name, self.id) else: name = self.location_name diff --git a/evennia/locks/lockfuncs.py b/evennia/locks/lockfuncs.py index 4d3d1b2922..bdba148cf6 100644 --- a/evennia/locks/lockfuncs.py +++ b/evennia/locks/lockfuncs.py @@ -168,7 +168,8 @@ def perm(accessing_obj, accessed_obj, *args, **kwargs): if utils.inherits_from(accessing_obj, "evennia.objects.objects.DefaultObject") and accessing_obj.player: player = accessing_obj.player - perms_player = [p.lower() for p in player.permissions.all()] + # we strip eventual plural forms, so Builders == Builder + perms_player = [p.lower().rstrip("s") for p in player.permissions.all()] is_quell = player.attributes.get("_quell") if permission in _PERMISSION_HIERARCHY: diff --git a/evennia/locks/lockhandler.py b/evennia/locks/lockhandler.py index 2c0884d452..be35924efb 100644 --- a/evennia/locks/lockhandler.py +++ b/evennia/locks/lockhandler.py @@ -56,21 +56,21 @@ Example: We want to limit who may edit a particular object (let's call this access_type for 'edit', it depends on what the command is looking for). We want this to -only work for those with the Permission 'Builders'. So we use our lock +only work for those with the Permission 'Builder'. So we use our lock function above and define it like this: - 'edit:perm(Builders)' + 'edit:perm(Builder)' Here, the lock-function perm() will be called with the string -'Builders' (accessing_obj and accessed_obj are added automatically, +'Builder' (accessing_obj and accessed_obj are added automatically, you only need to add the args/kwargs, if any). -If we wanted to make sure the accessing object was BOTH a Builders and a +If we wanted to make sure the accessing object was BOTH a Builder and a GoodGuy, we could use AND: - 'edit:perm(Builders) AND perm(GoodGuy)' + 'edit:perm(Builder) AND perm(GoodGuy)' -To allow EITHER Builders and GoodGuys, we replace AND with OR. perm() is just +To allow EITHER Builder and GoodGuys, we replace AND with OR. perm() is just one example, the lock function can do anything and compare any properties of the calling object to decide if the lock is passed or not. @@ -79,7 +79,7 @@ the calling object to decide if the lock is passed or not. To make these work, add the string to the lockhandler of the object you want to apply the lock to: - obj.lockhandler.add('edit:perm(Builders)') + obj.lockhandler.add('edit:perm(Builder)') From then on, a command that wants to check for 'edit' access on this object would do something like this: @@ -541,13 +541,13 @@ def _test(): obj1 = TestObj() obj2 = TestObj() - #obj1.lock_storage = "owner:dbref(#4);edit:dbref(#5) or perm(Wizards);examine:perm(Builders);delete:perm(Wizards);get:all()" + #obj1.lock_storage = "owner:dbref(#4);edit:dbref(#5) or perm(Admin);examine:perm(Builder);delete:perm(Admin);get:all()" #obj1.lock_storage = "cmd:all();admin:id(1);listen:all();send:all()" - obj1.lock_storage = "listen:perm(Immortals)" + obj1.lock_storage = "listen:perm(Developer)" pdb.set_trace() obj1.locks = LockHandler(obj1) - obj2.permissions.add("Immortals") + obj2.permissions.add("Developer") obj2.id = 4 #obj1.locks.add("edit:attr(test)") diff --git a/evennia/locks/tests.py b/evennia/locks/tests.py index 50f99f9abb..0c47456ede 100644 --- a/evennia/locks/tests.py +++ b/evennia/locks/tests.py @@ -25,8 +25,8 @@ from evennia.locks import lockfuncs class TestLockCheck(EvenniaTest): def testrun(self): dbref = self.obj2.dbref - self.obj1.locks.add("owner:dbref(%s);edit:dbref(%s) or perm(Wizards);examine:perm(Builders) and id(%s);delete:perm(Wizards);get:all()" % (dbref, dbref, dbref)) - self.obj2.permissions.add('Wizards') + self.obj1.locks.add("owner:dbref(%s);edit:dbref(%s) or perm(Admin);examine:perm(Builder) and id(%s);delete:perm(Admin);get:all()" % (dbref, dbref, dbref)) + self.obj2.permissions.add('Admin') self.assertEquals(True, self.obj1.locks.check(self.obj2, 'owner')) self.assertEquals(True, self.obj1.locks.check(self.obj2, 'edit')) self.assertEquals(True, self.obj1.locks.check(self.obj2, 'examine')) @@ -39,11 +39,11 @@ class TestLockCheck(EvenniaTest): class TestLockfuncs(EvenniaTest): def testrun(self): - self.obj2.permissions.add('Wizards') + self.obj2.permissions.add('Admin') self.assertEquals(True, lockfuncs.true(self.obj2, self.obj1)) self.assertEquals(False, lockfuncs.false(self.obj2, self.obj1)) - self.assertEquals(True, lockfuncs.perm(self.obj2, self.obj1, 'Wizards')) - self.assertEquals(True, lockfuncs.perm_above(self.obj2, self.obj1, 'Builders')) + self.assertEquals(True, lockfuncs.perm(self.obj2, self.obj1, 'Admin')) + self.assertEquals(True, lockfuncs.perm_above(self.obj2, self.obj1, 'Builder')) dbref = self.obj2.dbref self.assertEquals(True, lockfuncs.dbref(self.obj2, self.obj1, '%s' % dbref)) self.obj2.db.testattr = 45 diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 3dda680476..0b3eb8a940 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -276,7 +276,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): builders. """ - if self.locks.check_lockstring(looker, "perm(Builders)"): + if self.locks.check_lockstring(looker, "perm(Builder)"): return "{}(#{})".format(self.name, self.id) return self.name @@ -973,15 +973,15 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): # controller, for example) self.locks.add(";".join([ - "control:perm(Immortals)", # edit locks/permissions, delete - "examine:perm(Builders)", # examine properties + "control:perm(Developer)", # edit locks/permissions, delete + "examine:perm(Builder)", # examine properties "view:all()", # look at object (visibility) - "edit:perm(Wizards)", # edit properties/attributes - "delete:perm(Wizards)", # delete object + "edit:perm(Admin)", # edit properties/attributes + "delete:perm(Admin)", # delete object "get:all()", # pick up object "call:true()", # allow to call commands on this object - "tell:perm(Wizards)", # allow emits to this object - "puppet:pperm(Immortals)"])) # lock down puppeting only to staff by default + "tell:perm(Admin)", # allow emits to this object + "puppet:pperm(Developer)"])) # lock down puppeting only to staff by default def basetype_posthook_setup(self): """ diff --git a/evennia/players/bots.py b/evennia/players/bots.py index f4a0c469ba..196806ff2d 100644 --- a/evennia/players/bots.py +++ b/evennia/players/bots.py @@ -96,7 +96,7 @@ class Bot(DefaultPlayer): # the text encoding to use. self.db.encoding = "utf-8" # A basic security setup - lockstring = "examine:perm(Wizards);edit:perm(Wizards);delete:perm(Wizards);boot:perm(Wizards);msg:false()" + lockstring = "examine:perm(Admin);edit:perm(Admin);delete:perm(Admin);boot:perm(Admin);msg:false()" self.locks.add(lockstring) # set the basics of being a bot script_key = "%s" % self.key diff --git a/evennia/players/players.py b/evennia/players/players.py index 92d5dbcf24..e2bb693720 100644 --- a/evennia/players/players.py +++ b/evennia/players/players.py @@ -566,8 +566,8 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): """ # A basic security setup - lockstring = "examine:perm(Wizards);edit:perm(Wizards);" \ - "delete:perm(Wizards);boot:perm(Wizards);msg:all()" + lockstring = "examine:perm(Admin);edit:perm(Admin);" \ + "delete:perm(Admin);boot:perm(Admin);msg:all()" self.locks.add(lockstring) # The ooc player cmdset diff --git a/evennia/server/initial_setup.py b/evennia/server/initial_setup.py index 4cdfc8763d..22cf720177 100644 --- a/evennia/server/initial_setup.py +++ b/evennia/server/initial_setup.py @@ -77,9 +77,9 @@ def create_objects(): god_player.swap_typeclass(player_typeclass, clean_attributes=True) god_player.basetype_setup() god_player.at_player_creation() - god_player.locks.add("examine:perm(Immortals);edit:false();delete:false();boot:false();msg:all()") + god_player.locks.add("examine:perm(Developer);edit:false();delete:false();boot:false();msg:all()") # this is necessary for quelling to work correctly. - god_player.permissions.add("Immortals") + god_player.permissions.add("Developer") # Limbo is the default "nowhere" starting room @@ -93,8 +93,8 @@ def create_objects(): god_character.id = 1 god_character.save() god_character.db.desc = _('This is User #1.') - god_character.locks.add("examine:perm(Immortals);edit:false();delete:false();boot:false();msg:all();puppet:false()") - god_character.permissions.add("Immortals") + god_character.locks.add("examine:perm(Developer);edit:false();delete:false();boot:false();msg:all();puppet:false()") + god_character.permissions.add("Developer") god_player.attributes.add("_first_login", True) god_player.attributes.add("_last_puppet", god_character) diff --git a/evennia/settings_default.py b/evennia/settings_default.py index 39a6165228..c77985b15f 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -485,15 +485,15 @@ MULTISESSION_MODE = 0 MAX_NR_CHARACTERS = 1 # The access hierarchy, in climbing order. A higher permission in the # hierarchy includes access of all levels below it. Used by the perm()/pperm() -# lock functions. -PERMISSION_HIERARCHY = ["Guests", # note-only used if GUEST_ENABLED=True - "Players", - "PlayerHelpers", - "Builders", - "Wizards", - "Immortals"] +# lock functions, which accepts both plural and singular (Admin & Admins) +PERMISSION_HIERARCHY = ["Guest", # note-only used if GUEST_ENABLED=True + "Player", + "Helper", + "Builder", + "Admin", + "Developer"] # The default permission given to all new players -PERMISSION_PLAYER_DEFAULT = "Players" +PERMISSION_PLAYER_DEFAULT = "Player" # Default sizes for client window (in number of characters), if client # is not supplying this on its own CLIENT_DEFAULT_WIDTH = 78 @@ -540,12 +540,12 @@ DEFAULT_CHANNELS = [ {"key": "Public", "aliases": ('ooc', 'pub'), "desc": "Public discussion", - "locks": "control:perm(Wizards);listen:all();send:all()"}, + "locks": "control:perm(Admin);listen:all();send:all()"}, # connection/mud info {"key": "MudInfo", "aliases": "", "desc": "Connection log", - "locks": "control:perm(Immortals);listen:perm(Wizards);send:false()"} + "locks": "control:perm(Developer);listen:perm(Admin);send:false()"} ] # Extra optional channel for receiving connection messages (" has (dis)connected"). # While the MudInfo channel will also receieve this, this channel is meant for non-staffers. From 2138e4cd70d9067b2bd46c9aceb5e765ae4847d4 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 18 Feb 2017 11:31:17 +0100 Subject: [PATCH 10/63] Run migrations! Update all Permissions and Locks to new Permission hierarchy. --- .../migrations/0008_lock_and_perm_rename.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 evennia/typeclasses/migrations/0008_lock_and_perm_rename.py diff --git a/evennia/typeclasses/migrations/0008_lock_and_perm_rename.py b/evennia/typeclasses/migrations/0008_lock_and_perm_rename.py new file mode 100644 index 0000000000..048fc67a22 --- /dev/null +++ b/evennia/typeclasses/migrations/0008_lock_and_perm_rename.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2017-01-25 22:30 +from __future__ import unicode_literals + +import re +from django.db import migrations + +def update_perms_and_locks(apps, schema_editor): + + # update all permissions + Tag = apps.get_model('typeclasses', 'Tag') + perm_map = {"guests": "guest", "players": "player", "playerhelpers":"helper", + "builders": "builder", "wizards":"admin", "immortals": "developer"} + + for perm in Tag.objects.filter(db_tagtype="permission"): + if perm.db_key in perm_map: + perm.db_key = perm_map[perm.db_key] + perm.save(update_fields=("db_key",)) + + # update all locks on all entities + apps_models = [("objects", "ObjectDB"), ("players", "PlayerDB"), ("scripts", "ScriptDB"), + ("comms", "ChannelDB")] + p_reg = re.compile(r"(?<=perm\()(\w+)(?=\))|(?<=perm_above\()(\w+)(?=\))", + re.IGNORECASE + re.UNICODE) + def _sub(match): + perm = match.group(1) + return perm_map[perm.lower()].capitalize() if perm.lower() in perm_map else perm + + for app_tuple in apps_models: + TClass = apps.get_model(*app_tuple) + for obj in TClass.objects.filter(db_lock_storage__icontains="perm"): + orig_lock = obj.db_lock_storage + repl_lock = p_reg.sub(_sub, orig_lock) + if repl_lock != orig_lock: + obj.db_lock_storage = repl_lock + obj.save(update_fields=('db_lock_storage',)) + +class Migration(migrations.Migration): + + dependencies = [ + ('typeclasses', '0007_tag_migrations_may_be_slow'), + ] + + operations = [ + + migrations.RunPython(update_perms_and_locks) + ] From 8ee817ce8bcf7e0ffce3e344aec19cfb9b42c665 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 18 Feb 2017 12:00:27 +0100 Subject: [PATCH 11/63] Fix lingering references to old perms/locks to resolve unittests. --- evennia/commands/default/building.py | 2 +- evennia/commands/default/player.py | 4 ++-- evennia/commands/default/tests.py | 6 +++--- evennia/contrib/rpsystem.py | 2 +- evennia/players/admin.py | 2 +- evennia/server/profiling/dummyrunner.py | 6 +++--- evennia/server/profiling/settings_mixin.py | 2 +- evennia/utils/test_resources.py | 4 ++-- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index a020b21483..b539dbc079 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -2625,7 +2625,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS): return elif isinstance(prototype, dict): # we got the prototype on the command line. We must make sure to not allow - # the 'exec' key unless we are immortals or higher. + # the 'exec' key unless we are developers or higher. if "exec" in prototype and not self.caller.check_permstring("Developer"): self.caller.msg("Spawn aborted: You don't have access to use the 'exec' prototype key.") return diff --git a/evennia/commands/default/player.py b/evennia/commands/default/player.py index 07d43cdf7c..a4c0ed9bfc 100644 --- a/evennia/commands/default/player.py +++ b/evennia/commands/default/player.py @@ -163,7 +163,7 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS): location=start_location, home=default_home, permissions=permissions) - # only allow creator (and immortals) to puppet this char + # only allow creator (and developers) to puppet this char new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" % (new_character.id, player.id)) player.db._playable_characters.append(new_character) @@ -403,7 +403,7 @@ class CmdWho(COMMAND_DEFAULT_CLASS): if self.cmdstring == "doing": show_session_data = False else: - show_session_data = player.check_permstring("Developer") or player.check_permstring("Wizards") + show_session_data = player.check_permstring("Developer") or player.check_permstring("Admins") nplayers = (SESSIONS.player_count()) if show_session_data: diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index 2bb227873a..08b21f279e 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -156,8 +156,8 @@ class TestAdmin(CommandTest): self.call(admin.CmdEmit(), "Char2 = Test", "Emitted to Char2:\nTest") def test_perm(self): - self.call(admin.CmdPerm(), "Obj = Builders", "Permission 'Builders' given to Obj (the Object/Character).") - self.call(admin.CmdPerm(), "Char2 = Builders", "Permission 'Builders' given to Char2 (the Object/Character).") + self.call(admin.CmdPerm(), "Obj = Builder", "Permission 'Builder' given to Obj (the Object/Character).") + self.call(admin.CmdPerm(), "Char2 = Builder", "Permission 'Builder' given to Char2 (the Object/Character).") def test_wall(self): self.call(admin.CmdWall(), "Test", "Announcing to all connected players ...") @@ -203,7 +203,7 @@ class TestPlayer(CommandTest): self.call(player.CmdCharCreate(), "Test1=Test char", "Created new character Test1. Use @ic Test1 to enter the game", caller=self.player) def test_quell(self): - self.call(player.CmdQuell(), "", "Quelling to current puppet's permissions (immortals).", caller=self.player) + self.call(player.CmdQuell(), "", "Quelling to current puppet's permissions (developer).", caller=self.player) class TestBuilding(CommandTest): diff --git a/evennia/contrib/rpsystem.py b/evennia/contrib/rpsystem.py index 7f453d67be..6bdf93d00d 100644 --- a/evennia/contrib/rpsystem.py +++ b/evennia/contrib/rpsystem.py @@ -1203,7 +1203,7 @@ class ContribRPObject(DefaultObject): nofound_string (str): optional custom string for not-found error message. multimatch_string (str): optional custom string for multimatch error header. use_dbref (bool or None): If None, only turn off use_dbref if we are of a lower - permission than Builders. Otherwise, honor the True/False value. + permission than Builder. Otherwise, honor the True/False value. Returns: match (Object, None or list): will return an Object/None if `quiet=False`, diff --git a/evennia/players/admin.py b/evennia/players/admin.py index 227a3828f7..6e43e6dd76 100644 --- a/evennia/players/admin.py +++ b/evennia/players/admin.py @@ -124,7 +124,7 @@ class PlayerForm(forms.ModelForm): help_text="In-game permissions. A comma-separated list of text " "strings checked by certain locks. They are often used for " "hierarchies, such as letting a Player have permission " - "'Wizards', 'Builders' etc. A Player permission can be " + "'Admin', 'Builder' etc. A Player permission can be " "overloaded by the permissions of a controlled Character. " "Normal players use 'Players' by default.") diff --git a/evennia/server/profiling/dummyrunner.py b/evennia/server/profiling/dummyrunner.py index 076366cfbe..f9676071b7 100644 --- a/evennia/server/profiling/dummyrunner.py +++ b/evennia/server/profiling/dummyrunner.py @@ -15,7 +15,7 @@ full step-by-step setup help. Basically (for testing default Evennia): - Use an empty/testing database. - - set PERMISSION_PLAYER_DEFAULT = "Builders" + - set PERMISSION_PLAYER_DEFAULT = "Builder" - start server, eventually with profiling active - launch this client runner @@ -95,7 +95,7 @@ ERROR_NO_MIXIN = \ from evennia.server.profiling.settings_mixin import * This will change the settings in the following way: - - change PERMISSION_PLAYER_DEFAULT to 'Immortals' to allow clients + - change PERMISSION_PLAYER_DEFAULT to 'Developer' to allow clients to test all commands - change PASSWORD_HASHERS to use a faster (but less safe) algorithm when creating large numbers of accounts at the same time @@ -136,7 +136,7 @@ Setup: `evennia migrate`) 2) in server/conf/settings.py, add - PERMISSION_PLAYER_DEFAULT="Builders" + PERMISSION_PLAYER_DEFAULT="Builder" This is so that the dummy players can test building operations. You can also customize the dummyrunner by modifying a setting diff --git a/evennia/server/profiling/settings_mixin.py b/evennia/server/profiling/settings_mixin.py index 523eb893e1..dfd9b766b4 100644 --- a/evennia/server/profiling/settings_mixin.py +++ b/evennia/server/profiling/settings_mixin.py @@ -18,4 +18,4 @@ PASSWORD_HASHERS = ( 'django.contrib.auth.hashers.MD5PasswordHasher', ) # make dummy clients able to test all commands -PERMISSION_PLAYER_DEFAULT = "Immortals" +PERMISSION_PLAYER_DEFAULT = "Developer" diff --git a/evennia/utils/test_resources.py b/evennia/utils/test_resources.py index 03535551d4..296a10b154 100644 --- a/evennia/utils/test_resources.py +++ b/evennia/utils/test_resources.py @@ -39,14 +39,14 @@ class EvenniaTest(TestCase): self.obj1 = create.create_object(self.object_typeclass, key="Obj", location=self.room1, home=self.room1) self.obj2 = create.create_object(self.object_typeclass, key="Obj2", location=self.room1, home=self.room1) self.char1 = create.create_object(self.character_typeclass, key="Char", location=self.room1, home=self.room1) - self.char1.permissions.add("Immortals") + self.char1.permissions.add("Developer") self.char2 = create.create_object(self.character_typeclass, key="Char2", location=self.room1, home=self.room1) self.char1.player = self.player self.player.db._last_puppet = self.char1 self.char2.player = self.player2 self.player2.db._last_puppet = self.char2 self.script = create.create_script(self.script_typeclass, key="Script") - self.player.permissions.add("Immortals") + self.player.permissions.add("Developer") # set up a fake session From e34d32bd60122db86bd35f9df5a5849a8c7d478b Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 2 Apr 2017 22:11:54 +0200 Subject: [PATCH 12/63] Add ability to batch-add tags, permissions and aliases along with category in the object/player create functions as well as in the tag handler. This is useful for direct assignment of categories from prototypes. --- evennia/objects/objects.py | 6 +++--- evennia/players/players.py | 2 +- evennia/typeclasses/tags.py | 34 +++++++++++++++++++++++++++++++++- evennia/utils/create.py | 6 +++--- evennia/utils/spawner.py | 8 ++++++-- evennia/utils/utils.py | 4 ++++ 6 files changed, 50 insertions(+), 10 deletions(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index c2c95798b8..e527892b58 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -944,17 +944,17 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): self.save(update_fields=updates) if cdict.get("permissions"): - self.permissions.add(cdict["permissions"]) + self.permissions.batch_add(cdict["permissions"]) if cdict.get("locks"): self.locks.add(cdict["locks"]) if cdict.get("aliases"): - self.aliases.add(cdict["aliases"]) + self.aliases.batch_add(cdict["aliases"]) if cdict.get("location"): cdict["location"].at_object_receive(self, None) self.at_after_move(None) if cdict.get("tags"): # this should be a list of tags - self.tags.add(cdict["tags"]) + self.tags.batch_add(cdict["tags"]) if cdict.get("attributes"): # this should be a dict of attrname:value keys, values = list(cdict["attributes"]), listvalues(cdict["attributes"]) diff --git a/evennia/players/players.py b/evennia/players/players.py index 970e9748ea..fa7fa9840e 100644 --- a/evennia/players/players.py +++ b/evennia/players/players.py @@ -624,7 +624,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): permissions = cdict["permissions"] del self._createdict - self.permissions.add(permissions) + self.permissions.batch_add(permissions) def at_access(self, result, accessing_obj, access_type, **kwargs): """ diff --git a/evennia/typeclasses/tags.py b/evennia/typeclasses/tags.py index e9bd24eed0..1f1896f8a9 100644 --- a/evennia/typeclasses/tags.py +++ b/evennia/typeclasses/tags.py @@ -10,6 +10,7 @@ respective handlers. """ from builtins import object +from collections import defaultdict from django.conf import settings from django.db import models @@ -239,7 +240,7 @@ class TagHandler(object): category (str, optional): Category of Tag. `None` is the default category. data (str, optional): Info text about the tag(s) added. This can not be used to store object-unique info but only - eventual info about the text itself. + eventual info about the tag itself. Notes: If the tag + category combination matches an already @@ -356,6 +357,37 @@ class TagHandler(object): else: return [to_str(tag.db_key) for tag in tags] + def batch_add(self, *tuples): + """ + Batch-add tags from a list of tuples. + + Args: + tuples (tuple or str): Any number of `tagstr` keys, `(keystr, category)` or + `(keystr, category, data)` tuples. + + Notes: + This will generate a mimimal number of self.add calls, + based on the number of categories involved (including + `None`) (data is not unique and may be overwritten by the content + of a latter tuple with the same category). + + """ + keys = defaultdict(list) + data = {} + for tup in tuples: + tup = make_iter(tup) + nlen = len(tup) + if nlen == 1: # just a key + keys[None].append(tup[0]) + elif nlen == 2: + keys[tup[1]].append(tup[0]) + else: + keys[tup[1]].append(tup[0]) + data[tup[1]] = tup[2] # overwrite previous + for category, key in keys.iteritems(): + self.add(tag=key, category=category, data=data.get(category, None)) + + def __str__(self): return ",".join(self.all()) diff --git a/evennia/utils/create.py b/evennia/utils/create.py index 7445fa20a5..9e2289a059 100644 --- a/evennia/utils/create.py +++ b/evennia/utils/create.py @@ -65,10 +65,10 @@ def create_object(typeclass=None, key=None, location=None, home=None, #dbref will be set. home (Object or str): Obj or #dbref to use as the object's home location. - permissions (str): A comma-separated string of permissions. + permissions (list): A list of permission strings or tuples (permstring, category). locks (str): one or more lockstrings, separated by semicolons. - aliases (list): A list of alternative keys. - tags (list): List of tag keys (using no category). + aliases (list): A list of alternative keys or tuples (aliasstring, category). + tags (list): List of tag keys or tuples (tagkey, category). destination (Object or str): Obj or #dbref to use as an Exit's target. report_to (Object): The object to return error messages to. diff --git a/evennia/utils/spawner.py b/evennia/utils/spawner.py index 28274e5531..c6e5e0dea3 100644 --- a/evennia/utils/spawner.py +++ b/evennia/utils/spawner.py @@ -182,9 +182,11 @@ def _batch_create_object(*objparams): def spawn(*prototypes, **kwargs): """ - Spawn a number of prototyped objects. Each argument should be a - prototype dictionary. + Spawn a number of prototyped objects. + Args: + prototypes (dict): Each argument should be a prototype + dictionary. Kwargs: prototype_modules (str or list): A python-path to a prototype module, or a list of such paths. These will be used to build @@ -196,6 +198,7 @@ def spawn(*prototypes, **kwargs): prototypes from prototype_modules. return_prototypes (bool): Only return a list of the prototype-parents (no object creation happens) + """ protparents = {} @@ -249,6 +252,7 @@ def spawn(*prototypes, **kwargs): alias_string = aliasval() if callable(aliasval) else aliasval tagval = prot.pop("tags", "") tags = tagval() if callable(tagval) else tagval + exval = prot.pop("exec", "") execs = make_iter(exval() if callable(exval) else exval) diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 79add32052..5bc3a20fc9 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -1834,3 +1834,7 @@ def get_game_dir_path(): else: os.chdir(os.pardir) raise RuntimeError("server/conf/settings.py not found: Must start from inside game dir.") + + + + From 58589126b8c3de4068f105db33f74442cf17d00a Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 6 Apr 2017 19:53:57 +0200 Subject: [PATCH 13/63] Reworked spawner with new batch_add functionality for both tags and attributes. --- evennia/typeclasses/attributes.py | 64 +++++++++++++++---------------- evennia/typeclasses/tags.py | 9 ++--- evennia/utils/spawner.py | 37 +++++++++++------- 3 files changed, 60 insertions(+), 50 deletions(-) diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index 58755b4955..fc0741b716 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -508,47 +508,47 @@ class AttributeHandler(object): # update cache self._setcache(keystr, category, new_attr) - def batch_add(self, key, value, category=None, lockstring="", - strattr=False, accessing_obj=None, default_access=True): + def batch_add(self, *args, **kwargs): """ Batch-version of `add()`. This is more efficient than repeat-calling add when having many Attributes to add. Args: - key (list): A list of Attribute names to add. - value (list): A list of values. It must match the `key` - list. If `strattr` keyword is set, all entries *must* be - strings. - category (str, optional): The category for the Attribute. - The default `None` is the normal category used. - lockstring (str, optional): A lock string limiting access - to the attribute. - strattr (bool, optional): Make this a string-only Attribute. - This is only ever useful for optimization purposes. - accessing_obj (object, optional): An entity to check for - the `attrcreate` access-type. If not passing, this method - will be exited. - default_access (bool, optional): What access to grant if - `accessing_obj` is given but no lock of the type - `attrcreate` is defined on the Attribute in question. + indata (tuple): Tuples of varying length representing the + Attribute to add to this object. + - `(key, value)` + - `(key, value, category)` + - `(key, value, category, lockstring)` + - `(key, value, category, lockstring, default_access)` + + Kwargs: + strattr (bool): If `True`, value must be a string. This + will save the value without pickling which is less + flexible but faster to search (not often used except + internally). Raises: - RuntimeError: If `key` and `value` lists are not of the - same lengths. + RuntimeError: If trying to pass a non-iterable as argument. + + Notes: + The indata tuple order matters, so if you want a lockstring + but no category, set the category to `None`. This method + does not have the ability to check editing permissions like + normal .add does, and is mainly used internally. It does not + use the normal self.add but apply the Attributes directly + to the database. + """ - if accessing_obj and not self.obj.access(accessing_obj, self._attrcreate, default=default_access): - # check create access - return - - keys, values = make_iter(key), make_iter(value) - - if len(keys) != len(values): - raise RuntimeError("AttributeHandler.add(): key and value lists of different length: %s vs %s" % key, value) - category = category.strip().lower() if category is not None else None new_attrobjs = [] - for ikey, keystr in enumerate(keys): - keystr = keystr.strip().lower() - new_value = values[ikey] + strattr = kwargs.get('strattr', False) + for tup in args: + if not is_iter(tup) or len(tup) < 2: + raise RuntimeError("batch_add requires iterables as arguments (got %r)." % tup) + ntup = len(tup) + keystr = str(tup[0]).strip().lower() + new_value = tup[1] + category = str(tup[2]).strip().lower() if tup > 2 else None + lockstring = tup[3] if tup > 3 else "" attr_objs = self._getcache(keystr, category) diff --git a/evennia/typeclasses/tags.py b/evennia/typeclasses/tags.py index 1f1896f8a9..67f0662f28 100644 --- a/evennia/typeclasses/tags.py +++ b/evennia/typeclasses/tags.py @@ -357,7 +357,7 @@ class TagHandler(object): else: return [to_str(tag.db_key) for tag in tags] - def batch_add(self, *tuples): + def batch_add(self, *args): """ Batch-add tags from a list of tuples. @@ -374,20 +374,19 @@ class TagHandler(object): """ keys = defaultdict(list) data = {} - for tup in tuples: + for tup in args: tup = make_iter(tup) nlen = len(tup) - if nlen == 1: # just a key + if nlen == 1: # just a key keys[None].append(tup[0]) elif nlen == 2: keys[tup[1]].append(tup[0]) else: keys[tup[1]].append(tup[0]) - data[tup[1]] = tup[2] # overwrite previous + data[tup[1]] = tup[2] # overwrite previous for category, key in keys.iteritems(): self.add(tag=key, category=category, data=data.get(category, None)) - def __str__(self): return ",".join(self.all()) diff --git a/evennia/utils/spawner.py b/evennia/utils/spawner.py index c6e5e0dea3..1d9b3b38c4 100644 --- a/evennia/utils/spawner.py +++ b/evennia/utils/spawner.py @@ -16,7 +16,8 @@ GOBLIN = { "resists": ["cold", "poison"], "attacks": ["fists"], "weaknesses": ["fire", "light"] - "tags:": ["mob", "evil"] + "tags": ["mob", "evil", ('greenskin','mob')] + "args": [("weapon", "sword")] } ``` @@ -31,21 +32,28 @@ Possible keywords are: permissions - string or list of permission strings locks - a lock-string aliases - string or list of strings - tags - string or list of strings - ndb_ - value of a nattribute (ndb_ is stripped) exec - this is a string of python code to execute or a list of such codes. This can be used e.g. to trigger custom handlers on the object. The - execution environment contains 'evennia' for the library and 'obj' - for accessing the just created object. - any other keywords are interpreted as Attributes and their values. + execution namespace contains 'evennia' for the library and 'obj' + tags - string or list of strings or tuples `(tagstr, category)`. Plain + strings will be result in tags with no category (default tags). + args - tuple or list of tuples of Attributes to add. This form allows + more complex Attributes to be set. Tuples at least specify `(key, value)` + but can also specify up to `(key, value, category, lockstring)`. If + you want to specify a lockstring but not a category, set the category + to `None`. + ndb_ - value of a nattribute (ndb_ is stripped) + other - any other name is interpreted as the key of an Attribute with + its value. Such Attributes have no categories. Each value can also be a callable that takes no arguments. It should return the value to enter into the field and will be called every time -the prototype is used to spawn an object. +the prototype is used to spawn an object. Note, if you want to store +a callable in an Attribute, embed it in a tuple to the `args` keyword. -By specifying a prototype, the child will inherit all prototype slots -it does not explicitly define itself, while overloading those that it -does specify. +By specifying the "prototype" key, the prototype becomes a child of +that prototype, inheritng all prototype slots it does not explicitly +define itself, while overloading those that it does specify. ```python GOBLIN_WIZARD = { @@ -252,6 +260,8 @@ def spawn(*prototypes, **kwargs): alias_string = aliasval() if callable(aliasval) else aliasval tagval = prot.pop("tags", "") tags = tagval() if callable(tagval) else tagval + attrval = prot.pop("args", "") + attributes = attrval() if callable(tagval) else attrval exval = prot.pop("exec", "") execs = make_iter(exval() if callable(exval) else exval) @@ -261,9 +271,10 @@ def spawn(*prototypes, **kwargs): for key, value in prot.items() if key.startswith("ndb_")) # the rest are attributes - attributes = dict((key, value() if callable(value) else value) - for key, value in prot.items() - if not (key in _CREATE_OBJECT_KWARGS or key.startswith("ndb_"))) + simple_attributes = [(key, value) if callable(value) else value + for key, value in prot.items() if not key.startswith("ndb_")] + attributes = attributes + simple_attributes + attributes = [tup for tup in attributes if not tup[0] in _CREATE_OBJECT_KWARGS] # pack for call into _batch_create_object objsparams.append((create_kwargs, permission_string, lock_string, From d44f7c46704c39e9a8a00ecd66aceacb13a8a8dc Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 9 Apr 2017 10:52:59 +0200 Subject: [PATCH 14/63] Clean up docstring of _batch_create in spawner. --- evennia/objects/objects.py | 11 +++++------ evennia/utils/spawner.py | 38 +++++++++++++++++++++++++++++--------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index e527892b58..4db1f63172 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -21,7 +21,8 @@ from evennia.commands.cmdsethandler import CmdSetHandler from evennia.commands import cmdhandler from evennia.utils import logger from evennia.utils.utils import (variable_from_module, lazy_property, - make_iter, to_unicode, calledby, is_iter) + make_iter, to_unicode, is_iter) +from django.utils.translation import ugettext as _ _MULTISESSION_MODE = settings.MULTISESSION_MODE @@ -32,8 +33,6 @@ _AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', # the sessid_max is based on the length of the db_sessid csv field (excluding commas) _SESSID_MAX = 16 if _MULTISESSION_MODE in (1, 3) else 1 -from django.utils.translation import ugettext as _ - class ObjectSessionHandler(object): """ @@ -816,8 +815,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ key = self.key num = 1 - for _ in (obj for obj in self.location.contents - if obj.key.startswith(key) and obj.key.lstrip(key).isdigit()): + for inum in (obj for obj in self.location.contents + if obj.key.startswith(key) and obj.key.lstrip(key).isdigit()): num += 1 return "%s%03i" % (key, num) new_key = new_key or find_clone_key() @@ -993,7 +992,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): "get:all()", # pick up object "call:true()", # allow to call commands on this object "tell:perm(Admin)", # allow emits to this object - "puppet:pperm(Developer)"])) # lock down puppeting only to staff by default + "puppet:pperm(Developer)"])) # lock down puppeting only to staff by default def basetype_posthook_setup(self): """ diff --git a/evennia/utils/spawner.py b/evennia/utils/spawner.py index 1d9b3b38c4..e355dcbe22 100644 --- a/evennia/utils/spawner.py +++ b/evennia/utils/spawner.py @@ -90,10 +90,6 @@ many traits with a normal *goblin*. from __future__ import print_function import copy -# TODO -# sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -# os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings' - from django.conf import settings from random import randint import evennia @@ -102,7 +98,9 @@ from evennia.utils.utils import make_iter, all_from_module, dbid_to_obj _CREATE_OBJECT_KWARGS = ("key", "location", "home", "destination") -_handle_dbref = lambda inp: dbid_to_obj(inp, ObjectDB) + +def _handle_dbref(inp): + dbid_to_obj(inp, ObjectDB) def _validate_prototype(key, prototype, protparents, visited): @@ -150,13 +148,35 @@ def _batch_create_object(*objparams): so make sure the spawned Typeclass works before using this! Args: - objsparams (any): Each argument should be a tuple of arguments + objsparams (tuple): Parameters for the respective creation/add + handlers in the following order: + - `create_kwargs` (dict): For use as new_obj = `ObjectDB(**create_kwargs)`. + - `permissions` (str): Permission string used with `new_obj.batch_add(permission)`. + - `lockstring` (str): Lockstring used with `new_obj.locks.add(lockstring)`. + - `aliases` (list): A list of alias strings for + adding with `new_object.aliases.batch_add(*aliases)`. + - `nattributes` (list): list of tuples `(key, value)` to be loop-added to + add with `new_obj.nattributes.add(*tuple)`. + - `attributes` (list): list of tuples `(key, value[,category[,lockstring]])` for + adding with `new_obj.attributes.batch_add(*attributes)`. + - `tags` (list): list of tuples `(key, category)` for adding + with `new_obj.tags.batch_add(*tags)`. + - `execs` (list): Code strings to execute together with the creation + of each object. They will be executed with `evennia` and `obj` + (the newly created object) available in the namespace. Execution + will happend after all other properties have been assigned and + is intended for calling custom handlers etc. for the respective creation/add handlers in the following - order: (create, permissions, locks, aliases, nattributes, - attributes) + order: (create_kwargs, permissions, locks, aliases, nattributes, + attributes, tags, execs) + Returns: objects (list): A list of created objects + Notes: + The `exec` list will execute arbitrary python code so don't allow this to be availble to + unprivileged users! + """ # bulk create all objects in one go @@ -272,7 +292,7 @@ def spawn(*prototypes, **kwargs): # the rest are attributes simple_attributes = [(key, value) if callable(value) else value - for key, value in prot.items() if not key.startswith("ndb_")] + for key, value in prot.items() if not key.startswith("ndb_")] attributes = attributes + simple_attributes attributes = [tup for tup in attributes if not tup[0] in _CREATE_OBJECT_KWARGS] From ee4dd20fd9f43e0c9e49aa281270cc368cc13fb0 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 9 Apr 2017 18:53:39 +0200 Subject: [PATCH 15/63] Fixes to batch_add mechanisms and spawner batch creation for passing unittests. --- evennia/objects/objects.py | 9 +++--- evennia/players/players.py | 2 +- evennia/typeclasses/attributes.py | 50 ++++++++++++++++++++----------- evennia/utils/spawner.py | 8 ++--- 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 4db1f63172..814d80acfa 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -943,21 +943,20 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): self.save(update_fields=updates) if cdict.get("permissions"): - self.permissions.batch_add(cdict["permissions"]) + self.permissions.batch_add(*cdict["permissions"]) if cdict.get("locks"): self.locks.add(cdict["locks"]) if cdict.get("aliases"): - self.aliases.batch_add(cdict["aliases"]) + self.aliases.batch_add(*cdict["aliases"]) if cdict.get("location"): cdict["location"].at_object_receive(self, None) self.at_after_move(None) if cdict.get("tags"): # this should be a list of tags - self.tags.batch_add(cdict["tags"]) + self.tags.batch_add(*cdict["tags"]) if cdict.get("attributes"): # this should be a dict of attrname:value - keys, values = list(cdict["attributes"]), listvalues(cdict["attributes"]) - self.attributes.batch_add(keys, values) + self.attributes.batch_add(*cdict["attributes"]) if cdict.get("nattributes"): # this should be a dict of nattrname:value for key, value in cdict["nattributes"].items(): diff --git a/evennia/players/players.py b/evennia/players/players.py index fa7fa9840e..0ea588dfe7 100644 --- a/evennia/players/players.py +++ b/evennia/players/players.py @@ -624,7 +624,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): permissions = cdict["permissions"] del self._createdict - self.permissions.batch_add(permissions) + self.permissions.batch_add(*permissions) def at_access(self, result, accessing_obj, access_type, **kwargs): """ diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index fc0741b716..d24fcbe845 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -10,6 +10,7 @@ which is a non-db version of Attributes. """ from builtins import object import re +import fnmatch import weakref from django.db import models @@ -20,7 +21,7 @@ from evennia.locks.lockhandler import LockHandler from evennia.utils.idmapper.models import SharedMemoryModel from evennia.utils.dbserialize import to_pickle, from_pickle from evennia.utils.picklefield import PickledObjectField -from evennia.utils.utils import lazy_property, to_str, make_iter +from evennia.utils.utils import lazy_property, to_str, make_iter, is_iter _TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE @@ -221,7 +222,11 @@ class AttributeHandler(object): query = {"%s__id" % self._model: self._objid, "attribute__db_model": self._model, "attribute__db_attrtype": self._attrtype} - attrs = [conn.attribute for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)] + attrs = [ + conn.attribute for conn in getattr( + self.obj, + self._m2m_fieldname).through.objects.filter( + **query)] self._cache = dict(("%s-%s" % (to_str(attr.db_key).lower(), attr.db_category.lower() if attr.db_category else None), attr) for attr in attrs) @@ -421,6 +426,7 @@ class AttributeHandler(object): class RetDefault(object): """Holds default values""" + def __init__(self): self.key = None self.value = default @@ -441,7 +447,8 @@ class AttributeHandler(object): if accessing_obj: # check 'attrread' locks - ret = [attr for attr in ret if attr.access(accessing_obj, self._attrread, default=default_access)] + ret = [attr for attr in ret if attr.access(accessing_obj, + self._attrread, default=default_access)] if strattr: ret = ret if return_obj else [attr.strvalue for attr in ret if attr] else: @@ -473,7 +480,8 @@ class AttributeHandler(object): `attrcreate` is defined on the Attribute in question. """ - if accessing_obj and not self.obj.access(accessing_obj, self._attrcreate, default=default_access): + if accessing_obj and not self.obj.access(accessing_obj, self._attrcreate, + default=default_access): # check create access return @@ -547,14 +555,17 @@ class AttributeHandler(object): ntup = len(tup) keystr = str(tup[0]).strip().lower() new_value = tup[1] - category = str(tup[2]).strip().lower() if tup > 2 else None - lockstring = tup[3] if tup > 3 else "" + category = str(tup[2]).strip().lower() if ntup > 2 else None + lockstring = tup[3] if ntup > 3 else "" attr_objs = self._getcache(keystr, category) if attr_objs: attr_obj = attr_objs[0] # update an existing attribute object + attr_obj.db_category = category + attr_obj.db_lock_storage = lockstring + attr_obj.save(update_fields=["db_category", "db_lock_storage"]) if strattr: # store as a simple string (will not notify OOB handlers) attr_obj.db_strvalue = new_value @@ -569,7 +580,8 @@ class AttributeHandler(object): "db_model": self._model, "db_attrtype": self._attrtype, "db_value": None if strattr else to_pickle(new_value), - "db_strvalue": value if strattr else None} + "db_strvalue": new_value if strattr else None, + "db_lock_storage": lockstring} new_attr = Attribute(**kwargs) new_attr.save() new_attrobjs.append(new_attr) @@ -605,8 +617,11 @@ class AttributeHandler(object): for keystr in make_iter(key): attr_objs = self._getcache(keystr, category) for attr_obj in attr_objs: - if not (accessing_obj and not attr_obj.access(accessing_obj, - self._attredit, default=default_access)): + if not ( + accessing_obj and not attr_obj.access( + accessing_obj, + self._attredit, + default=default_access)): try: attr_obj.delete() except AssertionError: @@ -698,7 +713,6 @@ Custom arg markers $N argument position (1-99) """ -import fnmatch _RE_NICK_ARG = re.compile(r"\\(\$)([1-9][0-9]?)") _RE_NICK_TEMPLATE_ARG = re.compile(r"(\$)([1-9][0-9]?)") _RE_NICK_SPACE = re.compile(r"\\ ") @@ -816,7 +830,8 @@ class NickHandler(AttributeHandler): else: retval = super(NickHandler, self).get(key=key, category=category, **kwargs) if retval: - return retval[3] if isinstance(retval, tuple) else [tup[3] for tup in make_iter(retval)] + return retval[3] if isinstance(retval, tuple) else \ + [tup[3] for tup in make_iter(retval)] return None def add(self, key, replacement, category="inputline", **kwargs): @@ -836,7 +851,8 @@ class NickHandler(AttributeHandler): nick_regex, nick_template = initialize_nick_templates(key + " $1", replacement + " $1") else: nick_regex, nick_template = initialize_nick_templates(key, replacement) - super(NickHandler, self).add(key, (nick_regex, nick_template, key, replacement), category=category, **kwargs) + super(NickHandler, self).add(key, (nick_regex, nick_template, key, replacement), + category=category, **kwargs) def remove(self, key, category="inputline", **kwargs): """ @@ -873,13 +889,12 @@ class NickHandler(AttributeHandler): """ nicks = {} for category in make_iter(categories): - nicks.update({nick.key: nick - for nick in make_iter(self.get(category=category, return_obj=True)) if nick and nick.key}) + nicks.update({nick.key: nick for nick in make_iter( + self.get(category=category, return_obj=True)) if nick and nick.key}) if include_player and self.obj.has_player: for category in make_iter(categories): - nicks.update({nick.key: nick - for nick in make_iter(self.obj.player.nicks.get(category=category, return_obj=True)) - if nick and nick.key}) + nicks.update({nick.key: nick for nick in make_iter(self.obj.player.nicks.get( + category=category, return_obj=True)) if nick and nick.key}) for key, nick in nicks.iteritems(): nick_regex, template, _, _ = nick.value regex = self._regex_cache.get(nick_regex) @@ -900,6 +915,7 @@ class NAttributeHandler(object): by the `.ndb` handler in the same way as `.db` does for the `AttributeHandler`. """ + def __init__(self, obj): """ Initialized on the object diff --git a/evennia/utils/spawner.py b/evennia/utils/spawner.py index e355dcbe22..a089e5dc34 100644 --- a/evennia/utils/spawner.py +++ b/evennia/utils/spawner.py @@ -272,15 +272,15 @@ def spawn(*prototypes, **kwargs): create_kwargs["db_typeclass_path"] = typval() if callable(typval) else typval # extract calls to handlers - permval = prot.pop("permissions", "") + permval = prot.pop("permissions", []) permission_string = permval() if callable(permval) else permval lockval = prot.pop("locks", "") lock_string = lockval() if callable(lockval) else lockval aliasval = prot.pop("aliases", "") alias_string = aliasval() if callable(aliasval) else aliasval - tagval = prot.pop("tags", "") + tagval = prot.pop("tags", []) tags = tagval() if callable(tagval) else tagval - attrval = prot.pop("args", "") + attrval = prot.pop("args", []) attributes = attrval() if callable(tagval) else attrval exval = prot.pop("exec", "") @@ -291,7 +291,7 @@ def spawn(*prototypes, **kwargs): for key, value in prot.items() if key.startswith("ndb_")) # the rest are attributes - simple_attributes = [(key, value) if callable(value) else value + simple_attributes = [(key, value()) if callable(value) else (key, value) for key, value in prot.items() if not key.startswith("ndb_")] attributes = attributes + simple_attributes attributes = [tup for tup in attributes if not tup[0] in _CREATE_OBJECT_KWARGS] From 00b8dd4881c894a58840051b1c742b6b7313611b Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 16 Apr 2017 17:31:13 +0200 Subject: [PATCH 16/63] Add non-working, initial concept for olc system. --- evennia/utils/olc/__init__.py | 0 evennia/utils/olc/olc.py | 148 +++++++++++++++ evennia/utils/olc/olc_fields.py | 291 ++++++++++++++++++++++++++++++ evennia/utils/olc/olc_menutree.py | 83 +++++++++ evennia/utils/olc/olc_utils.py | 13 ++ 5 files changed, 535 insertions(+) create mode 100644 evennia/utils/olc/__init__.py create mode 100644 evennia/utils/olc/olc.py create mode 100644 evennia/utils/olc/olc_fields.py create mode 100644 evennia/utils/olc/olc_menutree.py create mode 100644 evennia/utils/olc/olc_utils.py diff --git a/evennia/utils/olc/__init__.py b/evennia/utils/olc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/evennia/utils/olc/olc.py b/evennia/utils/olc/olc.py new file mode 100644 index 0000000000..5e16bd8b2f --- /dev/null +++ b/evennia/utils/olc/olc.py @@ -0,0 +1,148 @@ +""" +OLC - On-Line Creation + +This module is the core of the Evennia online creation helper system. +This is a resource intended for players with build privileges. + +While the OLC command can be used to start the OLC "from the top", the +system is also intended to be plugged in to enhance existing build commands +with a more menu-like building style. + +Functionality: + +- Prototype management: Allows to create and edit Prototype +dictionaries. Can store such Prototypes on the Builder Player as an Attribute +or centrally on a central store that all builders can fetch prototypes from. +- Creates a new entity either from an existing prototype or by creating the +prototype on the fly for the sake of that single object (the prototype can +then also be saved for future use). +- Recording of session, for performing a series of recorded build actions in sequence. +Stored so as to be possible to reproduce. +- Export of objects created in recording mode to a batchcode file (Immortals only). + + +""" + +from collections import OrderedDict +from time import time +from evennia.utils.evmenu import EvMenu +from evennia.commands.command import Command + + +# OLC settings +_SHOW_PROMPT = True # settings.OLC_SHOW_PROMPT +_DEFAULT_PROMPT = "" # settings.OLC_DEFAULT_PROMPT +_LEN_HISTORY = 10 # settings.OLC_HISTORY_LENGTH + + +# OLC Session + +def _new_session(): + + """ + This generates an empty olcsession structure, which is used to hold state + information in the olc but which can also be pickled. + + Returns: + olcsession (dict): An empty OLCSession. + + Notes: + This is a customized dict which the Attribute system will + understand how to pickle and depickle since it provides + iteration. + """ + return { + # header info + "caller": None, # the current user of this session + "modified": time.now(), # last time this olcsession was active + "db_model": None, # currently unused, ObjectDB for now + "prompt_template": _DEFAULT_PROMPT, # prompt display + "olcfields": OrderedDict(), # registered OLCFields. Order matters + "prototype_key": "", # current active prototype key + } + + +def _update_prompt(osession): + """ + Update the OLC status prompt. + + Returns: + prompt (str): The prompt based on the + prompt template, populated with + the olcsession state. + + """ + return "" + + +def search_entity(osession, query): + """ + Perform a query for a specified entity. Which type of entity is determined by the osession + state. + + Args: + query (str): This is a string, a #dbref or an extended search + + """ + osession['db_model'].__class__. + + + + +def display_prototype(osession): + """ + Display prototype fields according to the order of the registered olcfields. + + """ + # TODO: Simple one column display to begin with - make multi-column later + pkey = osession['prototype_key'] + outtxt = ["=== {pkey} ===".format(pkey=pkey)] + for field in osession['olcfields'].values(): + fname, flabel, fvalue = field.name, field.label, field.display() + outtxt.append(" {fieldname} ({label}): {value}".format(fieldname=fname, + label=flabel, value=fvalue)) + return '\n'.join(outtxt) + + +def display_field_value(osession, fieldname): + """ + Display info about a specific field. + """ + field = osession['olcfields'].get(fieldname, None) + if field: + return "{fieldname}: {value}".format(fieldname=field.name, value=field.display()) + + +# Access function + +def OLC(caller, target=None, startnode=None): + """ + This function is a common entry-point into the OLC menu system. It is used + by Evennia systems to jump into the different possible start points of the + OLC menu tree depending on what info is already available. + + Args: + caller (Object or Player): The one using the olc. + target (Object, optional): Object to operate on, if any is known. + startnode (str, optional): Where in the menu tree to start. If unset, + will be decided by whether target is given or not. + + """ + startnode = startnode or (target and "node_edit_top") or "node_top" + EvMenu(caller, "evennia.utils.olc.olc_menu", startnode=startnode, target=target) + + +class CmdOLC(Command): + """ + Test OLC + + Usage: + olc [target] + + Starts the olc to create a new object or to modify an existing one. + + """ + key = "olc" + def func(self): + OLC(self.caller, target=self.args) + diff --git a/evennia/utils/olc/olc_fields.py b/evennia/utils/olc/olc_fields.py new file mode 100644 index 0000000000..376d54f63c --- /dev/null +++ b/evennia/utils/olc/olc_fields.py @@ -0,0 +1,291 @@ +""" +OLC fields describe how to edit and display a specific piece of data of a prototype within the OLC system. + +The OLC system imports and adds these field classes to its prototype manipulation pages in order to +know what data to read and how to display it. + +""" +from collections import deque +# from django.conf import settings + +_OLC_VALIDATION_ERROR = """ +Error storing data in {fieldname}: + {value} +The reported error was + {error} +""" + +_LEN_HISTORY = 10 # settings.OLC_HISTORY_LENGTH + + +class OLCField(object): + """ + This is the parent for all OLC fields. This docstring acts + as the help text for the field. + + """ + # name of this field, for error reporting + key = "Empty field" + # if this field must have a value different than None + required = False + # used for displaying extra info in the OLC + label = "Empty field" + # initial value of field if not given + initial = None + # actions available on this field. Available actions + # are replace, edit, append, remove, clear, help + actions = ['replace', 'edit', 'remove', 'clear', 'help'] + + def __init__(self, olcsession): + self.olcsession = olcsession + self._value_history = deque([self.initial], _LEN_HISTORY) + self._history_pos = 0 + self._has_changed = False + + def __repr__(self): + return self.display() + + # storing data to the field in a history-aware way + @property + def value(self): + return self._value_history[self._history_pos] + + @value.setter + def value(self, value): + """ + Update field value by updating the history. + """ + original_value = value + try: + value = self.validate(value) + except Exception as err: + errtext = _OLC_VALIDATION_ERROR.format(fieldname=self.key, value=original_value, error=err) + self.olcsession.caller.msg(errtext) + return + if (self._value_history and isinstance(value, (basestring, bool, int, float)) and + self._value_history[0] == value): + # don't change/update history if re-adding the same thing + return + else: + self._has_changed = True + self._history_pos = 0 + self._value_history.appendleft(value) + + @value.deleter + def value(self): + self.history_pos = 0 + self._value_history.appendleft(self.initial) + + def history(self, step): + """ + Change history position. + + Args: + step (int): Step in the history stack. Positive movement + means moving futher back in history (with a maximum + of `settings.OLC_HISTORY_LENGTH`, negative steps + moves towards recent history (with 0 being the latest + value). + + """ + self._history_pos = min(len(self.value_history)-1, max(0, self._history_pos + step)) + + def has_changed(self): + """ + Check if this field has changed. + + Returns: + changed (bool): If the field changed or not. + + """ + return bool(self._has_changed) + + # overloadable methods + + def from_entity(self, entity, **kwargs): + """ + Populate this field from an entity. + + Args: + entity (any): An object to use for + populating this field (like an Object). + """ + pass + + def to_prototype(self, prototype): + """ + Store this field value in a prototype. + + Args: + prototype (dict): The prototype dict + to update with the value of this field. + """ + pass + + def validate(self, value, **kwargs): + """ + Validate/preprocess data to store in this field. + + Args: + value (any): An input value to + validate + + Kwargs: + any (any): Optional info to send to field. + + Returns: + validated_value (any): The value, correctly + validated and/or processed to store in this field. + + Raises: + Exception: If the field was given an + invalid value to validate. + + """ + return str(value) + + def display(self): + """ + How to display the field contents in the OLC display. + + """ + return self.value + + +# OLCFields for all the standard model properties +# key, location, destination, home, aliases, +# permissions, tags, attributes +# ... + + +class OLCKeyField(OLCField): + """ + The name (key) of the object is its main identifier, used + throughout listings even if may not always be visible to + the end user. + """ + key = 'Name' + required = True + label = "The object's name" + + def from_entity(self, entity, **kwargs): + self.value = entity.db_key + + def to_prototype(self, prototype): + prototype['key'] = self.value + + +class OLCLocationField(OLCField): + """ + An object's location is usually a Room but could be any + other in-game entity. By convention, Rooms themselves have + a None location. Objects are otherwise only placed in a + None location to take them out of the game. + """ + key = 'Location' + required = False + label = "The object's current location" + + def validate(self, value): + return self.olcsession.search_by_string(value) + + def from_entity(self, entity, **kwargs): + self.value = entity.db_location + + def to_prototype(self, prototype): + prototype['location'] = self.value + + +class OLCHomeField(OLCField): + """ + An object's home location acts as a fallback when various + extreme situations occur. An example is when a location is + deleted - all its content (except exits) are then not deleted + but are moved to each object's home location. + """ + key = 'Home' + required = True + label = "The object's home location" + + def validate(self, value): + return self.olcsession.search_by_string(value) + + def from_entity(self, entity, **kwargs): + self.value = entity.db_home + + def to_prototype(self, prototype): + prototype['home'] = self.value + +class OLCDestinationField(OLCField): + """ + An object's destination is usually not set unless the object + represents an exit between game locations. If set, the + destination should be set to the location you get to when + passing through this exit. + + """ + key = 'Destination' + required = False + label = "The object's (usually exit's) destination" + + def validate(self, value): + return self.olcsession.search_by_string(value) + + def from_entity(self, entity, **kwargs): + self.value = entity.db_destination + + def to_prototype(self, prototype): + prototype['destination'] = self.value + + +class OLCAliasField(OLCField): + """ + Specify as a comma-separated list. Use quotes around the + alias if the alias itself contains a comma. + + Aliases are alternate names for an object. An alias is just + as fast to search for as a key and two objects are assumed + to have the same name is *either* their name or any of their + aliases match. + + """ + key = 'Aliases' + required = False + label = "The object's alternative name or names" + actions = OLCField.actions + ['append'] + + def validate(self, value): + return split_by_comma(value) + + def from_entity(self, entity, **kwargs): + self.value = list(entity.db_aliases.all()) + + def to_prototype(self, prototype): + prototype['aliases'] = self.value + + +class OLCTagField(OLCField): + """ + Specify as a comma-separated list of tagname or tagname:category. + + Aliases are alternate names for an object. An alias is just + as fast to search for as a key and two objects are assumed + to have the same name is *either* their name or any of their + aliases match. + + """ + key = 'Aliases' + required = False + label = "The object's (usually exit's) destination" + actions = OLCField.actions + ['append'] + + def validate(self, value): + return [tagstr.split(':', 1) if ':' in tagstr else (tagstr, None) + for tagstr in split_by_comma(value)] + + def from_entity(self, entity, **kwargs): + self.value = entity.tags.all(return_key_and_category=True) + + def to_prototype(self, prototype): + prototype['tags'] = self.value + diff --git a/evennia/utils/olc/olc_menutree.py b/evennia/utils/olc/olc_menutree.py new file mode 100644 index 0000000000..a550782949 --- /dev/null +++ b/evennia/utils/olc/olc_menutree.py @@ -0,0 +1,83 @@ +""" +This describes the menu structure/logic of the OLC system editor, using the EvMenu subsystem. The +various nodes are modular and will when possible make use of the various utilities of the OLC rather +than hard-coding things in each node. + +Menu structure: + + start: + new object + edit object + manage prototypes + export session to batchcode file (immortals only) + + new/edit object: + Protoype + Typeclass + Key + Location + Destination + PErmissions + LOcks + Attributes + TAgs + Scripts + + create/update object + copy object + save prototype + save/delete object + update existing objects + + manage prototypes + list prototype + search prototype + import prototype (from global store) + + export session + +""" + +def node_top(caller, raw_input): + # top level node + # links to edit, manage, export + text = """OnLine Creation System""" + options = ({"key": ("|yN|new", "new", "n"), + "desc": "New object", + "goto": "node_new_top", + "exec": _obj_to_prototype}, + {"key": ("|yE|ndit", "edit", "e", "m"), + "desc": "Edit existing object", + "goto": "node_edit_top", + "exec": _obj_to_prototype}, + {"key": ("|yP|nrototype", "prototype", "manage", "p", "m"), + "desc": "Manage prototypes", + "goto": "node_prototype_top"}, + {"key": ("E|yx|nport", "export", "x"), + "desc": "Export to prototypes", + "goto": "node_prototype_top"}, + {"key": ("|yQ|nuit", "quit", "q"), + "desc": "Quit OLC", + "goto": "node_quit"},) + return text, options + +def node_quit(caller, raw_input): + return 'Exiting.', None + +def node_new_top(caller, raw_input): + pass + +def node_edit_top(caller, raw_input): + # edit top level + text = """Edit object""" + + +def node_prototype_top(caller, raw_input): + # manage prototypes + pass + + +def node_export_top(caller, raw_input): + # export top level + pass + diff --git a/evennia/utils/olc/olc_utils.py b/evennia/utils/olc/olc_utils.py new file mode 100644 index 0000000000..68bfd3ac9d --- /dev/null +++ b/evennia/utils/olc/olc_utils.py @@ -0,0 +1,13 @@ +""" +Miscellaneous utilities for the OLC system. + +""" +import csv + + +def search_by_string(olcsession, query): + pass + + +def split_by_comma(string): + return csv.reader([string], skipinitialspace=True) From f718a1aadde94e07f4ecfdcad917d1a73d84adc2 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 16 Apr 2017 20:57:23 +0200 Subject: [PATCH 17/63] Start attempt at a smart-search mechanism for limiting searches more creatively on the command line. Non-working code. --- evennia/typeclasses/managers.py | 52 ++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/evennia/typeclasses/managers.py b/evennia/typeclasses/managers.py index b6c486b166..fea8e8cc7d 100644 --- a/evennia/typeclasses/managers.py +++ b/evennia/typeclasses/managers.py @@ -4,10 +4,11 @@ abstract models in dbobjects.py (and which are thus shared by all Attributes and TypedObjects). """ +import shlex from functools import update_wrapper from django.db.models import Q from evennia.utils import idmapper -from evennia.utils.utils import make_iter, variable_from_module +from evennia.utils.utils import make_iter, variable_from_module, to_unicode __all__ = ("TypedObjectManager", ) _GA = object.__getattribute__ @@ -356,6 +357,55 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): return make_iter(tag)[0] # object-manager methods + def smart_search(self, query): + """ + Search by supplying a string with optional extra search criteria to aid the query. + + Args: + query (str): A search criteria that accepts extra search criteria on the + following forms: [key|alias|#dbref...] [tag==[:category]...] [attr==::category...] + " != " != " + Returns: + matches (queryset): A queryset result matching all queries exactly. If wanting to use spaces or + ==, != in tags or attributes, enclose them in quotes. + + Note: + The flexibility of this method is limited by the input line format. Tag/attribute + matching only works for matching primitives. For even more complex queries, such as + 'in' operations or object field matching, use the full django query language. + + """ + RE_TAG= + # shlex splits by spaces unless escaped by quotes + querysplit = shlex.split(to_unicode(query, force=True)) + plustags, plusattrs, negtags, negattrs = [], [], [], [] + for ipart, part in enumerate(querysplit): + # tags are on the form tag or tag:category + if part.startswith('tag==') or part.startswith('tag!='): + tagstr, category = part, None + if ":" in part: + tagstr, category = part.split(':', 1) + plustags.append((tagstr, category)) + elif part.startswith('tag!='): + tagstr, category = part, None + if ":" in part: + tagstr, category = part.split(':', 1) + negtags.append((tagstr, category)) + elif part.startswith('attr=='): + # + tagstr, category = part, None + if ":" in part: + tagstr, category = part.split(':', 1) + negtags.append((tagstr, category)) + + + + tags = [part.split(':', 1) if ':' in part else (part, None) + for part in spacesplit if part.startswith('tag==') or part.startswith('tag!=')] + attrs = + + + def dbref(self, dbref, reqhash=True): """ From 4b38642666c7f4b42b2fca95e28bc3f8ae4084e9 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 19 Apr 2017 06:40:24 +0200 Subject: [PATCH 18/63] Add unfinished smart_search manager method for future use with the olc. --- evennia/typeclasses/managers.py | 54 ++++++++++++++++----------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/evennia/typeclasses/managers.py b/evennia/typeclasses/managers.py index fea8e8cc7d..26466dee07 100644 --- a/evennia/typeclasses/managers.py +++ b/evennia/typeclasses/managers.py @@ -364,7 +364,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): Args: query (str): A search criteria that accepts extra search criteria on the following forms: [key|alias|#dbref...] [tag==[:category]...] [attr==::category...] - " != " != " + " != " != " Returns: matches (queryset): A queryset result matching all queries exactly. If wanting to use spaces or ==, != in tags or attributes, enclose them in quotes. @@ -375,36 +375,36 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): 'in' operations or object field matching, use the full django query language. """ - RE_TAG= # shlex splits by spaces unless escaped by quotes querysplit = shlex.split(to_unicode(query, force=True)) - plustags, plusattrs, negtags, negattrs = [], [], [], [] + queries, plustags, plusattrs, negtags, negattrs = [], [], [], [], [] for ipart, part in enumerate(querysplit): + key, rest = part, "" + if ":" in part: + key, rest = part.split(':', 1) # tags are on the form tag or tag:category - if part.startswith('tag==') or part.startswith('tag!='): - tagstr, category = part, None - if ":" in part: - tagstr, category = part.split(':', 1) - plustags.append((tagstr, category)) - elif part.startswith('tag!='): - tagstr, category = part, None - if ":" in part: - tagstr, category = part.split(':', 1) - negtags.append((tagstr, category)) - elif part.startswith('attr=='): - # - tagstr, category = part, None - if ":" in part: - tagstr, category = part.split(':', 1) - negtags.append((tagstr, category)) - - - - tags = [part.split(':', 1) if ':' in part else (part, None) - for part in spacesplit if part.startswith('tag==') or part.startswith('tag!=')] - attrs = - - + if key.startswith('tag=='): + plustags.append((key[5:], rest)) + continue + elif key.startswith('tag!='): + negtags.append((key[5:], rest)) + continue + # attrs are on the form attr:value or attr:value:category + elif rest: + value, category = rest, "" + if ":" in rest: + value, category = rest.split(':', 1) + if key.startswith('attr=='): + plusattrs.append((key[7:], value, category)) + continue + elif key.startswith('attr!='): + negattrs.append((key[7:], value, category)) + continue + # if we get here, we are entering a key search criterion which + # we assume is one word. + queries.append(part) + # build query from components + query = ' '.join(queries) def dbref(self, dbref, reqhash=True): From dd7d18f041370e8e0b9397205e4cdacfd32335fd Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 19 Apr 2017 21:31:15 +0200 Subject: [PATCH 19/63] Move smart-search (still not complete, pending changes to queryset returns as per roadmap). --- evennia/objects/manager.py | 1 + evennia/typeclasses/managers.py | 103 ++++++++++++++++---------------- 2 files changed, 53 insertions(+), 51 deletions(-) diff --git a/evennia/objects/manager.py b/evennia/objects/manager.py index a21a23de52..82eae2a1bc 100644 --- a/evennia/objects/manager.py +++ b/evennia/objects/manager.py @@ -431,6 +431,7 @@ class ObjectDBManager(TypedObjectManager): return matches # alias for backwards compatibility object_search = search_object + search = search_object # # ObjectManager Copy method diff --git a/evennia/typeclasses/managers.py b/evennia/typeclasses/managers.py index 26466dee07..523823a98f 100644 --- a/evennia/typeclasses/managers.py +++ b/evennia/typeclasses/managers.py @@ -356,57 +356,6 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): tag.save() return make_iter(tag)[0] - # object-manager methods - def smart_search(self, query): - """ - Search by supplying a string with optional extra search criteria to aid the query. - - Args: - query (str): A search criteria that accepts extra search criteria on the - following forms: [key|alias|#dbref...] [tag==[:category]...] [attr==::category...] - " != " != " - Returns: - matches (queryset): A queryset result matching all queries exactly. If wanting to use spaces or - ==, != in tags or attributes, enclose them in quotes. - - Note: - The flexibility of this method is limited by the input line format. Tag/attribute - matching only works for matching primitives. For even more complex queries, such as - 'in' operations or object field matching, use the full django query language. - - """ - # shlex splits by spaces unless escaped by quotes - querysplit = shlex.split(to_unicode(query, force=True)) - queries, plustags, plusattrs, negtags, negattrs = [], [], [], [], [] - for ipart, part in enumerate(querysplit): - key, rest = part, "" - if ":" in part: - key, rest = part.split(':', 1) - # tags are on the form tag or tag:category - if key.startswith('tag=='): - plustags.append((key[5:], rest)) - continue - elif key.startswith('tag!='): - negtags.append((key[5:], rest)) - continue - # attrs are on the form attr:value or attr:value:category - elif rest: - value, category = rest, "" - if ":" in rest: - value, category = rest.split(':', 1) - if key.startswith('attr=='): - plusattrs.append((key[7:], value, category)) - continue - elif key.startswith('attr!='): - negattrs.append((key[7:], value, category)) - continue - # if we get here, we are entering a key search criterion which - # we assume is one word. - queries.append(part) - # build query from components - query = ' '.join(queries) - - def dbref(self, dbref, reqhash=True): """ Determing if input is a valid dbref. @@ -565,6 +514,58 @@ class TypeclassManager(TypedObjectManager): """ + # object-manager methods + def smart_search(self, query): + """ + Search by supplying a string with optional extra search criteria to aid the query. + + Args: + query (str): A search criteria that accepts extra search criteria on the + + following forms: [key|alias|#dbref...] [tag==[:category]...] [attr==::category...] + " != " != " + Returns: + matches (queryset): A queryset result matching all queries exactly. If wanting to use spaces or + ==, != in tags or attributes, enclose them in quotes. + + Note: + The flexibility of this method is limited by the input line format. Tag/attribute + matching only works for matching primitives. For even more complex queries, such as + 'in' operations or object field matching, use the full django query language. + + """ + # shlex splits by spaces unless escaped by quotes + querysplit = shlex.split(to_unicode(query, force=True)) + queries, plustags, plusattrs, negtags, negattrs = [], [], [], [], [] + for ipart, part in enumerate(querysplit): + key, rest = part, "" + if ":" in part: + key, rest = part.split(':', 1) + # tags are on the form tag or tag:category + if key.startswith('tag=='): + plustags.append((key[5:], rest)) + continue + elif key.startswith('tag!='): + negtags.append((key[5:], rest)) + continue + # attrs are on the form attr:value or attr:value:category + elif rest: + value, category = rest, "" + if ":" in rest: + value, category = rest.split(':', 1) + if key.startswith('attr=='): + plusattrs.append((key[7:], value, category)) + continue + elif key.startswith('attr!='): + negattrs.append((key[7:], value, category)) + continue + # if we get here, we are entering a key search criterion which + # we assume is one word. + queries.append(part) + # build query from components + query = ' '.join(queries) + #TODO + def get(self, *args, **kwargs): """ Overload the standard get. This will limit itself to only From cf77d90c719ce27cc644e0678944f90aca21d066 Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 20 Apr 2017 19:43:39 +0200 Subject: [PATCH 20/63] Make all manager methods return querysets. This removes all the last remnants of the old return_typeclass/return_typeclass_list decorators that were a remnant of the old pre-proxy Typeclass system. Resolves #1206. --- evennia/comms/managers.py | 19 ++++------- evennia/contrib/email_login.py | 11 +++---- evennia/contrib/mail.py | 2 +- evennia/objects/manager.py | 10 ------ evennia/players/manager.py | 13 ++------ evennia/scripts/manager.py | 11 +++---- evennia/typeclasses/managers.py | 57 ++++----------------------------- 7 files changed, 25 insertions(+), 98 deletions(-) diff --git a/evennia/comms/managers.py b/evennia/comms/managers.py index 48b9bbf2b2..e778bc20ae 100644 --- a/evennia/comms/managers.py +++ b/evennia/comms/managers.py @@ -5,10 +5,8 @@ Comm system components. """ from __future__ import print_function -from django.db import models from django.db.models import Q -from evennia.typeclasses.managers import (TypedObjectManager, TypeclassManager, - returns_typeclass_list, returns_typeclass) +from evennia.typeclasses.managers import (TypedObjectManager, TypeclassManager) from evennia.utils import logger _GA = object.__getattribute__ @@ -131,6 +129,7 @@ def to_object(inp, objtype='player'): # an unknown return None + # # Msg manager # @@ -314,6 +313,7 @@ class MsgManager(TypedObjectManager): # back-compatibility alias message_search = search_message + # # Channel manager # @@ -332,7 +332,6 @@ class ChannelDBManager(TypedObjectManager): subscribed to the Channel. """ - @returns_typeclass_list def get_all_channels(self): """ Get all channels. @@ -343,7 +342,6 @@ class ChannelDBManager(TypedObjectManager): """ return self.all() - @returns_typeclass def get_channel(self, channelkey): """ Return the channel object if given its key. @@ -366,7 +364,6 @@ class ChannelDBManager(TypedObjectManager): return channels[0] return None - @returns_typeclass_list def get_subscriptions(self, subscriber): """ Return all channels a given entity is subscribed to. @@ -385,7 +382,6 @@ class ChannelDBManager(TypedObjectManager): return subscriber.object_subscription_set.all() return [] - @returns_typeclass_list def search_channel(self, ostring, exact=True): """ Search the channel database for a particular channel. @@ -397,7 +393,8 @@ class ChannelDBManager(TypedObjectManager): """ channels = [] - if not ostring: return channels + if not ostring: + return channels try: # try an id match first dbref = int(ostring.strip('#')) @@ -414,16 +411,14 @@ class ChannelDBManager(TypedObjectManager): if not channels: # still no match. Search by alias. channels = [channel for channel in self.all() - if ostring.lower() in [a.lower - for a in channel.aliases.all()]] + if ostring.lower() in [a.lower for a in channel.aliases.all()]] return channels # back-compatibility alias channel_search = search_channel + class ChannelManager(ChannelDBManager, TypeclassManager): """ Wrapper to group the typeclass manager to a consistent name. """ pass - - diff --git a/evennia/contrib/email_login.py b/evennia/contrib/email_login.py index 5495361b60..488982e73e 100644 --- a/evennia/contrib/email_login.py +++ b/evennia/contrib/email_login.py @@ -34,10 +34,9 @@ from django.conf import settings from evennia.players.models import PlayerDB from evennia.objects.models import ObjectDB from evennia.server.models import ServerConfig -from evennia.comms.models import ChannelDB from evennia.commands.cmdset import CmdSet -from evennia.utils import create, logger, utils, ansi +from evennia.utils import logger, utils, ansi from evennia.commands.default.muxcommand import MuxCommand from evennia.commands.cmdhandler import CMD_LOGINSTART from evennia.commands.default import unloggedin as default_unloggedin # Used in CmdUnconnectedCreate @@ -100,14 +99,13 @@ class CmdUnconnectedConnect(MuxCommand): session.msg(string) return # We have at least one result, so we can check the password. - if not player.check_password(password): + if not player[0].check_password(password): session.msg("Incorrect password.") return # Check IP and/or name bans bans = ServerConfig.objects.conf("server_bans") - if bans and (any(tup[0] == player.name for tup in bans) - or + if bans and (any(tup[0] == player.name for tup in bans) or any(tup[2].match(session.address[0]) for tup in bans if tup[2])): # this is a banned IP or name! string = "|rYou have been banned and cannot continue from here." @@ -207,8 +205,7 @@ class CmdUnconnectedCreate(MuxCommand): # Check IP and/or name bans bans = ServerConfig.objects.conf("server_bans") - if bans and (any(tup[0] == playername.lower() for tup in bans) - or + if bans and (any(tup[0] == playername.lower() for tup in bans) or any(tup[2].match(session.address) for tup in bans if tup[2])): # this is a banned IP or name! string = "|rYou have been banned and cannot continue from here." \ diff --git a/evennia/contrib/mail.py b/evennia/contrib/mail.py index 176229fe85..f071f44a7f 100644 --- a/evennia/contrib/mail.py +++ b/evennia/contrib/mail.py @@ -92,7 +92,7 @@ class CmdMail(default_cmds.MuxCommand): player = self.caller.player except AttributeError: player = self.caller - messages = Msg.objects.get_by_tag(category="mail", raw_queryset=True).filter(db_receivers_players=player) + messages = Msg.objects.get_by_tag(category="mail").filter(db_receivers_players=player) return messages def send_mail(self, recipients, subject, message, caller): diff --git a/evennia/objects/manager.py b/evennia/objects/manager.py index 82eae2a1bc..ae70896d85 100644 --- a/evennia/objects/manager.py +++ b/evennia/objects/manager.py @@ -7,7 +7,6 @@ from django.db.models import Q from django.conf import settings from django.db.models.fields import exceptions from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager -from evennia.typeclasses.managers import returns_typeclass, returns_typeclass_list from evennia.utils.utils import to_unicode, is_iter, make_iter, string_partial_matching from builtins import int @@ -56,7 +55,6 @@ class ObjectDBManager(TypedObjectManager): # player related - @returns_typeclass def get_object_with_player(self, ostring, exact=True, candidates=None): """ Search for an object based on its player's name or dbref. @@ -93,7 +91,6 @@ class ObjectDBManager(TypedObjectManager): else: return string_partial_matching(ply_cands, ostring, ret_index=False) - @returns_typeclass_list def get_objs_with_key_and_typeclass(self, oname, otypeclass_path, candidates=None): """ Returns objects based on simultaneous key and typeclass match. @@ -112,7 +109,6 @@ class ObjectDBManager(TypedObjectManager): # attr/property related - @returns_typeclass_list def get_objs_with_attr(self, attribute_name, candidates=None): """ Get objects based on having a certain Attribute defined. @@ -130,7 +126,6 @@ class ObjectDBManager(TypedObjectManager): if obj]) or Q() return list(self.filter(cand_restriction & Q(db_attributes__db_key=attribute_name))) - @returns_typeclass_list def get_objs_with_attr_value(self, attribute_name, attribute_value, candidates=None, typeclasses=None): """ Get all objects having the given attrname set to the given value. @@ -169,7 +164,6 @@ class ObjectDBManager(TypedObjectManager): db_value=attribute_value)] return chain(*results) - @returns_typeclass_list def get_objs_with_db_property(self, property_name, candidates=None): """ Get all objects having a given db field property. @@ -191,7 +185,6 @@ class ObjectDBManager(TypedObjectManager): except exceptions.FieldError: return [] - @returns_typeclass_list def get_objs_with_db_property_value(self, property_name, property_value, candidates=None, typeclasses=None): """ Get objects with a specific field name and value. @@ -222,7 +215,6 @@ class ObjectDBManager(TypedObjectManager): (property_name, type(property_value))) return [] - @returns_typeclass_list def get_contents(self, location, excludeobj=None): """ Get all objects that has a location set to this one. @@ -238,7 +230,6 @@ class ObjectDBManager(TypedObjectManager): exclude_restriction = Q(pk__in=[_GA(obj, "id") for obj in make_iter(excludeobj)]) if excludeobj else Q() return self.filter(db_location=location).exclude(exclude_restriction) - @returns_typeclass_list def get_objs_with_key_or_alias(self, ostring, exact=True, candidates=None, typeclasses=None): """ @@ -303,7 +294,6 @@ class ObjectDBManager(TypedObjectManager): # main search methods and helper functions - @returns_typeclass_list def search_object(self, searchdata, attribute_name=None, typeclass=None, diff --git a/evennia/players/manager.py b/evennia/players/manager.py index 15bb79f49d..2c5cb48b75 100644 --- a/evennia/players/manager.py +++ b/evennia/players/manager.py @@ -5,10 +5,7 @@ The managers for the custom Player object and permissions. import datetime from django.utils import timezone from django.contrib.auth.models import UserManager -#from functools import update_wrapper -from evennia.typeclasses.managers import (returns_typeclass_list, returns_typeclass, - TypedObjectManager, TypeclassManager) -from evennia.utils.utils import make_iter +from evennia.typeclasses.managers import (TypedObjectManager, TypeclassManager) __all__ = ("PlayerManager",) @@ -51,7 +48,6 @@ class PlayerDBManager(TypedObjectManager, UserManager): """ return self.count() - @returns_typeclass_list def get_connected_players(self): """ Get all currently connected players. @@ -63,7 +59,6 @@ class PlayerDBManager(TypedObjectManager, UserManager): """ return self.filter(db_is_connected=True) - @returns_typeclass_list def get_recently_created_players(self, days=7): """ Get players recently created. @@ -80,7 +75,6 @@ class PlayerDBManager(TypedObjectManager, UserManager): start_date = end_date - tdelta return self.filter(date_joined__range=(start_date, end_date)) - @returns_typeclass_list def get_recently_connected_players(self, days=7): """ Get players recently connected to the game. @@ -99,7 +93,6 @@ class PlayerDBManager(TypedObjectManager, UserManager): return self.filter(last_login__range=( start_date, end_date)).order_by('-last_login') - @returns_typeclass def get_player_from_email(self, uemail): """ Search player by @@ -114,7 +107,6 @@ class PlayerDBManager(TypedObjectManager, UserManager): """ return self.filter(email__iexact=uemail) - @returns_typeclass def get_player_from_uid(self, uid): """ Get a player by id. @@ -131,7 +123,6 @@ class PlayerDBManager(TypedObjectManager, UserManager): except self.model.DoesNotExist: return None - @returns_typeclass def get_player_from_name(self, uname): """ Get player object based on name. @@ -148,7 +139,6 @@ class PlayerDBManager(TypedObjectManager, UserManager): except self.model.DoesNotExist: return None - @returns_typeclass_list def search_player(self, ostring, exact=True, typeclass=None): """ Searches for a particular player by name or @@ -185,5 +175,6 @@ class PlayerDBManager(TypedObjectManager, UserManager): # back-compatibility alias player_search = search_player + class PlayerManager(PlayerDBManager, TypeclassManager): pass diff --git a/evennia/scripts/manager.py b/evennia/scripts/manager.py index bd0f612099..46ae22e05c 100644 --- a/evennia/scripts/manager.py +++ b/evennia/scripts/manager.py @@ -4,7 +4,6 @@ The custom manager for Scripts. from django.db.models import Q from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager -from evennia.typeclasses.managers import returns_typeclass_list from evennia.utils.utils import make_iter __all__ = ("ScriptManager",) _GA = object.__getattribute__ @@ -35,7 +34,6 @@ class ScriptDBManager(TypedObjectManager): copy_script """ - @returns_typeclass_list def get_all_scripts_on_obj(self, obj, key=None): """ Find all Scripts related to a particular object. @@ -68,7 +66,6 @@ class ScriptDBManager(TypedObjectManager): else: return self.filter(db_obj=obj) - @returns_typeclass_list def get_all_scripts(self, key=None): """ Get all scripts in the database. @@ -200,7 +197,7 @@ class ScriptDBManager(TypedObjectManager): elif obj: scripts = self.get_all_scripts_on_obj(obj, key=key) else: - scripts = self.get_all_scripts(key=key) #self.model.get_all_cached_instances() + scripts = self.get_all_scripts(key=key) if not scripts: # no scripts available to validate @@ -216,7 +213,6 @@ class ScriptDBManager(TypedObjectManager): VALIDATE_ITERATION -= 1 return nr_started, nr_stopped - @returns_typeclass_list def search_script(self, ostring, obj=None, only_timed=False): """ Search for a particular script. @@ -236,8 +232,8 @@ class ScriptDBManager(TypedObjectManager): if dbref or dbref == 0: # this is a dbref, try to find the script directly dbref_match = self.dbref_search(dbref) - if dbref_match and not ((obj and obj != dbref_match.obj) - or (only_timed and dbref_match.interval)): + if dbref_match and not ((obj and obj != dbref_match.obj) or + (only_timed and dbref_match.interval)): return [dbref_match] # not a dbref; normal search @@ -273,5 +269,6 @@ class ScriptDBManager(TypedObjectManager): locks=new_locks, autostart=True) return new_script + class ScriptManager(ScriptDBManager, TypeclassManager): pass diff --git a/evennia/typeclasses/managers.py b/evennia/typeclasses/managers.py index 523823a98f..f212ea6b4d 100644 --- a/evennia/typeclasses/managers.py +++ b/evennia/typeclasses/managers.py @@ -5,7 +5,6 @@ all Attributes and TypedObjects). """ import shlex -from functools import update_wrapper from django.db.models import Q from evennia.utils import idmapper from evennia.utils.utils import make_iter, variable_from_module, to_unicode @@ -14,40 +13,6 @@ __all__ = ("TypedObjectManager", ) _GA = object.__getattribute__ _Tag = None -# -# Decorators -# - -def returns_typeclass_list(method): - """ - Decorator: Always returns a list, even if it is empty. - - """ - def func(self, *args, **kwargs): - self.__doc__ = method.__doc__ - raw_queryset = kwargs.pop('raw_queryset', False) - result = method(self, *args, **kwargs) - if raw_queryset: - return result - else: - return list(result) - return update_wrapper(func, method) - - -def returns_typeclass(method): - """ - Decorator: Returns a single typeclass match or None. - - """ - def func(self, *args, **kwargs): - self.__doc__ = method.__doc__ - query = method(self, *args, **kwargs) - if hasattr(query, "__iter__"): - result = list(query) - return result[0] if result else None - else: - return query - return update_wrapper(func, method) # Managers @@ -59,7 +24,6 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): # common methods for all typed managers. These are used # in other methods. Returns querysets. - # Attribute manager methods def get_attribute(self, key=None, category=None, value=None, strvalue=None, obj=None, attrtype=None): """ @@ -125,7 +89,6 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): """ return self.get_attribute(key=key, category=category, value=value, strvalue=strvalue, obj=obj) - @returns_typeclass_list def get_by_attribute(self, key=None, category=None, value=None, strvalue=None, attrtype=None): """ Return objects having attributes with the given key, category, @@ -257,7 +220,6 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): """ return self.get_tag(key=key, category=category, obj=obj, tagtype="alias") - @returns_typeclass_list def get_by_tag(self, key=None, category=None, tagtype=None): """ Return objects having tags with a given key or category or @@ -384,7 +346,6 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): return None return dbref - @returns_typeclass def get_id(self, dbref): """ Find object with given dbref. @@ -416,7 +377,6 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): """ return self.get_id(dbref) - @returns_typeclass_list def get_dbref_range(self, min_dbref=None, max_dbref=None): """ Get objects within a certain range of dbrefs. @@ -455,7 +415,6 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): self.filter(db_typeclass_path=typeclass_path).count() return dbtotals - @returns_typeclass_list def typeclass_search(self, typeclass, include_children=False, include_parents=False): """ Searches through all objects returning those which has a @@ -585,7 +544,7 @@ class TypeclassManager(TypedObjectManager): on the model base used. """ - kwargs.update({"db_typeclass_path":self.model.path}) + kwargs.update({"db_typeclass_path": self.model.path}) return super(TypeclassManager, self).get(**kwargs) def filter(self, *args, **kwargs): @@ -603,7 +562,7 @@ class TypeclassManager(TypedObjectManager): objects (queryset): The objects found. """ - kwargs.update({"db_typeclass_path":self.model.path}) + kwargs.update({"db_typeclass_path": self.model.path}) return super(TypeclassManager, self).filter(*args, **kwargs) def all(self): @@ -683,8 +642,8 @@ class TypeclassManager(TypedObjectManager): """ paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__) - for cls in self._get_subclasses(self.model)] - kwargs.update({"db_typeclass_path__in":paths}) + for cls in self._get_subclasses(self.model)] + kwargs.update({"db_typeclass_path__in": paths}) return super(TypeclassManager, self).get(**kwargs) def filter_family(self, *args, **kwargs): @@ -704,8 +663,8 @@ class TypeclassManager(TypedObjectManager): """ # query, including all subclasses paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__) - for cls in self._get_subclasses(self.model)] - kwargs.update({"db_typeclass_path__in":paths}) + for cls in self._get_subclasses(self.model)] + kwargs.update({"db_typeclass_path__in": paths}) return super(TypeclassManager, self).filter(*args, **kwargs) def all_family(self): @@ -718,7 +677,5 @@ class TypeclassManager(TypedObjectManager): """ paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__) - for cls in self._get_subclasses(self.model)] + for cls in self._get_subclasses(self.model)] return super(TypeclassManager, self).all().filter(db_typeclass_path__in=paths) - - From f9e7b01f572637992e1ca066d61ad5cde49b52ee Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 20 Apr 2017 20:58:35 +0200 Subject: [PATCH 21/63] Add **kwargs options to at_* hooks to all typeclasses, for greater flexibility for users. Resolves #1276. --- evennia/commands/default/tests.py | 2 +- evennia/comms/comms.py | 83 +++++++++++++++----- evennia/objects/objects.py | 124 ++++++++++++++++++++++-------- evennia/players/players.py | 59 +++++++++++--- evennia/scripts/scripts.py | 32 ++++++-- 5 files changed, 228 insertions(+), 72 deletions(-) diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index 9e3ec66dc0..fc053b313a 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -74,7 +74,7 @@ class CommandTest(EvenniaTest): cmdobj.parse() cmdobj.func() cmdobj.at_post_cmd() - # clean out prettytable sugar. We only operate on text-type + # clean out evtable sugar. We only operate on text-type stored_msg = [args[0] if args and args[0] else kwargs.get("text",utils.to_str(kwargs, force_string=True)) for name, args, kwargs in receiver.msg.mock_calls] # Get the first element of a tuple if msg received a tuple instead of a string diff --git a/evennia/comms/comms.py b/evennia/comms/comms.py index f50d3bf4a8..ba35e4c552 100644 --- a/evennia/comms/comms.py +++ b/evennia/comms/comms.py @@ -100,11 +100,17 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): string = "" return string - def mute(self, subscriber): + def mute(self, subscriber, **kwargs): """ Adds an entity to the list of muted subscribers. A muted subscriber will no longer see channel messages, but may use channel commands. + + Args: + subscriber (Object or Player): Subscriber to mute. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + """ mutelist = self.mutelist if subscriber not in mutelist: @@ -113,11 +119,16 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): return True return False - def unmute(self, subscriber): + def unmute(self, subscriber, **kwargs): """ - Removes an entity to the list of muted subscribers. - A muted subscriber will no longer see channel messages, + Removes an entity to the list of muted subscribers. A muted subscriber will no longer see channel messages, but may use channel commands. + + Args: + subscriber (Object or Player): The subscriber to unmute. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + """ mutelist = self.mutelist if subscriber in mutelist: @@ -126,13 +137,15 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): return True return False - def connect(self, subscriber): + def connect(self, subscriber, **kwargs): """ Connect the user to this channel. This checks access. Args: subscriber (Player or Object): the entity to subscribe to this channel. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Returns: success (bool): Whether or not the addition was @@ -154,13 +167,15 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): self.post_join_channel(subscriber) return True - def disconnect(self, subscriber): + def disconnect(self, subscriber, **kwargs): """ Disconnect entity from this channel. Args: subscriber (Player of Object): the entity to disconnect. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Returns: success (bool): Whether or not the removal was @@ -179,7 +194,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): self.post_leave_channel(subscriber) return True - def access(self, accessing_obj, access_type='listen', default=False, no_superuser_bypass=False): + def access(self, accessing_obj, access_type='listen', default=False, no_superuser_bypass=False, **kwargs): """ Determines if another object has permission to access. @@ -189,6 +204,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): default (bool, optional): What to return if no lock of access_type was found no_superuser_bypass (bool, optional): Turns off superuser lock bypass. Be careful with this one. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Returns: return (bool): Result of lock check. @@ -209,7 +226,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): CHANNELHANDLER.update() def message_transform(self, msgobj, emit=False, prefix=True, - sender_strings=None, external=False): + sender_strings=None, external=False, **kwargs): """ Generates the formatted string sent to listeners on a channel. @@ -220,6 +237,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): prefix (bool, optional): Prefix `msg` with a text given by `self.channel_prefix`. sender_strings (list, optional): Used by bots etc, one string per external sender. external (bool, optional): If this is an external sender or not. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). """ if sender_strings or external: @@ -231,7 +250,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): msgobj.message = body return msgobj - def distribute_message(self, msgobj, online=False): + def distribute_message(self, msgobj, online=False, **kwargs): """ Method for grabbing all listeners that a message should be sent to on this channel, and sending them a message. @@ -240,6 +259,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): msgobj (Msg or TempMsg): Message to distribute. online (bool): Only send to receivers who are actually online (not currently used): + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Notes: This is also where logging happens, if enabled. @@ -332,7 +353,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): # hooks - def channel_prefix(self, msg=None, emit=False): + def channel_prefix(self, msg=None, emit=False, **kwargs): """ Hook method. How the channel should prefix itself for users. @@ -341,6 +362,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): msg (str, optional): Prefix text emit (bool, optional): Switches to emit mode, which usually means to not prefix the channel's info. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Returns: prefix (str): The created channel prefix. @@ -348,12 +371,14 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): """ return '' if emit else '[%s] ' % self.key - def format_senders(self, senders=None): + def format_senders(self, senders=None, **kwargs): """ Hook method. Function used to format a list of sender names. Args: senders (list): Sender object names. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Returns: formatted_list (str): The list of names formatted appropriately. @@ -368,7 +393,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): return '' return ', '.join(senders) - def pose_transform(self, msgobj, sender_string): + def pose_transform(self, msgobj, sender_string, **kwargs): """ Hook method. Detects if the sender is posing, and modifies the message accordingly. @@ -376,6 +401,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): Args: msgobj (Msg or TempMsg): The message to analyze for a pose. sender_string (str): The name of the sender/poser. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Returns: string (str): A message that combines the `sender_string` @@ -398,7 +425,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): else: return '%s: %s' % (sender_string, message) - def format_external(self, msgobj, senders, emit=False): + def format_external(self, msgobj, senders, emit=False, **kwargs): """ Hook method. Used for formatting external messages. This is needed as a separate operation because the senders of external @@ -409,6 +436,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): msgobj (Msg or TempMsg): The message to send. senders (list): Strings, one per sender. emit (bool, optional): A sender-agnostic message or not. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Returns: transformed (str): A formatted string. @@ -419,13 +448,15 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): senders = ', '.join(senders) return self.pose_transform(msgobj, senders) - def format_message(self, msgobj, emit=False): + def format_message(self, msgobj, emit=False, **kwargs): """ Hook method. Formats a message body for display. Args: msgobj (Msg or TempMsg): The message object to send. emit (bool, optional): The message is agnostic of senders. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Returns: transformed (str): The formatted message. @@ -443,13 +474,15 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): senders = ', '.join(senders) return self.pose_transform(msgobj, senders) - def pre_join_channel(self, joiner): + def pre_join_channel(self, joiner, **kwargs): """ Hook method. Runs right before a channel is joined. If this returns a false value, channel joining is aborted. Args: joiner (object): The joining object. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Returns: should_join (bool): If `False`, channel joining is aborted. @@ -457,23 +490,27 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): """ return True - def post_join_channel(self, joiner): + def post_join_channel(self, joiner, **kwargs): """ Hook method. Runs right after an object or player joins a channel. Args: joiner (object): The joining object. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). """ pass - def pre_leave_channel(self, leaver): + def pre_leave_channel(self, leaver, **kwargs): """ Hook method. Runs right before a user leaves a channel. If this returns a false value, leaving the channel will be aborted. Args: leaver (object): The leaving object. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Returns: should_leave (bool): If `False`, channel parting is aborted. @@ -481,17 +518,19 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): """ return True - def post_leave_channel(self, leaver): + def post_leave_channel(self, leaver, **kwargs): """ Hook method. Runs right after an object or player leaves a channel. Args: leaver (object): The leaving object. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). """ pass - def pre_send_message(self, msg): + def pre_send_message(self, msg, **kwargs): """ Hook method. Runs before a message is sent to the channel and should return the message object, after any transformations. @@ -499,6 +538,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): Args: msg (Msg or TempMsg): Message to send. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Returns: result (Msg, TempMsg or bool): If False, abort send. @@ -506,12 +547,14 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): """ return msg - def post_send_message(self, msg): + def post_send_message(self, msg, **kwargs): """ Hook method. Run after a message is sent to the channel. Args: msg (Msg or TempMsg): Message sent. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). """ pass diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 814d80acfa..eed07fe8be 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -1048,7 +1048,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ pass - def at_pre_puppet(self, player, session=None): + def at_pre_puppet(self, player, session=None, **kwargs): """ Called just before a Player connects to this object to puppet it. @@ -1056,14 +1056,20 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): Args: player (Player): This is the connecting player. session (Session): Session controlling the connection. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + """ pass - def at_post_puppet(self): + def at_post_puppet(self, **kwargs): """ Called just after puppeting has been completed and all Player<->Object links have been established. + Args: + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Note: You can use `self.player` and `self.sessions.get()` to get player and sessions at this point; the last entry in the @@ -1073,11 +1079,14 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ self.player.db._last_puppet = self - def at_pre_unpuppet(self): + def at_pre_unpuppet(self, **kwargs): """ Called just before beginning to un-connect a puppeting from this Player. + Args: + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Note: You can use `self.player` and `self.sessions.get()` to get player and sessions at this point; the last entry in the @@ -1087,7 +1096,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ pass - def at_post_unpuppet(self, player, session=None): + def at_post_unpuppet(self, player, session=None, **kwargs): """ Called just after the Player successfully disconnected from this object, severing all connections. @@ -1097,6 +1106,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): from this object. session (Session): Session id controlling the connection that just disconnected. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). """ pass @@ -1140,13 +1151,15 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): # hooks called when moving the object - def at_before_move(self, destination): + def at_before_move(self, destination, **kwargs): """ Called just before starting to move this object to destination. Args: destination (Object): The object we are moving to + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Returns: shouldmove (bool): If we should move or not. @@ -1159,7 +1172,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): # return has_perm(self, destination, "can_move") return True - def announce_move_from(self, destination, msg=None, mapping=None): + def announce_move_from(self, destination, msg=None, mapping=None, **kwargs): """ Called if the move is to be announced. This is called while we are still standing in the old @@ -1169,6 +1182,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): destination (Object): The place we are going to. msg (str, optional): a replacement message. mapping (dict, optional): additional mapping objects. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). You can override this method and call its parent with a message to simply change the default message. In the string, @@ -1200,7 +1215,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): location.msg_contents(string, exclude=(self, ), mapping=mapping) - def announce_move_to(self, source_location, msg=None, mapping=None): + def announce_move_to(self, source_location, msg=None, mapping=None, **kwargs): """ Called after the move if the move was not quiet. At this point we are standing in the new location. @@ -1209,14 +1224,17 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): source_location (Object): The place we came from msg (str, optional): the replacement message if location. mapping (dict, optional): additional mapping objects. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). - You can override this method and call its parent with a - message to simply change the default message. In the string, - you can use the following as mappings (between braces): - object: the object which is moving. - exit: the exit from which the object is moving (if found). - origin: the location of the object before the move. - destination: the location of the object after moving. + Notes: + You can override this method and call its parent with a + message to simply change the default message. In the string, + you can use the following as mappings (between braces): + object: the object which is moving. + exit: the exit from which the object is moving (if found). + origin: the location of the object before the move. + destination: the location of the object after moving. """ @@ -1253,7 +1271,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): destination.msg_contents(string, exclude=(self, ), mapping=mapping) - def at_after_move(self, source_location): + def at_after_move(self, source_location, **kwargs): """ Called after move has completed, regardless of quiet mode or not. Allows changes to the object due to the location it is @@ -1261,22 +1279,26 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): Args: source_location (Object): Wwhere we came from. This may be `None`. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). """ pass - def at_object_leave(self, moved_obj, target_location): + def at_object_leave(self, moved_obj, target_location, **kwargs): """ Called just before an object leaves from inside this object Args: moved_obj (Object): The object leaving target_location (Object): Where `moved_obj` is going. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). """ pass - def at_object_receive(self, moved_obj, source_location): + def at_object_receive(self, moved_obj, source_location, **kwargs): """ Called after an object has been moved into this object. @@ -1284,11 +1306,13 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): moved_obj (Object): The object moved into this one source_location (Object): Where `moved_object` came from. Note that this could be `None`. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). """ pass - def at_traverse(self, traversing_object, target_location): + def at_traverse(self, traversing_object, target_location, **kwargs): """ This hook is responsible for handling the actual traversal, normally by calling @@ -1301,11 +1325,13 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): Args: traversing_object (Object): Object traversing us. target_location (Object): Where target is going. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). """ pass - def at_after_traverse(self, traversing_object, source_location): + def at_after_traverse(self, traversing_object, source_location, **kwargs): """ Called just after an object successfully used this object to traverse to another object (i.e. this object is a type of @@ -1314,19 +1340,23 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): Args: traversing_object (Object): The object traversing us. source_location (Object): Where `traversing_object` came from. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Notes: The target location should normally be available as `self.destination`. """ pass - def at_failed_traverse(self, traversing_object): + def at_failed_traverse(self, traversing_object, **kwargs): """ This is called if an object fails to traverse this object for some reason. Args: traversing_object (Object): The object that failed traversing us. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Notes: Using the default exits, this hook will not be called if an @@ -1387,13 +1417,15 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): # hooks called by the default cmdset. - def return_appearance(self, looker): + def return_appearance(self, looker, **kwargs): """ This formats a description. It is the hook a 'look' command should call. Args: looker (Object): Object doing the looking. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). """ if not looker: return "" @@ -1420,7 +1452,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): string += "\n|wYou see:|n " + ", ".join(users + things) return string - def at_look(self, target): + def at_look(self, target, **kwargs): """ Called when this object performs a look. It allows to customize just what this means. It will not itself @@ -1430,6 +1462,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): target (Object): The target being looked at. This is commonly an object or the current location. It will be checked for the "view" type access. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Returns: lookstring (str): A ready-processed look string @@ -1450,22 +1484,27 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): return description - def at_desc(self, looker=None): + def at_desc(self, looker=None, **kwargs): """ This is called whenever someone looks at this object. - looker (Object): The object requesting the description. + Args: + looker (Object, optional): The object requesting the description. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). """ pass - def at_get(self, getter): + def at_get(self, getter, **kwargs): """ Called by the default `get` command when this object has been picked up. Args: getter (Object): The object getting this object. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Notes: This hook cannot stop the pickup from happening. Use @@ -1474,7 +1513,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ pass - def at_give(self, giver, getter): + def at_give(self, giver, getter, **kwargs): """ Called by the default `give` command when this object has been given. @@ -1482,6 +1521,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): Args: giver (Object): The object giving this object. getter (Object): The object getting this object. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Notes: This hook cannot stop the give from happening. Use @@ -1490,13 +1531,15 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ pass - def at_drop(self, dropper): + def at_drop(self, dropper, **kwargs): """ Called by the default `drop` command when this object has been dropped. Args: dropper (Object): The object which just dropped this object. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Notes: This hook cannot stop the drop from happening. Use @@ -1505,7 +1548,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ pass - def at_say(self, speaker, message): + def at_say(self, speaker, message, **kwargs): """ Called on this object if an object inside this object speaks. The string returned from this method is the final form of the @@ -1514,6 +1557,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): Args: speaker (Object): The object speaking. message (str): The words spoken. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Notes: You should not need to add things like 'you say: ' or @@ -1551,7 +1596,7 @@ class DefaultCharacter(DefaultObject): # add the default cmdset self.cmdset.add_default(settings.CMDSET_CHARACTER, permanent=True) - def at_after_move(self, source_location): + def at_after_move(self, source_location, **kwargs): """ We make sure to look around after a move. @@ -1559,7 +1604,7 @@ class DefaultCharacter(DefaultObject): if self.location.access(self, "view"): self.msg(self.at_look(self.location)) - def at_pre_puppet(self, player, session=None): + def at_pre_puppet(self, player, session=None, **kwargs): """ Return the character from storage in None location in `at_post_unpuppet`. Args: @@ -1575,11 +1620,14 @@ class DefaultCharacter(DefaultObject): else: player.msg("|r%s has no location and no home is set.|n" % self, session=session) # Note to set home. - def at_post_puppet(self): + def at_post_puppet(self, **kwargs): """ Called just after puppeting has been completed and all Player<->Object links have been established. + Args: + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Note: You can use `self.player` and `self.sessions.get()` to get player and sessions at this point; the last entry in the @@ -1594,7 +1642,7 @@ class DefaultCharacter(DefaultObject): obj.msg("%s has entered the game." % self.get_display_name(obj), from_obj=from_obj) self.location.for_contents(message, exclude=[self], from_obj=self) - def at_post_unpuppet(self, player, session=None): + def at_post_unpuppet(self, player, session=None, **kwargs): """ We stove away the character when the player goes ooc/logs off, otherwise the character object will remain in the room also @@ -1605,6 +1653,8 @@ class DefaultCharacter(DefaultObject): from this object. session (Session): Session controlling the connection that just disconnected. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). """ if not self.sessions.count(): # only remove this char from grid if no sessions control it anymore. @@ -1694,6 +1744,8 @@ class ExitCommand(command.Command): Args: caller (Object): The object (usually a character) that entered an ambiguous command. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Returns: A string with identifying information to disambiguate the command, conventionally with a preceding space. @@ -1804,7 +1856,7 @@ class DefaultExit(DefaultObject): """ self.cmdset.remove_default() - def at_traverse(self, traversing_object, target_location): + def at_traverse(self, traversing_object, target_location, **kwargs): """ This implements the actual traversal. The traverse lock has already been checked (in the Exit command) at this point. @@ -1812,6 +1864,8 @@ class DefaultExit(DefaultObject): Args: traversing_object (Object): Object traversing us. target_location (Object): Where target is going. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). """ source_location = traversing_object.location @@ -1825,12 +1879,14 @@ class DefaultExit(DefaultObject): # No shorthand error message. Call hook. self.at_failed_traverse(traversing_object) - def at_failed_traverse(self, traversing_object): + def at_failed_traverse(self, traversing_object, **kwargs): """ Overloads the default hook to implement a simple default error message. Args: traversing_object (Object): The object that failed traversing us. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Notes: Using the default exits, this hook will not be called if an diff --git a/evennia/players/players.py b/evennia/players/players.py index 0ea588dfe7..2c67e360f4 100644 --- a/evennia/players/players.py +++ b/evennia/players/players.py @@ -664,7 +664,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): """ pass - def at_first_login(self): + def at_first_login(self, **kwargs): """ Called the very first time this player logs into the game. Note that this is called *before* at_pre_login, so no session @@ -672,14 +672,22 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): this point. This hook is intended for player-specific setup like configurations. + Args: + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + """ pass - def at_pre_login(self): + def at_pre_login(self, **kwargs): """ Called every time the user logs in, just before the actual login-state is set. + Args: + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + """ pass @@ -706,13 +714,15 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): else: logger.log_info("[%s]: %s" % (now, message)) - def at_post_login(self, session=None): + def at_post_login(self, session=None, **kwargs): """ Called at the end of the login process, just before letting the player loose. Args: session (Session, optional): Session logging in, if any. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Notes: This is called *before* an eventual Character's @@ -754,7 +764,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): self.msg(self.at_look(target=self.db._playable_characters, session=session)) - def at_failed_login(self, session): + def at_failed_login(self, session, **kwargs): """ Called by the login process if a user account is targeted correctly but provided with an invalid password. By default it does nothing, @@ -762,42 +772,60 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): Args: session (session): Session logging in. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). """ pass - def at_disconnect(self, reason=None): + def at_disconnect(self, reason=None, **kwargs): """ Called just before user is disconnected. Args: reason (str, optional): The reason given for the disconnect, (echoed to the connection channel by default). + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + """ reason = reason and "(%s)" % reason or "" self._send_to_connect_channel("|R%s disconnected %s|n" % (self.key, reason)) - def at_post_disconnect(self): + def at_post_disconnect(self, **kwargs): """ This is called *after* disconnection is complete. No messages can be relayed to the player from here. After this call, the player should not be accessed any more, making this a good spot for deleting it (in the case of a guest player account, for example). + + Args: + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + """ pass - def at_message_receive(self, message, from_obj=None): + def at_message_receive(self, message, from_obj=None, **kwargs): """ This is currently unused. + Args: + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + """ return True - def at_message_send(self, message, to_object): + def at_message_send(self, message, to_object, **kwargs): """ This is currently unused. + Args: + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + """ pass @@ -817,7 +845,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): """ pass - def at_look(self, target=None, session=None): + def at_look(self, target=None, session=None, **kwargs): """ Called when this object executes a look. It allows to customize just what this means. @@ -826,6 +854,8 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): target (Object or list, optional): An object or a list objects to inspect. session (Session, optional): The session doing this look. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). Returns: look_string (str): A prepared look string, ready to send @@ -898,13 +928,15 @@ class DefaultGuest(DefaultPlayer): This class is used for guest logins. Unlike Players, Guests and their characters are deleted after disconnection. """ - def at_post_login(self, session=None): + def at_post_login(self, session=None, **kwargs): """ In theory, guests only have one character regardless of which MULTISESSION_MODE we're in. They don't get a choice. Args: session (Session, optional): Session connecting. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). """ self._send_to_connect_channel("|G%s connected|n" % self.key) @@ -922,9 +954,14 @@ class DefaultGuest(DefaultPlayer): print "deleting Character:", character character.delete() - def at_post_disconnect(self): + def at_post_disconnect(self, **kwargs): """ Once having disconnected, destroy the guest's characters and + + Args: + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + """ super(DefaultGuest, self).at_post_disconnect() characters = self.db._playable_characters diff --git a/evennia/scripts/scripts.py b/evennia/scripts/scripts.py index 6764b2e7a8..4fdef4ef69 100644 --- a/evennia/scripts/scripts.py +++ b/evennia/scripts/scripts.py @@ -115,7 +115,6 @@ class ExtendedLoopingCall(LoopingCall): self.starttime = self.clock.seconds() self() - def next_call_time(self): """ Get the next call time. This also takes the eventual effect @@ -473,11 +472,16 @@ class DefaultScript(ScriptBase): if task: task.force_repeat() - def at_first_save(self): + def at_first_save(self, **kwargs): """ This is called after very first time this object is saved. Generally, you don't need to overload this, but only the hooks called by this method. + + Args: + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + """ self.at_script_creation() @@ -516,10 +520,10 @@ class DefaultScript(ScriptBase): # auto-start script (default) self.start() - def at_script_creation(self): """ Only called once, by the create function. + """ pass @@ -528,28 +532,44 @@ class DefaultScript(ScriptBase): Is called to check if the script is valid to run at this time. Should return a boolean. The method is assumed to collect all needed information from its related self.obj. + """ return not self._is_deleted - def at_start(self): + def at_start(self, **kwargs): """ Called whenever the script is started, which for persistent scripts is at least once every server start. It will also be called when starting again after a pause (such as after a server reload) + + Args: + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + """ pass - def at_repeat(self): + def at_repeat(self, **kwargs): """ Called repeatedly if this Script is set to repeat regularly. + + Args: + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + """ pass - def at_stop(self): + def at_stop(self, **kwargs): """ Called whenever when it's time for this script to stop (either because is_valid returned False or it runs out of iterations) + + Args + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + """ pass From 7853b2e382aee3243d8e3655a185bfeda27009e9 Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 21 Apr 2017 21:45:16 +0200 Subject: [PATCH 22/63] Fix error in mail contrib due to change of manage query return change. --- evennia/contrib/mail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/mail.py b/evennia/contrib/mail.py index f071f44a7f..176229fe85 100644 --- a/evennia/contrib/mail.py +++ b/evennia/contrib/mail.py @@ -92,7 +92,7 @@ class CmdMail(default_cmds.MuxCommand): player = self.caller.player except AttributeError: player = self.caller - messages = Msg.objects.get_by_tag(category="mail").filter(db_receivers_players=player) + messages = Msg.objects.get_by_tag(category="mail", raw_queryset=True).filter(db_receivers_players=player) return messages def send_mail(self, recipients, subject, message, caller): From e8a1daa526508c9f3c9ddfee74a84e905bde6411 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 22 Apr 2017 13:31:29 +0200 Subject: [PATCH 23/63] Fix error with raw_queryset kwarg being removed. --- evennia/contrib/mail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/mail.py b/evennia/contrib/mail.py index 176229fe85..f071f44a7f 100644 --- a/evennia/contrib/mail.py +++ b/evennia/contrib/mail.py @@ -92,7 +92,7 @@ class CmdMail(default_cmds.MuxCommand): player = self.caller.player except AttributeError: player = self.caller - messages = Msg.objects.get_by_tag(category="mail", raw_queryset=True).filter(db_receivers_players=player) + messages = Msg.objects.get_by_tag(category="mail").filter(db_receivers_players=player) return messages def send_mail(self, recipients, subject, message, caller): From 69d736220361aadc9b35b76a19c22160712858d9 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 6 May 2017 22:31:44 +0200 Subject: [PATCH 24/63] Add more work on OLC field structures. --- evennia/utils/olc/olc_fields.py | 141 ++++++++++++++++++++++++++++---- 1 file changed, 127 insertions(+), 14 deletions(-) diff --git a/evennia/utils/olc/olc_fields.py b/evennia/utils/olc/olc_fields.py index 376d54f63c..e637099592 100644 --- a/evennia/utils/olc/olc_fields.py +++ b/evennia/utils/olc/olc_fields.py @@ -6,6 +6,9 @@ know what data to read and how to display it. """ from collections import deque +from evennia.utils.utils import to_str, to_unicode +from evennia.utils.olc import olc_utils + # from django.conf import settings _OLC_VALIDATION_ERROR = """ @@ -18,6 +21,22 @@ The reported error was _LEN_HISTORY = 10 # settings.OLC_HISTORY_LENGTH +class InvalidActionError(RuntimeError): + """ + Raised when trying to perform a field action the field + does not support. + """ + pass + + +class ValidationError(RuntimeError): + """ + Raised when failing to validate new data being entered + into the field (from any source) + """ + pass + + class OLCField(object): """ This is the parent for all OLC fields. This docstring acts @@ -31,10 +50,10 @@ class OLCField(object): # used for displaying extra info in the OLC label = "Empty field" # initial value of field if not given - initial = None + default = None # actions available on this field. Available actions # are replace, edit, append, remove, clear, help - actions = ['replace', 'edit', 'remove', 'clear', 'help'] + actions = ['replace', 'edit', 'clear', 'help'] def __init__(self, olcsession): self.olcsession = olcsession @@ -43,7 +62,74 @@ class OLCField(object): self._has_changed = False def __repr__(self): - return self.display() + return to_str(self.display()) + + def __unicode__(self): + return to_unicode(self.display()) + + # perform actions + # TODO - include editor in check! + def do_replace(self, newval): + """ + Replace field value. + + Args: + newval (any): New value to replace existing one. + + Raises: + InvalidActionError: If replacing is not allowed. + + """ + if 'replace' in self.actions: + self.value = newval + else: + raise InvalidActionError('Replace {value}->{newval}'.format(value=self.value, newval)) + + def can_edit(self): + """ + Check if we may edit. + + Returns: + can_edit (bool): If we can edit or not. + + """ + if 'edit' in self.actions: + return self.value + return False + + def do_clear(self): + """ + Clear field back to default. + + Returns: + default (any): The field'd default value, now set. + + Raises: + InvalidActionError: If clearing is not allowed. + + """ + if 'clear' in self.actions: + # don't validate this + object.__setattr__(self, 'value', self.default) + return self.value + else: + raise InvalidActionError('Clear') + + def get_help(self): + """ + Get the help text for the field. + + Returns: + help (str): Field help text. + + Raises: + InvalidActionError: If help is not given for this field, + either becuase it's disallowed or unset. + + """ + if 'help' not in self.actions or not self.__doc__: + raise InvalidActioNError('Help') + return self.__doc__ # storing data to the field in a history-aware way @property @@ -60,8 +146,7 @@ class OLCField(object): value = self.validate(value) except Exception as err: errtext = _OLC_VALIDATION_ERROR.format(fieldname=self.key, value=original_value, error=err) - self.olcsession.caller.msg(errtext) - return + raise ValidationError(errtxt) if (self._value_history and isinstance(value, (basestring, bool, int, float)) and self._value_history[0] == value): # don't change/update history if re-adding the same thing @@ -104,7 +189,9 @@ class OLCField(object): def from_entity(self, entity, **kwargs): """ - Populate this field from an entity. + Populate this field by retrieving data from an entity. + All fields on a page will have this called, so must + be able to handle also incompatible entities. Args: entity (any): An object to use for @@ -124,7 +211,7 @@ class OLCField(object): def validate(self, value, **kwargs): """ - Validate/preprocess data to store in this field. + Validate/preprocess incoming data to store in this field. Args: value (any): An input value to @@ -187,7 +274,7 @@ class OLCLocationField(OLCField): label = "The object's current location" def validate(self, value): - return self.olcsession.search_by_string(value) + return olc_utils.search_by_string(self.olcsession, value) def from_entity(self, entity, **kwargs): self.value = entity.db_location @@ -208,7 +295,7 @@ class OLCHomeField(OLCField): label = "The object's home location" def validate(self, value): - return self.olcsession.search_by_string(value) + return olc_utils.search_by_string(self.olcsession, value) def from_entity(self, entity, **kwargs): self.value = entity.db_home @@ -216,6 +303,7 @@ class OLCHomeField(OLCField): def to_prototype(self, prototype): prototype['home'] = self.value + class OLCDestinationField(OLCField): """ An object's destination is usually not set unless the object @@ -229,7 +317,7 @@ class OLCDestinationField(OLCField): label = "The object's (usually exit's) destination" def validate(self, value): - return self.olcsession.search_by_string(value) + return olc_utils.search_by_string(self.olcsession, value) def from_entity(self, entity, **kwargs): self.value = entity.db_destination @@ -238,6 +326,7 @@ class OLCDestinationField(OLCField): prototype['destination'] = self.value +# batch-setting aliases class OLCAliasField(OLCField): """ Specify as a comma-separated list. Use quotes around the @@ -255,7 +344,7 @@ class OLCAliasField(OLCField): actions = OLCField.actions + ['append'] def validate(self, value): - return split_by_comma(value) + return olc_utils.split_by_comma(value) def from_entity(self, entity, **kwargs): self.value = list(entity.db_aliases.all()) @@ -264,6 +353,7 @@ class OLCAliasField(OLCField): prototype['aliases'] = self.value +# batch-setting tags class OLCTagField(OLCField): """ Specify as a comma-separated list of tagname or tagname:category. @@ -276,12 +366,12 @@ class OLCTagField(OLCField): """ key = 'Aliases' required = False - label = "The object's (usually exit's) destination" + label = "Alternative ways to refer to this object." actions = OLCField.actions + ['append'] def validate(self, value): - return [tagstr.split(':', 1) if ':' in tagstr else (tagstr, None) - for tagstr in split_by_comma(value)] + return [tuple(tagstr.split(':', 1)) if ':' in tagstr else (tagstr, None) + for tagstr in olc_utils.split_by_comma(value)] def from_entity(self, entity, **kwargs): self.value = entity.tags.all(return_key_and_category=True) @@ -289,3 +379,26 @@ class OLCTagField(OLCField): def to_prototype(self, prototype): prototype['tags'] = self.value + +# batch-setting attributes +class OLCAttributeField(OLCField): + """ + Specify as a comma-separated list of attrname=value or attrname:category=value. + + Attributes are arbitrary pieces of data attached to an object. They can + contain references to other objects as well as simple Python structures such + as lists and dicts. + + """ + key = 'Attributes' + required = False + label = "Additional data attached to this object." + actions = OLCField.actions + ['append'] + + def validate(self, value): + return [tuple(lhs.split(':', 1) + [rhs]) if ':' in lhs else (lhs, None) + (rhs, ) + for lhs, rhs in (attrstr.split('=', 1) if ':' in attrstr else ((attrstr, None),)) + for attrstr in olc_utils.split_by_comma(value)] + + def from_entity(self, entity, **kwargs): + self.value = entity.attributes.all From 2b1a4fc49e587141c0c0b12527db2efdcc85b05e Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 6 May 2017 23:21:33 +0200 Subject: [PATCH 25/63] Add concept of olc batchfields. --- evennia/utils/olc/olc_fields.py | 95 +++++++++++++++++++++++++++------ evennia/utils/spawner.py | 4 +- 2 files changed, 82 insertions(+), 17 deletions(-) diff --git a/evennia/utils/olc/olc_fields.py b/evennia/utils/olc/olc_fields.py index e637099592..eb059b6e8d 100644 --- a/evennia/utils/olc/olc_fields.py +++ b/evennia/utils/olc/olc_fields.py @@ -69,7 +69,7 @@ class OLCField(object): # perform actions # TODO - include editor in check! - def do_replace(self, newval): + def action_replace(self, newval): """ Replace field value. @@ -85,7 +85,7 @@ class OLCField(object): else: raise InvalidActionError('Replace {value}->{newval}'.format(value=self.value, newval)) - def can_edit(self): + def action_edit(self): """ Check if we may edit. @@ -97,7 +97,7 @@ class OLCField(object): return self.value return False - def do_clear(self): + def action_clear(self): """ Clear field back to default. @@ -115,7 +115,7 @@ class OLCField(object): else: raise InvalidActionError('Clear') - def get_help(self): + def action_help(self): """ Get the help text for the field. @@ -326,8 +326,46 @@ class OLCDestinationField(OLCField): prototype['destination'] = self.value +class OLCBatchField(OLCField): + """ + A field managing multiple values that can be appended to and + a given component popped out. + """ + actions = OLCField.actions + ['append', 'pop'] + + def action_append(self, value): + """ + Append a new value to this field. + + Args: + value (any): The value to append. + + """ + value = self.value + value.append(value) + self.value = value + + def action_pop(self, index=-1): + """ + Pop an element from the field. + + Args: + index (int, optional): Pop this index, otherwise pop the last + element in the field. + + Returns: + element (any or None): The popped element or None. + + """ + lst = self.value + try: + return lst.pop(int(index)) + except IndexError: + return None + + # batch-setting aliases -class OLCAliasField(OLCField): +class OLCAliasBatchField(OLCBatchField): """ Specify as a comma-separated list. Use quotes around the alias if the alias itself contains a comma. @@ -341,7 +379,6 @@ class OLCAliasField(OLCField): key = 'Aliases' required = False label = "The object's alternative name or names" - actions = OLCField.actions + ['append'] def validate(self, value): return olc_utils.split_by_comma(value) @@ -354,7 +391,7 @@ class OLCAliasField(OLCField): # batch-setting tags -class OLCTagField(OLCField): +class OLCTagBatchField(OLCBatchField): """ Specify as a comma-separated list of tagname or tagname:category. @@ -367,11 +404,14 @@ class OLCTagField(OLCField): key = 'Aliases' required = False label = "Alternative ways to refer to this object." - actions = OLCField.actions + ['append'] def validate(self, value): - return [tuple(tagstr.split(':', 1)) if ':' in tagstr else (tagstr, None) - for tagstr in olc_utils.split_by_comma(value)] + if isinstance(value, basestring): + return [tuple(tagstr.split(':', 1)) if ':' in tagstr else (tagstr, None) + for tagstr in olc_utils.split_by_comma(value)] + else: + # assume a list of (key, category) - just let it pass + return value def from_entity(self, entity, **kwargs): self.value = entity.tags.all(return_key_and_category=True) @@ -379,9 +419,15 @@ class OLCTagField(OLCField): def to_prototype(self, prototype): prototype['tags'] = self.value + def display(self): + outstr = [] + for key, category in self.value: + outstr.append("{key}:{category}".format(key=key, category=category)) + return '\n'.join(outstr) + # batch-setting attributes -class OLCAttributeField(OLCField): +class OLCAttributeBatchField(OLCBatchField): """ Specify as a comma-separated list of attrname=value or attrname:category=value. @@ -396,9 +442,28 @@ class OLCAttributeField(OLCField): actions = OLCField.actions + ['append'] def validate(self, value): - return [tuple(lhs.split(':', 1) + [rhs]) if ':' in lhs else (lhs, None) + (rhs, ) - for lhs, rhs in (attrstr.split('=', 1) if ':' in attrstr else ((attrstr, None),)) - for attrstr in olc_utils.split_by_comma(value)] + if isinstance(value, basestring): + return [tuple(lhs.split(':', 1) + [rhs]) if ':' in lhs else (lhs, None) + (rhs, ) + for lhs, rhs in (attrstr.split('=', 1) if ':' in attrstr else ((attrstr, None),)) + for attrstr in olc_utils.split_by_comma(value)] + else: + # we assume this is a list of Attributes + return [(attr.key, attr.category, attr.value) for attr in value] def from_entity(self, entity, **kwargs): - self.value = entity.attributes.all + self.value = entity.attributes.all() + + def to_prototype(self, prototype): + for key, category, value in self.value: + prototype['attrs'] = (key, value, category) + + def display(self): + outstr = [] + for key, category, value in self.value: + outstr.append("{key}:{category} = {value}".format(key=key, category=category, value=value)) + return '\n'.join(outstr) + + +# Details - individual attrs/tags/aliases on an object rather than batch-adding + + diff --git a/evennia/utils/spawner.py b/evennia/utils/spawner.py index a089e5dc34..965819ca72 100644 --- a/evennia/utils/spawner.py +++ b/evennia/utils/spawner.py @@ -37,7 +37,7 @@ Possible keywords are: execution namespace contains 'evennia' for the library and 'obj' tags - string or list of strings or tuples `(tagstr, category)`. Plain strings will be result in tags with no category (default tags). - args - tuple or list of tuples of Attributes to add. This form allows + attrs - tuple or list of tuples of Attributes to add. This form allows more complex Attributes to be set. Tuples at least specify `(key, value)` but can also specify up to `(key, value, category, lockstring)`. If you want to specify a lockstring but not a category, set the category @@ -280,7 +280,7 @@ def spawn(*prototypes, **kwargs): alias_string = aliasval() if callable(aliasval) else aliasval tagval = prot.pop("tags", []) tags = tagval() if callable(tagval) else tagval - attrval = prot.pop("args", []) + attrval = prot.pop("attrs", []) attributes = attrval() if callable(tagval) else attrval exval = prot.pop("exec", "") From de552782a086bb94376ae2d0ee0d34632d98655a Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 15 May 2017 20:59:26 +0200 Subject: [PATCH 26/63] Add single tag fields. --- evennia/utils/olc/olc_fields.py | 79 ++++++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 11 deletions(-) diff --git a/evennia/utils/olc/olc_fields.py b/evennia/utils/olc/olc_fields.py index eb059b6e8d..882b15282d 100644 --- a/evennia/utils/olc/olc_fields.py +++ b/evennia/utils/olc/olc_fields.py @@ -364,6 +364,23 @@ class OLCBatchField(OLCField): return None +# setting single Alias +class OLCAliasField(OLCField): + key = "Alias" + required = False + label = "An alternative name for the object" + + def from_entity(self, entity, **kwargs): + if "index" in kwargs: + self.value = entity.aliases.all()[int(kwargs)] + + def to_prototype(self, prototype): + if is_iter(prototype["aliases"]): + prototype["aliases"].append(self.value) + else: + prototype["aliases"] = [self.value] + + # batch-setting aliases class OLCAliasBatchField(OLCBatchField): """ @@ -384,26 +401,52 @@ class OLCAliasBatchField(OLCBatchField): return olc_utils.split_by_comma(value) def from_entity(self, entity, **kwargs): - self.value = list(entity.db_aliases.all()) + self.value = list(entity.aliases.all()) def to_prototype(self, prototype): prototype['aliases'] = self.value -# batch-setting tags +# setting single Tag +class OLCTagField(OLCField): + """ + Specify as tagname or tagname:category + + Tags are used to identify groups of objects for later quick retrieval. + This is very useful for anything from creating zones of rooms to + easily find all Characters belonging a given group etc. A tag can also + have a category for a second level of grouping. + + """ + key = "Tag" + required = False + label = "A single label for the object." + + def validate(self, value): + + def from_entity(self, entity, **kwargs): + if "index" in kwargs: + self.value = entity.tags.all()[int(kwargs)] + + def to_prototype(self, prototype): + if is_iter(prototype["tags"]): prototype["tags"].append(self.value) + else: + prototype["tags"] = [self.value] + + +# batch-setting Tags class OLCTagBatchField(OLCBatchField): """ Specify as a comma-separated list of tagname or tagname:category. - Aliases are alternate names for an object. An alias is just - as fast to search for as a key and two objects are assumed - to have the same name is *either* their name or any of their - aliases match. + Tags are used to identify groups of objects for later quick retrieval. + This is very useful for anything from creating zones of rooms to + easily find all Characters belonging a given group etc. """ - key = 'Aliases' + key = 'Tags' required = False - label = "Alternative ways to refer to this object." + label = "Attach labels to objects to group and find them." def validate(self, value): if isinstance(value, basestring): @@ -426,6 +469,23 @@ class OLCTagBatchField(OLCBatchField): return '\n'.join(outstr) +# setting single Attribute +class OLCAttributeField(OLCField): + key = "Attribute" + required = False + label = "An alternative name for the object" + + def from_entity(self, entity, **kwargs): + if "index" in kwargs: + self.value = entity.attributes.all()[int(kwargs)] + + def to_prototype(self, prototype): + if is_iter(prototype["attrs"]): + prototype["attrs"].append(self.value) + else: + prototype["attrs"] = [self.value] + + # batch-setting attributes class OLCAttributeBatchField(OLCBatchField): """ @@ -464,6 +524,3 @@ class OLCAttributeBatchField(OLCBatchField): return '\n'.join(outstr) -# Details - individual attrs/tags/aliases on an object rather than batch-adding - - From 88a44fc0cf60ad8d13ab43ca8c991c0a32d3bd26 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 17 May 2017 19:49:46 +0200 Subject: [PATCH 27/63] Start working on OLCPage structure. --- evennia/utils/olc/olc_fields.py | 47 +++++++++++++++------------------ 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/evennia/utils/olc/olc_fields.py b/evennia/utils/olc/olc_fields.py index 882b15282d..0c758946a4 100644 --- a/evennia/utils/olc/olc_fields.py +++ b/evennia/utils/olc/olc_fields.py @@ -53,7 +53,7 @@ class OLCField(object): default = None # actions available on this field. Available actions # are replace, edit, append, remove, clear, help - actions = ['replace', 'edit', 'clear', 'help'] + actions = ['edit', 'clear', 'help'] def __init__(self, olcsession): self.olcsession = olcsession @@ -69,35 +69,27 @@ class OLCField(object): # perform actions # TODO - include editor in check! - def action_replace(self, newval): + def action_edit(self, *args): """ - Replace field value. + Edit field value. Args: - newval (any): New value to replace existing one. + newval (any): New value to add/replace with. Raises: - InvalidActionError: If replacing is not allowed. + InvalidActionError: If editing is not allowed. """ - if 'replace' in self.actions: - self.value = newval + if args: + newval = args[0] + if 'edit' in self.actions: + self.value = newval + return else: - raise InvalidActionError('Replace {value}->{newval}'.format(value=self.value, newval)) + newval = "" + raise InvalidActionError('Edit {value}->{newval}'.format(value=self.value, newval)) - def action_edit(self): - """ - Check if we may edit. - - Returns: - can_edit (bool): If we can edit or not. - - """ - if 'edit' in self.actions: - return self.value - return False - - def action_clear(self): + def action_clear(self, *args): """ Clear field back to default. @@ -112,10 +104,9 @@ class OLCField(object): # don't validate this object.__setattr__(self, 'value', self.default) return self.value - else: - raise InvalidActionError('Clear') + raise InvalidActionError('Clear') - def action_help(self): + def action_help(self, *args): """ Get the help text for the field. @@ -423,13 +414,18 @@ class OLCTagField(OLCField): label = "A single label for the object." def validate(self, value): + category = None + if ':' in value: + value, category = value.rsplit(':', 1) + return (value, category) def from_entity(self, entity, **kwargs): if "index" in kwargs: self.value = entity.tags.all()[int(kwargs)] def to_prototype(self, prototype): - if is_iter(prototype["tags"]): prototype["tags"].append(self.value) + if is_iter(prototype["tags"]): + prototype["tags"].append(self.value) else: prototype["tags"] = [self.value] @@ -469,6 +465,7 @@ class OLCTagBatchField(OLCBatchField): return '\n'.join(outstr) +# TODO fix this to correctly handle key, value, category # setting single Attribute class OLCAttributeField(OLCField): key = "Attribute" From 60fcb471ec4d1513c45c17c153b0e2b83b5b605e Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 17 May 2017 22:45:41 +0200 Subject: [PATCH 28/63] Primitive display of olc fields, not working yet. --- evennia/utils/olc/olc.py | 22 +++++++++++++++------- evennia/utils/olc/olc_fields.py | 18 +++++++++--------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/evennia/utils/olc/olc.py b/evennia/utils/olc/olc.py index 5e16bd8b2f..0e8754e84e 100644 --- a/evennia/utils/olc/olc.py +++ b/evennia/utils/olc/olc.py @@ -23,8 +23,8 @@ Stored so as to be possible to reproduce. """ -from collections import OrderedDict from time import time +from collections import OrderedDict from evennia.utils.evmenu import EvMenu from evennia.commands.command import Command @@ -46,15 +46,11 @@ def _new_session(): Returns: olcsession (dict): An empty OLCSession. - Notes: - This is a customized dict which the Attribute system will - understand how to pickle and depickle since it provides - iteration. """ return { # header info "caller": None, # the current user of this session - "modified": time.now(), # last time this olcsession was active + "modified": time(), "db_model": None, # currently unused, ObjectDB for now "prompt_template": _DEFAULT_PROMPT, # prompt display "olcfields": OrderedDict(), # registered OLCFields. Order matters @@ -84,7 +80,7 @@ def search_entity(osession, query): query (str): This is a string, a #dbref or an extended search """ - osession['db_model'].__class__. + pass @@ -115,6 +111,18 @@ def display_field_value(osession, fieldname): # Access function +from evennia.utils.olc import olc_pages +def display_obj(obj): + """ + Test of displaying object using fields and pages. + """ + olcsession = _new_session() + olcsession['caller'] = obj + page = olc_pages.OLCObjectPage(olcsession) + obj.msg(str(page)) + + + def OLC(caller, target=None, startnode=None): """ This function is a common entry-point into the OLC menu system. It is used diff --git a/evennia/utils/olc/olc_fields.py b/evennia/utils/olc/olc_fields.py index 0c758946a4..bd6217d49c 100644 --- a/evennia/utils/olc/olc_fields.py +++ b/evennia/utils/olc/olc_fields.py @@ -56,12 +56,11 @@ class OLCField(object): actions = ['edit', 'clear', 'help'] def __init__(self, olcsession): - self.olcsession = olcsession - self._value_history = deque([self.initial], _LEN_HISTORY) + self._value_history = deque([self.default], _LEN_HISTORY) self._history_pos = 0 self._has_changed = False - def __repr__(self): + def __str__(self): return to_str(self.display()) def __unicode__(self): @@ -87,7 +86,7 @@ class OLCField(object): return else: newval = "" - raise InvalidActionError('Edit {value}->{newval}'.format(value=self.value, newval)) + raise InvalidActionError('Edit {value}->{newval}'.format(value=self.value, newval=newval)) def action_clear(self, *args): """ @@ -136,7 +135,7 @@ class OLCField(object): try: value = self.validate(value) except Exception as err: - errtext = _OLC_VALIDATION_ERROR.format(fieldname=self.key, value=original_value, error=err) + errtxt = _OLC_VALIDATION_ERROR.format(fieldname=self.key, value=original_value, error=err) raise ValidationError(errtxt) if (self._value_history and isinstance(value, (basestring, bool, int, float)) and self._value_history[0] == value): @@ -150,7 +149,7 @@ class OLCField(object): @value.deleter def value(self): self.history_pos = 0 - self._value_history.appendleft(self.initial) + self._value_history.appendleft(self.default) def history(self, step): """ @@ -187,6 +186,7 @@ class OLCField(object): Args: entity (any): An object to use for populating this field (like an Object). + """ pass @@ -197,6 +197,7 @@ class OLCField(object): Args: prototype (dict): The prototype dict to update with the value of this field. + """ pass @@ -216,7 +217,7 @@ class OLCField(object): validated and/or processed to store in this field. Raises: - Exception: If the field was given an + ValidateException: If the field was given an invalid value to validate. """ @@ -232,8 +233,7 @@ class OLCField(object): # OLCFields for all the standard model properties # key, location, destination, home, aliases, -# permissions, tags, attributes -# ... +# permissions, tags, attributes ... class OLCKeyField(OLCField): From ce57b52dcf2a3cfb94735ec623fe61fc290eabbb Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 21 May 2017 07:49:58 +0200 Subject: [PATCH 29/63] Move olc to separate branch for now. This is because it has increased in scope and would delay the release of the devel branch unnecessarily. --- evennia/utils/olc/__init__.py | 0 evennia/utils/olc/olc.py | 156 --------- evennia/utils/olc/olc_fields.py | 523 ------------------------------ evennia/utils/olc/olc_menutree.py | 83 ----- evennia/utils/olc/olc_utils.py | 13 - 5 files changed, 775 deletions(-) delete mode 100644 evennia/utils/olc/__init__.py delete mode 100644 evennia/utils/olc/olc.py delete mode 100644 evennia/utils/olc/olc_fields.py delete mode 100644 evennia/utils/olc/olc_menutree.py delete mode 100644 evennia/utils/olc/olc_utils.py diff --git a/evennia/utils/olc/__init__.py b/evennia/utils/olc/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/evennia/utils/olc/olc.py b/evennia/utils/olc/olc.py deleted file mode 100644 index 0e8754e84e..0000000000 --- a/evennia/utils/olc/olc.py +++ /dev/null @@ -1,156 +0,0 @@ -""" -OLC - On-Line Creation - -This module is the core of the Evennia online creation helper system. -This is a resource intended for players with build privileges. - -While the OLC command can be used to start the OLC "from the top", the -system is also intended to be plugged in to enhance existing build commands -with a more menu-like building style. - -Functionality: - -- Prototype management: Allows to create and edit Prototype -dictionaries. Can store such Prototypes on the Builder Player as an Attribute -or centrally on a central store that all builders can fetch prototypes from. -- Creates a new entity either from an existing prototype or by creating the -prototype on the fly for the sake of that single object (the prototype can -then also be saved for future use). -- Recording of session, for performing a series of recorded build actions in sequence. -Stored so as to be possible to reproduce. -- Export of objects created in recording mode to a batchcode file (Immortals only). - - -""" - -from time import time -from collections import OrderedDict -from evennia.utils.evmenu import EvMenu -from evennia.commands.command import Command - - -# OLC settings -_SHOW_PROMPT = True # settings.OLC_SHOW_PROMPT -_DEFAULT_PROMPT = "" # settings.OLC_DEFAULT_PROMPT -_LEN_HISTORY = 10 # settings.OLC_HISTORY_LENGTH - - -# OLC Session - -def _new_session(): - - """ - This generates an empty olcsession structure, which is used to hold state - information in the olc but which can also be pickled. - - Returns: - olcsession (dict): An empty OLCSession. - - """ - return { - # header info - "caller": None, # the current user of this session - "modified": time(), - "db_model": None, # currently unused, ObjectDB for now - "prompt_template": _DEFAULT_PROMPT, # prompt display - "olcfields": OrderedDict(), # registered OLCFields. Order matters - "prototype_key": "", # current active prototype key - } - - -def _update_prompt(osession): - """ - Update the OLC status prompt. - - Returns: - prompt (str): The prompt based on the - prompt template, populated with - the olcsession state. - - """ - return "" - - -def search_entity(osession, query): - """ - Perform a query for a specified entity. Which type of entity is determined by the osession - state. - - Args: - query (str): This is a string, a #dbref or an extended search - - """ - pass - - - - -def display_prototype(osession): - """ - Display prototype fields according to the order of the registered olcfields. - - """ - # TODO: Simple one column display to begin with - make multi-column later - pkey = osession['prototype_key'] - outtxt = ["=== {pkey} ===".format(pkey=pkey)] - for field in osession['olcfields'].values(): - fname, flabel, fvalue = field.name, field.label, field.display() - outtxt.append(" {fieldname} ({label}): {value}".format(fieldname=fname, - label=flabel, value=fvalue)) - return '\n'.join(outtxt) - - -def display_field_value(osession, fieldname): - """ - Display info about a specific field. - """ - field = osession['olcfields'].get(fieldname, None) - if field: - return "{fieldname}: {value}".format(fieldname=field.name, value=field.display()) - - -# Access function - -from evennia.utils.olc import olc_pages -def display_obj(obj): - """ - Test of displaying object using fields and pages. - """ - olcsession = _new_session() - olcsession['caller'] = obj - page = olc_pages.OLCObjectPage(olcsession) - obj.msg(str(page)) - - - -def OLC(caller, target=None, startnode=None): - """ - This function is a common entry-point into the OLC menu system. It is used - by Evennia systems to jump into the different possible start points of the - OLC menu tree depending on what info is already available. - - Args: - caller (Object or Player): The one using the olc. - target (Object, optional): Object to operate on, if any is known. - startnode (str, optional): Where in the menu tree to start. If unset, - will be decided by whether target is given or not. - - """ - startnode = startnode or (target and "node_edit_top") or "node_top" - EvMenu(caller, "evennia.utils.olc.olc_menu", startnode=startnode, target=target) - - -class CmdOLC(Command): - """ - Test OLC - - Usage: - olc [target] - - Starts the olc to create a new object or to modify an existing one. - - """ - key = "olc" - def func(self): - OLC(self.caller, target=self.args) - diff --git a/evennia/utils/olc/olc_fields.py b/evennia/utils/olc/olc_fields.py deleted file mode 100644 index bd6217d49c..0000000000 --- a/evennia/utils/olc/olc_fields.py +++ /dev/null @@ -1,523 +0,0 @@ -""" -OLC fields describe how to edit and display a specific piece of data of a prototype within the OLC system. - -The OLC system imports and adds these field classes to its prototype manipulation pages in order to -know what data to read and how to display it. - -""" -from collections import deque -from evennia.utils.utils import to_str, to_unicode -from evennia.utils.olc import olc_utils - -# from django.conf import settings - -_OLC_VALIDATION_ERROR = """ -Error storing data in {fieldname}: - {value} -The reported error was - {error} -""" - -_LEN_HISTORY = 10 # settings.OLC_HISTORY_LENGTH - - -class InvalidActionError(RuntimeError): - """ - Raised when trying to perform a field action the field - does not support. - """ - pass - - -class ValidationError(RuntimeError): - """ - Raised when failing to validate new data being entered - into the field (from any source) - """ - pass - - -class OLCField(object): - """ - This is the parent for all OLC fields. This docstring acts - as the help text for the field. - - """ - # name of this field, for error reporting - key = "Empty field" - # if this field must have a value different than None - required = False - # used for displaying extra info in the OLC - label = "Empty field" - # initial value of field if not given - default = None - # actions available on this field. Available actions - # are replace, edit, append, remove, clear, help - actions = ['edit', 'clear', 'help'] - - def __init__(self, olcsession): - self._value_history = deque([self.default], _LEN_HISTORY) - self._history_pos = 0 - self._has_changed = False - - def __str__(self): - return to_str(self.display()) - - def __unicode__(self): - return to_unicode(self.display()) - - # perform actions - # TODO - include editor in check! - def action_edit(self, *args): - """ - Edit field value. - - Args: - newval (any): New value to add/replace with. - - Raises: - InvalidActionError: If editing is not allowed. - - """ - if args: - newval = args[0] - if 'edit' in self.actions: - self.value = newval - return - else: - newval = "" - raise InvalidActionError('Edit {value}->{newval}'.format(value=self.value, newval=newval)) - - def action_clear(self, *args): - """ - Clear field back to default. - - Returns: - default (any): The field'd default value, now set. - - Raises: - InvalidActionError: If clearing is not allowed. - - """ - if 'clear' in self.actions: - # don't validate this - object.__setattr__(self, 'value', self.default) - return self.value - raise InvalidActionError('Clear') - - def action_help(self, *args): - """ - Get the help text for the field. - - Returns: - help (str): Field help text. - - Raises: - InvalidActionError: If help is not given for this field, - either becuase it's disallowed or unset. - - """ - if 'help' not in self.actions or not self.__doc__: - raise InvalidActioNError('Help') - return self.__doc__ - - # storing data to the field in a history-aware way - @property - def value(self): - return self._value_history[self._history_pos] - - @value.setter - def value(self, value): - """ - Update field value by updating the history. - """ - original_value = value - try: - value = self.validate(value) - except Exception as err: - errtxt = _OLC_VALIDATION_ERROR.format(fieldname=self.key, value=original_value, error=err) - raise ValidationError(errtxt) - if (self._value_history and isinstance(value, (basestring, bool, int, float)) and - self._value_history[0] == value): - # don't change/update history if re-adding the same thing - return - else: - self._has_changed = True - self._history_pos = 0 - self._value_history.appendleft(value) - - @value.deleter - def value(self): - self.history_pos = 0 - self._value_history.appendleft(self.default) - - def history(self, step): - """ - Change history position. - - Args: - step (int): Step in the history stack. Positive movement - means moving futher back in history (with a maximum - of `settings.OLC_HISTORY_LENGTH`, negative steps - moves towards recent history (with 0 being the latest - value). - - """ - self._history_pos = min(len(self.value_history)-1, max(0, self._history_pos + step)) - - def has_changed(self): - """ - Check if this field has changed. - - Returns: - changed (bool): If the field changed or not. - - """ - return bool(self._has_changed) - - # overloadable methods - - def from_entity(self, entity, **kwargs): - """ - Populate this field by retrieving data from an entity. - All fields on a page will have this called, so must - be able to handle also incompatible entities. - - Args: - entity (any): An object to use for - populating this field (like an Object). - - """ - pass - - def to_prototype(self, prototype): - """ - Store this field value in a prototype. - - Args: - prototype (dict): The prototype dict - to update with the value of this field. - - """ - pass - - def validate(self, value, **kwargs): - """ - Validate/preprocess incoming data to store in this field. - - Args: - value (any): An input value to - validate - - Kwargs: - any (any): Optional info to send to field. - - Returns: - validated_value (any): The value, correctly - validated and/or processed to store in this field. - - Raises: - ValidateException: If the field was given an - invalid value to validate. - - """ - return str(value) - - def display(self): - """ - How to display the field contents in the OLC display. - - """ - return self.value - - -# OLCFields for all the standard model properties -# key, location, destination, home, aliases, -# permissions, tags, attributes ... - - -class OLCKeyField(OLCField): - """ - The name (key) of the object is its main identifier, used - throughout listings even if may not always be visible to - the end user. - """ - key = 'Name' - required = True - label = "The object's name" - - def from_entity(self, entity, **kwargs): - self.value = entity.db_key - - def to_prototype(self, prototype): - prototype['key'] = self.value - - -class OLCLocationField(OLCField): - """ - An object's location is usually a Room but could be any - other in-game entity. By convention, Rooms themselves have - a None location. Objects are otherwise only placed in a - None location to take them out of the game. - """ - key = 'Location' - required = False - label = "The object's current location" - - def validate(self, value): - return olc_utils.search_by_string(self.olcsession, value) - - def from_entity(self, entity, **kwargs): - self.value = entity.db_location - - def to_prototype(self, prototype): - prototype['location'] = self.value - - -class OLCHomeField(OLCField): - """ - An object's home location acts as a fallback when various - extreme situations occur. An example is when a location is - deleted - all its content (except exits) are then not deleted - but are moved to each object's home location. - """ - key = 'Home' - required = True - label = "The object's home location" - - def validate(self, value): - return olc_utils.search_by_string(self.olcsession, value) - - def from_entity(self, entity, **kwargs): - self.value = entity.db_home - - def to_prototype(self, prototype): - prototype['home'] = self.value - - -class OLCDestinationField(OLCField): - """ - An object's destination is usually not set unless the object - represents an exit between game locations. If set, the - destination should be set to the location you get to when - passing through this exit. - - """ - key = 'Destination' - required = False - label = "The object's (usually exit's) destination" - - def validate(self, value): - return olc_utils.search_by_string(self.olcsession, value) - - def from_entity(self, entity, **kwargs): - self.value = entity.db_destination - - def to_prototype(self, prototype): - prototype['destination'] = self.value - - -class OLCBatchField(OLCField): - """ - A field managing multiple values that can be appended to and - a given component popped out. - """ - actions = OLCField.actions + ['append', 'pop'] - - def action_append(self, value): - """ - Append a new value to this field. - - Args: - value (any): The value to append. - - """ - value = self.value - value.append(value) - self.value = value - - def action_pop(self, index=-1): - """ - Pop an element from the field. - - Args: - index (int, optional): Pop this index, otherwise pop the last - element in the field. - - Returns: - element (any or None): The popped element or None. - - """ - lst = self.value - try: - return lst.pop(int(index)) - except IndexError: - return None - - -# setting single Alias -class OLCAliasField(OLCField): - key = "Alias" - required = False - label = "An alternative name for the object" - - def from_entity(self, entity, **kwargs): - if "index" in kwargs: - self.value = entity.aliases.all()[int(kwargs)] - - def to_prototype(self, prototype): - if is_iter(prototype["aliases"]): - prototype["aliases"].append(self.value) - else: - prototype["aliases"] = [self.value] - - -# batch-setting aliases -class OLCAliasBatchField(OLCBatchField): - """ - Specify as a comma-separated list. Use quotes around the - alias if the alias itself contains a comma. - - Aliases are alternate names for an object. An alias is just - as fast to search for as a key and two objects are assumed - to have the same name is *either* their name or any of their - aliases match. - - """ - key = 'Aliases' - required = False - label = "The object's alternative name or names" - - def validate(self, value): - return olc_utils.split_by_comma(value) - - def from_entity(self, entity, **kwargs): - self.value = list(entity.aliases.all()) - - def to_prototype(self, prototype): - prototype['aliases'] = self.value - - -# setting single Tag -class OLCTagField(OLCField): - """ - Specify as tagname or tagname:category - - Tags are used to identify groups of objects for later quick retrieval. - This is very useful for anything from creating zones of rooms to - easily find all Characters belonging a given group etc. A tag can also - have a category for a second level of grouping. - - """ - key = "Tag" - required = False - label = "A single label for the object." - - def validate(self, value): - category = None - if ':' in value: - value, category = value.rsplit(':', 1) - return (value, category) - - def from_entity(self, entity, **kwargs): - if "index" in kwargs: - self.value = entity.tags.all()[int(kwargs)] - - def to_prototype(self, prototype): - if is_iter(prototype["tags"]): - prototype["tags"].append(self.value) - else: - prototype["tags"] = [self.value] - - -# batch-setting Tags -class OLCTagBatchField(OLCBatchField): - """ - Specify as a comma-separated list of tagname or tagname:category. - - Tags are used to identify groups of objects for later quick retrieval. - This is very useful for anything from creating zones of rooms to - easily find all Characters belonging a given group etc. - - """ - key = 'Tags' - required = False - label = "Attach labels to objects to group and find them." - - def validate(self, value): - if isinstance(value, basestring): - return [tuple(tagstr.split(':', 1)) if ':' in tagstr else (tagstr, None) - for tagstr in olc_utils.split_by_comma(value)] - else: - # assume a list of (key, category) - just let it pass - return value - - def from_entity(self, entity, **kwargs): - self.value = entity.tags.all(return_key_and_category=True) - - def to_prototype(self, prototype): - prototype['tags'] = self.value - - def display(self): - outstr = [] - for key, category in self.value: - outstr.append("{key}:{category}".format(key=key, category=category)) - return '\n'.join(outstr) - - -# TODO fix this to correctly handle key, value, category -# setting single Attribute -class OLCAttributeField(OLCField): - key = "Attribute" - required = False - label = "An alternative name for the object" - - def from_entity(self, entity, **kwargs): - if "index" in kwargs: - self.value = entity.attributes.all()[int(kwargs)] - - def to_prototype(self, prototype): - if is_iter(prototype["attrs"]): - prototype["attrs"].append(self.value) - else: - prototype["attrs"] = [self.value] - - -# batch-setting attributes -class OLCAttributeBatchField(OLCBatchField): - """ - Specify as a comma-separated list of attrname=value or attrname:category=value. - - Attributes are arbitrary pieces of data attached to an object. They can - contain references to other objects as well as simple Python structures such - as lists and dicts. - - """ - key = 'Attributes' - required = False - label = "Additional data attached to this object." - actions = OLCField.actions + ['append'] - - def validate(self, value): - if isinstance(value, basestring): - return [tuple(lhs.split(':', 1) + [rhs]) if ':' in lhs else (lhs, None) + (rhs, ) - for lhs, rhs in (attrstr.split('=', 1) if ':' in attrstr else ((attrstr, None),)) - for attrstr in olc_utils.split_by_comma(value)] - else: - # we assume this is a list of Attributes - return [(attr.key, attr.category, attr.value) for attr in value] - - def from_entity(self, entity, **kwargs): - self.value = entity.attributes.all() - - def to_prototype(self, prototype): - for key, category, value in self.value: - prototype['attrs'] = (key, value, category) - - def display(self): - outstr = [] - for key, category, value in self.value: - outstr.append("{key}:{category} = {value}".format(key=key, category=category, value=value)) - return '\n'.join(outstr) - - diff --git a/evennia/utils/olc/olc_menutree.py b/evennia/utils/olc/olc_menutree.py deleted file mode 100644 index a550782949..0000000000 --- a/evennia/utils/olc/olc_menutree.py +++ /dev/null @@ -1,83 +0,0 @@ -""" -This describes the menu structure/logic of the OLC system editor, using the EvMenu subsystem. The -various nodes are modular and will when possible make use of the various utilities of the OLC rather -than hard-coding things in each node. - -Menu structure: - - start: - new object - edit object - manage prototypes - export session to batchcode file (immortals only) - - new/edit object: - Protoype - Typeclass - Key - Location - Destination - PErmissions - LOcks - Attributes - TAgs - Scripts - - create/update object - copy object - save prototype - save/delete object - update existing objects - - manage prototypes - list prototype - search prototype - import prototype (from global store) - - export session - -""" - -def node_top(caller, raw_input): - # top level node - # links to edit, manage, export - text = """OnLine Creation System""" - options = ({"key": ("|yN|new", "new", "n"), - "desc": "New object", - "goto": "node_new_top", - "exec": _obj_to_prototype}, - {"key": ("|yE|ndit", "edit", "e", "m"), - "desc": "Edit existing object", - "goto": "node_edit_top", - "exec": _obj_to_prototype}, - {"key": ("|yP|nrototype", "prototype", "manage", "p", "m"), - "desc": "Manage prototypes", - "goto": "node_prototype_top"}, - {"key": ("E|yx|nport", "export", "x"), - "desc": "Export to prototypes", - "goto": "node_prototype_top"}, - {"key": ("|yQ|nuit", "quit", "q"), - "desc": "Quit OLC", - "goto": "node_quit"},) - return text, options - -def node_quit(caller, raw_input): - return 'Exiting.', None - -def node_new_top(caller, raw_input): - pass - -def node_edit_top(caller, raw_input): - # edit top level - text = """Edit object""" - - -def node_prototype_top(caller, raw_input): - # manage prototypes - pass - - -def node_export_top(caller, raw_input): - # export top level - pass - diff --git a/evennia/utils/olc/olc_utils.py b/evennia/utils/olc/olc_utils.py deleted file mode 100644 index 68bfd3ac9d..0000000000 --- a/evennia/utils/olc/olc_utils.py +++ /dev/null @@ -1,13 +0,0 @@ -""" -Miscellaneous utilities for the OLC system. - -""" -import csv - - -def search_by_string(olcsession, query): - pass - - -def split_by_comma(string): - return csv.reader([string], skipinitialspace=True) From 7e416e0cd6e34c77693d68c3e95129f512059099 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 6 Jun 2017 14:56:13 +0200 Subject: [PATCH 30/63] Start fixing things for django 1.10. --- evennia/server/server.py | 2 +- evennia/typeclasses/models.py | 4 +++- requirements.txt | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/evennia/server/server.py b/evennia/server/server.py index 038846e5b4..70d07bbea1 100644 --- a/evennia/server/server.py +++ b/evennia/server/server.py @@ -195,7 +195,7 @@ class Evennia(object): Optimize some SQLite stuff at startup since we can't save it to the database. """ - if ((".".join(str(i) for i in django.VERSION) < "1.2" and settings.DATABASE_ENGINE == "sqlite3") + if ((".".join(str(i) for i in django.VERSION) < "1.2" and settings.DATABASES.get('default', {}).get('ENGINE') == "sqlite3") or (hasattr(settings, 'DATABASES') and settings.DATABASES.get("default", {}).get('ENGINE', None) == 'django.db.backends.sqlite3')): diff --git a/evennia/typeclasses/models.py b/evennia/typeclasses/models.py index 3cb4af35d5..98aaa0e57f 100644 --- a/evennia/typeclasses/models.py +++ b/evennia/typeclasses/models.py @@ -98,7 +98,9 @@ class TypeclassBase(SharedMemoryModelBase): # this is a copy of django.db.models.base.__new__ # with a few lines changed as per # https://code.djangoproject.com/ticket/11560 - new_class = patched_new(cls, name, bases, attrs) + #new_class = patched_new(cls, name, bases, attrs) + new_class = super(TypeclassBase, cls).__new__(cls, name, bases, attrs) + #new_class = patched_new(cls, name, bases, attrs) # attach signals signals.post_save.connect(post_save, sender=new_class) diff --git a/requirements.txt b/requirements.txt index d1e4790d4c..9c7f10fba6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # Evennia dependencies, for Linux/Mac platforms -django >= 1.8, < 1.10 +django >= 1.9, < 1.11 twisted >= 16.0.0 mock >= 1.0.1 pillow == 2.9.0 From a727547b6fafad721d3f4bc07a8c4bb45388ff92 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 6 Jun 2017 15:44:53 +0200 Subject: [PATCH 31/63] Test typeclass upgrade for new django version. Not working yet. --- evennia/typeclasses/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/evennia/typeclasses/models.py b/evennia/typeclasses/models.py index 98aaa0e57f..dc7060da8c 100644 --- a/evennia/typeclasses/models.py +++ b/evennia/typeclasses/models.py @@ -29,6 +29,7 @@ from builtins import object from django.db.models import signals +from django.db.models.base import ModelBase from django.db import models from django.core.exceptions import ObjectDoesNotExist from django.conf import settings @@ -99,8 +100,9 @@ class TypeclassBase(SharedMemoryModelBase): # with a few lines changed as per # https://code.djangoproject.com/ticket/11560 #new_class = patched_new(cls, name, bases, attrs) - new_class = super(TypeclassBase, cls).__new__(cls, name, bases, attrs) + #new_class = super(TypeclassBase, cls).__new__(cls, name, bases, attrs) #new_class = patched_new(cls, name, bases, attrs) + new_class = super(ModelBase, cls).__new__(cls, name, bases, attrs) # attach signals signals.post_save.connect(post_save, sender=new_class) From cdeffc1f0841ee0b86f6cadaebcce5be5200f927 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 6 Jun 2017 17:31:22 +0200 Subject: [PATCH 32/63] Add some debug outputs. --- evennia/typeclasses/models.py | 1 + evennia/typeclasses/tags.py | 1 + 2 files changed, 2 insertions(+) diff --git a/evennia/typeclasses/models.py b/evennia/typeclasses/models.py index dc7060da8c..f827169b9f 100644 --- a/evennia/typeclasses/models.py +++ b/evennia/typeclasses/models.py @@ -103,6 +103,7 @@ class TypeclassBase(SharedMemoryModelBase): #new_class = super(TypeclassBase, cls).__new__(cls, name, bases, attrs) #new_class = patched_new(cls, name, bases, attrs) new_class = super(ModelBase, cls).__new__(cls, name, bases, attrs) + print "name:", name, new_class._meta.proxy # attach signals signals.post_save.connect(post_save, sender=new_class) diff --git a/evennia/typeclasses/tags.py b/evennia/typeclasses/tags.py index e9bd24eed0..dea0616087 100644 --- a/evennia/typeclasses/tags.py +++ b/evennia/typeclasses/tags.py @@ -107,6 +107,7 @@ class TagHandler(object): query = {"%s__id" % self._model : self._objid, "tag__db_model" : self._model, "tag__db_tagtype" : self._tagtype} + print("CACHE:", query, self._m2m_fieldname) tags = [conn.tag for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)] self._cache = dict(("%s-%s" % (to_str(tag.db_key).lower(), tag.db_category.lower() if tag.db_category else None), From ce3558d654dd041b5ae51d440399a294dae7cb2f Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 6 Jun 2017 18:45:41 +0200 Subject: [PATCH 33/63] Go to Django 1.10. Remove support for Django 1.9. --- evennia/typeclasses/django_new_patch.py | 240 ------------------------ evennia/typeclasses/models.py | 16 +- evennia/typeclasses/tags.py | 1 - evennia/utils/idmapper/models.py | 2 +- requirements.txt | 2 +- 5 files changed, 6 insertions(+), 255 deletions(-) delete mode 100644 evennia/typeclasses/django_new_patch.py diff --git a/evennia/typeclasses/django_new_patch.py b/evennia/typeclasses/django_new_patch.py deleted file mode 100644 index 685b16b97a..0000000000 --- a/evennia/typeclasses/django_new_patch.py +++ /dev/null @@ -1,240 +0,0 @@ -""" -This is a patch of django.db.models.base.py:__new__, to allow for the -proxy system to allow multiple inheritance when both parents are of -the same base model. - -This patch is implemented as per -https://code.djangoproject.com/ticket/11560 and will hopefully be -possible to remove as it gets added to django's main branch. -""" - -# django patch imports -import sys -import copy -import warnings -from django.apps import apps -from django.db.models.base import ModelBase, subclass_exception -from django.core.exceptions import ObjectDoesNotExist -from django.db.models.options import Options -from django.core.exceptions import MultipleObjectsReturned, FieldError -from django.apps.config import MODELS_MODULE_NAME -from django.db.models.fields.related import OneToOneField -#/ django patch imports - -def patched_new(cls, name, bases, attrs): - "Patched version of __new__" - - super_new = super(ModelBase, cls).__new__ - - # Also ensure initialization is only performed for subclasses of Model - # (excluding Model class itself). - parents = [b for b in bases if isinstance(b, ModelBase)] - if not parents: - return super_new(cls, name, bases, attrs) - - # Create the class. - module = attrs.pop('__module__') - new_class = super_new(cls, name, bases, {'__module__': module}) - attr_meta = attrs.pop('Meta', None) - abstract = getattr(attr_meta, 'abstract', False) - if not attr_meta: - meta = getattr(new_class, 'Meta', None) - else: - meta = attr_meta - base_meta = getattr(new_class, '_meta', None) - - # Look for an application configuration to attach the model to. - app_config = apps.get_containing_app_config(module) - - kwargs = {} - if getattr(meta, 'app_label', None) is None: - - if app_config is None: - # If the model is imported before the configuration for its - # application is created (#21719), or isn't in an installed - # application (#21680), use the legacy logic to figure out the - # app_label by looking one level up from the package or module - # named 'models'. If no such package or module exists, fall - # back to looking one level up from the module this model is - # defined in. - - # For 'django.contrib.sites.models', this would be 'sites'. - # For 'geo.models.places' this would be 'geo'. - - if abstract: - kwargs = {"app_label": None} - else: - msg = ( - "Model class %s.%s doesn't declare an explicit app_label " - "and either isn't in an application in INSTALLED_APPS or " - "else was imported before its application was loaded. " % - (module, name)) - raise RuntimeError(msg) - - new_class.add_to_class('_meta', Options(meta, **kwargs)) - if not abstract: - new_class.add_to_class( - 'DoesNotExist', - subclass_exception( - str('DoesNotExist'), - tuple(x.DoesNotExist for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (ObjectDoesNotExist,), - module, - attached_to=new_class)) - new_class.add_to_class( - 'MultipleObjectsReturned', - subclass_exception( - str('MultipleObjectsReturned'), - tuple(x.MultipleObjectsReturned for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (MultipleObjectsReturned,), - module, - attached_to=new_class)) - if base_meta and not base_meta.abstract: - # Non-abstract child classes inherit some attributes from their - # non-abstract parent (unless an ABC comes before it in the - # method resolution order). - if not hasattr(meta, 'ordering'): - new_class._meta.ordering = base_meta.ordering - if not hasattr(meta, 'get_latest_by'): - new_class._meta.get_latest_by = base_meta.get_latest_by - - is_proxy = new_class._meta.proxy - - # If the model is a proxy, ensure that the base class - # hasn't been swapped out. - if is_proxy and base_meta and base_meta.swapped: - raise TypeError("%s cannot proxy the swapped model '%s'." % (name, base_meta.swapped)) - - if getattr(new_class, '_default_manager', None): - if not is_proxy: - # Multi-table inheritance doesn't inherit default manager from - # parents. - new_class._default_manager = None - new_class._base_manager = None - else: - # Proxy classes do inherit parent's default manager, if none is - # set explicitly. - new_class._default_manager = new_class._default_manager._copy_to_model(new_class) - new_class._base_manager = new_class._base_manager._copy_to_model(new_class) - - # Add all attributes to the class. - for obj_name, obj in attrs.items(): - new_class.add_to_class(obj_name, obj) - - # All the fields of any type declared on this model - new_fields = ( - new_class._meta.local_fields + - new_class._meta.local_many_to_many + - new_class._meta.virtual_fields - ) - field_names = set(f.name for f in new_fields) - - # Basic setup for proxy models. - if is_proxy: - base = None - for parent in [kls for kls in parents if hasattr(kls, '_meta')]: - if parent._meta.abstract: - if parent._meta.fields: - raise TypeError("Abstract base class containing model fields not permitted for proxy model '%s'." % name) - else: - continue - #if base is not None: # patch - while parent._meta.proxy: # patch - parent = parent._meta.proxy_for_model # patch - if base is not None and base is not parent: # patch - raise TypeError("Proxy model '%s' has more than one non-abstract model base class." % name) - else: - base = parent - if base is None: - raise TypeError("Proxy model '%s' has no non-abstract model base class." % name) - new_class._meta.setup_proxy(base) - new_class._meta.concrete_model = base._meta.concrete_model - else: - new_class._meta.concrete_model = new_class - - # Collect the parent links for multi-table inheritance. - parent_links = {} - for base in reversed([new_class] + parents): - # Conceptually equivalent to `if base is Model`. - if not hasattr(base, '_meta'): - continue - # Skip concrete parent classes. - if base != new_class and not base._meta.abstract: - continue - # Locate OneToOneField instances. - for field in base._meta.local_fields: - if isinstance(field, OneToOneField): - parent_links[field.rel.to] = field - - # Do the appropriate setup for any model parents. - for base in parents: - original_base = base - if not hasattr(base, '_meta'): - # Things without _meta aren't functional models, so they're - # uninteresting parents. - continue - - parent_fields = base._meta.local_fields + base._meta.local_many_to_many - # Check for clashes between locally declared fields and those - # on the base classes (we cannot handle shadowed fields at the - # moment). - for field in parent_fields: - if field.name in field_names: - raise FieldError( - 'Local field %r in class %r clashes ' - 'with field of similar name from ' - 'base class %r' % (field.name, name, base.__name__) - ) - if not base._meta.abstract: - # Concrete classes... - base = base._meta.concrete_model - if base in parent_links: - field = parent_links[base] - elif not is_proxy: - attr_name = '%s_ptr' % base._meta.model_name - field = OneToOneField(base, name=attr_name, - auto_created=True, parent_link=True) - # Only add the ptr field if it's not already present; - # e.g. migrations will already have it specified - if not hasattr(new_class, attr_name): - new_class.add_to_class(attr_name, field) - else: - field = None - new_class._meta.parents[base] = field - else: - # .. and abstract ones. - for field in parent_fields: - new_class.add_to_class(field.name, copy.deepcopy(field)) - - # Pass any non-abstract parent classes onto child. - new_class._meta.parents.update(base._meta.parents) - - # Inherit managers from the abstract base classes. - new_class.copy_managers(base._meta.abstract_managers) - - # Proxy models inherit the non-abstract managers from their base, - # unless they have redefined any of them. - if is_proxy: - new_class.copy_managers(original_base._meta.concrete_managers) - - # Inherit virtual fields (like GenericForeignKey) from the parent - # class - for field in base._meta.virtual_fields: - if base._meta.abstract and field.name in field_names: - raise FieldError( - 'Local field %r in class %r clashes ' - 'with field of similar name from ' - 'abstract base class %r' % (field.name, name, base.__name__) - ) - new_class.add_to_class(field.name, copy.deepcopy(field)) - - if abstract: - # Abstract base models can't be instantiated and don't appear in - # the list of models for an app. We do the final setup for them a - # little differently from normal models. - attr_meta.abstract = False - new_class.Meta = attr_meta - return new_class - - new_class._prepare() - new_class._meta.apps.register_model(new_class._meta.app_label, new_class) - - return new_class diff --git a/evennia/typeclasses/models.py b/evennia/typeclasses/models.py index f827169b9f..4fbbe891dd 100644 --- a/evennia/typeclasses/models.py +++ b/evennia/typeclasses/models.py @@ -95,15 +95,7 @@ class TypeclassBase(SharedMemoryModelBase): attrs["Meta"] = Meta attrs["Meta"].proxy = True - # patch for django proxy multi-inheritance - # this is a copy of django.db.models.base.__new__ - # with a few lines changed as per - # https://code.djangoproject.com/ticket/11560 - #new_class = patched_new(cls, name, bases, attrs) - #new_class = super(TypeclassBase, cls).__new__(cls, name, bases, attrs) - #new_class = patched_new(cls, name, bases, attrs) - new_class = super(ModelBase, cls).__new__(cls, name, bases, attrs) - print "name:", name, new_class._meta.proxy + new_class = ModelBase.__new__(cls, name, bases, attrs) # attach signals signals.post_save.connect(post_save, sender=new_class) @@ -207,7 +199,7 @@ class TypedObject(SharedMemoryModel): self.__class__ = class_from_module(self.__defaultclasspath__) except Exception: log_trace() - self.__class__ = self._meta.proxy_for_model or self.__class__ + self.__class__ = self._meta.concrete_model or self.__class__ finally: self.db_typeclass_path = typeclass_path elif self.db_typeclass_path: @@ -219,12 +211,12 @@ class TypedObject(SharedMemoryModel): self.__class__ = class_from_module(self.__defaultclasspath__) except Exception: log_trace() - self.__dbclass__ = self._meta.proxy_for_model or self.__class__ + self.__dbclass__ = self._meta.concrete_model or self.__class__ else: self.db_typeclass_path = "%s.%s" % (self.__module__, self.__class__.__name__) # important to put this at the end since _meta is based on the set __class__ try: - self.__dbclass__ = self._meta.proxy_for_model or self.__class__ + self.__dbclass__ = self._meta.concrete_model or self.__class__ except AttributeError: err_class = repr(self.__class__) self.__class__ = class_from_module("evennia.objects.objects.DefaultObject") diff --git a/evennia/typeclasses/tags.py b/evennia/typeclasses/tags.py index dea0616087..e9bd24eed0 100644 --- a/evennia/typeclasses/tags.py +++ b/evennia/typeclasses/tags.py @@ -107,7 +107,6 @@ class TagHandler(object): query = {"%s__id" % self._model : self._objid, "tag__db_model" : self._model, "tag__db_tagtype" : self._tagtype} - print("CACHE:", query, self._m2m_fieldname) tags = [conn.tag for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)] self._cache = dict(("%s-%s" % (to_str(tag.db_key).lower(), tag.db_category.lower() if tag.db_category else None), diff --git a/evennia/utils/idmapper/models.py b/evennia/utils/idmapper/models.py index 54013e3dd6..a28e32980c 100644 --- a/evennia/utils/idmapper/models.py +++ b/evennia/utils/idmapper/models.py @@ -77,7 +77,7 @@ class SharedMemoryModelBase(ModelBase): """ # the dbmodel is either the proxy base or ourselves - dbmodel = cls._meta.proxy_for_model if cls._meta.proxy else cls + dbmodel = cls._meta.concrete_model if cls._meta.proxy else cls cls.__dbclass__ = dbmodel if not hasattr(dbmodel, "__instance_cache__"): # we store __instance_cache__ only on the dbmodel base diff --git a/requirements.txt b/requirements.txt index 9c7f10fba6..3f507cde7b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # Evennia dependencies, for Linux/Mac platforms -django >= 1.9, < 1.11 +django > 1.9, < 1.11 twisted >= 16.0.0 mock >= 1.0.1 pillow == 2.9.0 From e2de340f7d658346c4c4ceb30473b6fc0fbea522 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 6 Jun 2017 18:56:04 +0200 Subject: [PATCH 34/63] Move to Django 1.11. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3f507cde7b..bbb9e60dbd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # Evennia dependencies, for Linux/Mac platforms -django > 1.9, < 1.11 +django > 1.10, < 2.0 twisted >= 16.0.0 mock >= 1.0.1 pillow == 2.9.0 From c60555b70a833c79290d747fbb7149323e85a432 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 6 Jun 2017 19:34:56 +0200 Subject: [PATCH 35/63] Add migrations to django 1.11. --- .../migrations/0011_auto_20170606_1731.py | 81 +++++++++++++++++++ evennia/comms/models.py | 22 ++--- .../migrations/0002_auto_20170606_1731.py | 20 +++++ evennia/help/models.py | 2 +- .../migrations/0006_auto_20170606_1731.py | 32 ++++++++ evennia/objects/models.py | 4 +- .../migrations/0006_auto_20170606_1731.py | 31 +++++++ .../migrations/0008_auto_20170606_1731.py | 25 ++++++ evennia/typeclasses/models.py | 4 +- 9 files changed, 206 insertions(+), 15 deletions(-) create mode 100644 evennia/comms/migrations/0011_auto_20170606_1731.py create mode 100644 evennia/help/migrations/0002_auto_20170606_1731.py create mode 100644 evennia/objects/migrations/0006_auto_20170606_1731.py create mode 100644 evennia/players/migrations/0006_auto_20170606_1731.py create mode 100644 evennia/scripts/migrations/0008_auto_20170606_1731.py diff --git a/evennia/comms/migrations/0011_auto_20170606_1731.py b/evennia/comms/migrations/0011_auto_20170606_1731.py new file mode 100644 index 0000000000..6d99ae85b3 --- /dev/null +++ b/evennia/comms/migrations/0011_auto_20170606_1731.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-06 17:31 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comms', '0010_auto_20161206_1912'), + ] + + operations = [ + migrations.AlterField( + model_name='channeldb', + name='db_attributes', + field=models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute'), + ), + migrations.AlterField( + model_name='channeldb', + name='db_object_subscriptions', + field=models.ManyToManyField(blank=True, db_index=True, related_name='object_subscription_set', to='objects.ObjectDB', verbose_name=b'subscriptions'), + ), + migrations.AlterField( + model_name='channeldb', + name='db_subscriptions', + field=models.ManyToManyField(blank=True, db_index=True, related_name='subscription_set', to=settings.AUTH_USER_MODEL, verbose_name=b'subscriptions'), + ), + migrations.AlterField( + model_name='channeldb', + name='db_tags', + field=models.ManyToManyField(help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag'), + ), + migrations.AlterField( + model_name='msg', + name='db_hide_from_channels', + field=models.ManyToManyField(blank=True, related_name='hide_from_channels_set', to='comms.ChannelDB'), + ), + migrations.AlterField( + model_name='msg', + name='db_hide_from_objects', + field=models.ManyToManyField(blank=True, related_name='hide_from_objects_set', to='objects.ObjectDB'), + ), + migrations.AlterField( + model_name='msg', + name='db_hide_from_players', + field=models.ManyToManyField(blank=True, related_name='hide_from_players_set', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='msg', + name='db_receivers_channels', + field=models.ManyToManyField(blank=True, help_text=b'channel recievers', related_name='channel_set', to='comms.ChannelDB'), + ), + migrations.AlterField( + model_name='msg', + name='db_receivers_objects', + field=models.ManyToManyField(blank=True, help_text=b'object receivers', related_name='receiver_object_set', to='objects.ObjectDB'), + ), + migrations.AlterField( + model_name='msg', + name='db_receivers_players', + field=models.ManyToManyField(blank=True, help_text=b'player receivers', related_name='receiver_player_set', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='msg', + name='db_sender_objects', + field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_object_set', to='objects.ObjectDB', verbose_name=b'sender(object)'), + ), + migrations.AlterField( + model_name='msg', + name='db_sender_players', + field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_player_set', to=settings.AUTH_USER_MODEL, verbose_name=b'sender(player)'), + ), + migrations.AlterField( + model_name='msg', + name='db_tags', + field=models.ManyToManyField(blank=True, help_text=b'tags on this message. Tags are simple string markers to identify, group and alias messages.', to='typeclasses.Tag'), + ), + ] diff --git a/evennia/comms/models.py b/evennia/comms/models.py index c7b5e8d7b9..5465e51c62 100644 --- a/evennia/comms/models.py +++ b/evennia/comms/models.py @@ -79,9 +79,9 @@ class Msg(SharedMemoryModel): # an IRC channel; normally there is only one, but if co-modification of # a message is allowed, there may be more than one "author" db_sender_players = models.ManyToManyField("players.PlayerDB", related_name='sender_player_set', - null=True, blank=True, verbose_name='sender(player)', db_index=True) + blank=True, verbose_name='sender(player)', db_index=True) db_sender_objects = models.ManyToManyField("objects.ObjectDB", related_name='sender_object_set', - null=True, blank=True, verbose_name='sender(object)', db_index=True) + blank=True, verbose_name='sender(object)', db_index=True) db_sender_external = models.CharField('external sender', max_length=255, null=True, blank=True, db_index=True, help_text="identifier for external sender, for example a sender over an " "IRC connection (i.e. someone who doesn't have an exixtence in-game).") @@ -89,11 +89,11 @@ class Msg(SharedMemoryModel): # comma-separated string of object dbrefs. Can be defined along # with channels below. db_receivers_players = models.ManyToManyField('players.PlayerDB', related_name='receiver_player_set', - null=True, blank=True, help_text="player receivers") + blank=True, help_text="player receivers") db_receivers_objects = models.ManyToManyField('objects.ObjectDB', related_name='receiver_object_set', - null=True, blank=True, help_text="object receivers") + blank=True, help_text="object receivers") db_receivers_channels = models.ManyToManyField("ChannelDB", related_name='channel_set', - null=True, blank=True, help_text="channel recievers") + blank=True, help_text="channel recievers") # header could be used for meta-info about the message if your system needs # it, or as a separate store for the mail subject line maybe. @@ -107,11 +107,11 @@ class Msg(SharedMemoryModel): help_text='access locks on this message.') # these can be used to filter/hide a given message from supplied objects/players/channels - db_hide_from_players = models.ManyToManyField("players.PlayerDB", related_name='hide_from_players_set', null=True, blank=True) - db_hide_from_objects = models.ManyToManyField("objects.ObjectDB", related_name='hide_from_objects_set', null=True, blank=True) - db_hide_from_channels = models.ManyToManyField("ChannelDB", related_name='hide_from_channels_set', null=True, blank=True) + db_hide_from_players = models.ManyToManyField("players.PlayerDB", related_name='hide_from_players_set', blank=True) + db_hide_from_objects = models.ManyToManyField("objects.ObjectDB", related_name='hide_from_objects_set', blank=True) + db_hide_from_channels = models.ManyToManyField("ChannelDB", related_name='hide_from_channels_set', blank=True) - db_tags = models.ManyToManyField(Tag, null=True, blank=True, + db_tags = models.ManyToManyField(Tag, blank=True, help_text='tags on this message. Tags are simple string markers to identify, group and alias messages.') # Database manager @@ -580,10 +580,10 @@ class ChannelDB(TypedObject): """ db_subscriptions = models.ManyToManyField("players.PlayerDB", - related_name="subscription_set", null=True, blank=True, verbose_name='subscriptions', db_index=True) + related_name="subscription_set", blank=True, verbose_name='subscriptions', db_index=True) db_object_subscriptions = models.ManyToManyField("objects.ObjectDB", - related_name="object_subscription_set", null=True, blank=True, verbose_name='subscriptions', db_index=True) + related_name="object_subscription_set", blank=True, verbose_name='subscriptions', db_index=True) # Database manager objects = managers.ChannelDBManager() diff --git a/evennia/help/migrations/0002_auto_20170606_1731.py b/evennia/help/migrations/0002_auto_20170606_1731.py new file mode 100644 index 0000000000..65ab4a5ee1 --- /dev/null +++ b/evennia/help/migrations/0002_auto_20170606_1731.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-06 17:31 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('help', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='helpentry', + name='db_tags', + field=models.ManyToManyField(blank=True, help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag'), + ), + ] diff --git a/evennia/help/models.py b/evennia/help/models.py index 48c16ed8bc..2433963b8f 100644 --- a/evennia/help/models.py +++ b/evennia/help/models.py @@ -58,7 +58,7 @@ class HelpEntry(SharedMemoryModel): # lock string storage db_lock_storage = models.TextField('locks', blank=True, help_text='normally view:all().') # tags are primarily used for permissions - db_tags = models.ManyToManyField(Tag, null=True, + db_tags = models.ManyToManyField(Tag, blank=True, help_text='tags on this object. Tags are simple string markers to identify, group and alias objects.') # (deprecated, only here to allow MUX helpfile load (don't use otherwise)). # TODO: remove this when not needed anymore. diff --git a/evennia/objects/migrations/0006_auto_20170606_1731.py b/evennia/objects/migrations/0006_auto_20170606_1731.py new file mode 100644 index 0000000000..74d48b29bb --- /dev/null +++ b/evennia/objects/migrations/0006_auto_20170606_1731.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-06 17:31 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models +import re + + +class Migration(migrations.Migration): + + dependencies = [ + ('objects', '0005_auto_20150403_2339'), + ] + + operations = [ + migrations.AlterField( + model_name='objectdb', + name='db_attributes', + field=models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute'), + ), + migrations.AlterField( + model_name='objectdb', + name='db_sessid', + field=models.CharField(help_text=b'csv list of session ids of connected Player, if any.', max_length=32, null=True, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:\\,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')], verbose_name=b'session id'), + ), + migrations.AlterField( + model_name='objectdb', + name='db_tags', + field=models.ManyToManyField(help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag'), + ), + ] diff --git a/evennia/objects/models.py b/evennia/objects/models.py index 009bae0b93..b3b00ce55a 100644 --- a/evennia/objects/models.py +++ b/evennia/objects/models.py @@ -18,6 +18,7 @@ from builtins import object from django.conf import settings from django.db import models from django.core.exceptions import ObjectDoesNotExist +from django.core.validators import validate_comma_separated_integer_list from evennia.typeclasses.models import TypedObject from evennia.objects.manager import ObjectDBManager @@ -172,7 +173,8 @@ class ObjectDB(TypedObject): db_player = models.ForeignKey("players.PlayerDB", null=True, verbose_name='player', on_delete=models.SET_NULL, help_text='a Player connected to this object, if any.') # the session id associated with this player, if any - db_sessid = models.CommaSeparatedIntegerField(null=True, max_length=32, verbose_name="session id", + db_sessid = models.CharField(null=True, max_length=32, validators=[validate_comma_separated_integer_list], + verbose_name="session id", help_text="csv list of session ids of connected Player, if any.") # The location in the game world. Since this one is likely # to change often, we set this with the 'location' property diff --git a/evennia/players/migrations/0006_auto_20170606_1731.py b/evennia/players/migrations/0006_auto_20170606_1731.py new file mode 100644 index 0000000000..bff35ca15c --- /dev/null +++ b/evennia/players/migrations/0006_auto_20170606_1731.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-06 17:31 +from __future__ import unicode_literals + +import django.contrib.auth.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('players', '0005_auto_20160905_0902'), + ] + + operations = [ + migrations.AlterField( + model_name='playerdb', + name='db_attributes', + field=models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute'), + ), + migrations.AlterField( + model_name='playerdb', + name='db_tags', + field=models.ManyToManyField(help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag'), + ), + migrations.AlterField( + model_name='playerdb', + name='username', + field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.ASCIIUsernameValidator()], verbose_name='username'), + ), + ] diff --git a/evennia/scripts/migrations/0008_auto_20170606_1731.py b/evennia/scripts/migrations/0008_auto_20170606_1731.py new file mode 100644 index 0000000000..b4a7f3201c --- /dev/null +++ b/evennia/scripts/migrations/0008_auto_20170606_1731.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-06 17:31 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('scripts', '0007_auto_20150403_2339'), + ] + + operations = [ + migrations.AlterField( + model_name='scriptdb', + name='db_attributes', + field=models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute'), + ), + migrations.AlterField( + model_name='scriptdb', + name='db_tags', + field=models.ManyToManyField(help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag'), + ), + ] diff --git a/evennia/typeclasses/models.py b/evennia/typeclasses/models.py index 4fbbe891dd..c163a95e4e 100644 --- a/evennia/typeclasses/models.py +++ b/evennia/typeclasses/models.py @@ -172,9 +172,9 @@ class TypedObject(SharedMemoryModel): db_lock_storage = models.TextField('locks', blank=True, help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.") # many2many relationships - db_attributes = models.ManyToManyField(Attribute, null=True, + db_attributes = models.ManyToManyField(Attribute, help_text='attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).') - db_tags = models.ManyToManyField(Tag, null=True, + db_tags = models.ManyToManyField(Tag, help_text='tags on this object. Tags are simple string markers to identify, group and alias objects.') # Database manager From f6ad6213d733fb29f5a477d08e34eca692967262 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 6 Jun 2017 19:43:28 +0200 Subject: [PATCH 36/63] Update launcher django requirement message. --- evennia/server/evennia_launcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evennia/server/evennia_launcher.py b/evennia/server/evennia_launcher.py index 09ca4a3c90..9f99886769 100644 --- a/evennia/server/evennia_launcher.py +++ b/evennia/server/evennia_launcher.py @@ -64,8 +64,8 @@ ENFORCED_SETTING = False # requirements PYTHON_MIN = '2.7' TWISTED_MIN = '16.0.0' -DJANGO_MIN = '1.8' -DJANGO_REC = '1.9' +DJANGO_MIN = '1.11' +DJANGO_REC = '1.11' sys.path[1] = EVENNIA_ROOT From 1ddddef23b38c3028544768805c2a55ecebcef29 Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 15 Jun 2017 22:21:21 +0200 Subject: [PATCH 37/63] Add better instructions for upgrading django. --- evennia/server/evennia_launcher.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/evennia/server/evennia_launcher.py b/evennia/server/evennia_launcher.py index 9f99886769..28f6c60bde 100644 --- a/evennia/server/evennia_launcher.py +++ b/evennia/server/evennia_launcher.py @@ -344,10 +344,13 @@ ERROR_DJANGO_MIN = \ ERROR: Django {dversion} found. Evennia requires version {django_min} or higher. - Install it with for example `pip install --upgrade django` + If you are using a virtualenv, use the command `pip install --upgrade -e evennia` where + `evennia` is the folder to where you cloned the Evennia library. If not + in a virtualenv you can install django with for example `pip install --upgrade django` or with `pip install django=={django_min}` to get a specific version. - It's also a good idea to run `evennia migrate` after this upgrade. + It's also a good idea to run `evennia migrate` after this upgrade. Ignore + any warnings and don't run `makemigrate` even if told to. """ NOTE_DJANGO_MIN = \ From af6aa52a4e9a786fb30d5532b35ccc8c7b741b18 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 17 Jun 2017 22:17:58 +0200 Subject: [PATCH 38/63] Merge migrations. --- .../comms/migrations/0012_merge_20170617_2017.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 evennia/comms/migrations/0012_merge_20170617_2017.py diff --git a/evennia/comms/migrations/0012_merge_20170617_2017.py b/evennia/comms/migrations/0012_merge_20170617_2017.py new file mode 100644 index 0000000000..a91de6d29c --- /dev/null +++ b/evennia/comms/migrations/0012_merge_20170617_2017.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-17 20:17 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('comms', '0011_auto_20170606_1731'), + ('comms', '0011_auto_20170217_2039'), + ] + + operations = [ + ] From 7458bb76e2c2b78fc873ac1bb77b4fc53e53b7a8 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 24 Jun 2017 21:32:20 +0200 Subject: [PATCH 39/63] Remove spurious patch import --- evennia/typeclasses/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/evennia/typeclasses/models.py b/evennia/typeclasses/models.py index c163a95e4e..728f1ef1c6 100644 --- a/evennia/typeclasses/models.py +++ b/evennia/typeclasses/models.py @@ -46,7 +46,6 @@ from evennia.utils.utils import ( is_iter, inherits_from, lazy_property, class_from_module) from evennia.utils.logger import log_trace -from evennia.typeclasses.django_new_patch import patched_new from .signals import remove_attributes_on_delete, post_save __all__ = ("TypedObject", ) From 99dbaad7bac88bb34d7b14ce2ef914661cd49ce6 Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 3 Jul 2017 23:05:52 +0200 Subject: [PATCH 40/63] Add new accounts app and copy players 1:1 to it. The players app still exists. --- evennia/settings_default.py | 1 + 1 file changed, 1 insertion(+) diff --git a/evennia/settings_default.py b/evennia/settings_default.py index 70a405f0b8..54e7f75ffa 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -717,6 +717,7 @@ INSTALLED_APPS = ( 'evennia.server', 'evennia.typeclasses', 'evennia.players', + 'evennia.accounts', 'evennia.objects', 'evennia.comms', 'evennia.help', From ee0e9cc053c4f6ec31be21837ed558eea69ce723 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 5 Jul 2017 08:30:06 +0200 Subject: [PATCH 41/63] First step with both account+player at the same time, copying player to account. --- evennia/accounts/__init__.py | 6 + evennia/accounts/accounts.py | 971 ++++++++++++++++++ evennia/accounts/admin.py | 255 +++++ evennia/accounts/bots.py | 419 ++++++++ evennia/accounts/manager.py | 180 ++++ evennia/accounts/migrations/0001_initial.py | 54 + .../migrations/0002_copy_player_to_account.py | 58 ++ evennia/accounts/migrations/__init__.py | 0 evennia/accounts/models.py | 169 +++ 9 files changed, 2112 insertions(+) create mode 100644 evennia/accounts/__init__.py create mode 100644 evennia/accounts/accounts.py create mode 100644 evennia/accounts/admin.py create mode 100644 evennia/accounts/bots.py create mode 100644 evennia/accounts/manager.py create mode 100644 evennia/accounts/migrations/0001_initial.py create mode 100644 evennia/accounts/migrations/0002_copy_player_to_account.py create mode 100644 evennia/accounts/migrations/__init__.py create mode 100644 evennia/accounts/models.py diff --git a/evennia/accounts/__init__.py b/evennia/accounts/__init__.py new file mode 100644 index 0000000000..4ba99b4568 --- /dev/null +++ b/evennia/accounts/__init__.py @@ -0,0 +1,6 @@ +""" +This sub-package defines the out-of-character entities known as +Accounts. These are equivalent to 'accounts' and can puppet one or +more Objects depending on settings. An Account has no in-game existence. + +""" diff --git a/evennia/accounts/accounts.py b/evennia/accounts/accounts.py new file mode 100644 index 0000000000..865aad57ba --- /dev/null +++ b/evennia/accounts/accounts.py @@ -0,0 +1,971 @@ +""" +Typeclass for Account objects + +Note that this object is primarily intended to +store OOC information, not game info! This +object represents the actual user (not their +character) and has NO actual precence in the +game world (this is handled by the associated +character object, so you should customize that +instead for most things). + +""" + +import time +from django.conf import settings +from django.utils import timezone +from evennia.typeclasses.models import TypeclassBase +from evennia.accounts.manager import AccountManager +from evennia.accounts.models import AccountDB +from evennia.objects.models import ObjectDB +from evennia.comms.models import ChannelDB +from evennia.commands import cmdhandler +from evennia.utils import logger +from evennia.utils.utils import (lazy_property, + make_iter, to_unicode, is_iter, + variable_from_module) +from evennia.typeclasses.attributes import NickHandler +from evennia.scripts.scripthandler import ScriptHandler +from evennia.commands.cmdsethandler import CmdSetHandler + +from django.utils.translation import ugettext as _ +from future.utils import with_metaclass + +__all__ = ("DefaultAccount",) + +_SESSIONS = None + +_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) +_MULTISESSION_MODE = settings.MULTISESSION_MODE +_MAX_NR_CHARACTERS = settings.MAX_NR_CHARACTERS +_CMDSET_ACCOUNT = settings.CMDSET_ACCOUNT +_CONNECT_CHANNEL = None + + +class AccountSessionHandler(object): + """ + Manages the session(s) attached to an account. + + """ + + def __init__(self, account): + """ + Initializes the handler. + + Args: + account (Account): The Account on which this handler is defined. + + """ + self.account = account + + def get(self, sessid=None): + """ + Get the sessions linked to this object. + + Args: + sessid (int, optional): Specify a given session by + session id. + + Returns: + sessions (list): A list of Session objects. If `sessid` + is given, this is a list with one (or zero) elements. + + """ + global _SESSIONS + if not _SESSIONS: + from evennia.server.sessionhandler import SESSIONS as _SESSIONS + if sessid: + return make_iter(_SESSIONS.session_from_account(self.account, sessid)) + else: + return _SESSIONS.sessions_from_account(self.account) + + def all(self): + """ + Alias to get(), returning all sessions. + + Returns: + sessions (list): All sessions. + + """ + return self.get() + + def count(self): + """ + Get amount of sessions connected. + + Returns: + sesslen (int): Number of sessions handled. + + """ + return len(self.get()) + + +class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): + """ + This is the base Typeclass for all Accounts. Accounts represent + the person playing the game and tracks account info, password + etc. They are OOC entities without presence in-game. An Account + can connect to a Character Object in order to "enter" the + game. + + Account Typeclass API: + + * Available properties (only available on initiated typeclass objects) + + - key (string) - name of account + - name (string)- wrapper for user.username + - aliases (list of strings) - aliases to the object. Will be saved to + database as AliasDB entries but returned as strings. + - dbref (int, read-only) - unique #id-number. Also "id" can be used. + - date_created (string) - time stamp of object creation + - permissions (list of strings) - list of permission strings + - user (User, read-only) - django User authorization object + - obj (Object) - game object controlled by account. 'character' can also + be used. + - sessions (list of Sessions) - sessions connected to this account + - is_superuser (bool, read-only) - if the connected user is a superuser + + * Handlers + + - locks - lock-handler: use locks.add() to add new lock strings + - db - attribute-handler: store/retrieve database attributes on this + self.db.myattr=val, val=self.db.myattr + - ndb - non-persistent attribute handler: same as db but does not + create a database entry when storing data + - scripts - script-handler. Add new scripts to object with scripts.add() + - cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object + - nicks - nick-handler. New nicks with nicks.add(). + + * Helper methods + + - msg(text=None, from_obj=None, session=None, options=None, **kwargs) + - execute_cmd(raw_string) + - search(ostring, global_search=False, attribute_name=None, + use_nicks=False, location=None, + ignore_errors=False, account=False) + - is_typeclass(typeclass, exact=False) + - swap_typeclass(new_typeclass, clean_attributes=False, no_default=True) + - access(accessing_obj, access_type='read', default=False, no_superuser_bypass=False) + - check_permstring(permstring) + + * Hook methods + + basetype_setup() + at_account_creation() + + > note that the following hooks are also found on Objects and are + usually handled on the character level: + + - at_init() + - at_access() + - at_cmdset_get(**kwargs) + - at_first_login() + - at_post_login(session=None) + - at_disconnect() + - at_message_receive() + - at_message_send() + - at_server_reload() + - at_server_shutdown() + + """ + + objects = AccountManager() + + # properties + @lazy_property + def cmdset(self): + return CmdSetHandler(self, True) + + @lazy_property + def scripts(self): + return ScriptHandler(self) + + @lazy_property + def nicks(self): + return NickHandler(self) + + @lazy_property + def sessions(self): + return AccountSessionHandler(self) + + # session-related methods + + def disconnect_session_from_account(self, session): + """ + Access method for disconnecting a given session from the + account (connection happens automatically in the + sessionhandler) + + Args: + session (Session): Session to disconnect. + + """ + global _SESSIONS + if not _SESSIONS: + from evennia.server.sessionhandler import SESSIONS as _SESSIONS + _SESSIONS.disconnect(session) + + # puppeting operations + + def puppet_object(self, session, obj): + """ + Use the given session to control (puppet) the given object (usually + a Character type). + + Args: + session (Session): session to use for puppeting + obj (Object): the object to start puppeting + + Raises: + RuntimeError: If puppeting is not possible, the + `exception.msg` will contain the reason. + + + """ + # safety checks + if not obj: + raise RuntimeError("Object not found") + if not session: + raise RuntimeError("Session not found") + if self.get_puppet(session) == obj: + # already puppeting this object + self.msg("You are already puppeting this object.") + return + if not obj.access(self, 'puppet'): + # no access + self.msg("You don't have permission to puppet '%s'." % obj.key) + return + if obj.account: + # object already puppeted + if obj.account == self: + if obj.sessions.count(): + # we may take over another of our sessions + # output messages to the affected sessions + if _MULTISESSION_MODE in (1, 3): + txt1 = "Sharing |c%s|n with another of your sessions." + txt2 = "|c%s|n|G is now shared from another of your sessions.|n" + self.msg(txt1 % obj.name, session=session) + self.msg(txt2 % obj.name, session=obj.sessions.all()) + else: + txt1 = "Taking over |c%s|n from another of your sessions." + txt2 = "|c%s|n|R is now acted from another of your sessions.|n" + self.msg(txt1 % obj.name, session=session) + self.msg(txt2 % obj.name, session=obj.sessions.all()) + self.unpuppet_object(obj.sessions.get()) + elif obj.account.is_connected: + # controlled by another account + self.msg("|c%s|R is already puppeted by another Account." % obj.key) + return + + # do the puppeting + if session.puppet: + # cleanly unpuppet eventual previous object puppeted by this session + self.unpuppet_object(session) + # if we get to this point the character is ready to puppet or it + # was left with a lingering account/session reference from an unclean + # server kill or similar + + obj.at_pre_puppet(self, session=session) + + # do the connection + obj.sessions.add(session) + obj.account = self + session.puid = obj.id + session.puppet = obj + # validate/start persistent scripts on object + obj.scripts.validate() + + # re-cache locks to make sure superuser bypass is updated + obj.locks.cache_lock_bypass(obj) + # final hook + obj.at_post_puppet() + + def unpuppet_object(self, session): + """ + Disengage control over an object. + + Args: + session (Session or list): The session or a list of + sessions to disengage from their puppets. + + Raises: + RuntimeError With message about error. + + """ + for session in make_iter(session): + obj = session.puppet + if obj: + # do the disconnect, but only if we are the last session to puppet + obj.at_pre_unpuppet() + obj.sessions.remove(session) + if not obj.sessions.count(): + del obj.account + obj.at_post_unpuppet(self, session=session) + # Just to be sure we're always clear. + session.puppet = None + session.puid = None + + def unpuppet_all(self): + """ + Disconnect all puppets. This is called by server before a + reset/shutdown. + """ + self.unpuppet_object(self.sessions.all()) + + def get_puppet(self, session): + """ + Get an object puppeted by this session through this account. This is + the main method for retrieving the puppeted object from the + account's end. + + Args: + session (Session): Find puppeted object based on this session + + Returns: + puppet (Object): The matching puppeted object, if any. + + """ + return session.puppet + + def get_all_puppets(self): + """ + Get all currently puppeted objects. + + Returns: + puppets (list): All puppeted objects currently controlled + by this Account. + + """ + return list(set(session.puppet for session in self.sessions.all() if session.puppet)) + + def __get_single_puppet(self): + """ + This is a legacy convenience link for use with `MULTISESSION_MODE`. + + Returns: + puppets (Object or list): Users of `MULTISESSION_MODE` 0 or 1 will + always get the first puppet back. Users of higher `MULTISESSION_MODE`s will + get a list of all puppeted objects. + + """ + puppets = self.get_all_puppets() + if _MULTISESSION_MODE in (0, 1): + return puppets and puppets[0] or None + return puppets + character = property(__get_single_puppet) + puppet = property(__get_single_puppet) + + # utility methods + + def delete(self, *args, **kwargs): + """ + Deletes the account permanently. + + Notes: + `*args` and `**kwargs` are passed on to the base delete + mechanism (these are usually not used). + + """ + for session in self.sessions.all(): + # unpuppeting all objects and disconnecting the user, if any + # sessions remain (should usually be handled from the + # deleting command) + try: + self.unpuppet_object(session) + except RuntimeError: + # no puppet to disconnect from + pass + session.sessionhandler.disconnect(session, reason=_("Account being deleted.")) + self.scripts.stop() + self.attributes.clear() + self.nicks.clear() + self.aliases.clear() + super(DefaultAccount, self).delete(*args, **kwargs) + # methods inherited from database model + + def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs): + """ + Evennia -> User + This is the main route for sending data back to the user from the + server. + + Args: + text (str, optional): text data to send + from_obj (Object or Account, optional): Object sending. If given, + its at_msg_send() hook will be called. + session (Session or list, optional): Session object or a list of + Sessions to receive this send. If given, overrules the + default send behavior for the current + MULTISESSION_MODE. + options (list): Protocol-specific options. Passed on to the protocol. + Kwargs: + any (dict): All other keywords are passed on to the protocol. + + """ + if from_obj: + # call hook + try: + from_obj.at_msg_send(text=text, to_obj=self, **kwargs) + except Exception: + # this may not be assigned. + pass + try: + if not self.at_msg_receive(text=text, **kwargs): + # abort message to this account + return + except Exception: + # this may not be assigned. + pass + + kwargs["options"] = options + + # session relay + sessions = make_iter(session) if session else self.sessions.all() + for session in sessions: + session.data_out(text=text, **kwargs) + + def execute_cmd(self, raw_string, session=None, **kwargs): + """ + Do something as this account. This method is never called normally, + but only when the account object itself is supposed to execute the + command. It takes account nicks into account, but not nicks of + eventual puppets. + + Args: + raw_string (str): Raw command input coming from the command line. + session (Session, optional): The session to be responsible + for the command-send + + Kwargs: + kwargs (any): Other keyword arguments will be added to the + found command object instance as variables before it + executes. This is unused by default Evennia but may be + used to set flags and change operating paramaters for + commands at run-time. + + """ + raw_string = to_unicode(raw_string) + raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_account=False) + if not session and _MULTISESSION_MODE in (0, 1): + # for these modes we use the first/only session + sessions = self.sessions.get() + session = sessions[0] if sessions else None + + return cmdhandler.cmdhandler(self, raw_string, + callertype="account", session=session, **kwargs) + + def search(self, searchdata, return_puppet=False, search_object=False, + typeclass=None, nofound_string=None, multimatch_string=None, **kwargs): + """ + This is similar to `DefaultObject.search` but defaults to searching + for Accounts only. + + Args: + searchdata (str or int): Search criterion, the Account's + key or dbref to search for. + return_puppet (bool, optional): Instructs the method to + return matches as the object the Account controls rather + than the Account itself (or None) if nothing is puppeted). + search_object (bool, optional): Search for Objects instead of + Accounts. This is used by e.g. the @examine command when + wanting to examine Objects while OOC. + typeclass (Account typeclass, optional): Limit the search + only to this particular typeclass. This can be used to + limit to specific account typeclasses or to limit the search + to a particular Object typeclass if `search_object` is True. + nofound_string (str, optional): A one-time error message + to echo if `searchdata` leads to no matches. If not given, + will fall back to the default handler. + multimatch_string (str, optional): A one-time error + message to echo if `searchdata` leads to multiple matches. + If not given, will fall back to the default handler. + + Return: + match (Account, Object or None): A single Account or Object match. + Notes: + Extra keywords are ignored, but are allowed in call in + order to make API more consistent with + objects.objects.DefaultObject.search. + + """ + # handle me, self and *me, *self + if isinstance(searchdata, basestring): + # handle wrapping of common terms + if searchdata.lower() in ("me", "*me", "self", "*self",): + return self + if search_object: + matches = ObjectDB.objects.object_search(searchdata, typeclass=typeclass) + else: + matches = AccountDB.objects.account_search(searchdata, typeclass=typeclass) + matches = _AT_SEARCH_RESULT(matches, self, query=searchdata, + nofound_string=nofound_string, + multimatch_string=multimatch_string) + if matches and return_puppet: + try: + return matches.puppet + except AttributeError: + return None + return matches + + def access(self, accessing_obj, access_type='read', default=False, no_superuser_bypass=False, **kwargs): + """ + Determines if another object has permission to access this + object in whatever way. + + Args: + accessing_obj (Object): Object trying to access this one. + access_type (str, optional): Type of access sought. + default (bool, optional): What to return if no lock of + access_type was found + no_superuser_bypass (bool, optional): Turn off superuser + lock bypassing. Be careful with this one. + + Kwargs: + kwargs (any): Passed to the at_access hook along with the result. + + Returns: + result (bool): Result of access check. + + """ + result = super(DefaultAccount, self).access(accessing_obj, access_type=access_type, + default=default, no_superuser_bypass=no_superuser_bypass) + self.at_access(result, accessing_obj, access_type, **kwargs) + return result + + @property + def idle_time(self): + """ + Returns the idle time of the least idle session in seconds. If + no sessions are connected it returns nothing. + """ + idle = [session.cmd_last_visible for session in self.sessions.all()] + if idle: + return time.time() - float(max(idle)) + return None + + @property + def connection_time(self): + """ + Returns the maximum connection time of all connected sessions + in seconds. Returns nothing if there are no sessions. + """ + conn = [session.conn_time for session in self.sessions.all()] + if conn: + return time.time() - float(min(conn)) + return None + + # account hooks + + def basetype_setup(self): + """ + This sets up the basic properties for an account. Overload this + with at_account_creation rather than changing this method. + + """ + # A basic security setup + lockstring = "examine:perm(Admin);edit:perm(Admin);" \ + "delete:perm(Admin);boot:perm(Admin);msg:all()" + self.locks.add(lockstring) + + # The ooc account cmdset + self.cmdset.add_default(_CMDSET_ACCOUNT, permanent=True) + + def at_account_creation(self): + """ + This is called once, the very first time the account is created + (i.e. first time they register with the game). It's a good + place to store attributes all accounts should have, like + configuration values etc. + + """ + # set an (empty) attribute holding the characters this account has + lockstring = "attrread:perm(Admins);attredit:perm(Admins);" \ + "attrcreate:perm(Admins)" + self.attributes.add("_playable_characters", [], lockstring=lockstring) + self.attributes.add("_saved_protocol_flags", {}, lockstring=lockstring) + + def at_init(self): + """ + This is always called whenever this object is initiated -- + that is, whenever it its typeclass is cached from memory. This + happens on-demand first time the object is used or activated + in some way after being created but also after each server + restart or reload. In the case of account objects, this usually + happens the moment the account logs in or reconnects after a + reload. + + """ + pass + + # Note that the hooks below also exist in the character object's + # typeclass. You can often ignore these and rely on the character + # ones instead, unless you are implementing a multi-character game + # and have some things that should be done regardless of which + # character is currently connected to this account. + + def at_first_save(self): + """ + This is a generic hook called by Evennia when this object is + saved to the database the very first time. You generally + don't override this method but the hooks called by it. + + """ + self.basetype_setup() + self.at_account_creation() + + permissions = settings.PERMISSION_ACCOUNT_DEFAULT + if hasattr(self, "_createdict"): + # this will only be set if the utils.create_account + # function was used to create the object. + cdict = self._createdict + if cdict.get("locks"): + self.locks.add(cdict["locks"]) + if cdict.get("permissions"): + permissions = cdict["permissions"] + del self._createdict + + self.permissions.batch_add(*permissions) + + def at_access(self, result, accessing_obj, access_type, **kwargs): + """ + This is triggered after an access-call on this Account has + completed. + + Args: + result (bool): The result of the access check. + accessing_obj (any): The object requesting the access + check. + access_type (str): The type of access checked. + + Kwargs: + kwargs (any): These are passed on from the access check + and can be used to relay custom instructions from the + check mechanism. + + Notes: + This method cannot affect the result of the lock check and + its return value is not used in any way. It can be used + e.g. to customize error messages in a central location or + create other effects based on the access result. + + """ + pass + + def at_cmdset_get(self, **kwargs): + """ + Called just *before* cmdsets on this account are requested by + the command handler. The cmdsets are available as + `self.cmdset`. If changes need to be done on the fly to the + cmdset before passing them on to the cmdhandler, this is the + place to do it. This is called also if the account currently + have no cmdsets. kwargs are usually not used unless the + cmdset is generated dynamically. + + """ + pass + + def at_first_login(self, **kwargs): + """ + Called the very first time this account logs into the game. + Note that this is called *before* at_pre_login, so no session + is established and usually no character is yet assigned at + this point. This hook is intended for account-specific setup + like configurations. + + Args: + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + + """ + pass + + def at_pre_login(self, **kwargs): + """ + Called every time the user logs in, just before the actual + login-state is set. + + Args: + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + + """ + pass + + def _send_to_connect_channel(self, message): + """ + Helper method for loading and sending to the comm channel + dedicated to connection messages. + + Args: + message (str): A message to send to the connect channel. + + """ + global _CONNECT_CHANNEL + if not _CONNECT_CHANNEL: + try: + _CONNECT_CHANNEL = ChannelDB.objects.filter(db_key=settings.DEFAULT_CHANNELS[1]["key"])[0] + except Exception: + logger.log_trace() + now = timezone.now() + now = "%02i-%02i-%02i(%02i:%02i)" % (now.year, now.month, + now.day, now.hour, now.minute) + if _CONNECT_CHANNEL: + _CONNECT_CHANNEL.tempmsg("[%s, %s]: %s" % (_CONNECT_CHANNEL.key, now, message)) + else: + logger.log_info("[%s]: %s" % (now, message)) + + def at_post_login(self, session=None, **kwargs): + """ + Called at the end of the login process, just before letting + the account loose. + + Args: + session (Session, optional): Session logging in, if any. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + + Notes: + This is called *before* an eventual Character's + `at_post_login` hook. By default it is used to set up + auto-puppeting based on `MULTISESSION_MODE`. + + """ + # if we have saved protocol flags on ourselves, load them here. + protocol_flags = self.attributes.get("_saved_protocol_flags", None) + if session and protocol_flags: + session.update_flags(**protocol_flags) + + # inform the client that we logged in through an OOB message + if session: + session.msg(logged_in={}) + + self._send_to_connect_channel("|G%s connected|n" % self.key) + if _MULTISESSION_MODE == 0: + # in this mode we should have only one character available. We + # try to auto-connect to our last conneted object, if any + try: + self.puppet_object(session, self.db._last_puppet) + except RuntimeError: + self.msg("The Character does not exist.") + return + elif _MULTISESSION_MODE == 1: + # in this mode all sessions connect to the same puppet. + try: + self.puppet_object(session, self.db._last_puppet) + except RuntimeError: + self.msg("The Character does not exist.") + return + elif _MULTISESSION_MODE in (2, 3): + # In this mode we by default end up at a character selection + # screen. We execute look on the account. + # we make sure to clean up the _playable_characers list in case + # any was deleted in the interim. + self.db._playable_characters = [char for char in self.db._playable_characters if char] + self.msg(self.at_look(target=self.db._playable_characters, + session=session)) + + def at_failed_login(self, session, **kwargs): + """ + Called by the login process if a user account is targeted correctly + but provided with an invalid password. By default it does nothing, + but exists to be overriden. + + Args: + session (session): Session logging in. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + """ + pass + + def at_disconnect(self, reason=None, **kwargs): + """ + Called just before user is disconnected. + + Args: + reason (str, optional): The reason given for the disconnect, + (echoed to the connection channel by default). + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + + + """ + reason = reason and "(%s)" % reason or "" + self._send_to_connect_channel("|R%s disconnected %s|n" % (self.key, reason)) + + def at_post_disconnect(self, **kwargs): + """ + This is called *after* disconnection is complete. No messages + can be relayed to the account from here. After this call, the + account should not be accessed any more, making this a good + spot for deleting it (in the case of a guest account account, + for example). + + Args: + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + + """ + pass + + def at_message_receive(self, message, from_obj=None, **kwargs): + """ + This is currently unused. + + Args: + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + + """ + return True + + def at_message_send(self, message, to_object, **kwargs): + """ + This is currently unused. + + Args: + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + + """ + pass + + def at_server_reload(self): + """ + This hook is called whenever the server is shutting down for + restart/reboot. If you want to, for example, save + non-persistent properties across a restart, this is the place + to do it. + """ + pass + + def at_server_shutdown(self): + """ + This hook is called whenever the server is shutting down fully + (i.e. not for a restart). + """ + pass + + def at_look(self, target=None, session=None, **kwargs): + """ + Called when this object executes a look. It allows to customize + just what this means. + + Args: + target (Object or list, optional): An object or a list + objects to inspect. + session (Session, optional): The session doing this look. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + + Returns: + look_string (str): A prepared look string, ready to send + off to any recipient (usually to ourselves) + + """ + + if target and not is_iter(target): + # single target - just show it + return target.return_appearance(self) + else: + # list of targets - make list to disconnect from db + characters = list(tar for tar in target if tar) if target else [] + sessions = self.sessions.all() + is_su = self.is_superuser + + # text shown when looking in the ooc area + result = ["Account |g%s|n (you are Out-of-Character)" % self.key] + + nsess = len(sessions) + result.append(nsess == 1 and "\n\n|wConnected session:|n" or "\n\n|wConnected sessions (%i):|n" % nsess) + for isess, sess in enumerate(sessions): + csessid = sess.sessid + addr = "%s (%s)" % (sess.protocol_key, isinstance(sess.address, tuple) + and str(sess.address[0]) or str(sess.address)) + result.append("\n %s %s" % (session.sessid == csessid and "|w* %s|n" % (isess + 1) + or " %s" % (isess + 1), addr)) + result.append("\n\n |whelp|n - more commands") + result.append("\n |wooc |n - talk on public channel") + + charmax = _MAX_NR_CHARACTERS if _MULTISESSION_MODE > 1 else 1 + + if is_su or len(characters) < charmax: + if not characters: + result.append("\n\n You don't have any characters yet. See |whelp @charcreate|n for creating one.") + else: + result.append("\n |w@charcreate [=description]|n - create new character") + result.append("\n |w@chardelete |n - delete a character (cannot be undone!)") + + if characters: + string_s_ending = len(characters) > 1 and "s" or "" + result.append("\n |w@ic |n - enter the game (|w@ooc|n to get back here)") + if is_su: + result.append("\n\nAvailable character%s (%i/unlimited):" % (string_s_ending, len(characters))) + else: + result.append("\n\nAvailable character%s%s:" + % (string_s_ending, charmax > 1 and " (%i/%i)" % (len(characters), charmax) or "")) + + for char in characters: + csessions = char.sessions.all() + if csessions: + for sess in csessions: + # character is already puppeted + sid = sess in sessions and sessions.index(sess) + 1 + if sess and sid: + result.append("\n - |G%s|n [%s] (played by you in session %i)" + % (char.key, ", ".join(char.permissions.all()), sid)) + else: + result.append("\n - |R%s|n [%s] (played by someone else)" + % (char.key, ", ".join(char.permissions.all()))) + else: + # character is "free to puppet" + result.append("\n - %s [%s]" % (char.key, ", ".join(char.permissions.all()))) + look_string = ("-" * 68) + "\n" + "".join(result) + "\n" + ("-" * 68) + return look_string + + +class DefaultGuest(DefaultAccount): + """ + This class is used for guest logins. Unlike Accounts, Guests and + their characters are deleted after disconnection. + """ + def at_post_login(self, session=None, **kwargs): + """ + In theory, guests only have one character regardless of which + MULTISESSION_MODE we're in. They don't get a choice. + + Args: + session (Session, optional): Session connecting. + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + + """ + self._send_to_connect_channel("|G%s connected|n" % self.key) + self.puppet_object(session, self.db._last_puppet) + + def at_server_shutdown(self): + """ + We repeat the functionality of `at_disconnect()` here just to + be on the safe side. + """ + super(DefaultGuest, self).at_server_shutdown() + characters = self.db._playable_characters + for character in characters: + if character: + print "deleting Character:", character + character.delete() + + def at_post_disconnect(self, **kwargs): + """ + Once having disconnected, destroy the guest's characters and + + Args: + **kwargs (dict): Arbitrary, optional arguments for users + overriding the call (unused by default). + + """ + super(DefaultGuest, self).at_post_disconnect() + characters = self.db._playable_characters + for character in characters: + if character: + character.delete() + self.delete() diff --git a/evennia/accounts/admin.py b/evennia/accounts/admin.py new file mode 100644 index 0000000000..d02b1baeac --- /dev/null +++ b/evennia/accounts/admin.py @@ -0,0 +1,255 @@ +# +# This sets up how models are displayed +# in the web admin interface. +# +from builtins import object + +from django import forms +from django.conf import settings +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin as BaseUserAdmin +from django.contrib.auth.forms import UserChangeForm, UserCreationForm +from evennia.accounts.models import AccountDB +from evennia.typeclasses.admin import AttributeInline, TagInline +from evennia.utils import create + + +# handle the custom User editor +class AccountDBChangeForm(UserChangeForm): + """ + Modify the accountdb class. + + """ + class Meta(object): + model = AccountDB + fields = '__all__' + + username = forms.RegexField( + label="Username", + max_length=30, + regex=r'^[\w. @+-]+$', + widget=forms.TextInput( + attrs={'size': '30'}), + error_messages={ + 'invalid': "This value may contain only letters, spaces, numbers " + "and @/./+/-/_ characters."}, + help_text="30 characters or fewer. Letters, spaces, digits and " + "@/./+/-/_ only.") + + def clean_username(self): + """ + Clean the username and check its existence. + + """ + username = self.cleaned_data['username'] + if username.upper() == self.instance.username.upper(): + return username + elif AccountDB.objects.filter(username__iexact=username): + raise forms.ValidationError('An account with that name ' + 'already exists.') + return self.cleaned_data['username'] + + +class AccountDBCreationForm(UserCreationForm): + """ + Create a new AccountDB instance. + """ + + class Meta(object): + model = AccountDB + fields = '__all__' + + username = forms.RegexField( + label="Username", + max_length=30, + regex=r'^[\w. @+-]+$', + widget=forms.TextInput( + attrs={'size': '30'}), + error_messages={ + 'invalid': "This value may contain only letters, spaces, numbers " + "and @/./+/-/_ characters."}, + help_text="30 characters or fewer. Letters, spaces, digits and " + "@/./+/-/_ only.") + + def clean_username(self): + """ + Cleanup username. + """ + username = self.cleaned_data['username'] + if AccountDB.objects.filter(username__iexact=username): + raise forms.ValidationError('An account with that name already ' + 'exists.') + return username + + +class AccountForm(forms.ModelForm): + """ + Defines how to display Accounts + + """ + class Meta(object): + model = AccountDB + fields = '__all__' + + db_key = forms.RegexField( + label="Username", + initial="AccountDummy", + max_length=30, + regex=r'^[\w. @+-]+$', + required=False, + widget=forms.TextInput(attrs={'size': '30'}), + error_messages={ + 'invalid': "This value may contain only letters, spaces, numbers" + " and @/./+/-/_ characters."}, + help_text="This should be the same as the connected Account's key " + "name. 30 characters or fewer. Letters, spaces, digits and " + "@/./+/-/_ only.") + + db_typeclass_path = forms.CharField( + label="Typeclass", + initial=settings.BASE_PLAYER_TYPECLASS, + widget=forms.TextInput( + attrs={'size': '78'}), + help_text="Required. Defines what 'type' of entity this is. This " + "variable holds a Python path to a module with a valid " + "Evennia Typeclass. Defaults to " + "settings.BASE_ACCOUNT_TYPECLASS.") + + db_permissions = forms.CharField( + label="Permissions", + initial=settings.PERMISSION_PLAYER_DEFAULT, + required=False, + widget=forms.TextInput( + attrs={'size': '78'}), + help_text="In-game permissions. A comma-separated list of text " + "strings checked by certain locks. They are often used for " + "hierarchies, such as letting an Account have permission " + "'Admin', 'Builder' etc. An Account permission can be " + "overloaded by the permissions of a controlled Character. " + "Normal accounts use 'Accounts' by default.") + + db_lock_storage = forms.CharField( + label="Locks", + widget=forms.Textarea(attrs={'cols': '100', 'rows': '2'}), + required=False, + help_text="In-game lock definition string. If not given, defaults " + "will be used. This string should be on the form " + "type:lockfunction(args);type2:lockfunction2(args);...") + db_cmdset_storage = forms.CharField( + label="cmdset", + initial=settings.CMDSET_PLAYER, + widget=forms.TextInput(attrs={'size': '78'}), + required=False, + help_text="python path to account cmdset class (set in " + "settings.CMDSET_ACCOUNT by default)") + + +class AccountInline(admin.StackedInline): + """ + Inline creation of Account + + """ + model = AccountDB + template = "admin/accounts/stacked.html" + form = AccountForm + fieldsets = ( + ("In-game Permissions and Locks", + {'fields': ('db_lock_storage',), + #{'fields': ('db_permissions', 'db_lock_storage'), + 'description': "These are permissions/locks for in-game use. " + "They are unrelated to website access rights."}), + ("In-game Account data", + {'fields': ('db_typeclass_path', 'db_cmdset_storage'), + 'description': "These fields define in-game-specific properties " + "for the Account object in-game."})) + + extra = 1 + max_num = 1 + + +class AccountTagInline(TagInline): + """ + Inline Account Tags. + + """ + model = AccountDB.db_tags.through + related_field = "accountdb" + + +class AccountAttributeInline(AttributeInline): + """ + Inline Account Attributes. + + """ + model = AccountDB.db_attributes.through + related_field = "accountdb" + + +class AccountDBAdmin(BaseUserAdmin): + """ + This is the main creation screen for Users/accounts + + """ + + list_display = ('username', 'email', 'is_staff', 'is_superuser') + form = AccountDBChangeForm + add_form = AccountDBCreationForm + inlines = [AccountTagInline, AccountAttributeInline] + fieldsets = ( + (None, {'fields': ('username', 'password', 'email')}), + ('Website profile', { + 'fields': ('first_name', 'last_name'), + 'description': "These are not used " + "in the default system."}), + ('Website dates', { + 'fields': ('last_login', 'date_joined'), + 'description': 'Relevant only to the website.'}), + ('Website Permissions', { + 'fields': ('is_active', 'is_staff', 'is_superuser', + 'user_permissions', 'groups'), + 'description': "These are permissions/permission groups for " + "accessing the admin site. They are unrelated to " + "in-game access rights."}), + ('Game Options', { + 'fields': ('db_typeclass_path', 'db_cmdset_storage', + 'db_lock_storage'), + 'description': 'These are attributes that are more relevant ' + 'to gameplay.'})) + # ('Game Options', {'fields': ( + # 'db_typeclass_path', 'db_cmdset_storage', + # 'db_permissions', 'db_lock_storage'), + # 'description': 'These are attributes that are ' + # 'more relevant to gameplay.'})) + + add_fieldsets = ( + (None, + {'fields': ('username', 'password1', 'password2', 'email'), + 'description': "These account details are shared by the admin " + "system and the game."},),) + + def save_model(self, request, obj, form, change): + """ + Custom save actions. + + Args: + request (Request): Incoming request. + obj (Object): Object to save. + form (Form): Related form instance. + change (bool): False if this is a new save and not an update. + + """ + obj.save() + if not change: + #calling hooks for new account + obj.set_class_from_typeclass(typeclass_path=settings.BASE_PLAYER_TYPECLASS) + obj.basetype_setup() + obj.at_account_creation() + + def response_add(self, request, obj, post_url_continue=None): + from django.http import HttpResponseRedirect + from django.core.urlresolvers import reverse + if '_continue' in request.POST: + return HttpResponseRedirect(reverse("admin:accounts_accountdb_change", args=[obj.id])) + return HttpResponseRedirect(reverse("admin:accounts_accountdb_change", args=[obj.id])) + +admin.site.register(AccountDB, AccountDBAdmin) diff --git a/evennia/accounts/bots.py b/evennia/accounts/bots.py new file mode 100644 index 0000000000..091370ef08 --- /dev/null +++ b/evennia/accounts/bots.py @@ -0,0 +1,419 @@ +""" +Bots are a special child typeclasses of +Account that are controlled by the server. + +""" +from __future__ import print_function +import time +from django.conf import settings +from evennia.accounts.accounts import DefaultAccount +from evennia.scripts.scripts import DefaultScript +from evennia.utils import search +from evennia.utils import utils + +_IDLE_TIMEOUT = settings.IDLE_TIMEOUT + +_IRC_ENABLED = settings.IRC_ENABLED +_RSS_ENABLED = settings.RSS_ENABLED + +_SESSIONS = None + + +# Bot helper utilities + +class BotStarter(DefaultScript): + """ + This non-repeating script has the + sole purpose of kicking its bot + into gear when it is initialized. + + """ + def at_script_creation(self): + """ + Called once, when script is created. + + """ + self.key = "botstarter" + self.desc = "bot start/keepalive" + self.persistent = True + self.db.started = False + if _IDLE_TIMEOUT > 0: + # call before idle_timeout triggers + self.interval = int(max(60, _IDLE_TIMEOUT * 0.90)) + self.start_delay = True + + def at_start(self): + """ + Kick bot into gear. + + """ + if not self.db.started: + self.account.start() + self.db.started = True + + def at_repeat(self): + """ + Called self.interval seconds to keep connection. We cannot use + the IDLE command from inside the game since the system will + not catch it (commands executed from the server side usually + has no sessions). So we update the idle counter manually here + instead. This keeps the bot getting hit by IDLE_TIMEOUT. + + """ + global _SESSIONS + if not _SESSIONS: + from evennia.server.sessionhandler import SESSIONS as _SESSIONS + for session in _SESSIONS.sessions_from_account(self.account): + session.update_session_counters(idle=True) + + def at_server_reload(self): + """ + If server reloads we don't need to reconnect the protocol + again, this is handled by the portal reconnect mechanism. + + """ + self.db.started = True + + def at_server_shutdown(self): + """ + Make sure we are shutdown. + + """ + self.db.started = False + +# +# Bot base class + + +class Bot(DefaultAccount): + """ + A Bot will start itself when the server starts (it will generally + not do so on a reload - that will be handled by the normal Portal + session resync) + + """ + + def basetype_setup(self): + """ + This sets up the basic properties for the bot. + + """ + # the text encoding to use. + self.db.encoding = "utf-8" + # A basic security setup + lockstring = "examine:perm(Admin);edit:perm(Admin);delete:perm(Admin);boot:perm(Admin);msg:false()" + self.locks.add(lockstring) + # set the basics of being a bot + script_key = "%s" % self.key + self.scripts.add(BotStarter, key=script_key) + self.is_bot = True + + def start(self, **kwargs): + """ + This starts the bot, whatever that may mean. + + """ + pass + + def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs): + """ + Evennia -> outgoing protocol + + """ + super(Bot, self).msg(text=text, from_obj=from_obj, session=session, options=options, **kwargs) + + def execute_cmd(self, raw_string, session=None): + """ + Incoming protocol -> Evennia + + """ + super(Bot, self).msg(raw_string, session=session) + + def at_server_shutdown(self): + """ + We need to handle this case manually since the shutdown may be + a reset. + + """ + for session in self.sessions.all(): + session.sessionhandler.disconnect(session) + + +# Bot implementations + +# IRC + +class IRCBot(Bot): + """ + Bot for handling IRC connections. + + """ + def start(self, ev_channel=None, irc_botname=None, irc_channel=None, irc_network=None, irc_port=None, irc_ssl=None): + """ + Start by telling the portal to start a new session. + + Args: + ev_channel (str): Key of the Evennia channel to connect to. + irc_botname (str): Name of bot to connect to irc channel. If + not set, use `self.key`. + irc_channel (str): Name of channel on the form `#channelname`. + irc_network (str): URL of the IRC network, like `irc.freenode.net`. + irc_port (str): Port number of the irc network, like `6667`. + irc_ssl (bool): Indicates whether to use SSL connection. + + """ + if not _IRC_ENABLED: + # the bot was created, then IRC was turned off. We delete + # ourselves (this will also kill the start script) + self.delete() + return + + global _SESSIONS + if not _SESSIONS: + from evennia.server.sessionhandler import SESSIONS as _SESSIONS + + # if keywords are given, store (the BotStarter script + # will not give any keywords, so this should normally only + # happen at initialization) + if irc_botname: + self.db.irc_botname = irc_botname + elif not self.db.irc_botname: + self.db.irc_botname = self.key + if ev_channel: + # connect to Evennia channel + channel = search.channel_search(ev_channel) + if not channel: + raise RuntimeError("Evennia Channel '%s' not found." % ev_channel) + channel = channel[0] + channel.connect(self) + self.db.ev_channel = channel + if irc_channel: + self.db.irc_channel = irc_channel + if irc_network: + self.db.irc_network = irc_network + if irc_port: + self.db.irc_port = irc_port + if irc_ssl: + self.db.irc_ssl = irc_ssl + + # instruct the server and portal to create a new session with + # the stored configuration + configdict = {"uid": self.dbid, + "botname": self.db.irc_botname, + "channel": self.db.irc_channel, + "network": self.db.irc_network, + "port": self.db.irc_port, + "ssl": self.db.irc_ssl} + _SESSIONS.start_bot_session("evennia.server.portal.irc.IRCBotFactory", configdict) + + def get_nicklist(self, caller): + """ + Retrive the nick list from the connected channel. + + Args: + caller (Object or Account): The requester of the list. This will + be stored and echoed to when the irc network replies with the + requested info. + + Notes: Since the return is asynchronous, the caller is stored internally + in a list; all callers in this list will get the nick info once it + returns (it is a custom OOB inputfunc option). The callback will not + survive a reload (which should be fine, it's very quick). + """ + if not hasattr(self, "_nicklist_callers"): + self._nicklist_callers = [] + self._nicklist_callers.append(caller) + super(IRCBot, self).msg(request_nicklist="") + return + + def ping(self, caller): + """ + Fire a ping to the IRC server. + + Args: + caller (Object or Account): The requester of the ping. + + """ + if not hasattr(self, "_ping_callers"): + self._ping_callers = [] + self._ping_callers.append(caller) + super(IRCBot, self).msg(ping="") + + def reconnect(self): + """ + Force a protocol-side reconnect of the client without + having to destroy/recreate the bot "account". + + """ + super(IRCBot, self).msg(reconnect="") + + def msg(self, text=None, **kwargs): + """ + Takes text from connected channel (only). + + Args: + text (str, optional): Incoming text from channel. + + Kwargs: + options (dict): Options dict with the following allowed keys: + - from_channel (str): dbid of a channel this text originated from. + - from_obj (list): list of objects this text. + + """ + from_obj = kwargs.get("from_obj", None) + options = kwargs.get("options", None) or {} + if not self.ndb.ev_channel and self.db.ev_channel: + # cache channel lookup + self.ndb.ev_channel = self.db.ev_channel + if "from_channel" in options and text and self.ndb.ev_channel.dbid == options["from_channel"]: + if not from_obj or from_obj != [self.id]: + super(IRCBot, self).msg(channel=text) + + def execute_cmd(self, session=None, txt=None, **kwargs): + """ + Take incoming data and send it to connected channel. This is + triggered by the bot_data_in Inputfunc. + + Args: + session (Session, optional): Session responsible for this + command. Note that this is the bot. + txt (str, optional): Command string. + Kwargs: + user (str): The name of the user who sent the message. + channel (str): The name of channel the message was sent to. + type (str): Nature of message. Either 'msg', 'action', 'nicklist' or 'ping'. + nicklist (list, optional): Set if `type='nicklist'`. This is a list of nicks returned by calling + the `self.get_nicklist`. It must look for a list `self._nicklist_callers` + which will contain all callers waiting for the nicklist. + timings (float, optional): Set if `type='ping'`. This is the return (in seconds) of a + ping request triggered with `self.ping`. The return must look for a list + `self._ping_callers` which will contain all callers waiting for the ping return. + + """ + if kwargs["type"] == "nicklist": + # the return of a nicklist request + if hasattr(self, "_nicklist_callers") and self._nicklist_callers: + chstr = "%s (%s:%s)" % (self.db.irc_channel, self.db.irc_network, self.db.irc_port) + nicklist = ", ".join(sorted(kwargs["nicklist"], key=lambda n: n.lower())) + for obj in self._nicklist_callers: + obj.msg("Nicks at %s:\n %s" % (chstr, nicklist)) + self._nicklist_callers = [] + return + + elif kwargs["type"] == "ping": + # the return of a ping + if hasattr(self, "_ping_callers") and self._ping_callers: + chstr = "%s (%s:%s)" % (self.db.irc_channel, self.db.irc_network, self.db.irc_port) + for obj in self._ping_callers: + obj.msg("IRC ping return from %s took %ss." % (chstr, kwargs["timing"])) + self._ping_callers = [] + return + + elif kwargs["type"] == "privmsg": + # A private message to the bot - a command. + user = kwargs["user"] + + if txt.lower().startswith("who"): + # return server WHO list (abbreviated for IRC) + global _SESSIONS + if not _SESSIONS: + from evennia.server.sessionhandler import SESSIONS as _SESSIONS + whos = [] + t0 = time.time() + for sess in _SESSIONS.get_sessions(): + delta_cmd = t0 - sess.cmd_last_visible + delta_conn = t0 - session.conn_time + account = sess.get_account() + whos.append("%s (%s/%s)" % (utils.crop("|w%s|n" % account.name, width=25), + utils.time_format(delta_conn, 0), + utils.time_format(delta_cmd, 1))) + text = "Who list (online/idle): %s" % ", ".join(sorted(whos, key=lambda w: w.lower())) + elif txt.lower().startswith("about"): + # some bot info + text = "This is an Evennia IRC bot connecting from '%s'." % settings.SERVERNAME + else: + text = "I understand 'who' and 'about'." + super(IRCBot, self).msg(privmsg=((text,), {"user": user})) + else: + # something to send to the main channel + if kwargs["type"] == "action": + # An action (irc pose) + text = "%s@%s %s" % (kwargs["user"], kwargs["channel"], txt) + else: + # msg - A normal channel message + text = "%s@%s: %s" % (kwargs["user"], kwargs["channel"], txt) + + if not self.ndb.ev_channel and self.db.ev_channel: + # cache channel lookup + self.ndb.ev_channel = self.db.ev_channel + if self.ndb.ev_channel: + self.ndb.ev_channel.msg(text, senders=self.id) + +# +# RSS + + +class RSSBot(Bot): + """ + An RSS relayer. The RSS protocol itself runs a ticker to update + its feed at regular intervals. + + """ + def start(self, ev_channel=None, rss_url=None, rss_rate=None): + """ + Start by telling the portal to start a new RSS session + + Args: + ev_channel (str): Key of the Evennia channel to connect to. + rss_url (str): Full URL to the RSS feed to subscribe to. + rss_rate (int): How often for the feedreader to update. + + Raises: + RuntimeError: If `ev_channel` does not exist. + + """ + if not _RSS_ENABLED: + # The bot was created, then RSS was turned off. Delete ourselves. + self.delete() + return + + global _SESSIONS + if not _SESSIONS: + from evennia.server.sessionhandler import SESSIONS as _SESSIONS + + if ev_channel: + # connect to Evennia channel + channel = search.channel_search(ev_channel) + if not channel: + raise RuntimeError("Evennia Channel '%s' not found." % ev_channel) + channel = channel[0] + self.db.ev_channel = channel + if rss_url: + self.db.rss_url = rss_url + if rss_rate: + self.db.rss_rate = rss_rate + # instruct the server and portal to create a new session with + # the stored configuration + configdict = {"uid": self.dbid, + "url": self.db.rss_url, + "rate": self.db.rss_rate} + _SESSIONS.start_bot_session("evennia.server.portal.rss.RSSBotFactory", configdict) + + def execute_cmd(self, txt=None, session=None, **kwargs): + """ + Take incoming data and send it to connected channel. This is + triggered by the bot_data_in Inputfunc. + + Args: + session (Session, optional): Session responsible for this + command. + txt (str, optional): Command string. + kwargs (dict, optional): Additional Information passed from bot. + Not used by the RSSbot by default. + + """ + if not self.ndb.ev_channel and self.db.ev_channel: + # cache channel lookup + self.ndb.ev_channel = self.db.ev_channel + if self.ndb.ev_channel: + self.ndb.ev_channel.msg(txt, senders=self.id) diff --git a/evennia/accounts/manager.py b/evennia/accounts/manager.py new file mode 100644 index 0000000000..8156f03952 --- /dev/null +++ b/evennia/accounts/manager.py @@ -0,0 +1,180 @@ +""" +The managers for the custom Account object and permissions. +""" + +import datetime +from django.utils import timezone +from django.contrib.auth.models import UserManager +from evennia.typeclasses.managers import (TypedObjectManager, TypeclassManager) +__all__ = ("AccountManager",) + + +# +# Account Manager +# + +class AccountDBManager(TypedObjectManager, UserManager): + """ + This AccountManager implements methods for searching + and manipulating Accounts directly from the database. + + Evennia-specific search methods (will return Characters if + possible or a Typeclass/list of Typeclassed objects, whereas + Django-general methods will return Querysets or database objects): + + dbref (converter) + dbref_search + get_dbref_range + object_totals + typeclass_search + num_total_accounts + get_connected_accounts + get_recently_created_accounts + get_recently_connected_accounts + get_account_from_email + get_account_from_uid + get_account_from_name + account_search (equivalent to evennia.search_account) + #swap_character + + """ + def num_total_accounts(self): + """ + Get total number of accounts. + + Returns: + count (int): The total number of registered accounts. + + """ + return self.count() + + def get_connected_accounts(self): + """ + Get all currently connected accounts. + + Returns: + count (list): Account objects with currently + connected sessions. + + """ + return self.filter(db_is_connected=True) + + def get_recently_created_accounts(self, days=7): + """ + Get accounts recently created. + + Args: + days (int, optional): How many days in the past "recently" means. + + Returns: + accounts (list): The Accounts created the last `days` interval. + + """ + end_date = timezone.now() + tdelta = datetime.timedelta(days) + start_date = end_date - tdelta + return self.filter(date_joined__range=(start_date, end_date)) + + def get_recently_connected_accounts(self, days=7): + """ + Get accounts recently connected to the game. + + Args: + days (int, optional): Number of days backwards to check + + Returns: + accounts (list): The Accounts connected to the game in the + last `days` interval. + + """ + end_date = timezone.now() + tdelta = datetime.timedelta(days) + start_date = end_date - tdelta + return self.filter(last_login__range=( + start_date, end_date)).order_by('-last_login') + + def get_account_from_email(self, uemail): + """ + Search account by + Returns an account object based on email address. + + Args: + uemail (str): An email address to search for. + + Returns: + account (Account): A found account, if found. + + """ + return self.filter(email__iexact=uemail) + + def get_account_from_uid(self, uid): + """ + Get an account by id. + + Args: + uid (int): Account database id. + + Returns: + account (Account): The result. + + """ + try: + return self.get(id=uid) + except self.model.DoesNotExist: + return None + + def get_account_from_name(self, uname): + """ + Get account object based on name. + + Args: + uname (str): The Account name to search for. + + Returns: + account (Account): The found account. + + """ + try: + return self.get(username__iexact=uname) + except self.model.DoesNotExist: + return None + + def search_account(self, ostring, exact=True, typeclass=None): + """ + Searches for a particular account by name or + database id. + + Args: + ostring (str or int): A key string or database id. + exact (bool, optional): Only valid for string matches. If + `True`, requires exact (non-case-sensitive) match, + otherwise also match also keys containing the `ostring` + (non-case-sensitive fuzzy match). + typeclass (str or Typeclass, optional): Limit the search only to + accounts of this typeclass. + + """ + dbref = self.dbref(ostring) + if dbref or dbref == 0: + # bref search is always exact + matches = self.filter(id=dbref) + if matches: + return matches + query = {"username__iexact" if exact else "username__icontains": ostring} + if typeclass: + # we accept both strings and actual typeclasses + if callable(typeclass): + typeclass = u"%s.%s" % (typeclass.__module__, typeclass.__name__) + else: + typeclass = u"%s" % typeclass + query["db_typeclass_path"] = typeclass + if exact: + return self.filter(**query) + else: + return self.filter(**query) + # back-compatibility alias + account_search = search_account + + +class AccountManager(AccountDBManager, TypeclassManager): + pass diff --git a/evennia/accounts/migrations/0001_initial.py b/evennia/accounts/migrations/0001_initial.py new file mode 100644 index 0000000000..85e2303490 --- /dev/null +++ b/evennia/accounts/migrations/0001_initial.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-07-03 19:13 +from __future__ import unicode_literals + +import django.contrib.auth.validators +from django.db import migrations, models +import django.utils.timezone +import evennia.accounts.manager + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0008_alter_user_username_max_length'), + ('typeclasses', '0008_lock_and_perm_rename'), + ] + + operations = [ + migrations.CreateModel( + name='AccountDB', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.ASCIIUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=30, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('db_key', models.CharField(db_index=True, max_length=255, verbose_name=b'key')), + ('db_typeclass_path', models.CharField(help_text=b"this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.", max_length=255, null=True, verbose_name=b'typeclass')), + ('db_date_created', models.DateTimeField(auto_now_add=True, verbose_name=b'creation date')), + ('db_lock_storage', models.TextField(blank=True, help_text=b"locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.", verbose_name=b'locks')), + ('db_is_connected', models.BooleanField(default=False, help_text=b'If player is connected to game or not', verbose_name=b'is_connected')), + ('db_cmdset_storage', models.CharField(help_text=b'optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.', max_length=255, null=True, verbose_name=b'cmdset')), + ('db_is_bot', models.BooleanField(default=False, help_text=b'Used to identify irc/rss bots', verbose_name=b'is_bot')), + ('db_attributes', models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute')), + ('db_tags', models.ManyToManyField(help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'Account', + }, + managers=[ + ('objects', evennia.accounts.manager.AccountDBManager()), + ], + ), + ] diff --git a/evennia/accounts/migrations/0002_copy_player_to_account.py b/evennia/accounts/migrations/0002_copy_player_to_account.py new file mode 100644 index 0000000000..5c32856c4b --- /dev/null +++ b/evennia/accounts/migrations/0002_copy_player_to_account.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-07-03 19:17 +from __future__ import unicode_literals + +from django.apps import apps as global_apps +from django.db import migrations + + +def forwards(apps, schema_editor): + try: + PlayerDB = apps.get_model('players', 'PlayerDB') + except LookupError: + # playerdb not available. Skip. + return + + AccountDB = apps.get_model('accounts', 'AccountDB') + for player in PlayerDB.objects.all(): + account = AccountDB(id=player.id, + password=player.password, + is_superuser=player.is_superuser, + last_login=player.last_login, + username=player.username, + first_name=player.first_name, + last_name=player.last_name, + email=player.email, + is_staff=player.is_staff, + is_active=player.is_active, + date_joined=player.date_joined, + db_key=player.db_key, + db_typeclass_path=player.db_typeclass_path, + db_date_created=player.db_date_created, + db_lock_storage=player.db_lock_storage, + db_is_connected=player.db_is_connected, + db_cmdset_storage=player.db_cmdset_storage, + db_is_bot=player.db_is_bot) + account.save() + for group in player.groups.all(): + account.groups.add(group) + for user_permission in player.user_permissions.all(): + account.user_permissions.add(user_permission) + for attr in player.db_attributes.all(): + account.db_attributes.add(attr) + for tag in player.db_tags.all(): + account.db_tags.add(tag) + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0001_initial'), + ] + + operations = [ + migrations.RunPython(forwards, migrations.RunPython.noop) + ] + + if global_apps.is_installed('players'): + dependencies.append(('players', '0006_auto_20170606_1731')) diff --git a/evennia/accounts/migrations/__init__.py b/evennia/accounts/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/evennia/accounts/models.py b/evennia/accounts/models.py new file mode 100644 index 0000000000..6b09064f10 --- /dev/null +++ b/evennia/accounts/models.py @@ -0,0 +1,169 @@ +""" +Account + +The account class is an extension of the default Django user class, +and is customized for the needs of Evennia. + +We use the Account to store a more mud-friendly style of permission +system as well as to allow the admin more flexibility by storing +attributes on the Account. Within the game we should normally use the +Account manager's methods to create users so that permissions are set +correctly. + +To make the Account model more flexible for your own game, it can also +persistently store attributes of its own. This is ideal for extra +account info and OOC account configuration variables etc. + +""" +from builtins import object + +from django.conf import settings +from django.db import models +from django.contrib.auth.models import AbstractUser +from django.utils.encoding import smart_str + +from evennia.accounts.manager import AccountDBManager +from evennia.typeclasses.models import TypedObject +from evennia.utils.utils import make_iter + +__all__ = ("AccountDB",) + +#_ME = _("me") +#_SELF = _("self") + +_MULTISESSION_MODE = settings.MULTISESSION_MODE + +_GA = object.__getattribute__ +_SA = object.__setattr__ +_DA = object.__delattr__ + +_TYPECLASS = None + + +#------------------------------------------------------------ +# +# AccountDB +# +#------------------------------------------------------------ + +class AccountDB(TypedObject, AbstractUser): + """ + This is a special model using Django's 'profile' functionality + and extends the default Django User model. It is defined as such + by use of the variable AUTH_PROFILE_MODULE in the settings. + One accesses the fields/methods. We try use this model as much + as possible rather than User, since we can customize this to + our liking. + + The TypedObject supplies the following (inherited) properties: + + - key - main name + - typeclass_path - the path to the decorating typeclass + - typeclass - auto-linked typeclass + - date_created - time stamp of object creation + - permissions - perm strings + - dbref - #id of object + - db - persistent attribute storage + - ndb - non-persistent attribute storage + + The AccountDB adds the following properties: + + - is_connected - If any Session is currently connected to this Account + - name - alias for user.username + - sessions - sessions connected to this account + - is_superuser - bool if this account is a superuser + - is_bot - bool if this account is a bot and not a real account + + """ + + # + # AccountDB Database model setup + # + # inherited fields (from TypedObject): + # db_key, db_typeclass_path, db_date_created, db_permissions + + # store a connected flag here too, not just in sessionhandler. + # This makes it easier to track from various out-of-process locations + db_is_connected = models.BooleanField(default=False, + verbose_name="is_connected", + help_text="If player is connected to game or not") + # database storage of persistant cmdsets. + db_cmdset_storage = models.CharField('cmdset', max_length=255, null=True, + help_text="optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.") + # marks if this is a "virtual" bot account object + db_is_bot = models.BooleanField(default=False, verbose_name="is_bot", help_text="Used to identify irc/rss bots") + + # Database manager + objects = AccountDBManager() + + # defaults + __settingsclasspath__ = settings.BASE_SCRIPT_TYPECLASS + __defaultclasspath__ = "evennia.accounts.accounts.DefaultAccount" + __applabel__ = "accounts" + + class Meta(object): + verbose_name = 'Account' + + # cmdset_storage property + # This seems very sensitive to caching, so leaving it be for now /Griatch + #@property + def __cmdset_storage_get(self): + """ + Getter. Allows for value = self.name. Returns a list of cmdset_storage. + """ + storage = self.db_cmdset_storage + # we need to check so storage is not None + return [path.strip() for path in storage.split(',')] if storage else [] + + #@cmdset_storage.setter + def __cmdset_storage_set(self, value): + """ + Setter. Allows for self.name = value. Stores as a comma-separated + string. + """ + _SA(self, "db_cmdset_storage", ",".join(str(val).strip() for val in make_iter(value))) + _GA(self, "save")() + + #@cmdset_storage.deleter + def __cmdset_storage_del(self): + "Deleter. Allows for del self.name" + _SA(self, "db_cmdset_storage", None) + _GA(self, "save")() + cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set, __cmdset_storage_del) + + # + # property/field access + # + + def __str__(self): + return smart_str("%s(account %s)" % (self.name, self.dbid)) + + def __unicode__(self): + return u"%s(account#%s)" % (self.name, self.dbid) + + #@property + def __username_get(self): + return self.username + + def __username_set(self, value): + self.username = value + self.save(update_fields=["username"]) + + def __username_del(self): + del self.username + + # aliases + name = property(__username_get, __username_set, __username_del) + key = property(__username_get, __username_set, __username_del) + + #@property + def __uid_get(self): + "Getter. Retrieves the user id" + return self.id + + def __uid_set(self, value): + raise Exception("User id cannot be set!") + + def __uid_del(self): + raise Exception("User id cannot be deleted!") + uid = property(__uid_get, __uid_set, __uid_del) From 63c96de4433c899771aebe6fe4b9cc5ed195ab92 Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 6 Jul 2017 22:29:52 +0200 Subject: [PATCH 42/63] Create parallel Player/Account fields and copy all --- .../migrations/0013_auto_20170705_1726.py | 51 +++++++++++++++++++ .../migrations/0014_auto_20170705_1736.py | 43 ++++++++++++++++ evennia/comms/models.py | 15 +++++- .../migrations/0007_objectdb_db_account.py | 22 ++++++++ .../migrations/0008_auto_20170705_1736.py | 32 ++++++++++++ evennia/objects/models.py | 4 ++ .../migrations/0009_scriptdb_db_account.py | 22 ++++++++ .../migrations/0010_auto_20170705_1736.py | 33 ++++++++++++ evennia/scripts/models.py | 4 ++ evennia/utils/idmapper/manager.py | 2 +- 10 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 evennia/comms/migrations/0013_auto_20170705_1726.py create mode 100644 evennia/comms/migrations/0014_auto_20170705_1736.py create mode 100644 evennia/objects/migrations/0007_objectdb_db_account.py create mode 100644 evennia/objects/migrations/0008_auto_20170705_1736.py create mode 100644 evennia/scripts/migrations/0009_scriptdb_db_account.py create mode 100644 evennia/scripts/migrations/0010_auto_20170705_1736.py diff --git a/evennia/comms/migrations/0013_auto_20170705_1726.py b/evennia/comms/migrations/0013_auto_20170705_1726.py new file mode 100644 index 0000000000..6076a12431 --- /dev/null +++ b/evennia/comms/migrations/0013_auto_20170705_1726.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-07-05 17:26 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0002_copy_player_to_account'), + ('comms', '0012_merge_20170617_2017'), + ] + + operations = [ + migrations.AddField( + model_name='channeldb', + name='db_account_subscriptions', + field=models.ManyToManyField(blank=True, db_index=True, related_name='account_subscription_set', to='accounts.AccountDB', verbose_name=b'account subscriptions'), + ), + migrations.AddField( + model_name='msg', + name='db_hide_from_accounts', + field=models.ManyToManyField(blank=True, related_name='hide_from_accounts_set', to='accounts.AccountDB'), + ), + migrations.AddField( + model_name='msg', + name='db_receivers_accounts', + field=models.ManyToManyField(blank=True, help_text=b'account receivers', related_name='receiver_account_set', to='accounts.AccountDB'), + ), + migrations.AddField( + model_name='msg', + name='db_sender_accounts', + field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_account_set', to='accounts.AccountDB', verbose_name=b'sender(account)'), + ), + migrations.AlterField( + model_name='channeldb', + name='db_object_subscriptions', + field=models.ManyToManyField(blank=True, db_index=True, related_name='object_subscription_set', to='objects.ObjectDB', verbose_name=b'object subscriptions'), + ), + migrations.AlterField( + model_name='msg', + name='db_receivers_scripts', + field=models.ManyToManyField(blank=True, help_text=b'script_receivers', related_name='receiver_script_set', to='scripts.ScriptDB'), + ), + migrations.AlterField( + model_name='msg', + name='db_sender_scripts', + field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_script_set', to='scripts.ScriptDB', verbose_name=b'sender(script)'), + ), + ] diff --git a/evennia/comms/migrations/0014_auto_20170705_1736.py b/evennia/comms/migrations/0014_auto_20170705_1736.py new file mode 100644 index 0000000000..a8ece9e756 --- /dev/null +++ b/evennia/comms/migrations/0014_auto_20170705_1736.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-07-05 17:36 +from __future__ import unicode_literals + +from django.db import migrations + + +def forwards(apps, schema_editor): + + try: + apps.get_model('players', 'PlayerDB') + except LookupError: + return + AccountDB = apps.get_model('accounts', 'AccountDB') + + Msg = apps.get_model('comms', 'Msg') + for msg in Msg.objects.all(): + for player in msg.db_sender_players.all(): + account = AccountDB.objects.get(id=player.id) + msg.db_sender_accounts.add(account) + for player in msg.db_receivers_players.all(): + account = AccountDB.objects.get(id=player.id) + msg.db_receivers_accounts.add(account) + for player in msg.db_hide_from_players.all(): + account = AccountDB.objects.get(id=player.id) + msg.db_hide_from_accounts.add(account) + + ChannelDB = apps.get_model('comms', 'ChannelDB') + for channel in ChannelDB.objects.all(): + for player in channel.db_subscriptions.all(): + account = AccountDB.objects.get(id=player.id) + channel.db_account_subscriptions.add(account) + + +class Migration(migrations.Migration): + + dependencies = [ + ('comms', '0013_auto_20170705_1726'), + ] + + operations = [ + migrations.RunPython(forwards) + ] diff --git a/evennia/comms/models.py b/evennia/comms/models.py index ac1ad29933..30d65dd8ce 100644 --- a/evennia/comms/models.py +++ b/evennia/comms/models.py @@ -80,8 +80,12 @@ class Msg(SharedMemoryModel): # Sender is either a player, an object or an external sender, like # an IRC channel; normally there is only one, but if co-modification of # a message is allowed, there may be more than one "author" + # TODO Player-Account db_sender_players = models.ManyToManyField("players.PlayerDB", related_name='sender_player_set', blank=True, verbose_name='sender(player)', db_index=True) + db_sender_accounts = models.ManyToManyField("accounts.AccountDB", related_name='sender_account_set', + blank=True, verbose_name='sender(account)', db_index=True) + db_sender_objects = models.ManyToManyField("objects.ObjectDB", related_name='sender_object_set', blank=True, verbose_name='sender(object)', db_index=True) db_sender_scripts = models.ManyToManyField("scripts.ScriptDB", related_name='sender_script_set', @@ -92,8 +96,12 @@ class Msg(SharedMemoryModel): # The destination objects of this message. Stored as a # comma-separated string of object dbrefs. Can be defined along # with channels below. + # TODO Player-Account db_receivers_players = models.ManyToManyField('players.PlayerDB', related_name='receiver_player_set', blank=True, help_text="player receivers") + db_receivers_accounts = models.ManyToManyField('accounts.AccountDB', related_name='receiver_account_set', + blank=True, help_text="account receivers") + db_receivers_objects = models.ManyToManyField('objects.ObjectDB', related_name='receiver_object_set', blank=True, help_text="object receivers") db_receivers_scripts = models.ManyToManyField('scripts.ScriptDB', related_name='receiver_script_set', @@ -114,6 +122,8 @@ class Msg(SharedMemoryModel): # these can be used to filter/hide a given message from supplied objects/players/channels db_hide_from_players = models.ManyToManyField("players.PlayerDB", related_name='hide_from_players_set', blank=True) + db_hide_from_accounts = models.ManyToManyField("accounts.AccountDB", related_name='hide_from_accounts_set', blank=True) + db_hide_from_objects = models.ManyToManyField("objects.ObjectDB", related_name='hide_from_objects_set', blank=True) db_hide_from_channels = models.ManyToManyField("ChannelDB", related_name='hide_from_channels_set', blank=True) @@ -605,11 +615,14 @@ class ChannelDB(TypedObject): - db_object_subscriptions: The Object subscriptions. """ + # TODO Player-Account db_subscriptions = models.ManyToManyField("players.PlayerDB", related_name="subscription_set", blank=True, verbose_name='subscriptions', db_index=True) + db_account_subscriptions = models.ManyToManyField("accounts.AccountDB", + related_name="account_subscription_set", blank=True, verbose_name='account subscriptions', db_index=True) db_object_subscriptions = models.ManyToManyField("objects.ObjectDB", - related_name="object_subscription_set", blank=True, verbose_name='subscriptions', db_index=True) + related_name="object_subscription_set", blank=True, verbose_name='object subscriptions', db_index=True) # Database manager objects = managers.ChannelDBManager() diff --git a/evennia/objects/migrations/0007_objectdb_db_account.py b/evennia/objects/migrations/0007_objectdb_db_account.py new file mode 100644 index 0000000000..b27c75c944 --- /dev/null +++ b/evennia/objects/migrations/0007_objectdb_db_account.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-07-05 17:27 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0002_copy_player_to_account'), + ('objects', '0006_auto_20170606_1731'), + ] + + operations = [ + migrations.AddField( + model_name='objectdb', + name='db_account', + field=models.ForeignKey(help_text=b'an Account connected to this object, if any.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.AccountDB', verbose_name=b'account'), + ), + ] diff --git a/evennia/objects/migrations/0008_auto_20170705_1736.py b/evennia/objects/migrations/0008_auto_20170705_1736.py new file mode 100644 index 0000000000..02b2ca857c --- /dev/null +++ b/evennia/objects/migrations/0008_auto_20170705_1736.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-07-05 17:36 +from __future__ import unicode_literals + +from django.db import migrations + + +def forwards(apps, schema_editor): + + try: + apps.get_model('players', 'PlayerDB') + except LookupError: + return + AccountDB = apps.get_model('accounts', 'AccountDB') + ObjectDB = apps.get_model('objects', 'ObjectDB') + + for object in ObjectDB.objects.all(): + player = object.db_player + if player: + account = AccountDB.objects.get(id=player.id) + object.db_account = account + object.save(update_fields=['db_account']) + +class Migration(migrations.Migration): + + dependencies = [ + ('objects', '0007_objectdb_db_account'), + ] + + operations = [ + migrations.RunPython(forwards) + ] diff --git a/evennia/objects/models.py b/evennia/objects/models.py index b3b00ce55a..52e75353ee 100644 --- a/evennia/objects/models.py +++ b/evennia/objects/models.py @@ -170,8 +170,12 @@ class ObjectDB(TypedObject): # will automatically save and cache the data more efficiently. # If this is a character object, the player is connected here. + # TODO Player-Account db_player = models.ForeignKey("players.PlayerDB", null=True, verbose_name='player', on_delete=models.SET_NULL, help_text='a Player connected to this object, if any.') + db_account = models.ForeignKey("accounts.AccountDB", null=True, verbose_name='account', on_delete=models.SET_NULL, + help_text='an Account connected to this object, if any.') + # the session id associated with this player, if any db_sessid = models.CharField(null=True, max_length=32, validators=[validate_comma_separated_integer_list], verbose_name="session id", diff --git a/evennia/scripts/migrations/0009_scriptdb_db_account.py b/evennia/scripts/migrations/0009_scriptdb_db_account.py new file mode 100644 index 0000000000..99baf70a5f --- /dev/null +++ b/evennia/scripts/migrations/0009_scriptdb_db_account.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-07-05 17:27 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0002_copy_player_to_account'), + ('scripts', '0008_auto_20170606_1731'), + ] + + operations = [ + migrations.AddField( + model_name='scriptdb', + name='db_account', + field=models.ForeignKey(blank=True, help_text=b'the account to store this script on (should not be set if db_obj is set)', null=True, on_delete=django.db.models.deletion.CASCADE, to='accounts.AccountDB', verbose_name=b'scripted account'), + ), + ] diff --git a/evennia/scripts/migrations/0010_auto_20170705_1736.py b/evennia/scripts/migrations/0010_auto_20170705_1736.py new file mode 100644 index 0000000000..6c9b427e74 --- /dev/null +++ b/evennia/scripts/migrations/0010_auto_20170705_1736.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-07-05 17:36 +from __future__ import unicode_literals + +from django.db import migrations + + +def forwards(apps, schema_editor): + + try: + apps.get_model('players', 'PlayerDB') + except LookupError: + return + AccountDB = apps.get_model('accounts', 'AccountDB') + ScriptDB = apps.get_model('scripts', 'ScriptDB') + + for script in ScriptDB.objects.all(): + player = script.db_player + if player: + account = AccountDB.objects.get(id=player.id) + script.db_account = account + script.save(update_fields=['db_account']) + + +class Migration(migrations.Migration): + + dependencies = [ + ('scripts', '0009_scriptdb_db_account'), + ] + + operations = [ + migrations.RunPython(forwards) + ] diff --git a/evennia/scripts/models.py b/evennia/scripts/models.py index afeff10b55..c9bcf4994a 100644 --- a/evennia/scripts/models.py +++ b/evennia/scripts/models.py @@ -86,8 +86,12 @@ class ScriptDB(TypedObject): # A reference to the database object affected by this Script, if any. db_obj = models.ForeignKey("objects.ObjectDB", null=True, blank=True, verbose_name='scripted object', help_text='the object to store this script on, if not a global script.') + # TODO Player-Account db_player = models.ForeignKey("players.PlayerDB", null=True, blank=True, verbose_name="scripted player", help_text='the player to store this script on (should not be set if obj is set)') + db_account = models.ForeignKey("accounts.AccountDB", null=True, blank=True, verbose_name="scripted account", + help_text='the account to store this script on (should not be set if db_obj is set)') + # how often to run Script (secs). -1 means there is no timer db_interval = models.IntegerField('interval', default=-1, help_text='how often to repeat script, in seconds. -1 means off.') # start script right away or wait interval seconds first diff --git a/evennia/utils/idmapper/manager.py b/evennia/utils/idmapper/manager.py index 8d32e0bf17..e9354b343b 100644 --- a/evennia/utils/idmapper/manager.py +++ b/evennia/utils/idmapper/manager.py @@ -24,8 +24,8 @@ class SharedMemoryManager(Manager): if key.endswith('__exact'): key = key[:-len('__exact')] if key in ('pk', self.model._meta.pk.attname): - inst = self.model.get_cached_instance(kwargs[items[0]]) try: + inst = self.model.get_cached_instance(kwargs[items[0]]) # we got the item from cache, but if this is a fk, check it's ours if getattr(inst, str(self.field).split(".")[-1]) != self.instance: inst = None From 34443fa4e68b05121db10d4665ff6ee9c7a4159d Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 6 Jul 2017 23:17:01 +0200 Subject: [PATCH 43/63] Last point where Player & Account coexist - migrate here first. --- .../migrations/0015_auto_20170706_2041.py | 27 +++++++++++++++++++ evennia/comms/models.py | 9 ------- .../0009_remove_objectdb_db_player.py | 19 +++++++++++++ evennia/objects/models.py | 3 --- .../0011_remove_scriptdb_db_player.py | 19 +++++++++++++ evennia/scripts/models.py | 3 --- 6 files changed, 65 insertions(+), 15 deletions(-) create mode 100644 evennia/comms/migrations/0015_auto_20170706_2041.py create mode 100644 evennia/objects/migrations/0009_remove_objectdb_db_player.py create mode 100644 evennia/scripts/migrations/0011_remove_scriptdb_db_player.py diff --git a/evennia/comms/migrations/0015_auto_20170706_2041.py b/evennia/comms/migrations/0015_auto_20170706_2041.py new file mode 100644 index 0000000000..ec5fc29201 --- /dev/null +++ b/evennia/comms/migrations/0015_auto_20170706_2041.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-07-06 20:41 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('comms', '0014_auto_20170705_1736'), + ] + + operations = [ + migrations.RemoveField( + model_name='channeldb', + name='db_subscriptions', + ), + migrations.RemoveField( + model_name='msg', + name='db_receivers_players', + ), + migrations.RemoveField( + model_name='msg', + name='db_sender_players', + ), + ] diff --git a/evennia/comms/models.py b/evennia/comms/models.py index 30d65dd8ce..1c696e35bd 100644 --- a/evennia/comms/models.py +++ b/evennia/comms/models.py @@ -80,9 +80,6 @@ class Msg(SharedMemoryModel): # Sender is either a player, an object or an external sender, like # an IRC channel; normally there is only one, but if co-modification of # a message is allowed, there may be more than one "author" - # TODO Player-Account - db_sender_players = models.ManyToManyField("players.PlayerDB", related_name='sender_player_set', - blank=True, verbose_name='sender(player)', db_index=True) db_sender_accounts = models.ManyToManyField("accounts.AccountDB", related_name='sender_account_set', blank=True, verbose_name='sender(account)', db_index=True) @@ -96,9 +93,6 @@ class Msg(SharedMemoryModel): # The destination objects of this message. Stored as a # comma-separated string of object dbrefs. Can be defined along # with channels below. - # TODO Player-Account - db_receivers_players = models.ManyToManyField('players.PlayerDB', related_name='receiver_player_set', - blank=True, help_text="player receivers") db_receivers_accounts = models.ManyToManyField('accounts.AccountDB', related_name='receiver_account_set', blank=True, help_text="account receivers") @@ -615,9 +609,6 @@ class ChannelDB(TypedObject): - db_object_subscriptions: The Object subscriptions. """ - # TODO Player-Account - db_subscriptions = models.ManyToManyField("players.PlayerDB", - related_name="subscription_set", blank=True, verbose_name='subscriptions', db_index=True) db_account_subscriptions = models.ManyToManyField("accounts.AccountDB", related_name="account_subscription_set", blank=True, verbose_name='account subscriptions', db_index=True) diff --git a/evennia/objects/migrations/0009_remove_objectdb_db_player.py b/evennia/objects/migrations/0009_remove_objectdb_db_player.py new file mode 100644 index 0000000000..80161a1f13 --- /dev/null +++ b/evennia/objects/migrations/0009_remove_objectdb_db_player.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-07-06 20:41 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('objects', '0008_auto_20170705_1736'), + ] + + operations = [ + migrations.RemoveField( + model_name='objectdb', + name='db_player', + ), + ] diff --git a/evennia/objects/models.py b/evennia/objects/models.py index 52e75353ee..4b6606a71b 100644 --- a/evennia/objects/models.py +++ b/evennia/objects/models.py @@ -170,9 +170,6 @@ class ObjectDB(TypedObject): # will automatically save and cache the data more efficiently. # If this is a character object, the player is connected here. - # TODO Player-Account - db_player = models.ForeignKey("players.PlayerDB", null=True, verbose_name='player', on_delete=models.SET_NULL, - help_text='a Player connected to this object, if any.') db_account = models.ForeignKey("accounts.AccountDB", null=True, verbose_name='account', on_delete=models.SET_NULL, help_text='an Account connected to this object, if any.') diff --git a/evennia/scripts/migrations/0011_remove_scriptdb_db_player.py b/evennia/scripts/migrations/0011_remove_scriptdb_db_player.py new file mode 100644 index 0000000000..d3746a5bd9 --- /dev/null +++ b/evennia/scripts/migrations/0011_remove_scriptdb_db_player.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-07-06 20:41 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('scripts', '0010_auto_20170705_1736'), + ] + + operations = [ + migrations.RemoveField( + model_name='scriptdb', + name='db_player', + ), + ] diff --git a/evennia/scripts/models.py b/evennia/scripts/models.py index c9bcf4994a..fe08881918 100644 --- a/evennia/scripts/models.py +++ b/evennia/scripts/models.py @@ -86,9 +86,6 @@ class ScriptDB(TypedObject): # A reference to the database object affected by this Script, if any. db_obj = models.ForeignKey("objects.ObjectDB", null=True, blank=True, verbose_name='scripted object', help_text='the object to store this script on, if not a global script.') - # TODO Player-Account - db_player = models.ForeignKey("players.PlayerDB", null=True, blank=True, verbose_name="scripted player", - help_text='the player to store this script on (should not be set if obj is set)') db_account = models.ForeignKey("accounts.AccountDB", null=True, blank=True, verbose_name="scripted account", help_text='the account to store this script on (should not be set if db_obj is set)') From a14e11640b9f107ebcf538aa71f6149214c57368 Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 7 Jul 2017 22:38:24 +0200 Subject: [PATCH 44/63] Correction of the migration sequence both for clean and old databases. --- evennia/objects/migrations/0001_initial.py | 2 +- evennia/typeclasses/migrations/0002_auto_20150109_0913.py | 1 + evennia/typeclasses/migrations/0005_auto_20160625_1812.py | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/evennia/objects/migrations/0001_initial.py b/evennia/objects/migrations/0001_initial.py index 9c0527c47c..2ad6323226 100644 --- a/evennia/objects/migrations/0001_initial.py +++ b/evennia/objects/migrations/0001_initial.py @@ -8,7 +8,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('typeclasses', '0001_initial'), + ('typeclasses', '0002_auto_20150109_0913'), ] operations = [ diff --git a/evennia/typeclasses/migrations/0002_auto_20150109_0913.py b/evennia/typeclasses/migrations/0002_auto_20150109_0913.py index fbb7ac169b..66753f4830 100644 --- a/evennia/typeclasses/migrations/0002_auto_20150109_0913.py +++ b/evennia/typeclasses/migrations/0002_auto_20150109_0913.py @@ -8,6 +8,7 @@ class Migration(migrations.Migration): dependencies = [ ('typeclasses', '0001_initial'), + ('auth', '0007_alter_validators_add_error_messages') ] operations = [ diff --git a/evennia/typeclasses/migrations/0005_auto_20160625_1812.py b/evennia/typeclasses/migrations/0005_auto_20160625_1812.py index 6414112272..b922446e38 100644 --- a/evennia/typeclasses/migrations/0005_auto_20160625_1812.py +++ b/evennia/typeclasses/migrations/0005_auto_20160625_1812.py @@ -22,6 +22,8 @@ class Migration(migrations.Migration): dependencies = [ ('typeclasses', '0004_auto_20151101_1759'), + ('comms', '0010_auto_20161206_1912'), + ('help', '0001_initial') ] operations = [ From 5590ee22584c556625621106944ef1b407c6afa9 Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 7 Jul 2017 23:47:21 +0200 Subject: [PATCH 45/63] Rename all instances of Player->Account. --- evennia/__init__.py | 44 +- evennia/accounts/admin.py | 8 +- evennia/commands/__init__.py | 2 +- evennia/commands/cmdhandler.py | 84 +- evennia/commands/cmdparser.py | 2 +- evennia/commands/cmdset.py | 12 +- evennia/commands/cmdsethandler.py | 14 +- evennia/commands/command.py | 10 +- evennia/commands/default/admin.py | 146 +-- evennia/commands/default/building.py | 94 +- evennia/commands/default/cmdset_character.py | 6 +- evennia/commands/default/cmdset_player.py | 73 -- evennia/commands/default/cmdset_session.py | 4 +- evennia/commands/default/comms.py | 124 +-- evennia/commands/default/general.py | 16 +- evennia/commands/default/help.py | 6 +- evennia/commands/default/muxcommand.py | 38 +- evennia/commands/default/player.py | 836 ------------------ evennia/commands/default/system.py | 34 +- evennia/commands/default/tests.py | 62 +- evennia/commands/default/unloggedin.py | 164 ++-- evennia/commands/tests.py | 22 +- evennia/comms/channelhandler.py | 8 +- evennia/comms/comms.py | 46 +- evennia/comms/managers.py | 66 +- evennia/comms/models.py | 98 +- evennia/contrib/barter.py | 4 +- evennia/contrib/chargen.py | 34 +- evennia/contrib/egi_client/client.py | 6 +- evennia/contrib/email_login.py | 84 +- evennia/contrib/events/callbackhandler.py | 4 +- evennia/contrib/events/commands.py | 2 +- evennia/contrib/events/scripts.py | 4 +- evennia/contrib/events/typeclasses.py | 32 +- evennia/contrib/extended_room.py | 4 +- evennia/contrib/mail.py | 36 +- evennia/contrib/mapbuilder.py | 8 +- evennia/contrib/menu_login.py | 48 +- evennia/contrib/rpsystem.py | 10 +- evennia/contrib/tests.py | 46 +- .../tutorial_examples/cmdset_red_button.py | 6 +- .../tutorial_examples/red_button_scripts.py | 12 +- evennia/contrib/tutorial_world/mob.py | 6 +- evennia/contrib/tutorial_world/objects.py | 8 +- evennia/contrib/tutorial_world/rooms.py | 30 +- evennia/contrib/wilderness.py | 8 +- evennia/game_template/commands/command.py | 16 +- .../game_template/commands/default_cmdsets.py | 12 +- .../server/conf/serversession.py | 6 +- evennia/game_template/typeclasses/channels.py | 12 +- .../game_template/typeclasses/characters.py | 10 +- evennia/game_template/typeclasses/objects.py | 20 +- evennia/game_template/typeclasses/players.py | 102 --- evennia/locks/lockfuncs.py | 82 +- evennia/locks/lockhandler.py | 14 +- evennia/objects/admin.py | 4 +- evennia/objects/manager.py | 18 +- evennia/objects/models.py | 14 +- evennia/objects/objects.py | 114 +-- evennia/scripts/__init__.py | 2 +- evennia/scripts/manager.py | 14 +- evennia/scripts/models.py | 16 +- evennia/scripts/scripthandler.py | 6 +- evennia/server/amp.py | 4 +- evennia/server/deprecations.py | 6 +- evennia/server/evennia_launcher.py | 38 +- evennia/server/initial_setup.py | 54 +- evennia/server/inputfuncs.py | 44 +- evennia/server/portal/ssh.py | 30 +- evennia/server/profiling/dummyrunner.py | 22 +- .../server/profiling/dummyrunner_settings.py | 12 +- evennia/server/profiling/memplot.py | 2 +- evennia/server/profiling/settings_mixin.py | 4 +- evennia/server/server.py | 30 +- evennia/server/serversession.py | 76 +- evennia/server/session.py | 6 +- evennia/server/sessionhandler.py | 104 +-- evennia/settings_default.py | 48 +- evennia/typeclasses/__init__.py | 2 +- evennia/typeclasses/admin.py | 8 +- evennia/typeclasses/attributes.py | 10 +- evennia/typeclasses/models.py | 14 +- evennia/utils/create.py | 66 +- evennia/utils/dbserialize.py | 2 +- evennia/utils/evmenu.py | 26 +- evennia/utils/evmore.py | 12 +- evennia/utils/search.py | 44 +- evennia/utils/test_resources.py | 24 +- evennia/utils/tests.py | 2 +- evennia/utils/utils.py | 4 +- evennia/web/utils/backends.py | 18 +- evennia/web/utils/general_context.py | 4 +- evennia/web/webclient/views.py | 14 +- evennia/web/website/views.py | 40 +- 94 files changed, 1316 insertions(+), 2327 deletions(-) delete mode 100644 evennia/commands/default/cmdset_player.py delete mode 100644 evennia/commands/default/player.py delete mode 100644 evennia/game_template/typeclasses/players.py diff --git a/evennia/__init__.py b/evennia/__init__.py index b65e1edf9b..06bcfd8b31 100644 --- a/evennia/__init__.py +++ b/evennia/__init__.py @@ -25,7 +25,7 @@ from builtins import object # Typeclasses -DefaultPlayer = None +DefaultAccount = None DefaultGuest = None DefaultObject = None DefaultCharacter = None @@ -36,7 +36,7 @@ DefaultScript = None # Database models ObjectDB = None -PlayerDB = None +AccountDB = None ScriptDB = None ChannelDB = None Msg = None @@ -51,7 +51,7 @@ InterruptCommand = None # search functions search_object = None search_script = None -search_player = None +search_account = None search_channel = None search_message = None search_help = None @@ -60,7 +60,7 @@ search_tag = None # create functions create_object = None create_script = None -create_player = None +create_account = None create_channel = None create_message = None create_help_entry = None @@ -117,17 +117,17 @@ def _init(): Evennia has fully initialized all its models. It sets up the API in a safe environment where all models are available already. """ - global DefaultPlayer, DefaultObject, DefaultGuest, DefaultCharacter + global DefaultAccount, DefaultObject, DefaultGuest, DefaultCharacter global DefaultRoom, DefaultExit, DefaultChannel, DefaultScript - global ObjectDB, PlayerDB, ScriptDB, ChannelDB, Msg + global ObjectDB, AccountDB, ScriptDB, ChannelDB, Msg global Command, CmdSet, default_cmds, syscmdkeys, InterruptCommand - global search_object, search_script, search_player, search_channel, search_help, search_tag - global create_object, create_script, create_player, create_channel, create_message, create_help_entry + global search_object, search_script, search_account, search_channel, search_help, search_tag + global create_object, create_script, create_account, create_channel, create_message, create_help_entry global settings,lockfuncs, logger, utils, gametime, ansi, spawn, managers global contrib, TICKER_HANDLER, MONITOR_HANDLER, SESSION_HANDLER, CHANNEL_HANDLER - from .players.players import DefaultPlayer - from .players.players import DefaultGuest + from .accounts.accounts import DefaultAccount + from .accounts.accounts import DefaultGuest from .objects.objects import DefaultObject from .objects.objects import DefaultCharacter from .objects.objects import DefaultRoom @@ -137,7 +137,7 @@ def _init(): # Database models from .objects.models import ObjectDB - from .players.models import PlayerDB + from .accounts.models import AccountDB from .scripts.models import ScriptDB from .comms.models import ChannelDB from .comms.models import Msg @@ -149,7 +149,7 @@ def _init(): # search functions from .utils.search import search_object from .utils.search import search_script - from .utils.search import search_player + from .utils.search import search_account from .utils.search import search_message from .utils.search import search_channel from .utils.search import search_help @@ -158,7 +158,7 @@ def _init(): # create functions from .utils.create import create_object from .utils.create import create_script - from .utils.create import create_player + from .utils.create import create_account from .utils.create import create_channel from .utils.create import create_message from .utils.create import create_help_entry @@ -202,7 +202,7 @@ def _init(): Links to instantiated database managers. helpentry - HelpEntry.objects - players - PlayerDB.objects + accounts - AccountDB.objects scripts - ScriptDB.objects msgs - Msg.objects channels - Channel.objects @@ -213,7 +213,7 @@ def _init(): """ from .help.models import HelpEntry - from .players.models import PlayerDB + from .accounts.models import AccountDB from .scripts.models import ScriptDB from .comms.models import Msg, ChannelDB from .objects.models import ObjectDB @@ -223,7 +223,7 @@ def _init(): # create container's properties helpentries = HelpEntry.objects - players = PlayerDB.objects + accounts = AccountDB.objects scripts = ScriptDB.objects msgs = Msg.objects channels = ChannelDB.objects @@ -232,7 +232,7 @@ def _init(): attributes = Attribute.objects tags = Tag.objects # remove these so they are not visible as properties - del HelpEntry, PlayerDB, ScriptDB, Msg, ChannelDB + del HelpEntry, AccountDB, ScriptDB, Msg, ChannelDB #del ExternalChannelConnection del ObjectDB, ServerConfig, Tag, Attribute @@ -250,10 +250,10 @@ def _init(): """ from .commands.default.cmdset_character import CharacterCmdSet - from .commands.default.cmdset_player import PlayerCmdSet + from .commands.default.cmdset_account import AccountCmdSet from .commands.default.cmdset_unloggedin import UnloggedinCmdSet from .commands.default.cmdset_session import SessionCmdSet - from .commands.default.muxcommand import MuxCommand, MuxPlayerCommand + from .commands.default.muxcommand import MuxCommand, MuxAccountCommand def __init__(self): "populate the object with commands" @@ -265,14 +265,14 @@ def _init(): from .commands.default import (admin, batchprocess, building, comms, general, - player, help, system, unloggedin) + account, help, system, unloggedin) add_cmds(admin) add_cmds(building) add_cmds(batchprocess) add_cmds(building) add_cmds(comms) add_cmds(general) - add_cmds(player) + add_cmds(account) add_cmds(help) add_cmds(system) add_cmds(unloggedin) @@ -293,7 +293,7 @@ def _init(): CMD_MULTIMATCH - multiple command matches were found CMD_CHANNEL - the command name is a channel name CMD_LOGINSTART - this command will be called as the very - first command when a player connects to + first command when an account connects to the server. To access in code, do 'from evennia import syscmdkeys' then diff --git a/evennia/accounts/admin.py b/evennia/accounts/admin.py index d02b1baeac..ce068dfd6a 100644 --- a/evennia/accounts/admin.py +++ b/evennia/accounts/admin.py @@ -107,7 +107,7 @@ class AccountForm(forms.ModelForm): db_typeclass_path = forms.CharField( label="Typeclass", - initial=settings.BASE_PLAYER_TYPECLASS, + initial=settings.BASE_ACCOUNT_TYPECLASS, widget=forms.TextInput( attrs={'size': '78'}), help_text="Required. Defines what 'type' of entity this is. This " @@ -117,7 +117,7 @@ class AccountForm(forms.ModelForm): db_permissions = forms.CharField( label="Permissions", - initial=settings.PERMISSION_PLAYER_DEFAULT, + initial=settings.PERMISSION_ACCOUNT_DEFAULT, required=False, widget=forms.TextInput( attrs={'size': '78'}), @@ -137,7 +137,7 @@ class AccountForm(forms.ModelForm): "type:lockfunction(args);type2:lockfunction2(args);...") db_cmdset_storage = forms.CharField( label="cmdset", - initial=settings.CMDSET_PLAYER, + initial=settings.CMDSET_ACCOUNT, widget=forms.TextInput(attrs={'size': '78'}), required=False, help_text="python path to account cmdset class (set in " @@ -241,7 +241,7 @@ class AccountDBAdmin(BaseUserAdmin): obj.save() if not change: #calling hooks for new account - obj.set_class_from_typeclass(typeclass_path=settings.BASE_PLAYER_TYPECLASS) + obj.set_class_from_typeclass(typeclass_path=settings.BASE_ACCOUNT_TYPECLASS) obj.basetype_setup() obj.at_account_creation() diff --git a/evennia/commands/__init__.py b/evennia/commands/__init__.py index 5f77fa3a30..93497beffd 100644 --- a/evennia/commands/__init__.py +++ b/evennia/commands/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ This sub-package contains Evennia's command system. It handles -everything related to parsing input from the player, building cmdsets +everything related to parsing input from the account, building cmdsets and executing the code associated with a found command class. commands.default contains all the default "mux-like" commands of diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index 64336111a2..a7299df013 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -13,7 +13,7 @@ command line. The processing of a command works as follows: - object cmdsets: all objects at caller's location are scanned for non-empty cmdsets. This includes cmdsets on exits. - caller: the caller is searched for its own currently active cmdset. - - player: lastly the cmdsets defined on caller.player are added. + - account: lastly the cmdsets defined on caller.account are added. 3. The collected cmdsets are merged together to a combined, current cmdset. 4. If the input string is empty -> check for CMD_NOINPUT command in current cmdset or fallback to error message. Exit. @@ -85,7 +85,7 @@ CMD_LOGINSTART = "__unloggedin_look_command" _SEARCH_AT_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) # Output strings. The first is the IN_GAME_ERRORS return, the second -# is the normal "production message to echo to the player. +# is the normal "production message to echo to the account. _ERROR_UNTRAPPED = ( """ @@ -210,7 +210,7 @@ def _process_input(caller, prompt, result, cmd, generator): part of yielding from a Command's `func`. Args: - caller (Character, Player or Session): the caller. + caller (Character, Account or Session): the caller. prompt (basestring): The sent prompt. result (basestring): The unprocessed answer. cmd (Command): The command itself. @@ -248,20 +248,20 @@ class ErrorReported(Exception): # Helper function @inlineCallbacks -def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string): +def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string): """ Gather all relevant cmdsets and merge them. Args: - caller (Session, Player or Object): The entity executing the command. Which + caller (Session, Account or Object): The entity executing the command. Which type of object this is depends on the current game state; for example when the user is not logged in, this will be a Session, when being OOC - it will be a Player and when puppeting an object this will (often) be + it will be an Account and when puppeting an object this will (often) be a Character Object. In the end it depends on where the cmdset is stored. session (Session or None): The Session associated with caller, if any. - player (Player or None): The calling Player associated with caller, if any. + account (Account or None): The calling Account associated with caller, if any. obj (Object or None): The Object associated with caller, if any. - callertype (str): This identifies caller as either "player", "object" or "session" + callertype (str): This identifies caller as either "account", "object" or "session" to avoid having to do this check internally. raw_string (str): The input string. This is only used for error reporting. @@ -272,18 +272,18 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string): Notes: The cdmsets are merged in order or generality, so that the Object's cmdset is merged last (and will thus take precedence - over same-named and same-prio commands on Player and Session). + over same-named and same-prio commands on Account and Session). """ try: @inlineCallbacks - def _get_channel_cmdset(player_or_obj): + def _get_channel_cmdset(account_or_obj): """ Helper-method; Get channel-cmdsets """ - # Create cmdset for all player's available channels + # Create cmdset for all account's available channels try: - channel_cmdset = yield CHANNELHANDLER.get_cmdset(player_or_obj) + channel_cmdset = yield CHANNELHANDLER.get_cmdset(account_or_obj) returnValue([channel_cmdset]) except Exception: _msg_err(caller, _ERROR_CMDSETS) @@ -313,8 +313,8 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string): _GA(lobj, "at_cmdset_get")(caller=caller) except Exception: logger.log_trace() - # the call-type lock is checked here, it makes sure a player - # is not seeing e.g. the commands on a fellow player (which is why + # the call-type lock is checked here, it makes sure an account + # is not seeing e.g. the commands on a fellow account (which is why # the no_superuser_bypass must be True) local_obj_cmdsets = \ yield list(chain.from_iterable( @@ -355,9 +355,9 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string): # we are calling the command from the session level report_to = session current, cmdsets = yield _get_cmdsets(session) - if player: # this automatically implies logged-in - pcurrent, player_cmdsets = yield _get_cmdsets(player) - cmdsets += player_cmdsets + if account: # this automatically implies logged-in + pcurrent, account_cmdsets = yield _get_cmdsets(account) + cmdsets += account_cmdsets current = current + pcurrent if obj: ocurrent, obj_cmdsets = yield _get_cmdsets(obj) @@ -374,13 +374,13 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string): channel_cmdsets = yield _get_channel_cmdset(obj) cmdsets += channel_cmdsets if not current.no_channels: - channel_cmdsets = yield _get_channel_cmdset(player) + channel_cmdsets = yield _get_channel_cmdset(account) cmdsets += channel_cmdsets - elif callertype == "player": - # we are calling the command from the player level - report_to = player - current, cmdsets = yield _get_cmdsets(player) + elif callertype == "account": + # we are calling the command from the account level + report_to = account + current, cmdsets = yield _get_cmdsets(account) if obj: ocurrent, obj_cmdsets = yield _get_cmdsets(obj) current = current + ocurrent @@ -395,7 +395,7 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string): # also objs may have channels cmdsets += yield _get_channel_cmdset(obj) if not current.no_channels: - cmdsets += yield _get_channel_cmdset(player) + cmdsets += yield _get_channel_cmdset(account) elif callertype == "object": # we are calling the command from the object level @@ -472,22 +472,22 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess This is the main mechanism that handles any string sent to the engine. Args: - called_by (Session, Player or Object): Object from which this + called_by (Session, Account or Object): Object from which this command was called. which this was called from. What this is depends on the game state. raw_string (str): The command string as given on the command line. _testing (bool, optional): Used for debug purposes and decides if we should actually execute the command or not. If True, the command instance will be returned. - callertype (str, optional): One of "session", "player" or + callertype (str, optional): One of "session", "account" or "object". These are treated in decending order, so when the Session is the caller, it will merge its own cmdset into - cmdsets from both Player and eventual puppeted Object (and - cmdsets in its room etc). A Player will only include its own + cmdsets from both Account and eventual puppeted Object (and + cmdsets in its room etc). An Account will only include its own cmdset and the Objects and so on. Merge order is the same order, so that Object cmdsets are merged in last, giving them precendence for same-name and same-prio commands. - session (Session, optional): Relevant if callertype is "player" - the session will help + session (Session, optional): Relevant if callertype is "account" - the session will help retrieve the correct cmdsets from puppeted objects. cmdobj (Command, optional): If given a command instance, this will be executed using `called_by` as the caller, `raw_string` representing its arguments and (optionally) @@ -513,7 +513,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess """ @inlineCallbacks - def _run_command(cmd, cmdname, args, raw_cmdname, cmdset, session, player): + def _run_command(cmd, cmdname, args, raw_cmdname, cmdset, session, account): """ Helper function: This initializes and runs the Command instance once the parser has identified it as either a normal @@ -528,7 +528,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess as cmdname). cmdset (CmdSet): Command sert the command belongs to (if any).. session (Session): Session of caller (if any). - player (Player): Player of caller (if any). + account (Account): Account of caller (if any). Returns: deferred (Deferred): this will fire with the return of the @@ -548,7 +548,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess cmd.args = args cmd.cmdset = cmdset cmd.session = session - cmd.player = player + cmd.account = account cmd.raw_string = unformatted_raw_string #cmd.obj # set via on-object cmdset handler for each command, # since this may be different for every command when @@ -618,13 +618,13 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess raw_string = to_unicode(raw_string, force_string=True) - session, player, obj = session, None, None + session, account, obj = session, None, None if callertype == "session": session = called_by - player = session.player + account = session.account obj = session.puppet - elif callertype == "player": - player = called_by + elif callertype == "account": + account = called_by if session: obj = yield session.puppet elif callertype == "object": @@ -633,10 +633,10 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess raise RuntimeError("cmdhandler: callertype %s is not valid." % callertype) # the caller will be the one to receive messages and excert its permissions. # we assign the caller with preference 'bottom up' - caller = obj or player or session - # The error_to is the default recipient for errors. Tries to make sure a player + caller = obj or account or session + # The error_to is the default recipient for errors. Tries to make sure an account # does not get spammed for errors while preserving character mirroring. - error_to = obj or session or player + error_to = obj or session or account try: # catch bugs in cmdhandler itself try: # catch special-type commands @@ -648,11 +648,11 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess unformatted_raw_string = "%s%s" % (cmdname, args) cmdset = None session = session - player = player + account = account else: # no explicit cmdobject given, figure it out - cmdset = yield get_and_merge_cmdsets(caller, session, player, obj, + cmdset = yield get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string) if not cmdset: # this is bad and shouldn't happen. @@ -722,7 +722,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess raise ExecSystemCommand(cmd, sysarg) # A normal command. - ret = yield _run_command(cmd, cmdname, args, raw_cmdname, cmdset, session, player) + ret = yield _run_command(cmd, cmdname, args, raw_cmdname, cmdset, session, account) returnValue(ret) except ErrorReported as exc: @@ -738,7 +738,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess if syscmd: ret = yield _run_command(syscmd, syscmd.key, sysarg, - unformatted_raw_string, cmdset, session, player) + unformatted_raw_string, cmdset, session, account) returnValue(ret) elif sysarg: # return system arg diff --git a/evennia/commands/cmdparser.py b/evennia/commands/cmdparser.py index 6cb9ee0143..100e19311d 100644 --- a/evennia/commands/cmdparser.py +++ b/evennia/commands/cmdparser.py @@ -22,7 +22,7 @@ def cmdparser(raw_string, cmdset, caller, match_index=None): Args: raw_string (str): The unparsed text entered by the caller. cmdset (CmdSet): The merged, currently valid cmdset - caller (Session, Player or Object): The caller triggering this parsing. + caller (Session, Account or Object): The caller triggering this parsing. match_index (int, optional): Index to pick a given match in a list of same-named command matches. If this is given, it suggests this is not the first time this function was called: normally diff --git a/evennia/commands/cmdset.py b/evennia/commands/cmdset.py index fa86734242..7035d2fbaf 100644 --- a/evennia/commands/cmdset.py +++ b/evennia/commands/cmdset.py @@ -4,7 +4,7 @@ A Command Set (CmdSet) holds a set of commands. The Cmdsets can be merged and combined to create new sets of commands in a non-destructive way. This makes them very powerful for implementing custom game states where different commands (or different variations -of commands) are available to the players depending on circumstance. +of commands) are available to the accounts depending on circumstance. The available merge operations are partly borrowed from mathematical Set theory. @@ -110,9 +110,9 @@ class CmdSet(with_metaclass(_CmdSetMeta, object)): merger (i.e. A above) automatically taking precedence. But if allow_duplicates is true, the result will be a merger with more than one of each - name match. This will usually lead to the player + name match. This will usually lead to the account receiving a multiple-match error higher up the road, - but can be good for things like cmdsets on non-player + but can be good for things like cmdsets on non-account objects in a room, to allow the system to warn that more than one 'ball' in the room has the same 'kick' command defined on it, so it may offer a chance to @@ -134,7 +134,7 @@ class CmdSet(with_metaclass(_CmdSetMeta, object)): commands no_channels - ignore the name of channels when matching against commands (WARNING- this is dangerous since the - player can then not even ask staff for help if + account can then not even ask staff for help if something goes wrong) @@ -167,9 +167,9 @@ class CmdSet(with_metaclass(_CmdSetMeta, object)): Creates a new CmdSet instance. Args: - cmdsetobj (Session, Player, Object, optional): This is the database object + cmdsetobj (Session, Account, Object, optional): This is the database object to which this particular instance of cmdset is related. It - is often a character but may also be a regular object, Player + is often a character but may also be a regular object, Account or Session. key (str, optional): The idenfier for this cmdset. This helps if wanting to selectively remov cmdsets. diff --git a/evennia/commands/cmdsethandler.py b/evennia/commands/cmdsethandler.py index 3f01dbcac2..2327493524 100644 --- a/evennia/commands/cmdsethandler.py +++ b/evennia/commands/cmdsethandler.py @@ -9,8 +9,8 @@ intelligent container that, when added to other CmdSet make sure that same-name commands are treated correctly (usually so there are no doublets). This temporary but up-to-date merger of CmdSet is jointly called the Current Cmset. It is this Current CmdSet that the -commandhandler looks through whenever a player enters a command (it -also adds CmdSets from objects in the room in real-time). All player +commandhandler looks through whenever an account enters a command (it +also adds CmdSets from objects in the room in real-time). All account objects have a 'default cmdset' containing all the normal in-game mud commands (look etc). @@ -19,12 +19,12 @@ So what is all this cmdset complexity good for? In its simplest form, a CmdSet has no commands, only a key name. In this case the cmdset's use is up to each individual game - it can be used by an AI module for example (mobs in cmdset 'roam' move from room -to room, in cmdset 'attack' they enter combat with players). +to room, in cmdset 'attack' they enter combat with accounts). Defining commands in cmdsets offer some further powerful game-design consequences however. Here are some examples: -As mentioned above, all players always have at least the Default +As mentioned above, all accounts always have at least the Default CmdSet. This contains the set of all normal-use commands in-game, stuff like look and @desc etc. Now assume our players end up in a dark room. You don't want the player to be able to do much in that dark @@ -37,7 +37,7 @@ and have this completely replace the default cmdset. Another example: Say you want your players to be able to go fishing. You could implement this as a 'fish' command that fails -whenever the player has no fishing rod. Easy enough. But what if you +whenever the account has no fishing rod. Easy enough. But what if you want to make fishing more complex - maybe you want four-five different commands for throwing your line, reeling in, etc? Most players won't (we assume) have fishing gear, and having all those detailed commands @@ -48,7 +48,7 @@ for a minor thing like fishing? So instead you put all those detailed fishing commands into their own CommandSet called 'Fishing'. Whenever the player gives the command 'fish' (presumably the code checks there is also water nearby), only -THEN this CommandSet is added to the Cmdhandler of the player. The +THEN this CommandSet is added to the Cmdhandler of the account. The 'throw' command (which normally throws rocks) is replaced by the custom 'fishing variant' of throw. What has happened is that the Fishing CommandSet was merged on top of the Default ones, and due to @@ -128,7 +128,7 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False): Args: path (str): The path to the command set to load. cmdsetobj (CmdSet): The database object/typeclass on which this cmdset is to be - assigned (this can be also channels and exits, as well as players + assigned (this can be also channels and exits, as well as accounts but there will always be such an object) emit_to_obj (Object, optional): If given, error is emitted to this object (in addition to logging) diff --git a/evennia/commands/command.py b/evennia/commands/command.py index c5d1515d83..2b62e6221b 100644 --- a/evennia/commands/command.py +++ b/evennia/commands/command.py @@ -152,14 +152,14 @@ class Command(with_metaclass(CommandMeta, object)): is_exit = False # define the command not only by key but by the regex form of its arguments arg_regex = settings.COMMAND_DEFAULT_ARG_REGEX - # whether self.msg sends to all sessions of a related player/object (default + # whether self.msg sends to all sessions of a related account/object (default # is to only send to the session sending the command). msg_all_sessions = settings.COMMAND_DEFAULT_MSG_ALL_SESSIONS # auto-set (by Evennia on command instantiation) are: # obj - which object this command is defined on # session - which session is responsible for triggering this command. Only set - # if triggered by a player. + # if triggered by an account. def __init__(self, **kwargs): """ @@ -307,7 +307,7 @@ class Command(with_metaclass(CommandMeta, object)): session=None, **kwargs): """ This is a shortcut instead of calling msg() directly on an - object - it will detect if caller is an Object or a Player and + object - it will detect if caller is an Object or an Account and also appends self.session automatically if self.msg_all_sessions is False. Args: @@ -340,7 +340,7 @@ class Command(with_metaclass(CommandMeta, object)): Args: raw_string (str): Execute this string as a command input. session (Session, optional): If not given, the current command's Session will be used. - obj (Object or Player, optional): Object or Player on which to call the execute_cmd. + obj (Object or Account, optional): Object or Account on which to call the execute_cmd. If not given, self.caller will be used. Kwargs: @@ -443,7 +443,7 @@ class Command(with_metaclass(CommandMeta, object)): commands the caller can use. Args: - caller (Object or Player): the caller asking for help on the command. + caller (Object or Account): the caller asking for help on the command. cmdset (CmdSet): the command set (if you need additional commands). Returns: diff --git a/evennia/commands/default/admin.py b/evennia/commands/default/admin.py index 6f8182a7de..473d57aa23 100644 --- a/evennia/commands/default/admin.py +++ b/evennia/commands/default/admin.py @@ -16,22 +16,22 @@ COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY] # limit members for API inclusion -__all__ = ("CmdBoot", "CmdBan", "CmdUnban", "CmdDelPlayer", +__all__ = ("CmdBoot", "CmdBan", "CmdUnban", "CmdDelAccount", "CmdEmit", "CmdNewPassword", "CmdPerm", "CmdWall") class CmdBoot(COMMAND_DEFAULT_CLASS): """ - kick a player from the server. + kick an account from the server. Usage - @boot[/switches] [: reason] + @boot[/switches] [: reason] Switches: - quiet - Silently boot without informing player + quiet - Silently boot without informing account sid - boot by session id instead of name or dbref - Boot a player object from the server. If a reason is + Boot an account object from the server. If a reason is supplied it will be echoed to the user unless /quiet is set. """ @@ -45,7 +45,7 @@ class CmdBoot(COMMAND_DEFAULT_CLASS): args = self.args if not args: - caller.msg("Usage: @boot[/switches] [:reason]") + caller.msg("Usage: @boot[/switches] [:reason]") return if ':' in args: @@ -64,10 +64,10 @@ class CmdBoot(COMMAND_DEFAULT_CLASS): boot_list.append(sess) break else: - # Boot by player object - pobj = search.player_search(args) + # Boot by account object + pobj = search.account_search(args) if not pobj: - caller.msg("Player %s was not found." % args) + caller.msg("Account %s was not found." % args) return pobj = pobj[0] if not pobj.access(caller, 'boot'): @@ -75,12 +75,12 @@ class CmdBoot(COMMAND_DEFAULT_CLASS): caller.msg(string) return # we have a bootable object with a connected user - matches = SESSIONS.sessions_from_player(pobj) + matches = SESSIONS.sessions_from_account(pobj) for match in matches: boot_list.append(match) if not boot_list: - caller.msg("No matching sessions found. The Player does not seem to be online.") + caller.msg("No matching sessions found. The Account does not seem to be online.") return # Carry out the booting of the sessions in the boot list. @@ -93,7 +93,7 @@ class CmdBoot(COMMAND_DEFAULT_CLASS): for session in boot_list: session.msg(feedback) - session.player.disconnect_session_from_player(session) + session.account.disconnect_session_from_account(session) # regex matching IP addresses with wildcards, eg. 233.122.4.* @@ -118,7 +118,7 @@ def list_bans(banlist): class CmdBan(COMMAND_DEFAULT_CLASS): """ - ban a player from the server + ban an account from the server Usage: @ban [ [: reason]] @@ -128,8 +128,8 @@ class CmdBan(COMMAND_DEFAULT_CLASS): This command bans a user from accessing the game. Supply an optional reason to be able to later remember why the ban was put in place. - It is often preferable to ban a player from the server than to - delete a player with @delplayer. If banned by name, that player + It is often preferable to ban an account from the server than to + delete an account with @delaccount. If banned by name, that account account can no longer be logged into. IP (Internet Protocol) address banning allows blocking all access @@ -206,12 +206,12 @@ class CmdBan(COMMAND_DEFAULT_CLASS): class CmdUnban(COMMAND_DEFAULT_CLASS): """ - remove a ban from a player + remove a ban from an account Usage: @unban - This will clear a player name/ip ban previously set with the @ban + This will clear an account name/ip ban previously set with the @ban command. Use this command without an argument to view a numbered list of bans. Use the numbers in this list to select which one to unban. @@ -249,23 +249,23 @@ class CmdUnban(COMMAND_DEFAULT_CLASS): (num, " ".join([s for s in ban[:2]]))) -class CmdDelPlayer(COMMAND_DEFAULT_CLASS): +class CmdDelAccount(COMMAND_DEFAULT_CLASS): """ - delete a player from the server + delete an account from the server Usage: - @delplayer[/switch] [: reason] + @delaccount[/switch] [: reason] Switch: - delobj - also delete the player's currently + delobj - also delete the account's currently assigned in-game object. Completely deletes a user from the server database, making their nick and e-mail again available. """ - key = "@delplayer" - locks = "cmd:perm(delplayer) or perm(Developer)" + key = "@delaccount" + locks = "cmd:perm(delaccount) or perm(Developer)" help_category = "Admin" def func(self): @@ -274,49 +274,49 @@ class CmdDelPlayer(COMMAND_DEFAULT_CLASS): caller = self.caller args = self.args - if hasattr(caller, 'player'): - caller = caller.player + if hasattr(caller, 'account'): + caller = caller.account if not args: - self.msg("Usage: @delplayer [: reason]") + self.msg("Usage: @delaccount [: reason]") return reason = "" if ':' in args: args, reason = [arg.strip() for arg in args.split(':', 1)] - # We use player_search since we want to be sure to find also players + # We use account_search since we want to be sure to find also accounts # that lack characters. - players = search.player_search(args) + accounts = search.account_search(args) - if not players: - self.msg('Could not find a player by that name.') + if not accounts: + self.msg('Could not find an account by that name.') return - if len(players) > 1: + if len(accounts) > 1: string = "There were multiple matches:\n" - string += "\n".join(" %s %s" % (player.id, player.key) for player in players) + string += "\n".join(" %s %s" % (account.id, account.key) for account in accounts) self.msg(string) return # one single match - player = players.pop() + account = accounts.pop() - if not player.access(caller, 'delete'): - string = "You don't have the permissions to delete that player." + if not account.access(caller, 'delete'): + string = "You don't have the permissions to delete that account." self.msg(string) return - uname = player.username - # boot the player then delete - self.msg("Informing and disconnecting player ...") + uname = account.username + # boot the account then delete + self.msg("Informing and disconnecting account ...") string = "\nYour account '%s' is being *permanently* deleted.\n" % uname if reason: string += " Reason given:\n '%s'" % reason - player.msg(string) - player.delete() - self.msg("Player %s was successfully deleted." % uname) + account.msg(string) + account.delete() + self.msg("Account %s was successfully deleted." % uname) class CmdEmit(COMMAND_DEFAULT_CLASS): @@ -330,14 +330,14 @@ class CmdEmit(COMMAND_DEFAULT_CLASS): Switches: room : limit emits to rooms only (default) - players : limit emits to players only + accounts : limit emits to accounts only contents : send to the contents of matched objects too Emits a message to the selected objects or to your immediate surroundings. If the object is a room, send to its contents. @remit and @pemit are just limited forms of @emit, for sending to rooms and - to players respectively. + to accounts respectively. """ key = "@emit" aliases = ["@pemit", "@remit"] @@ -359,7 +359,7 @@ class CmdEmit(COMMAND_DEFAULT_CLASS): return rooms_only = 'rooms' in self.switches - players_only = 'players' in self.switches + accounts_only = 'accounts' in self.switches send_to_contents = 'contents' in self.switches # we check which command was used to force the switches @@ -367,7 +367,7 @@ class CmdEmit(COMMAND_DEFAULT_CLASS): rooms_only = True send_to_contents = True elif self.cmdstring == '@pemit': - players_only = True + accounts_only = True if not self.rhs: message = self.args @@ -384,8 +384,8 @@ class CmdEmit(COMMAND_DEFAULT_CLASS): if rooms_only and obj.location is not None: caller.msg("%s is not a room. Ignored." % objname) continue - if players_only and not obj.has_player: - caller.msg("%s has no active player. Ignored." % objname) + if accounts_only and not obj.has_account: + caller.msg("%s has no active account. Ignored." % objname) continue if obj.access(caller, 'tell'): obj.msg(message) @@ -400,12 +400,12 @@ class CmdEmit(COMMAND_DEFAULT_CLASS): class CmdNewPassword(COMMAND_DEFAULT_CLASS): """ - change the password of a player + change the password of an account Usage: @userpassword = - Set a player's password. + Set an account's password. """ key = "@userpassword" @@ -421,32 +421,32 @@ class CmdNewPassword(COMMAND_DEFAULT_CLASS): self.msg("Usage: @userpassword = ") return - # the player search also matches 'me' etc. - player = caller.search_player(self.lhs) - if not player: + # the account search also matches 'me' etc. + account = caller.search_account(self.lhs) + if not account: return - player.set_password(self.rhs) - player.save() - self.msg("%s - new password set to '%s'." % (player.name, self.rhs)) - if player.character != caller: - player.msg("%s has changed your password to '%s'." % (caller.name, + account.set_password(self.rhs) + account.save() + self.msg("%s - new password set to '%s'." % (account.name, self.rhs)) + if account.character != caller: + account.msg("%s has changed your password to '%s'." % (caller.name, self.rhs)) class CmdPerm(COMMAND_DEFAULT_CLASS): """ - set the permissions of a player/object + set the permissions of an account/object Usage: @perm[/switch] [= [,,...]] - @perm[/switch] * [= [,,...]] + @perm[/switch] * [= [,,...]] Switches: - del : delete the given permission from or . - player : set permission on a player (same as adding * to name) + del : delete the given permission from or . + account : set permission on an account (same as adding * to name) This command sets/clears individual permission strings on an object - or player. If no permission is given, list all permissions on . + or account. If no permission is given, list all permissions on . """ key = "@perm" aliases = "@setperm" @@ -465,11 +465,11 @@ class CmdPerm(COMMAND_DEFAULT_CLASS): caller.msg(string) return - playermode = 'player' in self.switches or lhs.startswith('*') + accountmode = 'account' in self.switches or lhs.startswith('*') lhs = lhs.lstrip("*") - if playermode: - obj = caller.search_player(lhs) + if accountmode: + obj = caller.search_account(lhs) else: obj = caller.search(lhs, global_search=True) if not obj: @@ -485,19 +485,19 @@ class CmdPerm(COMMAND_DEFAULT_CLASS): string += "" else: string += ", ".join(obj.permissions.all()) - if (hasattr(obj, 'player') and - hasattr(obj.player, 'is_superuser') and - obj.player.is_superuser): + if (hasattr(obj, 'account') and + hasattr(obj.account, 'is_superuser') and + obj.account.is_superuser): string += "\n(... but this object is currently controlled by a SUPERUSER! " string += "All access checks are passed automatically.)" caller.msg(string) return # we supplied an argument on the form obj = perm - locktype = "edit" if playermode else "control" + locktype = "edit" if accountmode else "control" if not obj.access(caller, locktype): caller.msg("You are not allowed to edit this %s's permissions." - % ("player" if playermode else "object")) + % ("account" if accountmode else "object")) return caller_result = [] @@ -528,7 +528,7 @@ class CmdPerm(COMMAND_DEFAULT_CLASS): caller_result.append("\nPermission '%s' is already defined on %s." % (rhs, obj.name)) else: obj.permissions.add(perm) - plystring = "the Player" if playermode else "the Object/Character" + plystring = "the Account" if accountmode else "the Object/Character" caller_result.append("\nPermission '%s' given to %s (%s)." % (rhs, obj.name, plystring)) target_result.append("\n%s gives you (%s, %s) the permission '%s'." % (caller.name, obj.name, plystring, rhs)) @@ -544,7 +544,7 @@ class CmdWall(COMMAND_DEFAULT_CLASS): Usage: @wall - Announces a message to all connected players. + Announces a message to all connected accounts. """ key = "@wall" locks = "cmd:perm(wall) or perm(Admin)" @@ -556,5 +556,5 @@ class CmdWall(COMMAND_DEFAULT_CLASS): self.caller.msg("Usage: @wall ") return message = "%s shouts \"%s\"" % (self.caller.name, self.args) - self.msg("Announcing to all connected players ...") + self.msg("Announcing to all connected accounts ...") SESSIONS.announce_all(message) diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 40d9443051..493da73122 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -607,7 +607,7 @@ class CmdDestroy(COMMAND_DEFAULT_CLASS): switches: override - The @destroy command will usually avoid accidentally - destroying player objects. This switch overrides this safety. + destroying account objects. This switch overrides this safety. examples: @destroy house, roof, door, 44-78 @destroy 5-10, flower, 45 @@ -640,8 +640,8 @@ class CmdDestroy(COMMAND_DEFAULT_CLASS): objname = obj.name if not (obj.access(caller, "control") or obj.access(caller, 'delete')): return "\nYou don't have permission to delete %s." % objname - if obj.player and not 'override' in self.switches: - return "\nObject %s is controlled by an active player. Use /override to delete anyway." % objname + if obj.account and not 'override' in self.switches: + return "\nObject %s is controlled by an active account. Use /override to delete anyway." % objname if obj.dbid == int(settings.DEFAULT_HOME.lstrip("#")): return "\nYou are trying to delete |c%s|n, which is set as DEFAULT_HOME. " \ "Re-point settings.DEFAULT_HOME to another " \ @@ -1108,7 +1108,7 @@ class CmdName(ObjManipCommand): @name obj = name;alias1;alias2 Rename an object to something new. Use *obj to - rename a player. + rename an account. """ @@ -1129,22 +1129,22 @@ class CmdName(ObjManipCommand): if self.lhs_objs: objname = self.lhs_objs[0]['name'] if objname.startswith("*"): - # player mode - obj = caller.player.search(objname.lstrip("*")) + # account mode + obj = caller.account.search(objname.lstrip("*")) if obj: if self.rhs_objs[0]['aliases']: - caller.msg("Players can't have aliases.") + caller.msg("Accounts can't have aliases.") return newname = self.rhs if not newname: caller.msg("No name defined!") return if not (obj.access(caller, "control") or obj.access(caller, "edit")): - caller.msg("You don't have right to edit this player %s." % obj) + caller.msg("You don't have right to edit this account %s." % obj) return obj.username = newname obj.save() - caller.msg("Player's name changed to '%s'." % newname) + caller.msg("Account's name changed to '%s'." % newname) return # object search, also with * obj = caller.search(objname) @@ -1326,7 +1326,7 @@ def _convert_from_string(cmd, strobj): be converted to a string and a warning will be given. We need to convert like this since all data being sent over the - telnet connection by the Player is text - but we will want to + telnet connection by the Account is text - but we will want to store it as the "real" python type so we can do convenient comparisons later (e.g. obj.db.value = 2, if value is stored as a string this will always fail). @@ -1381,13 +1381,13 @@ def _convert_from_string(cmd, strobj): class CmdSetAttribute(ObjManipCommand): """ - set attribute on an object or player + set attribute on an object or account Usage: @set / = @set / = @set / - @set */attr = + @set */attr = Switch: edit: Open the line editor (string values only) @@ -1419,7 +1419,7 @@ class CmdSetAttribute(ObjManipCommand): """ This may be overridden by subclasses in case restrictions need to be placed on whether certain objects can have attributes set by certain - players. + accounts. This function is expected to display its own error message. @@ -1505,7 +1505,7 @@ class CmdSetAttribute(ObjManipCommand): attrs = self.lhs_objattr[0]['attrs'] if objname.startswith('*'): - obj = caller.search_player(objname.lstrip('*')) + obj = caller.search_account(objname.lstrip('*')) else: obj = caller.search(objname) if not obj: @@ -1831,17 +1831,17 @@ class CmdExamine(ObjManipCommand): Usage: examine [[/attrname]] - examine [*[/attrname]] + examine [*[/attrname]] Switch: - player - examine a Player (same as adding *) + account - examine an Account (same as adding *) object - examine an Object (useful when OOC) The examine command shows detailed game info about an object and optionally a specific attribute on it. If object is not specified, the current location is examined. - Append a * before the search string to examine a player. + Append a * before the search string to examine an account. """ key = "@examine" @@ -1850,7 +1850,7 @@ class CmdExamine(ObjManipCommand): help_category = "Building" arg_regex = r"(/\w+?(\s|$))|\s|$" - player_mode = False + account_mode = False def list_attribute(self, crop, attr, value): """ @@ -1910,15 +1910,15 @@ class CmdExamine(ObjManipCommand): for sess in obj.sessions.all())) if hasattr(obj, "email") and obj.email: string += "\n|wEmail|n: |c%s|n" % obj.email - if hasattr(obj, "has_player") and obj.has_player: - string += "\n|wPlayer|n: |c%s|n" % obj.player.name - perms = obj.player.permissions.all() - if obj.player.is_superuser: + if hasattr(obj, "has_account") and obj.has_account: + string += "\n|wAccount|n: |c%s|n" % obj.account.name + perms = obj.account.permissions.all() + if obj.account.is_superuser: perms = [""] elif not perms: perms = [""] - string += "\n|wPlayer Perms|n: %s" % (", ".join(perms)) - if obj.player.attributes.has("_quell"): + string += "\n|wAccount Perms|n: %s" % (", ".join(perms)) + if obj.account.attributes.has("_quell"): string += " |r(quelled)|n" string += "\n|wTypeclass|n: %s (%s)" % (obj.typename, obj.typeclass_path) @@ -1961,16 +1961,16 @@ class CmdExamine(ObjManipCommand): # this gets all components of the currently merged set all_cmdsets = [(cmdset.key, cmdset) for cmdset in avail_cmdset.merged_from] - # we always at least try to add player- and session sets since these are ignored + # we always at least try to add account- and session sets since these are ignored # if we merge on the object level. - if hasattr(obj, "player") and obj.player: - all_cmdsets.extend([(cmdset.key, cmdset) for cmdset in obj.player.cmdset.all()]) + if hasattr(obj, "account") and obj.account: + all_cmdsets.extend([(cmdset.key, cmdset) for cmdset in obj.account.cmdset.all()]) if obj.sessions.count(): # if there are more sessions than one on objects it's because of multisession mode 3. # we only show the first session's cmdset here (it is -in principle- possible that # different sessions have different cmdsets but for admins who want such madness # it is better that they overload with their own CmdExamine to handle it). - all_cmdsets.extend([(cmdset.key, cmdset) for cmdset in obj.player.sessions.all()[0].cmdset.all()]) + all_cmdsets.extend([(cmdset.key, cmdset) for cmdset in obj.account.sessions.all()[0].cmdset.all()]) else: try: # we have to protect this since many objects don't have sessions. @@ -2011,7 +2011,7 @@ class CmdExamine(ObjManipCommand): for content in obj.contents: if content.destination: exits.append(content) - elif content.player: + elif content.account: pobjs.append(content) else: things.append(content) @@ -2051,7 +2051,7 @@ class CmdExamine(ObjManipCommand): self.msg(caller.at_look(obj)) return # using callback for printing result whenever function returns. - get_and_merge_cmdsets(obj, self.session, self.player, obj, "object", self.raw_string).addCallback(get_cmdset_callback) + get_and_merge_cmdsets(obj, self.session, self.account, obj, "object", self.raw_string).addCallback(get_cmdset_callback) else: self.msg("You need to supply a target to examine.") return @@ -2063,13 +2063,13 @@ class CmdExamine(ObjManipCommand): obj_name = objdef['name'] obj_attrs = objdef['attrs'] - self.player_mode = utils.inherits_from(caller, "evennia.players.players.DefaultPlayer") or \ - "player" in self.switches or obj_name.startswith('*') - if self.player_mode: + self.account_mode = utils.inherits_from(caller, "evennia.accounts.accounts.DefaultAccount") or \ + "account" in self.switches or obj_name.startswith('*') + if self.account_mode: try: - obj = caller.search_player(obj_name.lstrip('*')) + obj = caller.search_account(obj_name.lstrip('*')) except AttributeError: - # this means we are calling examine from a player object + # this means we are calling examine from an account object obj = caller.search(obj_name.lstrip('*'), search_object = 'object' in self.switches) else: obj = caller.search(obj_name) @@ -2089,12 +2089,12 @@ class CmdExamine(ObjManipCommand): else: if obj.sessions.count(): mergemode = "session" - elif self.player_mode: - mergemode = "player" + elif self.account_mode: + mergemode = "account" else: mergemode = "object" # using callback to print results whenever function returns. - get_and_merge_cmdsets(obj, self.session, self.player, obj, mergemode, self.raw_string).addCallback(get_cmdset_callback) + get_and_merge_cmdsets(obj, self.session, self.account, obj, mergemode, self.raw_string).addCallback(get_cmdset_callback) class CmdFind(COMMAND_DEFAULT_CLASS): @@ -2102,7 +2102,7 @@ class CmdFind(COMMAND_DEFAULT_CLASS): search the database for objects Usage: - @find[/switches] [= dbrefmin[-dbrefmax]] + @find[/switches] [= dbrefmin[-dbrefmax]] Switches: room - only look for rooms (location=None) @@ -2111,7 +2111,7 @@ class CmdFind(COMMAND_DEFAULT_CLASS): exact- only exact matches are returned. Searches the database for an object of a particular name or exact #dbref. - Use *playername to search for a player. The switches allows for + Use *accountname to search for an account. The switches allows for limiting object matches to certain game entities. Dbrefmin and dbrefmax limits matches to within the given dbrefs range, or above/below if only one is given. @@ -2148,22 +2148,22 @@ class CmdFind(COMMAND_DEFAULT_CLASS): high = max(low, high) is_dbref = utils.dbref(searchstring) - is_player = searchstring.startswith("*") + is_account = searchstring.startswith("*") restrictions = "" if self.switches: restrictions = ", %s" % (",".join(self.switches)) - if is_dbref or is_player: + if is_dbref or is_account: if is_dbref: # a dbref search result = caller.search(searchstring, global_search=True, quiet=True) string = "|wExact dbref match|n(#%i-#%i%s):" % (low, high, restrictions) else: - # a player search + # an account search searchstring = searchstring.lstrip("*") - result = caller.search_player(searchstring, quiet=True) + result = caller.search_account(searchstring, quiet=True) string = "|wMatch|n(#%i-#%i%s):" % (low, high, restrictions) if "room" in switches: @@ -2181,7 +2181,7 @@ class CmdFind(COMMAND_DEFAULT_CLASS): result=result[0] string += "\n|g %s - %s|n" % (result.get_display_name(caller), result.path) else: - # Not a player/dbref search but a wider search; build a queryset. + # Not an account/dbref search but a wider search; build a queryset. # Searchs for key and aliases if "exact" in switches: keyquery = Q(db_key__iexact=searchstring, id__gte=low, id__lte=high) @@ -2274,10 +2274,10 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS): if not obj_to_teleport: caller.msg("Did not find object to teleport.") return - if obj_to_teleport.has_player: + if obj_to_teleport.has_account: caller.msg("Cannot teleport a puppeted object " "(%s, puppeted by %s) to a None-location." % ( - obj_to_teleport.key, obj_to_teleport.player)) + obj_to_teleport.key, obj_to_teleport.account)) return caller.msg("Teleported %s -> None-location." % obj_to_teleport) if obj_to_teleport.location and not tel_quietly: diff --git a/evennia/commands/default/cmdset_character.py b/evennia/commands/default/cmdset_character.py index 7283ce82f6..f1d023fcf8 100644 --- a/evennia/commands/default/cmdset_character.py +++ b/evennia/commands/default/cmdset_character.py @@ -1,8 +1,8 @@ """ This module ties together all the commands default Character objects have available (i.e. IC commands). Note that some commands, such as -communication-commands are instead put on the player level, in the -Player cmdset. Player commands remain available also to Characters. +communication-commands are instead put on the account level, in the +Account cmdset. Account commands remain available also to Characters. """ from evennia.commands.cmdset import CmdSet from evennia.commands.default import general, help, admin, system @@ -41,7 +41,7 @@ class CharacterCmdSet(CmdSet): self.add(system.CmdPy()) self.add(system.CmdScripts()) self.add(system.CmdObjects()) - self.add(system.CmdPlayers()) + self.add(system.CmdAccounts()) self.add(system.CmdService()) self.add(system.CmdAbout()) self.add(system.CmdTime()) diff --git a/evennia/commands/default/cmdset_player.py b/evennia/commands/default/cmdset_player.py deleted file mode 100644 index 5ed27ff8a9..0000000000 --- a/evennia/commands/default/cmdset_player.py +++ /dev/null @@ -1,73 +0,0 @@ -""" - -This is the cmdset for Player (OOC) commands. These are -stored on the Player object and should thus be able to handle getting -a Player object as caller rather than a Character. - -Note - in order for session-rerouting (in MULTISESSION_MODE=2) to -function, all commands in this cmdset should use the self.msg() -command method rather than caller.msg(). -""" - -from evennia.commands.cmdset import CmdSet -from evennia.commands.default import help, comms, admin, system -from evennia.commands.default import building, player - - -class PlayerCmdSet(CmdSet): - """ - Implements the player command set. - """ - - key = "DefaultPlayer" - priority = -10 - - def at_cmdset_creation(self): - "Populates the cmdset" - - # Player-specific commands - self.add(player.CmdOOCLook()) - self.add(player.CmdIC()) - self.add(player.CmdOOC()) - self.add(player.CmdCharCreate()) - self.add(player.CmdCharDelete()) - #self.add(player.CmdSessions()) - self.add(player.CmdWho()) - self.add(player.CmdOption()) - self.add(player.CmdQuit()) - self.add(player.CmdPassword()) - self.add(player.CmdColorTest()) - self.add(player.CmdQuell()) - - # testing - self.add(building.CmdExamine()) - - # Help command - self.add(help.CmdHelp()) - - # system commands - self.add(system.CmdReload()) - self.add(system.CmdReset()) - self.add(system.CmdShutdown()) - self.add(system.CmdPy()) - - # Admin commands - self.add(admin.CmdDelPlayer()) - self.add(admin.CmdNewPassword()) - - # Comm commands - self.add(comms.CmdAddCom()) - self.add(comms.CmdDelCom()) - self.add(comms.CmdAllCom()) - self.add(comms.CmdChannels()) - self.add(comms.CmdCdestroy()) - self.add(comms.CmdChannelCreate()) - self.add(comms.CmdClock()) - self.add(comms.CmdCBoot()) - self.add(comms.CmdCemit()) - self.add(comms.CmdCWho()) - self.add(comms.CmdCdesc()) - self.add(comms.CmdPage()) - self.add(comms.CmdIRC2Chan()) - self.add(comms.CmdIRCStatus()) - self.add(comms.CmdRSS2Chan()) diff --git a/evennia/commands/default/cmdset_session.py b/evennia/commands/default/cmdset_session.py index 8095d3119c..af6a1c42bd 100644 --- a/evennia/commands/default/cmdset_session.py +++ b/evennia/commands/default/cmdset_session.py @@ -2,7 +2,7 @@ This module stores session-level commands. """ from evennia.commands.cmdset import CmdSet -from evennia.commands.default import player +from evennia.commands.default import account class SessionCmdSet(CmdSet): """ @@ -13,4 +13,4 @@ class SessionCmdSet(CmdSet): def at_cmdset_creation(self): "Populate the cmdset" - self.add(player.CmdSessions()) + self.add(account.CmdSessions()) diff --git a/evennia/commands/default/comms.py b/evennia/commands/default/comms.py index 1b0f96f52f..ae086cb7b9 100644 --- a/evennia/commands/default/comms.py +++ b/evennia/commands/default/comms.py @@ -2,16 +2,16 @@ Comsystem command module. Comm commands are OOC commands and intended to be made available to -the Player at all times (they go into the PlayerCmdSet). So we -make sure to homogenize self.caller to always be the player object +the Account at all times (they go into the AccountCmdSet). So we +make sure to homogenize self.caller to always be the account object for easy handling. """ from past.builtins import cmp from django.conf import settings from evennia.comms.models import ChannelDB, Msg -from evennia.players.models import PlayerDB -from evennia.players import bots +from evennia.accounts.models import AccountDB +from evennia.accounts import bots from evennia.comms.channelhandler import CHANNELHANDLER from evennia.locks.lockhandler import LockException from evennia.utils import create, utils, evtable @@ -69,14 +69,14 @@ class CmdAddCom(COMMAND_DEFAULT_CLASS): locks = "cmd:not pperm(channel_banned)" # this is used by the COMMAND_DEFAULT_CLASS parent - player_caller = True + account_caller = True def func(self): """Implement the command""" caller = self.caller args = self.args - player = caller + account = caller if not args: self.msg("Usage: addcom [alias =] channelname.") @@ -96,21 +96,21 @@ class CmdAddCom(COMMAND_DEFAULT_CLASS): return # check permissions - if not channel.access(player, 'listen'): + if not channel.access(account, 'listen'): self.msg("%s: You are not allowed to listen to this channel." % channel.key) return string = "" - if not channel.has_connection(player): + if not channel.has_connection(account): # we want to connect as well. - if not channel.connect(player): - # if this would have returned True, the player is connected + if not channel.connect(account): + # if this would have returned True, the account is connected self.msg("%s: You are not allowed to join this channel." % channel.key) return else: string += "You now listen to the channel %s. " % channel.key else: - if channel.unmute(player): + if channel.unmute(account): string += "You unmute channel %s." % channel.key else: string += "You are already connected to channel %s." % channel.key @@ -145,13 +145,13 @@ class CmdDelCom(COMMAND_DEFAULT_CLASS): locks = "cmd:not perm(channel_banned)" # this is used by the COMMAND_DEFAULT_CLASS parent - player_caller = True + account_caller = True def func(self): """Implementing the command. """ caller = self.caller - player = caller + account = caller if not self.args: self.msg("Usage: delcom ") @@ -161,7 +161,7 @@ class CmdDelCom(COMMAND_DEFAULT_CLASS): channel = find_channel(caller, ostring, silent=True, noaliases=True) if channel: # we have given a channel name - unsubscribe - if not channel.has_connection(player): + if not channel.has_connection(account): self.msg("You are not listening to that channel.") return chkey = channel.key.lower() @@ -171,7 +171,7 @@ class CmdDelCom(COMMAND_DEFAULT_CLASS): for nick in [nick for nick in make_iter(caller.nicks.get(category="channel", return_obj=True)) if nick and nick.pk and nick.value[3].lower() == chkey]: nick.delete() - disconnect = channel.disconnect(player) + disconnect = channel.disconnect(account) if disconnect: wipednicks = " Eventual aliases were removed." if delnicks else "" self.msg("You stop listening to channel '%s'.%s" % (channel.key, wipednicks)) @@ -209,7 +209,7 @@ class CmdAllCom(COMMAND_DEFAULT_CLASS): help_category = "Comms" # this is used by the COMMAND_DEFAULT_CLASS parent - player_caller = True + account_caller = True def func(self): """Runs the function""" @@ -273,7 +273,7 @@ class CmdChannels(COMMAND_DEFAULT_CLASS): locks = "cmd: not pperm(channel_banned)" # this is used by the COMMAND_DEFAULT_CLASS parent - player_caller = True + account_caller = True def func(self): """Implement function""" @@ -345,7 +345,7 @@ class CmdCdestroy(COMMAND_DEFAULT_CLASS): locks = "cmd: not pperm(channel_banned)" # this is used by the COMMAND_DEFAULT_CLASS parent - player_caller = True + account_caller = True def func(self): """Destroy objects cleanly.""" @@ -372,15 +372,15 @@ class CmdCdestroy(COMMAND_DEFAULT_CLASS): class CmdCBoot(COMMAND_DEFAULT_CLASS): """ - kick a player from a channel you control + kick an account from a channel you control Usage: - @cboot[/quiet] = [:reason] + @cboot[/quiet] = [:reason] Switches: quiet - don't notify the channel - Kicks a player or object from a channel you control. + Kicks an account or object from a channel you control. """ @@ -389,13 +389,13 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS): help_category = "Comms" # this is used by the COMMAND_DEFAULT_CLASS parent - player_caller = True + account_caller = True def func(self): """implement the function""" if not self.args or not self.rhs: - string = "Usage: @cboot[/quiet] = [:reason]" + string = "Usage: @cboot[/quiet] = [:reason]" self.msg(string) return @@ -404,12 +404,12 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS): return reason = "" if ":" in self.rhs: - playername, reason = self.rhs.rsplit(":", 1) - searchstring = playername.lstrip('*') + accountname, reason = self.rhs.rsplit(":", 1) + searchstring = accountname.lstrip('*') else: searchstring = self.rhs.lstrip('*') - player = self.caller.search(searchstring, player=True) - if not player: + account = self.caller.search(searchstring, account=True) + if not account: return if reason: reason = " (reason: %s)" % reason @@ -417,20 +417,20 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS): string = "You don't control this channel." self.msg(string) return - if player not in channel.db_subscriptions.all(): - string = "Player %s is not connected to channel %s." % (player.key, channel.key) + if account not in channel.db_subscriptions.all(): + string = "Account %s is not connected to channel %s." % (account.key, channel.key) self.msg(string) return if "quiet" not in self.switches: - string = "%s boots %s from channel.%s" % (self.caller, player.key, reason) + string = "%s boots %s from channel.%s" % (self.caller, account.key, reason) channel.msg(string) - # find all player's nicks linked to this channel and delete them + # find all account's nicks linked to this channel and delete them for nick in [nick for nick in - player.character.nicks.get(category="channel") or [] + account.character.nicks.get(category="channel") or [] if nick.value[3].lower() == channel.key]: nick.delete() - # disconnect player - channel.disconnect(player) + # disconnect account + channel.disconnect(account) CHANNELHANDLER.update() @@ -453,11 +453,11 @@ class CmdCemit(COMMAND_DEFAULT_CLASS): key = "@cemit" aliases = ["@cmsg"] - locks = "cmd: not pperm(channel_banned) and pperm(Player)" + locks = "cmd: not pperm(channel_banned) and pperm(Account)" help_category = "Comms" # this is used by the COMMAND_DEFAULT_CLASS parent - player_caller = True + account_caller = True def func(self): """Implement function""" @@ -496,7 +496,7 @@ class CmdCWho(COMMAND_DEFAULT_CLASS): help_category = "Comms" # this is used by the COMMAND_DEFAULT_CLASS parent - player_caller = True + account_caller = True def func(self): """implement function""" @@ -530,11 +530,11 @@ class CmdChannelCreate(COMMAND_DEFAULT_CLASS): key = "@ccreate" aliases = "channelcreate" - locks = "cmd:not pperm(channel_banned) and pperm(Player)" + locks = "cmd:not pperm(channel_banned) and pperm(Account)" help_category = "Comms" # this is used by the COMMAND_DEFAULT_CLASS parent - player_caller = True + account_caller = True def func(self): """Implement the command""" @@ -587,7 +587,7 @@ class CmdClock(COMMAND_DEFAULT_CLASS): help_category = "Comms" # this is used by the COMMAND_DEFAULT_CLASS parent - player_caller = True + account_caller = True def func(self): """run the function""" @@ -639,7 +639,7 @@ class CmdCdesc(COMMAND_DEFAULT_CLASS): help_category = "Comms" # this is used by the COMMAND_DEFAULT_CLASS parent - player_caller = True + account_caller = True def func(self): """Implement command""" @@ -666,10 +666,10 @@ class CmdCdesc(COMMAND_DEFAULT_CLASS): class CmdPage(COMMAND_DEFAULT_CLASS): """ - send a private message to another player + send a private message to another account Usage: - page[/switches] [,,... = ] + page[/switches] [,,... = ] tell '' page @@ -687,12 +687,12 @@ class CmdPage(COMMAND_DEFAULT_CLASS): help_category = "Comms" # this is used by the COMMAND_DEFAULT_CLASS parent - player_caller = True + account_caller = True def func(self): """Implement function using the Msg methods""" - # Since player_caller is set above, this will be a Player. + # Since account_caller is set above, this will be an Account. caller = self.caller # get the messages we've sent (not to channels) @@ -718,7 +718,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS): try: number = int(self.args) except ValueError: - self.msg("Usage: tell [ = msg]") + self.msg("Usage: tell [ = msg]") return if len(pages) > number: @@ -767,7 +767,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS): self.msg("Noone found to page.") return - header = "|wPlayer|n |c%s|n |wpages:|n" % caller.key + header = "|wAccount|n |c%s|n |wpages:|n" % caller.key message = self.rhs # if message begins with a :, we assume it is a 'page-pose' @@ -778,7 +778,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS): create.create_message(caller, message, receivers=recobjs) - # tell the players they got a message. + # tell the accounts they got a message. received = [] rstrings = [] for pobj in recobjs: @@ -805,7 +805,7 @@ def _list_bots(): bots (str): A table of bots or an error message. """ - ircbots = [bot for bot in PlayerDB.objects.filter(db_is_bot=True, username__startswith="ircbot-")] + ircbots = [bot for bot in AccountDB.objects.filter(db_is_bot=True, username__startswith="ircbot-")] if ircbots: from evennia.utils.evtable import EvTable table = EvTable("|w#dbref|n", "|wbotname|n", "|wev-channel|n", @@ -836,7 +836,7 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS): Example: @irc2chan myircchan = irc.dalnet.net 6667 #mychannel evennia-bot - @irc2chan public = irc.freenode.net 6667 #evgaming #evbot:players.mybot.MyBot + @irc2chan public = irc.freenode.net 6667 #evgaming #evbot:accounts.mybot.MyBot This creates an IRC bot that connects to a given IRC network and channel. If a custom typeclass path is given, this will be used @@ -868,11 +868,11 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS): if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches: botname = "ircbot-%s" % self.lhs - matches = PlayerDB.objects.filter(db_is_bot=True, username=botname) + matches = AccountDB.objects.filter(db_is_bot=True, username=botname) dbref = utils.dbref(self.lhs) if not matches and dbref: # try dbref match - matches = PlayerDB.objects.filter(db_is_bot=True, id=dbref) + matches = AccountDB.objects.filter(db_is_bot=True, id=dbref) if matches: matches[0].delete() self.msg("IRC connection destroyed.") @@ -906,16 +906,16 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS): irc_ssl = "ssl" in self.switches # create a new bot - bot = PlayerDB.objects.filter(username__iexact=botname) + bot = AccountDB.objects.filter(username__iexact=botname) if bot: # re-use an existing bot bot = bot[0] if not bot.is_bot: - self.msg("Player '%s' already exists and is not a bot." % botname) + self.msg("Account '%s' already exists and is not a bot." % botname) return else: try: - bot = create.create_player(botname, None, None, typeclass=botclass) + bot = create.create_account(botname, None, None, typeclass=botclass) except Exception as err: self.msg("|rError, could not create the bot:|n '%s'." % err) return @@ -963,7 +963,7 @@ class CmdIRCStatus(COMMAND_DEFAULT_CLASS): return matches = None if utils.dbref(botname): - matches = PlayerDB.objects.filter(db_is_bot=True, id=utils.dbref(botname)) + matches = AccountDB.objects.filter(db_is_bot=True, id=utils.dbref(botname)) if not matches: self.msg("No matching IRC-bot found. Use @ircstatus without arguments to list active bots.") return @@ -1038,7 +1038,7 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS): if 'list' in self.switches: # show all connections - rssbots = [bot for bot in PlayerDB.objects.filter(db_is_bot=True, username__startswith="rssbot-")] + rssbots = [bot for bot in AccountDB.objects.filter(db_is_bot=True, username__startswith="rssbot-")] if rssbots: from evennia.utils.evtable import EvTable table = EvTable("|wdbid|n", "|wupdate rate|n", "|wev-channel", @@ -1052,10 +1052,10 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS): if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches: botname = "rssbot-%s" % self.lhs - matches = PlayerDB.objects.filter(db_is_bot=True, db_key=botname) + matches = AccountDB.objects.filter(db_is_bot=True, db_key=botname) if not matches: # try dbref match - matches = PlayerDB.objects.filter(db_is_bot=True, id=self.args.lstrip("#")) + matches = AccountDB.objects.filter(db_is_bot=True, id=self.args.lstrip("#")) if matches: matches[0].delete() self.msg("RSS connection destroyed.") @@ -1072,14 +1072,14 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS): botname = "rssbot-%s" % url # create a new bot - bot = PlayerDB.objects.filter(username__iexact=botname) + bot = AccountDB.objects.filter(username__iexact=botname) if bot: # re-use existing bot bot = bot[0] if not bot.is_bot: - self.msg("Player '%s' already exists and is not a bot." % botname) + self.msg("Account '%s' already exists and is not a bot." % botname) return else: - bot = create.create_player(botname, None, None, typeclass=bots.RSSBot) + bot = create.create_account(botname, None, None, typeclass=bots.RSSBot) bot.start(ev_channel=channel, rss_url=url, rss_rate=10) self.msg("RSS reporter created. Fetching RSS.") diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index 18f28d4969..8bc766500a 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -47,7 +47,7 @@ class CmdLook(COMMAND_DEFAULT_CLASS): Usage: look look - look * + look * Observes your location or objects in your vicinity. """ @@ -86,7 +86,7 @@ class CmdNick(COMMAND_DEFAULT_CLASS): Switches: inputline - replace on the inputline (default) object - replace on object-lookup - player - replace on player-lookup + account - replace on account-lookup delete - remove nick by name or by index given by /list clearall - clear all nicks list - show all defined aliases (also "nicks" works) @@ -121,7 +121,7 @@ class CmdNick(COMMAND_DEFAULT_CLASS): caller = self.caller switches = self.switches - nicktypes = [switch for switch in switches if switch in ("object", "player", "inputline")] or ["inputline"] + nicktypes = [switch for switch in switches if switch in ("object", "account", "inputline")] or ["inputline"] nicklist = utils.make_iter(caller.nicks.get(return_obj=True) or []) @@ -441,7 +441,7 @@ class CmdWhisper(COMMAND_DEFAULT_CLASS): caller = self.caller if not self.lhs or not self.rhs: - caller.msg("Usage: whisper = ") + caller.msg("Usage: whisper = ") return receiver = caller.search(self.lhs) @@ -529,15 +529,15 @@ class CmdAccess(COMMAND_DEFAULT_CLASS): hierarchy_full = settings.PERMISSION_HIERARCHY string = "\n|wPermission Hierarchy|n (climbing):\n %s" % ", ".join(hierarchy_full) - if self.caller.player.is_superuser: + if self.caller.account.is_superuser: cperms = "" pperms = "" else: cperms = ", ".join(caller.permissions.all()) - pperms = ", ".join(caller.player.permissions.all()) + pperms = ", ".join(caller.account.permissions.all()) string += "\n|wYour access|n:" string += "\nCharacter |c%s|n: %s" % (caller.key, cperms) - if hasattr(caller, 'player'): - string += "\nPlayer |c%s|n: %s" % (caller.player.key, pperms) + if hasattr(caller, 'account'): + string += "\nAccount |c%s|n: %s" % (caller.account.key, pperms) caller.msg(string) diff --git a/evennia/commands/default/help.py b/evennia/commands/default/help.py index 6cd3b5b6fd..e62024ee03 100644 --- a/evennia/commands/default/help.py +++ b/evennia/commands/default/help.py @@ -60,7 +60,7 @@ class CmdHelp(Command): if self.session.protocol_key in ("websocket", "ajax/comet"): try: - options = self.player.db._saved_webclient_options + options = self.account.db._saved_webclient_options if options and options["helppopup"]: usemore = False except KeyError: @@ -128,12 +128,12 @@ class CmdHelp(Command): Helper method. If this return True, the given cmd auto-help will be viewable in the help listing. Override this to easily select what is shown to - the player. Note that only commands available + the account. Note that only commands available in the caller's merged cmdset are available. Args: cmd (Command): Command class from the merged cmdset - caller (Character, Player or Session): The current caller + caller (Character, Account or Session): The current caller executing the help command. """ diff --git a/evennia/commands/default/muxcommand.py b/evennia/commands/default/muxcommand.py index 1fb431292d..425da509ab 100644 --- a/evennia/commands/default/muxcommand.py +++ b/evennia/commands/default/muxcommand.py @@ -1,13 +1,13 @@ """ The command template for the default MUX-style command set. There -is also an Player/OOC version that makes sure caller is a Player object. +is also an Account/OOC version that makes sure caller is an Account object. """ from evennia.utils import utils from evennia.commands.command import Command # limit symbol import for API -__all__ = ("MuxCommand", "MuxPlayerCommand") +__all__ = ("MuxCommand", "MuxAccountCommand") class MuxCommand(Command): @@ -128,17 +128,17 @@ class MuxCommand(Command): self.rhs = rhs self.rhslist = rhslist - # if the class has the player_caller property set on itself, we make - # sure that self.caller is always the player if possible. We also create + # if the class has the account_caller property set on itself, we make + # sure that self.caller is always the account if possible. We also create # a special property "character" for the puppeted object, if any. This - # is convenient for commands defined on the Player only. - if hasattr(self, "player_caller") and self.player_caller: + # is convenient for commands defined on the Account only. + if hasattr(self, "account_caller") and self.account_caller: if utils.inherits_from(self.caller, "evennia.objects.objects.DefaultObject"): # caller is an Object/Character self.character = self.caller - self.caller = self.caller.player - elif utils.inherits_from(self.caller, "evennia.players.players.DefaultPlayer"): - # caller was already a Player + self.caller = self.caller.account + elif utils.inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount"): + # caller was already an Account self.character = self.caller.get_puppet(self.session) else: self.character = None @@ -177,32 +177,32 @@ class MuxCommand(Command): self.caller.msg(string) -class MuxPlayerCommand(MuxCommand): +class MuxAccountCommand(MuxCommand): """ - This is an on-Player version of the MuxCommand. Since these commands sit - on Players rather than on Characters/Objects, we need to check + This is an on-Account version of the MuxCommand. Since these commands sit + on Accounts rather than on Characters/Objects, we need to check this in the parser. - Player commands are available also when puppeting a Character, it's + Account commands are available also when puppeting a Character, 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 + This class makes sure that caller is always an Account object, while creating a new property "character" that is set only if a - character is actually attached to this Player and Session. + character is actually attached to this Account and Session. """ def parse(self): """ We run the parent parser as usual, then fix the result """ - super(MuxPlayerCommand, self).parse() + super(MuxAccountCommand, self).parse() if utils.inherits_from(self.caller, "evennia.objects.objects.DefaultObject"): # caller is an Object/Character self.character = self.caller - self.caller = self.caller.player - elif utils.inherits_from(self.caller, "evennia.players.players.DefaultPlayer"): - # caller was already a Player + self.caller = self.caller.account + elif utils.inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount"): + # caller was already an Account self.character = self.caller.get_puppet(self.session) else: self.character = None diff --git a/evennia/commands/default/player.py b/evennia/commands/default/player.py deleted file mode 100644 index 3bca2c317d..0000000000 --- a/evennia/commands/default/player.py +++ /dev/null @@ -1,836 +0,0 @@ -""" -Player (OOC) commands. These are stored on the Player object -and self.caller is thus always a Player, not an Object/Character. - -These commands go in the PlayerCmdset and are accessible also -when puppeting a Character (although with lower priority) - -These commands use the player_caller property which tells the command -parent (MuxCommand, usually) to setup caller correctly. They use -self.player to make sure to always use the player object rather than -self.caller (which change depending on the level you are calling from) -The property self.character can be used to access the character when -these commands are triggered with a connected character (such as the -case of the @ooc command), it is None if we are OOC. - -Note that under MULTISESSION_MODE > 2, Player commands should use -self.msg() and similar methods to reroute returns to the correct -method. Otherwise all text will be returned to all connected sessions. - -""" -from builtins import range - -import time -from django.conf import settings -from evennia.server.sessionhandler import SESSIONS -from evennia.utils import utils, create, search, evtable - -COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS) - -_MAX_NR_CHARACTERS = settings.MAX_NR_CHARACTERS -_MULTISESSION_MODE = settings.MULTISESSION_MODE - -# limit symbol import for API -__all__ = ("CmdOOCLook", "CmdIC", "CmdOOC", "CmdPassword", "CmdQuit", - "CmdCharCreate", "CmdOption", "CmdSessions", "CmdWho", - "CmdColorTest", "CmdQuell") - - -class MuxPlayerLookCommand(COMMAND_DEFAULT_CLASS): - """ - Custom parent (only) parsing for OOC looking, sets a "playable" - property on the command based on the parsing. - - """ - - def parse(self): - """Custom parsing""" - - super(MuxPlayerLookCommand, self).parse() - - if _MULTISESSION_MODE < 2: - # only one character allowed - not used in this mode - self.playable = None - return - - playable = self.player.db._playable_characters - if playable is not None: - # clean up list if character object was deleted in between - if None in playable: - playable = [character for character in playable if character] - self.player.db._playable_characters = playable - # store playable property - if self.args: - self.playable = dict((utils.to_str(char.key.lower()), char) - for char in playable).get(self.args.lower(), None) - else: - self.playable = playable - - -# Obs - these are all intended to be stored on the Player, and as such, -# use self.player instead of self.caller, just to be sure. Also self.msg() -# is used to make sure returns go to the right session - -# note that this is inheriting from MuxPlayerLookCommand, -# and has the .playable property. -class CmdOOCLook(MuxPlayerLookCommand): - """ - look while out-of-character - - Usage: - look - - Look in the ooc state. - """ - - # This is an OOC version of the look command. Since a - # Player doesn't have an in-game existence, there is no - # concept of location or "self". If we are controlling - # a character, pass control over to normal look. - - key = "look" - aliases = ["l", "ls"] - locks = "cmd:all()" - help_category = "General" - - # this is used by the parent - player_caller = True - - def func(self): - """implement the ooc look command""" - - if _MULTISESSION_MODE < 2: - # only one character allowed - self.msg("You are out-of-character (OOC).\nUse |w@ic|n to get back into the game.") - return - - # call on-player look helper method - self.msg(self.player.at_look(target=self.playable, session=self.session)) - - -class CmdCharCreate(COMMAND_DEFAULT_CLASS): - """ - create a new character - - Usage: - @charcreate [= desc] - - Create a new character, optionally giving it a description. You - may use upper-case letters in the name - you will nevertheless - always be able to access your character using lower-case letters - if you want. - """ - key = "@charcreate" - locks = "cmd:pperm(Player)" - help_category = "General" - - # this is used by the parent - player_caller = True - - def func(self): - """create the new character""" - player = self.player - if not self.args: - self.msg("Usage: @charcreate [= description]") - return - key = self.lhs - desc = self.rhs - - charmax = _MAX_NR_CHARACTERS if _MULTISESSION_MODE > 1 else 1 - - if not player.is_superuser and \ - (player.db._playable_characters and - len(player.db._playable_characters) >= charmax): - self.msg("You may only create a maximum of %i characters." % charmax) - return - from evennia.objects.models import ObjectDB - typeclass = settings.BASE_CHARACTER_TYPECLASS - - if ObjectDB.objects.filter(db_typeclass_path=typeclass, db_key__iexact=key): - # check if this Character already exists. Note that we are only - # searching the base character typeclass here, not any child - # classes. - self.msg("|rA character named '|w%s|r' already exists.|n" % key) - return - - # create the character - start_location = ObjectDB.objects.get_id(settings.START_LOCATION) - default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME) - permissions = settings.PERMISSION_PLAYER_DEFAULT - new_character = create.create_object(typeclass, key=key, - location=start_location, - home=default_home, - permissions=permissions) - # only allow creator (and developers) to puppet this char - new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" % - (new_character.id, player.id)) - player.db._playable_characters.append(new_character) - if desc: - new_character.db.desc = desc - elif not new_character.db.desc: - new_character.db.desc = "This is a Player." - self.msg("Created new character %s. Use |w@ic %s|n to enter the game as this character." - % (new_character.key, new_character.key)) - - -class CmdCharDelete(COMMAND_DEFAULT_CLASS): - """ - delete a character - this cannot be undone! - - Usage: - @chardelete - - Permanently deletes one of your characters. - """ - key = "@chardelete" - locks = "cmd:pperm(Player)" - help_category = "General" - - def func(self): - """delete the character""" - player = self.player - - if not self.args: - self.msg("Usage: @chardelete ") - return - - # use the playable_characters list to search - match = [char for char in utils.make_iter(player.db._playable_characters) - if char.key.lower() == self.args.lower()] - if not match: - self.msg("You have no such character to delete.") - return - elif len(match) > 1: - self.msg("Aborting - there are two characters with the same name. Ask an admin to delete the right one.") - return - else: # one match - from evennia.utils.evmenu import get_input - - def _callback(caller, callback_prompt, result): - if result.lower() == "yes": - # only take action - delobj = caller.ndb._char_to_delete - key = delobj.key - caller.db._playable_characters = [pc for pc in caller.db._playable_characters if pc != delobj] - delobj.delete() - self.msg("Character '%s' was permanently deleted." % key) - else: - self.msg("Deletion was aborted.") - del caller.ndb._char_to_delete - - match = match[0] - player.ndb._char_to_delete = match - prompt = "|rThis will permanently destroy '%s'. This cannot be undone.|n Continue yes/[no]?" - get_input(player, prompt % match.key, _callback) - - -class CmdIC(COMMAND_DEFAULT_CLASS): - """ - control an object you have permission to puppet - - Usage: - @ic - - Go in-character (IC) as a given Character. - - This will attempt to "become" a different object assuming you have - the right to do so. Note that it's the PLAYER character that puppets - characters/objects and which needs to have the correct permission! - - You cannot become an object that is already controlled by another - player. In principle can be any in-game object as long - as you the player have access right to puppet it. - """ - - key = "@ic" - # lock must be all() for different puppeted objects to access it. - locks = "cmd:all()" - aliases = "@puppet" - help_category = "General" - - # this is used by the parent - player_caller = True - - def func(self): - """ - Main puppet method - """ - player = self.player - session = self.session - - new_character = None - if not self.args: - new_character = player.db._last_puppet - if not new_character: - self.msg("Usage: @ic ") - return - if not new_character: - # search for a matching character - new_character = [char for char in search.object_search(self.args) if char.access(player, "puppet")] - if not new_character: - self.msg("That is not a valid character choice.") - return - if len(new_character) > 1: - self.msg("Multiple targets with the same name:\n %s" - % ", ".join("%s(#%s)" % (obj.key, obj.id) for obj in new_character)) - return - else: - new_character = new_character[0] - try: - player.puppet_object(session, new_character) - player.db._last_puppet = new_character - except RuntimeError as exc: - self.msg("|rYou cannot become |C%s|n: %s" % (new_character.name, exc)) - - -# note that this is inheriting from MuxPlayerLookCommand, -# and as such has the .playable property. -class CmdOOC(MuxPlayerLookCommand): - """ - stop puppeting and go ooc - - Usage: - @ooc - - Go out-of-character (OOC). - - This will leave your current character and put you in a incorporeal OOC state. - """ - - key = "@ooc" - locks = "cmd:pperm(Player)" - aliases = "@unpuppet" - help_category = "General" - - # this is used by the parent - player_caller = True - - def func(self): - """Implement function""" - - player = self.player - session = self.session - - old_char = player.get_puppet(session) - if not old_char: - string = "You are already OOC." - self.msg(string) - return - - player.db._last_puppet = old_char - - # disconnect - try: - player.unpuppet_object(session) - self.msg("\n|GYou go OOC.|n\n") - - if _MULTISESSION_MODE < 2: - # only one character allowed - self.msg("You are out-of-character (OOC).\nUse |w@ic|n to get back into the game.") - return - - self.msg(player.at_look(target=self.playable, session=session)) - - except RuntimeError as exc: - self.msg("|rCould not unpuppet from |c%s|n: %s" % (old_char, exc)) - - -class CmdSessions(COMMAND_DEFAULT_CLASS): - """ - check your connected session(s) - - Usage: - @sessions - - Lists the sessions currently connected to your account. - - """ - key = "@sessions" - locks = "cmd:all()" - help_category = "General" - - # this is used by the parent - player_caller = True - - def func(self): - """Implement function""" - player = self.player - sessions = player.sessions.all() - table = evtable.EvTable("|wsessid", - "|wprotocol", - "|whost", - "|wpuppet/character", - "|wlocation") - for sess in sorted(sessions, key=lambda x: x.sessid): - char = player.get_puppet(sess) - table.add_row(str(sess.sessid), str(sess.protocol_key), - type(sess.address) == tuple and sess.address[0] or sess.address, - char and str(char) or "None", - char and str(char.location) or "N/A") - self.msg("|wYour current session(s):|n\n%s" % table) - - -class CmdWho(COMMAND_DEFAULT_CLASS): - """ - list who is currently online - - Usage: - who - doing - - Shows who is currently online. Doing is an alias that limits info - also for those with all permissions. - """ - - key = "who" - aliases = "doing" - locks = "cmd:all()" - - # this is used by the parent - player_caller = True - - def func(self): - """ - Get all connected players by polling session. - """ - - player = self.player - session_list = SESSIONS.get_sessions() - - session_list = sorted(session_list, key=lambda o: o.player.key) - - if self.cmdstring == "doing": - show_session_data = False - else: - show_session_data = player.check_permstring("Developer") or player.check_permstring("Admins") - - nplayers = (SESSIONS.player_count()) - if show_session_data: - # privileged info - table = evtable.EvTable("|wPlayer Name", - "|wOn for", - "|wIdle", - "|wPuppeting", - "|wRoom", - "|wCmds", - "|wProtocol", - "|wHost") - for session in session_list: - if not session.logged_in: - continue - delta_cmd = time.time() - session.cmd_last_visible - delta_conn = time.time() - session.conn_time - player = session.get_player() - puppet = session.get_puppet() - location = puppet.location.key if puppet and puppet.location else "None" - table.add_row(utils.crop(player.name, width=25), - utils.time_format(delta_conn, 0), - utils.time_format(delta_cmd, 1), - utils.crop(puppet.key if puppet else "None", width=25), - utils.crop(location, width=25), - session.cmd_total, - session.protocol_key, - isinstance(session.address, tuple) and session.address[0] or session.address) - else: - # unprivileged - table = evtable.EvTable("|wPlayer name", "|wOn for", "|wIdle") - for session in session_list: - if not session.logged_in: - continue - delta_cmd = time.time() - session.cmd_last_visible - delta_conn = time.time() - session.conn_time - player = session.get_player() - table.add_row(utils.crop(player.key, width=25), - utils.time_format(delta_conn, 0), - utils.time_format(delta_cmd, 1)) - is_one = nplayers == 1 - self.msg("|wPlayers:|n\n%s\n%s unique account%s logged in." - % (table, "One" if is_one else nplayers, "" if is_one else "s")) - - -class CmdOption(COMMAND_DEFAULT_CLASS): - """ - Set an account option - - Usage: - @option[/save] [name = value] - - Switch: - save - Save the current option settings for future logins. - clear - Clear the saved options. - - This command allows for viewing and setting client interface - settings. Note that saved options may not be able to be used if - later connecting with a client with different capabilities. - - - """ - key = "@option" - aliases = "@options" - locks = "cmd:all()" - - # this is used by the parent - player_caller = True - - def func(self): - """ - Implements the command - """ - if self.session is None: - return - - flags = self.session.protocol_flags - - # Display current options - if not self.args: - # list the option settings - - if "save" in self.switches: - # save all options - self.caller.db._saved_protocol_flags = flags - self.msg("|gSaved all options. Use @option/clear to remove.|n") - if "clear" in self.switches: - # clear all saves - self.caller.db._saved_protocol_flags = {} - self.msg("|gCleared all saved options.") - - options = dict(flags) # make a copy of the flag dict - saved_options = dict(self.caller.attributes.get("_saved_protocol_flags", default={})) - - if "SCREENWIDTH" in options: - if len(options["SCREENWIDTH"]) == 1: - options["SCREENWIDTH"] = options["SCREENWIDTH"][0] - else: - options["SCREENWIDTH"] = " \n".join("%s : %s" % (screenid, size) - for screenid, size in options["SCREENWIDTH"].iteritems()) - if "SCREENHEIGHT" in options: - if len(options["SCREENHEIGHT"]) == 1: - options["SCREENHEIGHT"] = options["SCREENHEIGHT"][0] - else: - options["SCREENHEIGHT"] = " \n".join("%s : %s" % (screenid, size) - for screenid, size in options["SCREENHEIGHT"].iteritems()) - options.pop("TTYPE", None) - - header = ("Name", "Value", "Saved") if saved_options else ("Name", "Value") - table = evtable.EvTable(*header) - for key in sorted(options): - row = [key, options[key]] - if saved_options: - saved = " |YYes|n" if key in saved_options else "" - changed = "|y*|n" if key in saved_options and flags[key] != saved_options[key] else "" - row.append("%s%s" % (saved, changed)) - table.add_row(*row) - self.msg("|wClient settings (%s):|n\n%s|n" % (self.session.protocol_key, table)) - - return - - if not self.rhs: - self.msg("Usage: @option [name = [value]]") - return - - # Try to assign new values - - def validate_encoding(new_encoding): - # helper: change encoding - try: - utils.to_str(utils.to_unicode("test-string"), encoding=new_encoding) - except LookupError: - raise RuntimeError("The encoding '|w%s|n' is invalid. " % new_encoding) - return val - - def validate_size(new_size): - return {0: int(new_size)} - - def validate_bool(new_bool): - return True if new_bool.lower() in ("true", "on", "1") else False - - def update(new_name, new_val, validator): - # helper: update property and report errors - try: - old_val = flags.get(new_name, False) - new_val = validator(new_val) - flags[new_name] = new_val - self.msg("Option |w%s|n was changed from '|w%s|n' to '|w%s|n'." % (new_name, old_val, new_val)) - return {new_name: new_val} - except Exception, err: - self.msg("|rCould not set option |w%s|r:|n %s" % (new_name, err)) - return False - - validators = {"ANSI": validate_bool, - "CLIENTNAME": utils.to_str, - "ENCODING": validate_encoding, - "MCCP": validate_bool, - "NOGOAHEAD": validate_bool, - "MXP": validate_bool, - "NOCOLOR": validate_bool, - "NOPKEEPALIVE": validate_bool, - "OOB": validate_bool, - "RAW": validate_bool, - "SCREENHEIGHT": validate_size, - "SCREENWIDTH": validate_size, - "SCREENREADER": validate_bool, - "TERM": utils.to_str, - "UTF-8": validate_bool, - "XTERM256": validate_bool, - "INPUTDEBUG": validate_bool} - - name = self.lhs.upper() - val = self.rhs.strip() - optiondict = False - if val and name in validators: - optiondict = update(name, val, validators[name]) - else: - self.msg("|rNo option named '|w%s|r'." % name) - if optiondict: - # a valid setting - if "save" in self.switches: - # save this option only - saved_options = self.player.attributes.get("_saved_protocol_flags", default={}) - saved_options.update(optiondict) - self.player.attributes.add("_saved_protocol_flags", saved_options) - for key in optiondict: - self.msg("|gSaved option %s.|n" % key) - if "clear" in self.switches: - # clear this save - for key in optiondict: - self.player.attributes.get("_saved_protocol_flags", {}).pop(key, None) - self.msg("|gCleared saved %s." % key) - self.session.update_flags(**optiondict) - - -class CmdPassword(COMMAND_DEFAULT_CLASS): - """ - change your password - - Usage: - @password = - - Changes your password. Make sure to pick a safe one. - """ - key = "@password" - locks = "cmd:pperm(Player)" - - # this is used by the parent - player_caller = True - - def func(self): - """hook function.""" - - player = self.player - if not self.rhs: - self.msg("Usage: @password = ") - return - oldpass = self.lhslist[0] # Both of these are - newpass = self.rhslist[0] # already stripped by parse() - if not player.check_password(oldpass): - self.msg("The specified old password isn't correct.") - elif len(newpass) < 3: - self.msg("Passwords must be at least three characters long.") - else: - player.set_password(newpass) - player.save() - self.msg("Password changed.") - - -class CmdQuit(COMMAND_DEFAULT_CLASS): - """ - quit the game - - Usage: - @quit - - Switch: - all - disconnect all connected sessions - - Gracefully disconnect your current session from the - game. Use the /all switch to disconnect from all sessions. - """ - key = "@quit" - locks = "cmd:all()" - - # this is used by the parent - player_caller = True - - def func(self): - """hook function""" - player = self.player - - if 'all' in self.switches: - player.msg("|RQuitting|n all sessions. Hope to see you soon again.", session=self.session) - for session in player.sessions.all(): - player.disconnect_session_from_player(session) - else: - nsess = len(player.sessions.all()) - if nsess == 2: - player.msg("|RQuitting|n. One session is still connected.", session=self.session) - elif nsess > 2: - player.msg("|RQuitting|n. %i sessions are still connected." % (nsess-1), session=self.session) - else: - # we are quitting the last available session - player.msg("|RQuitting|n. Hope to see you again, soon.", session=self.session) - player.disconnect_session_from_player(self.session) - - -class CmdColorTest(COMMAND_DEFAULT_CLASS): - """ - testing which colors your client support - - Usage: - @color ansi||xterm256 - - Prints a color map along with in-mud color codes to use to produce - them. It also tests what is supported in your client. Choices are - 16-color ansi (supported in most muds) or the 256-color xterm256 - standard. No checking is done to determine your client supports - color - if not you will see rubbish appear. - """ - key = "@color" - locks = "cmd:all()" - help_category = "General" - - # this is used by the parent - player_caller = True - - def table_format(self, table): - """ - Helper method to format the ansi/xterm256 tables. - Takes a table of columns [[val,val,...],[val,val,...],...] - """ - if not table: - return [[]] - - extra_space = 1 - max_widths = [max([len(str(val)) for val in col]) for col in table] - ftable = [] - for irow in range(len(table[0])): - ftable.append([str(col[irow]).ljust(max_widths[icol]) + " " * - extra_space for icol, col in enumerate(table)]) - return ftable - - def func(self): - """Show color tables""" - - if self.args.startswith("a"): - # show ansi 16-color table - from evennia.utils import ansi - ap = ansi.ANSI_PARSER - # ansi colors - # show all ansi color-related codes - col1 = ["%s%s|n" % (code, code.replace("|", "||")) for code, _ in ap.ext_ansi_map[48:56]] - col2 = ["%s%s|n" % (code, code.replace("|", "||")) for code, _ in ap.ext_ansi_map[56:64]] - col3 = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", "")) - for code, _ in ap.ext_ansi_map[-8:]] - col4 = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", "")) - for code, _ in ap.ansi_bright_bgs[-8:]] - col2.extend(["" for _ in range(len(col1)-len(col2))]) - table = utils.format_table([col1, col2, col4, col3]) - string = "ANSI colors:" - for row in table: - string += "\n " + " ".join(row) - self.msg(string) - self.msg("||X : black. ||/ : return, ||- : tab, ||_ : space, ||* : invert, ||u : underline\n" - "To combine background and foreground, add background marker last, e.g. ||r||[B.\n" - "Note: bright backgrounds like ||[r requires your client handling Xterm256 colors.") - - elif self.args.startswith("x"): - # show xterm256 table - table = [[], [], [], [], [], [], [], [], [], [], [], []] - for ir in range(6): - for ig in range(6): - for ib in range(6): - # foreground table - table[ir].append("|%i%i%i%s|n" % (ir, ig, ib, "||%i%i%i" % (ir, ig, ib))) - # background table - table[6+ir].append("|%i%i%i|[%i%i%i%s|n" - % (5 - ir, 5 - ig, 5 - ib, ir, ig, ib, "||[%i%i%i" % (ir, ig, ib))) - table = self.table_format(table) - string = "Xterm256 colors (if not all hues show, your client might not report that it can handle xterm256):" - string += "\n" + "\n".join("".join(row) for row in table) - table = [[], [], [], [], [], [], [], [], [], [], [], []] - for ibatch in range(4): - for igray in range(6): - letter = chr(97 + (ibatch*6 + igray)) - inverse = chr(122 - (ibatch*6 + igray)) - table[0 + igray].append("|=%s%s |n" % (letter, "||=%s" % letter)) - table[6 + igray].append("|=%s|[=%s%s |n" % (inverse, letter, "||[=%s" % letter)) - for igray in range(6): - # the last row (y, z) has empty columns - if igray < 2: - letter = chr(121 + igray) - inverse = chr(98 - igray) - fg = "|=%s%s |n" % (letter, "||=%s" % letter) - bg = "|=%s|[=%s%s |n" % (inverse, letter, "||[=%s" % letter) - else: - fg, bg = " ", " " - table[0 + igray].append(fg) - table[6 + igray].append(bg) - table = self.table_format(table) - string += "\n" + "\n".join("".join(row) for row in table) - self.msg(string) - else: - # malformed input - self.msg("Usage: @color ansi||xterm256") - - -class CmdQuell(COMMAND_DEFAULT_CLASS): - """ - use character's permissions instead of player's - - Usage: - quell - unquell - - Normally the permission level of the Player is used when puppeting a - Character/Object to determine access. This command will switch the lock - system to make use of the puppeted Object's permissions instead. This is - useful mainly for testing. - Hierarchical permission quelling only work downwards, thus a Player cannot - use a higher-permission Character to escalate their permission level. - Use the unquell command to revert back to normal operation. - """ - - key = "@quell" - aliases = ["@unquell"] - locks = "cmd:pperm(Player)" - help_category = "General" - - # this is used by the parent - player_caller = True - - def _recache_locks(self, player): - """Helper method to reset the lockhandler on an already puppeted object""" - if self.session: - char = self.session.puppet - if char: - # we are already puppeting an object. We need to reset - # the lock caches (otherwise the superuser status change - # won't be visible until repuppet) - char.locks.reset() - player.locks.reset() - - def func(self): - """Perform the command""" - player = self.player - permstr = player.is_superuser and " (superuser)" or "(%s)" % (", ".join(player.permissions.all())) - if self.cmdstring == '@unquell': - if not player.attributes.get('_quell'): - self.msg("Already using normal Player permissions %s." % permstr) - else: - player.attributes.remove('_quell') - self.msg("Player permissions %s restored." % permstr) - else: - if player.attributes.get('_quell'): - self.msg("Already quelling Player %s permissions." % permstr) - return - player.attributes.add('_quell', True) - puppet = self.session.puppet - if puppet: - cpermstr = "(%s)" % ", ".join(puppet.permissions.all()) - cpermstr = "Quelling to current puppet's permissions %s." % cpermstr - cpermstr += "\n(Note: If this is higher than Player permissions %s," \ - " the lowest of the two will be used.)" % permstr - cpermstr += "\nUse @unquell to return to normal permission usage." - self.msg(cpermstr) - else: - self.msg("Quelling Player permissions%s. Use @unquell to get them back." % permstr) - self._recache_locks(player) diff --git a/evennia/commands/default/system.py b/evennia/commands/default/system.py index 8ed81be7fc..9a5356bc0f 100644 --- a/evennia/commands/default/system.py +++ b/evennia/commands/default/system.py @@ -17,7 +17,7 @@ from django.conf import settings from evennia.server.sessionhandler import SESSIONS from evennia.scripts.models import ScriptDB from evennia.objects.models import ObjectDB -from evennia.players.models import PlayerDB +from evennia.accounts.models import AccountDB from evennia.utils import logger, utils, gametime, create from evennia.utils.eveditor import EvEditor from evennia.utils.evtable import EvTable @@ -455,24 +455,24 @@ class CmdObjects(COMMAND_DEFAULT_CLASS): caller.msg(string) -class CmdPlayers(COMMAND_DEFAULT_CLASS): +class CmdAccounts(COMMAND_DEFAULT_CLASS): """ - list all registered players + list all registered accounts Usage: - @players [nr] + @accounts [nr] - Lists statistics about the Players registered with the game. - It will list the amount of latest registered players + Lists statistics about the Accounts registered with the game. + It will list the amount of latest registered accounts If not given, defaults to 10. """ - key = "@players" - aliases = ["@listplayers"] - locks = "cmd:perm(listplayers) or perm(Admin)" + key = "@accounts" + aliases = ["@listaccounts"] + locks = "cmd:perm(listaccounts) or perm(Admin)" help_category = "System" def func(self): - """List the players""" + """List the accounts""" caller = self.caller if self.args and self.args.isdigit(): @@ -480,21 +480,21 @@ class CmdPlayers(COMMAND_DEFAULT_CLASS): else: nlim = 10 - nplayers = PlayerDB.objects.count() + naccounts = AccountDB.objects.count() # typeclass table - dbtotals = PlayerDB.objects.object_totals() + dbtotals = AccountDB.objects.object_totals() typetable = EvTable("|wtypeclass|n", "|wcount|n", "|w%%|n", border="cells", align="l") for path, count in dbtotals.items(): - typetable.add_row(path, count, "%.2f" % ((float(count) / nplayers) * 100)) + typetable.add_row(path, count, "%.2f" % ((float(count) / naccounts) * 100)) # last N table - plyrs = PlayerDB.objects.all().order_by("db_date_created")[max(0, nplayers - nlim):] + plyrs = AccountDB.objects.all().order_by("db_date_created")[max(0, naccounts - nlim):] latesttable = EvTable("|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", border="cells", align="l") for ply in plyrs: latesttable.add_row(utils.datetime_format(ply.date_created), ply.dbref, ply.key, ply.path) - string = "\n|wPlayer typeclass distribution:|n\n%s" % typetable - string += "\n|wLast %s Players created:|n\n%s" % (min(nplayers, nlim), latesttable) + string = "\n|wAccount typeclass distribution:|n\n%s" % typetable + string += "\n|wLast %s Accounts created:|n\n%s" % (min(naccounts, nlim), latesttable) caller.msg(string) @@ -644,7 +644,7 @@ class CmdTime(COMMAND_DEFAULT_CLASS): """ key = "@time" aliases = "@uptime" - locks = "cmd:perm(time) or perm(Player)" + locks = "cmd:perm(time) or perm(Account)" help_category = "System" def func(self): diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index b4d0f4554c..6f1415a14e 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -19,7 +19,7 @@ from mock import Mock from evennia.commands.default.cmdset_character import CharacterCmdSet from evennia.utils.test_resources import EvenniaTest -from evennia.commands.default import help, general, system, admin, player, building, batchprocess, comms +from evennia.commands.default import help, general, system, admin, account, building, batchprocess, comms from evennia.commands.command import Command, InterruptCommand from evennia.utils import ansi, utils from evennia.server.sessionhandler import SESSIONS @@ -63,7 +63,7 @@ class CommandTest(EvenniaTest): cmdobj.args = args cmdobj.cmdset = cmdset cmdobj.session = SESSIONS.session_from_sessid(1) - cmdobj.player = self.player + cmdobj.account = self.account cmdobj.raw_string = cmdobj.key + " " + args cmdobj.obj = obj or (caller if caller else self.char1) # test @@ -119,10 +119,10 @@ class TestGeneral(CommandTest): def test_nick(self): self.call(general.CmdNick(), "testalias = testaliasedstring1", "Nick 'testalias' mapped to 'testaliasedstring1'.") - self.call(general.CmdNick(), "/player testalias = testaliasedstring2", "Nick 'testalias' mapped to 'testaliasedstring2'.") + self.call(general.CmdNick(), "/account testalias = testaliasedstring2", "Nick 'testalias' mapped to 'testaliasedstring2'.") self.call(general.CmdNick(), "/object testalias = testaliasedstring3", "Nick 'testalias' mapped to 'testaliasedstring3'.") self.assertEqual(u"testaliasedstring1", self.char1.nicks.get("testalias")) - self.assertEqual(u"testaliasedstring2", self.char1.nicks.get("testalias", category="player")) + self.assertEqual(u"testaliasedstring2", self.char1.nicks.get("testalias", category="account")) self.assertEqual(u"testaliasedstring3", self.char1.nicks.get("testalias", category="object")) def test_get_and_drop(self): @@ -176,50 +176,50 @@ class TestAdmin(CommandTest): self.call(admin.CmdPerm(), "Char2 = Builder", "Permission 'Builder' given to Char2 (the Object/Character).") def test_wall(self): - self.call(admin.CmdWall(), "Test", "Announcing to all connected players ...") + self.call(admin.CmdWall(), "Test", "Announcing to all connected accounts ...") def test_ban(self): self.call(admin.CmdBan(), "Char", "NameBan char was added.") -class TestPlayer(CommandTest): +class TestAccount(CommandTest): def test_ooc_look(self): if settings.MULTISESSION_MODE < 2: - self.call(player.CmdOOCLook(), "", "You are outofcharacter (OOC).", caller=self.player) + self.call(account.CmdOOCLook(), "", "You are outofcharacter (OOC).", caller=self.account) if settings.MULTISESSION_MODE == 2: - self.call(player.CmdOOCLook(), "", "Account TestPlayer (you are OutofCharacter)", caller=self.player) + self.call(account.CmdOOCLook(), "", "Account TestAccount (you are OutofCharacter)", caller=self.account) def test_ooc(self): - self.call(player.CmdOOC(), "", "You go OOC.", caller=self.player) + self.call(account.CmdOOC(), "", "You go OOC.", caller=self.account) def test_ic(self): - self.player.unpuppet_object(self.session) - self.call(player.CmdIC(), "Char", "You become Char.", caller=self.player, receiver=self.char1) + self.account.unpuppet_object(self.session) + self.call(account.CmdIC(), "Char", "You become Char.", caller=self.account, receiver=self.char1) def test_password(self): - self.call(player.CmdPassword(), "testpassword = testpassword", "Password changed.", caller=self.player) + self.call(account.CmdPassword(), "testpassword = testpassword", "Password changed.", caller=self.account) def test_option(self): - self.call(player.CmdOption(), "", "Client settings", caller=self.player) + self.call(account.CmdOption(), "", "Client settings", caller=self.account) def test_who(self): - self.call(player.CmdWho(), "", "Players:", caller=self.player) + self.call(account.CmdWho(), "", "Accounts:", caller=self.account) def test_quit(self): - self.call(player.CmdQuit(), "", "Quitting. Hope to see you again, soon.", caller=self.player) + self.call(account.CmdQuit(), "", "Quitting. Hope to see you again, soon.", caller=self.account) def test_sessions(self): - self.call(player.CmdSessions(), "", "Your current session(s):", caller=self.player) + self.call(account.CmdSessions(), "", "Your current session(s):", caller=self.account) def test_color_test(self): - self.call(player.CmdColorTest(), "ansi", "ANSI colors:", caller=self.player) + self.call(account.CmdColorTest(), "ansi", "ANSI colors:", caller=self.account) def test_char_create(self): - self.call(player.CmdCharCreate(), "Test1=Test char", "Created new character Test1. Use @ic Test1 to enter the game", caller=self.player) + self.call(account.CmdCharCreate(), "Test1=Test char", "Created new character Test1. Use @ic Test1 to enter the game", caller=self.account) def test_quell(self): - self.call(player.CmdQuell(), "", "Quelling to current puppet's permissions (developer).", caller=self.player) + self.call(account.CmdQuell(), "", "Quelling to current puppet's permissions (developer).", caller=self.account) class TestBuilding(CommandTest): @@ -290,39 +290,39 @@ class TestComms(CommandTest): def setUp(self): super(CommandTest, self).setUp() - self.call(comms.CmdChannelCreate(), "testchan;test=Test Channel", "Created channel testchan and connected to it.", receiver=self.player) + self.call(comms.CmdChannelCreate(), "testchan;test=Test Channel", "Created channel testchan and connected to it.", receiver=self.account) def test_toggle_com(self): - self.call(comms.CmdAddCom(), "tc = testchan", "You are already connected to channel testchan. You can now", receiver=self.player) - self.call(comms.CmdDelCom(), "tc", "Your alias 'tc' for channel testchan was cleared.", receiver=self.player) + self.call(comms.CmdAddCom(), "tc = testchan", "You are already connected to channel testchan. You can now", receiver=self.account) + self.call(comms.CmdDelCom(), "tc", "Your alias 'tc' for channel testchan was cleared.", receiver=self.account) def test_channels(self): - self.call(comms.CmdChannels(), "" ,"Available channels (use comlist,addcom and delcom to manage", receiver=self.player) + self.call(comms.CmdChannels(), "" ,"Available channels (use comlist,addcom and delcom to manage", receiver=self.account) def test_all_com(self): - self.call(comms.CmdAllCom(), "", "Available channels (use comlist,addcom and delcom to manage", receiver=self.player) + self.call(comms.CmdAllCom(), "", "Available channels (use comlist,addcom and delcom to manage", receiver=self.account) def test_clock(self): - self.call(comms.CmdClock(), "testchan=send:all()", "Lock(s) applied. Current locks on testchan:", receiver=self.player) + self.call(comms.CmdClock(), "testchan=send:all()", "Lock(s) applied. Current locks on testchan:", receiver=self.account) def test_cdesc(self): - self.call(comms.CmdCdesc(), "testchan = Test Channel", "Description of channel 'testchan' set to 'Test Channel'.", receiver=self.player) + self.call(comms.CmdCdesc(), "testchan = Test Channel", "Description of channel 'testchan' set to 'Test Channel'.", receiver=self.account) def test_cemit(self): - self.call(comms.CmdCemit(), "testchan = Test Message", "[testchan] Test Message|Sent to channel testchan: Test Message", receiver=self.player) + self.call(comms.CmdCemit(), "testchan = Test Message", "[testchan] Test Message|Sent to channel testchan: Test Message", receiver=self.account) def test_cwho(self): - self.call(comms.CmdCWho(), "testchan", "Channel subscriptions\ntestchan:\n TestPlayer", receiver=self.player) + self.call(comms.CmdCWho(), "testchan", "Channel subscriptions\ntestchan:\n TestAccount", receiver=self.account) def test_page(self): - self.call(comms.CmdPage(), "TestPlayer2 = Test", "TestPlayer2 is offline. They will see your message if they list their pages later.|You paged TestPlayer2 with: 'Test'.", receiver=self.player) + self.call(comms.CmdPage(), "TestAccount2 = Test", "TestAccount2 is offline. They will see your message if they list their pages later.|You paged TestAccount2 with: 'Test'.", receiver=self.account) def test_cboot(self): # No one else connected to boot - self.call(comms.CmdCBoot(), "", "Usage: @cboot[/quiet] = [:reason]", receiver=self.player) + self.call(comms.CmdCBoot(), "", "Usage: @cboot[/quiet] = [:reason]", receiver=self.account) def test_cdestroy(self): - self.call(comms.CmdCdestroy(), "testchan" ,"[testchan] TestPlayer: testchan is being destroyed. Make sure to change your aliases.|Channel 'testchan' was destroyed.", receiver=self.player) + self.call(comms.CmdCdestroy(), "testchan" ,"[testchan] TestAccount: testchan is being destroyed. Make sure to change your aliases.|Channel 'testchan' was destroyed.", receiver=self.account) class TestBatchProcess(CommandTest): diff --git a/evennia/commands/default/unloggedin.py b/evennia/commands/default/unloggedin.py index 62fb2c684a..d29e236df9 100644 --- a/evennia/commands/default/unloggedin.py +++ b/evennia/commands/default/unloggedin.py @@ -7,7 +7,7 @@ from collections import defaultdict from random import getrandbits from django.conf import settings from django.contrib.auth import authenticate -from evennia.players.models import PlayerDB +from evennia.accounts.models import AccountDB from evennia.objects.models import ObjectDB from evennia.server.models import ServerConfig from evennia.comms.models import ChannelDB @@ -25,7 +25,7 @@ MULTISESSION_MODE = settings.MULTISESSION_MODE CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE # Helper function to throttle failed connection attempts. -# This can easily be used to limit player creation too, +# This can easily be used to limit account creation too, # (just supply a different storage dictionary), but this # would also block dummyrunner, so it's not added as default. @@ -77,17 +77,17 @@ def _throttle(session, maxlim=None, timeout=None, storage=_LATEST_FAILED_LOGINS) return False -def create_guest_player(session): +def create_guest_account(session): """ - Creates a guest player/character for this session, if one is available. + Creates a guest account/character for this session, if one is available. Args: - session (Session): the session which will use the guest player/character. + session (Session): the session which will use the guest account/character. Returns: - GUEST_ENABLED (boolean), player (Player): + GUEST_ENABLED (boolean), account (Account): the boolean is whether guest accounts are enabled at all. - the Player which was created from an available guest name. + the Account which was created from an available guest name. """ # check if guests are enabled. if not settings.GUEST_ENABLED: @@ -105,25 +105,25 @@ def create_guest_player(session): try: # Find an available guest name. - playername = None + accountname = None for name in settings.GUEST_LIST: - if not PlayerDB.objects.filter(username__iexact=playername).count(): - playername = name + if not AccountDB.objects.filter(username__iexact=accountname).count(): + accountname = name break - if not playername: + if not accountname: session.msg("All guest accounts are in use. Please try again later.") return True, None else: - # build a new player with the found guest playername + # build a new account with the found guest accountname password = "%016x" % getrandbits(64) home = ObjectDB.objects.get_id(settings.GUEST_HOME) permissions = settings.PERMISSION_GUEST_DEFAULT typeclass = settings.BASE_CHARACTER_TYPECLASS ptypeclass = settings.BASE_GUEST_TYPECLASS - new_player = _create_player(session, playername, password, permissions, ptypeclass) - if new_player: - _create_character(session, new_player, typeclass, home, permissions) - return True, new_player + new_account = _create_account(session, accountname, password, permissions, ptypeclass) + if new_account: + _create_character(session, new_account, typeclass, home, permissions) + return True, new_account except Exception: # We are in the middle between logged in and -not, so we have @@ -134,17 +134,17 @@ def create_guest_player(session): raise -def create_normal_player(session, name, password): +def create_normal_account(session, name, password): """ - Creates a player with the given name and password. + Creates an account with the given name and password. Args: - session (Session): the session which is requesting to create a player. - name (str): the name that the player wants to use for login. - password (str): the password desired by this player, for login. + session (Session): the session which is requesting to create an account. + name (str): the name that the account wants to use for login. + password (str): the password desired by this account, for login. Returns: - player (Player): the player which was created from the name and password. + account (Account): the account which was created from the name and password. """ # check for too many login errors too quick. if _throttle(session, maxlim=5, timeout=5*60): @@ -153,22 +153,22 @@ def create_normal_player(session, name, password): return None # Match account name and check password - player = authenticate(username=name, password=password) + account = authenticate(username=name, password=password) - if not player: - # No playername or password match + if not account: + # No accountname or password match session.msg("Incorrect login information given.") # this just updates the throttle _throttle(session) - # calls player hook for a failed login if possible. - player = PlayerDB.objects.get_player_from_name(name) - if player: - player.at_failed_login(session) + # calls account hook for a failed login if possible. + account = AccountDB.objects.get_account_from_name(name) + if account: + account.at_failed_login(session) return None # Check IP and/or name bans bans = ServerConfig.objects.conf("server_bans") - if bans and (any(tup[0] == player.name.lower() for tup in bans) + if bans and (any(tup[0] == account.name.lower() for tup in bans) or any(tup[2].match(session.address) for tup in bans if tup[2])): # this is a banned IP or name! @@ -178,7 +178,7 @@ def create_normal_player(session, name, password): session.sessionhandler.disconnect(session, "Good bye! Disconnecting.") return None - return player + return account class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS): @@ -186,8 +186,8 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS): connect to the game Usage (at login screen): - connect playername password - connect "player name" "pass word" + connect accountname password + connect "account name" "pass word" Use the create command to first create an account before logging in. @@ -204,7 +204,7 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS): have a unique position in that their func() receives a session object instead of a source_object like all other types of logged-in commands (this is because - there is no object yet before the player has logged in) + there is no object yet before the account has logged in) """ session = self.caller @@ -222,9 +222,9 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS): parts = parts[0].split(None, 1) # Guest login if len(parts) == 1 and parts[0].lower() == "guest": - enabled, new_player = create_guest_player(session) - if new_player: - session.sessionhandler.login(session, new_player) + enabled, new_account = create_guest_account(session) + if new_account: + session.sessionhandler.login(session, new_account) if enabled: return @@ -233,20 +233,20 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS): return name, password = parts - player = create_normal_player(session, name, password) - if player: - session.sessionhandler.login(session, player) + account = create_normal_account(session, name, password) + if account: + session.sessionhandler.login(session, account) class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS): """ - create a new player account + create a new account account Usage (at login screen): - create - create "player name" "pass word" + create + create "account name" "pass word" - This creates a new player account. + This creates a new account account. If you have spaces in your name, enclose it in double quotes. """ @@ -271,25 +271,25 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS): "\nIf or contains spaces, enclose it in double quotes." session.msg(string) return - playername, password = parts + accountname, password = parts # sanity checks - if not re.findall(r"^[\w. @+\-']+$", playername) or not (0 < len(playername) <= 30): + if not re.findall(r"^[\w. @+\-']+$", accountname) or not (0 < len(accountname) <= 30): # this echoes the restrictions made by django's auth # module (except not allowing spaces, for convenience of # logging in). - string = "\n\r Playername can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_/' only." + string = "\n\r Accountname can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_/' only." session.msg(string) return - # strip excessive spaces in playername - playername = re.sub(r"\s+", " ", playername).strip() - if PlayerDB.objects.filter(username__iexact=playername): - # player already exists (we also ignore capitalization here) - session.msg("Sorry, there is already a player with the name '%s'." % playername) + # strip excessive spaces in accountname + accountname = re.sub(r"\s+", " ", accountname).strip() + if AccountDB.objects.filter(username__iexact=accountname): + # account already exists (we also ignore capitalization here) + session.msg("Sorry, there is already an account with the name '%s'." % accountname) return - # Reserve playernames found in GUEST_LIST - if settings.GUEST_LIST and playername.lower() in (guest.lower() for guest in settings.GUEST_LIST): - string = "\n\r That name is reserved. Please choose another Playername." + # Reserve accountnames found in GUEST_LIST + if settings.GUEST_LIST and accountname.lower() in (guest.lower() for guest in settings.GUEST_LIST): + string = "\n\r That name is reserved. Please choose another Accountname." session.msg(string) return if not re.findall(r"^[\w. @+\-']+$", password) or not (3 < len(password)): @@ -301,7 +301,7 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS): # Check IP and/or name bans bans = ServerConfig.objects.conf("server_bans") - if bans and (any(tup[0] == playername.lower() for tup in bans) + if bans and (any(tup[0] == accountname.lower() for tup in bans) or any(tup[2].match(session.address) for tup in bans if tup[2])): # this is a banned IP or name! @@ -311,22 +311,22 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS): session.sessionhandler.disconnect(session, "Good bye! Disconnecting.") return - # everything's ok. Create the new player account. + # everything's ok. Create the new account account. try: - permissions = settings.PERMISSION_PLAYER_DEFAULT + permissions = settings.PERMISSION_ACCOUNT_DEFAULT typeclass = settings.BASE_CHARACTER_TYPECLASS - new_player = _create_player(session, playername, password, permissions) - if new_player: + new_account = _create_account(session, accountname, password, permissions) + if new_account: if MULTISESSION_MODE < 2: default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME) - _create_character(session, new_player, typeclass, default_home, permissions) + _create_character(session, new_account, typeclass, default_home, permissions) # tell the caller everything went well. string = "A new account '%s' was created. Welcome!" - if " " in playername: + if " " in accountname: string += "\n\nYou can now log in with the command 'connect \"%s\" '." else: string += "\n\nYou can now log with the command 'connect %s '." - session.msg(string % (playername, playername)) + session.msg(string % (accountname, accountname)) except Exception: # We are in the middle between logged in and -not, so we have @@ -344,7 +344,7 @@ class CmdUnconnectedQuit(COMMAND_DEFAULT_CLASS): quit We maintain a different version of the quit command - here for unconnected players for the sake of simplicity. The logged in + here for unconnected accounts for the sake of simplicity. The logged in version is a bit more complicated. """ key = "quit" @@ -516,50 +516,50 @@ class CmdUnconnectedScreenreader(COMMAND_DEFAULT_CLASS): self.session.sessionhandler.session_portal_sync(self.session) -def _create_player(session, playername, password, permissions, typeclass=None, email=None): +def _create_account(session, accountname, password, permissions, typeclass=None, email=None): """ - Helper function, creates a player of the specified typeclass. + Helper function, creates an account of the specified typeclass. """ try: - new_player = create.create_player(playername, email, password, permissions=permissions, typeclass=typeclass) + new_account = create.create_account(accountname, email, password, permissions=permissions, typeclass=typeclass) except Exception as e: - session.msg("There was an error creating the Player:\n%s\n If this problem persists, contact an admin." % e) + session.msg("There was an error creating the Account:\n%s\n If this problem persists, contact an admin." % e) logger.log_trace() return False - # This needs to be set so the engine knows this player is + # This needs to be set so the engine knows this account is # logging in for the first time. (so it knows to call the right # hooks during login later) - new_player.db.FIRST_LOGIN = True + new_account.db.FIRST_LOGIN = True - # join the new player to the public channel + # join the new account to the public channel pchannel = ChannelDB.objects.get_channel(settings.DEFAULT_CHANNELS[0]["key"]) - if not pchannel or not pchannel.connect(new_player): - string = "New player '%s' could not connect to public channel!" % new_player.key + if not pchannel or not pchannel.connect(new_account): + string = "New account '%s' could not connect to public channel!" % new_account.key logger.log_err(string) - return new_player + return new_account -def _create_character(session, new_player, typeclass, home, permissions): +def _create_character(session, new_account, typeclass, home, permissions): """ - Helper function, creates a character based on a player's name. + Helper function, creates a character based on an account's name. This is meant for Guest and MULTISESSION_MODE < 2 situations. """ try: - new_character = create.create_object(typeclass, key=new_player.key, home=home, permissions=permissions) + new_character = create.create_object(typeclass, key=new_account.key, home=home, permissions=permissions) # set playable character list - new_player.db._playable_characters.append(new_character) + new_account.db._playable_characters.append(new_character) - # allow only the character itself and the player to puppet this character (and Developers). + # allow only the character itself and the account to puppet this character (and Developers). new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" % - (new_character.id, new_player.id)) + (new_character.id, new_account.id)) # If no description is set, set a default description if not new_character.db.desc: - new_character.db.desc = "This is a Player." + new_character.db.desc = "This is an Account." # We need to set this to have @ic auto-connect to this character - new_player.db._last_puppet = new_character + new_account.db._last_puppet = new_character except Exception as e: session.msg("There was an error creating the Character:\n%s\n If this problem persists, contact an admin." % e) logger.log_trace() diff --git a/evennia/commands/tests.py b/evennia/commands/tests.py index e5037154cb..6e44fb51ad 100644 --- a/evennia/commands/tests.py +++ b/evennia/commands/tests.py @@ -266,15 +266,15 @@ class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest): deferred.addCallback(_callback) return deferred - def test_from_player(self): - from evennia.commands.default.cmdset_player import PlayerCmdSet + def test_from_account(self): + from evennia.commands.default.cmdset_account import AccountCmdSet a = self.cmdset_a a.no_channels = True - self.set_cmdsets(self.player, a) - deferred = cmdhandler.get_and_merge_cmdsets(self.player, None, self.player, None, "player", "") + self.set_cmdsets(self.account, a) + deferred = cmdhandler.get_and_merge_cmdsets(self.account, None, self.account, None, "account", "") # get_and_merge_cmdsets converts to lower-case internally. def _callback(cmdset): - pcmdset = PlayerCmdSet() + pcmdset = AccountCmdSet() pcmdset.at_cmdset_creation() pcmds = [cmd.key for cmd in pcmdset.commands] + ["a", "b", "c", "d"] self.assertTrue(all(cmd.key in pcmds for cmd in cmdset.commands)) @@ -305,18 +305,18 @@ class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest): def test_autocmdsets(self): import evennia - from evennia.commands.default.cmdset_player import PlayerCmdSet + from evennia.commands.default.cmdset_account import AccountCmdSet from evennia.comms.channelhandler import CHANNEL_HANDLER testchannel = evennia.create_channel("channeltest", locks="listen:all();send:all()") CHANNEL_HANDLER.add(testchannel) CHANNEL_HANDLER.update() - self.assertTrue(testchannel.connect(self.player)) - self.assertTrue(testchannel.has_connection(self.player)) + self.assertTrue(testchannel.connect(self.account)) + self.assertTrue(testchannel.has_connection(self.account)) a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d - self.set_cmdsets(self.player, a, b, c, d) - deferred = cmdhandler.get_and_merge_cmdsets(self.session, self.session, self.player, self.char1, "session", "") + self.set_cmdsets(self.account, a, b, c, d) + deferred = cmdhandler.get_and_merge_cmdsets(self.session, self.session, self.account, self.char1, "session", "") def _callback(cmdset): - pcmdset = PlayerCmdSet() + pcmdset = AccountCmdSet() pcmdset.at_cmdset_creation() pcmds = [cmd.key for cmd in pcmdset.commands] + ["a", "b", "c", "d"] + ["out"] self.assertTrue(all(cmd.key or hasattr(cmd, "is_channel") in pcmds for cmd in cmdset.commands)) diff --git a/evennia/comms/channelhandler.py b/evennia/comms/channelhandler.py index 520b0934d9..ff4166e132 100644 --- a/evennia/comms/channelhandler.py +++ b/evennia/comms/channelhandler.py @@ -111,7 +111,7 @@ class ChannelCommand(command.Command): self.msg(string % channelkey) return if msg == "on": - caller = caller if not hasattr(caller, 'player') else caller.player + caller = caller if not hasattr(caller, 'account') else caller.account unmuted = channel.unmute(caller) if unmuted: self.msg("You start listening to %s." % channel) @@ -119,7 +119,7 @@ class ChannelCommand(command.Command): self.msg("You were already listening to %s." % channel) return if msg == "off": - caller = caller if not hasattr(caller, 'player') else caller.player + caller = caller if not hasattr(caller, 'account') else caller.account muted = channel.mute(caller) if muted: self.msg("You stop listening to %s." % channel) @@ -133,7 +133,7 @@ class ChannelCommand(command.Command): if "[-]" in line else line for line in lines)) tail_log_file(log_file, self.history_start, 20, callback=send_msg) else: - caller = caller if not hasattr(caller, 'player') else caller.player + caller = caller if not hasattr(caller, 'account') else caller.account if caller in channel.mutelist: self.msg("You currently have %s muted." % channel) return @@ -144,7 +144,7 @@ class ChannelCommand(command.Command): Let users know that this command is for communicating on a channel. Args: - caller (TypedObject): A Character or Player who has entered an ambiguous command. + caller (TypedObject): A Character or Account who has entered an ambiguous command. Returns: A string with identifying information to disambiguate the object, conventionally with a preceding space. diff --git a/evennia/comms/comms.py b/evennia/comms/comms.py index 6cc9f5f6b3..f83f128107 100644 --- a/evennia/comms/comms.py +++ b/evennia/comms/comms.py @@ -62,26 +62,26 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): def has_connection(self, subscriber): """ - Checks so this player is actually listening + Checks so this account is actually listening to this channel. Args: - subscriber (Player or Object): Entity to check. + subscriber (Account or Object): Entity to check. Returns: has_sub (bool): Whether the subscriber is subscribing to this channel or not. Notes: - This will first try Player subscribers and only try Object - if the Player fails. + This will first try Account subscribers and only try Object + if the Account fails. """ has_sub = self.subscriptions.has(subscriber) - if not has_sub and hasattr(subscriber, "player"): + if not has_sub and hasattr(subscriber, "account"): # it's common to send an Object when we - # by default only allow Players to subscribe. - has_sub = self.subscriptions.has(subscriber.player) + # by default only allow Accounts to subscribe. + has_sub = self.subscriptions.has(subscriber.account) return has_sub @property @@ -94,7 +94,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): listening = [ob for ob in subs if ob.is_connected and ob not in self.mutelist] if subs: # display listening subscribers in bold - string = ", ".join([player.key if player not in listening else "|w%s|n" % player.key for player in subs]) + string = ", ".join([account.key if account not in listening else "|w%s|n" % account.key for account in subs]) else: string = "" return string @@ -106,7 +106,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): but may use channel commands. Args: - subscriber (Object or Player): Subscriber to mute. + subscriber (Object or Account): Subscriber to mute. **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default). @@ -124,7 +124,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): but may use channel commands. Args: - subscriber (Object or Player): The subscriber to unmute. + subscriber (Object or Account): The subscriber to unmute. **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default). @@ -141,7 +141,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): Connect the user to this channel. This checks access. Args: - subscriber (Player or Object): the entity to subscribe + subscriber (Account or Object): the entity to subscribe to this channel. **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default). @@ -171,7 +171,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): Disconnect entity from this channel. Args: - subscriber (Player of Object): the + subscriber (Account of Object): the entity to disconnect. **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default). @@ -265,7 +265,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): This is also where logging happens, if enabled. """ - # get all players or objects connected to this channel and send to them + # get all accounts or objects connected to this channel and send to them if online: subs = self.subscriptions.online() else: @@ -276,7 +276,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): continue try: # note our addition of the from_channel keyword here. This could be checked - # by a custom player.msg() to treat channel-receives differently. + # by a custom account.msg() to treat channel-receives differently. entity.msg(msgobj.message, from_obj=msgobj.senders, options={"from_channel": self.id}) except AttributeError as e: logger.log_trace("%s\nCannot send msg to '%s'." % (e, entity)) @@ -288,7 +288,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): def msg(self, msgobj, header=None, senders=None, sender_strings=None, keep_log=None, online=False, emit=False, external=False): """ - Send the given message to all players connected to channel. Note that + Send the given message to all accounts connected to channel. Note that no permission-checking is done here; it is assumed to have been done before calling this method. The optional keywords are not used if persistent is False. @@ -300,10 +300,10 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): (if persistent=False) or it will be used together with `header` and `senders` keywords to create a Msg instance on the fly. header (str, optional): A header for building the message. - senders (Object, Player or list, optional): Optional if persistent=False, used + senders (Object, Account or list, optional): Optional if persistent=False, used to build senders for the message. sender_strings (list, optional): Name strings of senders. Used for external - connections where the sender is not a player or object. + connections where the sender is not an account or object. When this is defined, external will be assumed. keep_log (bool or None, optional): This allows to temporarily change the logging status of this channel message. If `None`, the Channel's `keep_log` Attribute will @@ -311,8 +311,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): message only (note that for unlogged channels, a `True` value here will create a new log file only for this message). online (bool, optional) - If this is set true, only messages people who are - online. Otherwise, messages all players connected. This can - make things faster, but may not trigger listeners on players + online. Otherwise, messages all accounts connected. This can + make things faster, but may not trigger listeners on accounts that are offline. emit (bool, optional) - Signals to the message formatter that this message is not to be directly associated with a name. @@ -389,7 +389,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): Notes: This function exists separately so that external sources can use it to format source names in the same manner as - normal object/player names. + normal object/account names. """ if not senders: @@ -432,7 +432,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): """ Hook method. Used for formatting external messages. This is needed as a separate operation because the senders of external - messages may not be in-game objects/players, and so cannot + messages may not be in-game objects/accounts, and so cannot have things like custom user preferences. Args: @@ -495,7 +495,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): def post_join_channel(self, joiner, **kwargs): """ - Hook method. Runs right after an object or player joins a channel. + Hook method. Runs right after an object or account joins a channel. Args: joiner (object): The joining object. @@ -523,7 +523,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): def post_leave_channel(self, leaver, **kwargs): """ - Hook method. Runs right after an object or player leaves a channel. + Hook method. Runs right after an object or account leaves a channel. Args: leaver (object): The leaving object. diff --git a/evennia/comms/managers.py b/evennia/comms/managers.py index e778bc20ae..ce500f3ba4 100644 --- a/evennia/comms/managers.py +++ b/evennia/comms/managers.py @@ -10,7 +10,7 @@ from evennia.typeclasses.managers import (TypedObjectManager, TypeclassManager) from evennia.utils import logger _GA = object.__getattribute__ -_PlayerDB = None +_AccountDB = None _ObjectDB = None _ChannelDB = None _SESSIONS = None @@ -57,7 +57,7 @@ def dbref(inp, reqhash=True): def identify_object(inp): """ - Helper function. Identifies if an object is a player or an object; + Helper function. Identifies if an object is an account or an object; return its database model Args: @@ -65,14 +65,14 @@ def identify_object(inp): Returns: identified (tuple): This is a tuple with (`inp`, identifier) - where `identifier` is one of "player", "object", "channel", + where `identifier` is one of "account", "object", "channel", "string", "dbref" or None. """ if hasattr(inp, "__dbclass__"): clsname = inp.__dbclass__.__name__ - if clsname == "PlayerDB": - return inp, "player" + if clsname == "AccountDB": + return inp, "account" elif clsname == "ObjectDB": return inp ,"object" elif clsname == "ChannelDB": @@ -85,14 +85,14 @@ def identify_object(inp): return inp, None -def to_object(inp, objtype='player'): +def to_object(inp, objtype='account'): """ - Locates the object related to the given playername or channel key. + Locates the object related to the given accountname or channel key. If input was already the correct object, return it. Args: inp (any): The input object/string - objtype (str): Either 'player' or 'channel'. + objtype (str): Either 'account' or 'channel'. Returns: obj (object): The correct object related to `inp`. @@ -101,17 +101,17 @@ def to_object(inp, objtype='player'): obj, typ = identify_object(inp) if typ == objtype: return obj - if objtype == 'player': + if objtype == 'account': if typ == 'object': - return obj.player + return obj.account if typ == 'string': - return _PlayerDB.objects.get(user_username__iexact=obj) + return _AccountDB.objects.get(user_username__iexact=obj) if typ == 'dbref': - return _PlayerDB.objects.get(id=obj) + return _AccountDB.objects.get(id=obj) logger.log_err("%s %s %s %s %s", objtype, inp, obj, typ, type(inp)) raise CommError() elif objtype == 'object': - if typ == 'player': + if typ == 'account': return obj.obj if typ == 'string': return _ObjectDB.objects.get(db_key__iexact=obj) @@ -158,7 +158,7 @@ class MsgManager(TypedObjectManager): Returns: identified (tuple): This is a tuple with (`inp`, identifier) - where `identifier` is one of "player", "object", "channel", + where `identifier` is one of "account", "object", "channel", "string", "dbref" or None. """ @@ -183,10 +183,10 @@ class MsgManager(TypedObjectManager): def get_messages_by_sender(self, sender, exclude_channel_messages=False): """ Get all messages sent by one entity - this could be either a - player or an object + account or an object Args: - sender (Player or Object): The sender of the message. + sender (Account or Object): The sender of the message. exclude_channel_messages (bool, optional): Only return messages not aimed at a channel (that is, private tells for example) @@ -200,9 +200,9 @@ class MsgManager(TypedObjectManager): obj, typ = identify_object(sender) if exclude_channel_messages: # explicitly exclude channel recipients - if typ == 'player': - return list(self.filter(db_sender_players=obj, - db_receivers_channels__isnull=True).exclude(db_hide_from_players=obj)) + if typ == 'account': + return list(self.filter(db_sender_accounts=obj, + db_receivers_channels__isnull=True).exclude(db_hide_from_accounts=obj)) elif typ == 'object': return list(self.filter(db_sender_objects=obj, db_receivers_channels__isnull=True).exclude(db_hide_from_objects=obj)) @@ -210,8 +210,8 @@ class MsgManager(TypedObjectManager): raise CommError else: # get everything, channel or not - if typ == 'player': - return list(self.filter(db_sender_players=obj).exclude(db_hide_from_players=obj)) + if typ == 'account': + return list(self.filter(db_sender_accounts=obj).exclude(db_hide_from_accounts=obj)) elif typ == 'object': return list(self.filter(db_sender_objects=obj).exclude(db_hide_from_objects=obj)) else: @@ -222,7 +222,7 @@ class MsgManager(TypedObjectManager): Get all messages sent to one given recipient. Args: - recipient (Object, Player or Channel): The recipient of the messages to search for. + recipient (Object, Account or Channel): The recipient of the messages to search for. Returns: messages (list): Matching messages. @@ -232,8 +232,8 @@ class MsgManager(TypedObjectManager): """ obj, typ = identify_object(recipient) - if typ == 'player': - return list(self.filter(db_receivers_players=obj).exclude(db_hide_from_players=obj)) + if typ == 'account': + return list(self.filter(db_receivers_accounts=obj).exclude(db_hide_from_accounts=obj)) elif typ == 'object': return list(self.filter(db_receivers_objects=obj).exclude(db_hide_from_objects=obj)) elif typ == 'channel': @@ -260,9 +260,9 @@ class MsgManager(TypedObjectManager): one of the arguments must be given to do a search. Args: - sender (Object or Player, optional): Get messages sent by a particular player or object - receiver (Object, Player or Channel, optional): Get messages - received by a certain player,object or channel + sender (Object or Account, optional): Get messages sent by a particular account or object + receiver (Object, Account or Channel, optional): Get messages + received by a certain account,object or channel freetext (str): Search for a text string in a message. NOTE: This can potentially be slow, so make sure to supply one of the other arguments to limit the search. @@ -287,16 +287,16 @@ class MsgManager(TypedObjectManager): # filter by sender sender, styp = identify_object(sender) - if styp == 'player': - sender_restrict = Q(db_sender_players=sender) & ~Q(db_hide_from_players=sender) + if styp == 'account': + sender_restrict = Q(db_sender_accounts=sender) & ~Q(db_hide_from_accounts=sender) elif styp == 'object': sender_restrict = Q(db_sender_objects=sender) & ~Q(db_hide_from_objects=sender) else: sender_restrict = Q() # filter by receiver receiver, rtyp = identify_object(receiver) - if rtyp == 'player': - receiver_restrict = Q(db_receivers_players=receiver) & ~Q(db_hide_from_players=receiver) + if rtyp == 'account': + receiver_restrict = Q(db_receivers_accounts=receiver) & ~Q(db_hide_from_accounts=receiver) elif rtyp == 'object': receiver_restrict = Q(db_receivers_objects=receiver) & ~Q(db_hide_from_objects=receiver) elif rtyp == 'channel': @@ -369,14 +369,14 @@ class ChannelDBManager(TypedObjectManager): Return all channels a given entity is subscribed to. Args: - subscriber (Object or Player): The one subscribing. + subscriber (Object or Account): The one subscribing. Returns: subscriptions (list): Channel subscribed to. """ clsname = subscriber.__dbclass__.__name__ - if clsname == "PlayerDB": + if clsname == "AccountDB": return subscriber.subscription_set.all() if clsname == "ObjectDB": return subscriber.object_subscription_set.all() diff --git a/evennia/comms/models.py b/evennia/comms/models.py index 1c696e35bd..914b81da5f 100644 --- a/evennia/comms/models.py +++ b/evennia/comms/models.py @@ -13,7 +13,7 @@ For non-persistent (and slightly faster) use one can also use the TempMsg, which mimics the Msg API but without actually saving to the database. -Channels are central objects that act as targets for Msgs. Players can +Channels are central objects that act as targets for Msgs. Accounts can connect to channels by use of a ChannelConnect object (this object is necessary to easily be able to delete connections on the fly). """ @@ -48,16 +48,16 @@ _CHANNELHANDLER = None class Msg(SharedMemoryModel): """ A single message. This model describes all ooc messages - sent in-game, both to channels and between players. + sent in-game, both to channels and between accounts. The Msg class defines the following database fields (all accessed via specific handler methods): - - db_sender_players: Player senders + - db_sender_accounts: Account senders - db_sender_objects: Object senders - db_sender_scripts: Script senders - db_sender_external: External senders (defined as string names) - - db_receivers_players: Receiving players + - db_receivers_accounts: Receiving accounts - db_receivers_objects: Receiving objects - db_receivers_scripts: Receiveing scripts - db_receivers_channels: Receiving channels @@ -77,7 +77,7 @@ class Msg(SharedMemoryModel): # These databse fields are all set using their corresponding properties, # named same as the field, but withtout the db_* prefix. - # Sender is either a player, an object or an external sender, like + # Sender is either an account, an object or an external sender, like # an IRC channel; normally there is only one, but if co-modification of # a message is allowed, there may be more than one "author" db_sender_accounts = models.ManyToManyField("accounts.AccountDB", related_name='sender_account_set', @@ -114,8 +114,8 @@ class Msg(SharedMemoryModel): db_lock_storage = models.TextField('locks', blank=True, help_text='access locks on this message.') - # these can be used to filter/hide a given message from supplied objects/players/channels - db_hide_from_players = models.ManyToManyField("players.PlayerDB", related_name='hide_from_players_set', blank=True) + # these can be used to filter/hide a given message from supplied objects/accounts/channels + db_hide_from_accounts = models.ManyToManyField("accounts.AccountDB", related_name='hide_from_accounts_set', blank=True) db_hide_from_accounts = models.ManyToManyField("accounts.AccountDB", related_name='hide_from_accounts_set', blank=True) db_hide_from_objects = models.ManyToManyField("objects.ObjectDB", related_name='hide_from_objects_set', blank=True) @@ -156,7 +156,7 @@ class Msg(SharedMemoryModel): #@property def __senders_get(self): "Getter. Allows for value = self.sender" - return list(self.db_sender_players.all()) + \ + return list(self.db_sender_accounts.all()) + \ list(self.db_sender_objects.all()) + \ list(self.db_sender_scripts.all()) + \ self.extra_senders @@ -177,15 +177,15 @@ class Msg(SharedMemoryModel): clsname = sender.__dbclass__.__name__ if clsname == "ObjectDB": self.db_sender_objects.add(sender) - elif clsname == "PlayerDB": - self.db_sender_players.add(sender) + elif clsname == "AccountDB": + self.db_sender_accounts.add(sender) elif clsname == "ScriptDB": self.db_sender_scripts.add(sender) #@sender.deleter def __senders_del(self): "Deleter. Clears all senders" - self.db_sender_players.clear() + self.db_sender_accounts.clear() self.db_sender_objects.clear() self.db_sender_scripts.clear() self.db_sender_external = "" @@ -198,7 +198,7 @@ class Msg(SharedMemoryModel): Remove a single sender or a list of senders. Args: - senders (Player, Object, str or list): Senders to remove. + senders (Account, Object, str or list): Senders to remove. """ for sender in make_iter(senders): @@ -212,19 +212,19 @@ class Msg(SharedMemoryModel): clsname = sender.__dbclass__.__name__ if clsname == "ObjectDB": self.db_sender_objects.remove(sender) - elif clsname == "PlayerDB": - self.db_sender_players.remove(sender) + elif clsname == "AccountDB": + self.db_sender_accounts.remove(sender) elif clsname == "ScriptDB": - self.db_sender_players.remove(sender) + self.db_sender_accounts.remove(sender) # receivers property #@property def __receivers_get(self): """ Getter. Allows for value = self.receivers. - Returns four lists of receivers: players, objects, scripts and channels. + Returns four lists of receivers: accounts, objects, scripts and channels. """ - return list(self.db_receivers_players.all()) + \ + return list(self.db_receivers_accounts.all()) + \ list(self.db_receivers_objects.all()) + \ list(self.db_receivers_scripts.all()) + \ list(self.db_receivers_channels.all()) @@ -243,8 +243,8 @@ class Msg(SharedMemoryModel): clsname = receiver.__dbclass__.__name__ if clsname == "ObjectDB": self.db_receivers_objects.add(receiver) - elif clsname == "PlayerDB": - self.db_receivers_players.add(receiver) + elif clsname == "AccountDB": + self.db_receivers_accounts.add(receiver) elif clsname == "ScriptDB": self.db_receivers_scripts.add(receiver) elif clsname == "ChannelDB": @@ -254,7 +254,7 @@ class Msg(SharedMemoryModel): #@receivers.deleter def __receivers_del(self): "Deleter. Clears all receivers" - self.db_receivers_players.clear() + self.db_receivers_accounts.clear() self.db_receivers_objects.clear() self.db_receivers_scripts.clear() self.db_receivers_channels.clear() @@ -266,7 +266,7 @@ class Msg(SharedMemoryModel): Remove a single receiver or a list of receivers. Args: - receivers (Player, Object, Script, Channel or list): Receiver to remove. + receivers (Account, Object, Script, Channel or list): Receiver to remove. """ for receiver in make_iter(receivers): @@ -277,8 +277,8 @@ class Msg(SharedMemoryModel): clsname = receiver.__dbclass__.__name__ if clsname == "ObjectDB": self.db_receivers_objects.remove(receiver) - elif clsname == "PlayerDB": - self.db_receivers_players.remove(receiver) + elif clsname == "AccountDB": + self.db_receivers_accounts.remove(receiver) elif clsname == "ScriptDB": self.db_receivers_scripts.remove(receiver) elif clsname == "ChannelDB": @@ -309,9 +309,9 @@ class Msg(SharedMemoryModel): def __hide_from_get(self): """ Getter. Allows for value = self.hide_from. - Returns 3 lists of players, objects and channels + Returns 3 lists of accounts, objects and channels """ - return self.db_hide_from_players.all(), self.db_hide_from_objects.all(), self.db_hide_from_channels.all() + return self.db_hide_from_accounts.all(), self.db_hide_from_objects.all(), self.db_hide_from_channels.all() #@hide_from_sender.setter def __hide_from_set(self, hiders): @@ -322,8 +322,8 @@ class Msg(SharedMemoryModel): if not hasattr(hider, "__dbclass__"): raise ValueError("This is a not a typeclassed object!") clsname = hider.__dbclass__.__name__ - if clsname == "PlayerDB": - self.db_hide_from_players.add(hider.__dbclass__) + if clsname == "AccountDB": + self.db_hide_from_accounts.add(hider.__dbclass__) elif clsname == "ObjectDB": self.db_hide_from_objects.add(hider.__dbclass__) elif clsname == "ChannelDB": @@ -332,7 +332,7 @@ class Msg(SharedMemoryModel): #@hide_from_sender.deleter def __hide_from_del(self): "Deleter. Allows for del self.hide_from_senders" - self.db_hide_from_players.clear() + self.db_hide_from_accounts.clear() self.db_hide_from_objects.clear() self.db_hide_from_channels.clear() self.save() @@ -353,7 +353,7 @@ class Msg(SharedMemoryModel): Checks lock access. Args: - accessing_obj (Object or Player): The object trying to gain access. + accessing_obj (Object or Account): The object trying to gain access. access_type (str, optional): The type of lock access to check. default (bool): Fallback to use if `access_type` lock is not defined. @@ -383,13 +383,13 @@ class TempMsg(object): Args: senders (any or list, optional): Senders of the message. - receivers (Player, Object, Channel or list, optional): Receivers of this message. + receivers (Account, Object, Channel or list, optional): Receivers of this message. channels (Channel or list, optional): Channels to send to. message (str, optional): Message to send. header (str, optional): Header of message. type (str, optional): Message class, if any. lockstring (str, optional): Lock for the message. - hide_from (Player, Object, Channel or list, optional): Entities to hide this message from. + hide_from (Account, Object, Channel or list, optional): Entities to hide this message from. """ self.senders = senders and make_iter(senders) or [] @@ -419,7 +419,7 @@ class TempMsg(object): Remove a sender or a list of senders. Args: - sender (Object, Player, str or list): Senders to remove. + sender (Object, Account, str or list): Senders to remove. """ for o in make_iter(sender): @@ -433,7 +433,7 @@ class TempMsg(object): Remove a receiver or a list of receivers Args: - receiver (Object, Player, Channel, str or list): Receivers to remove. + receiver (Object, Account, Channel, str or list): Receivers to remove. """ for o in make_iter(receiver): @@ -447,7 +447,7 @@ class TempMsg(object): Checks lock access. Args: - accessing_obj (Object or Player): The object trying to gain access. + accessing_obj (Object or Account): The object trying to gain access. access_type (str, optional): The type of lock access to check. default (bool): Fallback to use if `access_type` lock is not defined. @@ -469,7 +469,7 @@ class SubscriptionHandler(object): """ This handler manages subscriptions to the channel and hides away which type of entity is - subscribing (Player or Object) + subscribing (Account or Object) """ def __init__(self, obj): """ @@ -483,7 +483,7 @@ class SubscriptionHandler(object): self._cache = None def _recache(self): - self._cache = {player : True for player in self.obj.db_subscriptions.all()} + self._cache = {account : True for account in self.obj.db_subscriptions.all()} self._cache.update({obj : True for obj in self.obj.db_object_subscriptions.all()}) def has(self, entity): @@ -491,12 +491,12 @@ class SubscriptionHandler(object): Check if the given entity subscribe to this channel Args: - entity (str, Player or Object): The entity to return. If + entity (str, Account or Object): The entity to return. If a string, it assumed to be the key or the #dbref of the entity. Returns: - subscriber (Player, Object or None): The given + subscriber (Account, Object or None): The given subscriber. """ @@ -509,7 +509,7 @@ class SubscriptionHandler(object): Subscribe an entity to this channel. Args: - entity (Player, Object or list): The entity or + entity (Account, Object or list): The entity or list of entities to subscribe to this channel. Note: @@ -527,7 +527,7 @@ class SubscriptionHandler(object): # chooses the right type if clsname == "ObjectDB": self.obj.db_object_subscriptions.add(subscriber) - elif clsname == "PlayerDB": + elif clsname == "AccountDB": self.obj.db_subscriptions.add(subscriber) _CHANNELHANDLER.cached_cmdsets.pop(subscriber, None) self._recache() @@ -537,7 +537,7 @@ class SubscriptionHandler(object): Remove a subscriber from the channel. Args: - entity (Player, Object or list): The entity or + entity (Account, Object or list): The entity or entities to un-subscribe from the channel. """ @@ -548,7 +548,7 @@ class SubscriptionHandler(object): if subscriber: clsname = subscriber.__dbclass__.__name__ # chooses the right type - if clsname == "PlayerDB": + if clsname == "AccountDB": self.obj.db_subscriptions.remove(entity) elif clsname == "ObjectDB": self.obj.db_object_subscriptions.remove(entity) @@ -561,7 +561,7 @@ class SubscriptionHandler(object): Returns: subscribers (list): The subscribers. This - may be a mix of Players and Objects! + may be a mix of Accounts and Objects! """ if self._cache is None: @@ -570,17 +570,17 @@ class SubscriptionHandler(object): def online(self): """ - Get all online players from our cache + Get all online accounts from our cache Returns: subscribers (list): Subscribers who are online or - are puppeted by an online player. + are puppeted by an online account. """ subs = [] for obj in self.all(): - if hasattr(obj, 'player'): - if not obj.player: + if hasattr(obj, 'account'): + if not obj.account: continue - obj = obj.player + obj = obj.account if not obj.is_connected: continue subs.append(obj) @@ -604,7 +604,7 @@ class ChannelDB(TypedObject): The Channel class defines the following database fields beyond the ones inherited from TypedObject: - - db_subscriptions: The Player subscriptions (this is the most + - db_subscriptions: The Account subscriptions (this is the most usual case, named this way for legacy. - db_object_subscriptions: The Object subscriptions. diff --git a/evennia/contrib/barter.py b/evennia/contrib/barter.py index a7ec15afcf..9932cc769a 100644 --- a/evennia/contrib/barter.py +++ b/evennia/contrib/barter.py @@ -411,7 +411,7 @@ class CmdTradeBase(Command): if ':' in self.args: self.args, self.emote = [part.strip() for part in self.args.rsplit(":", 1)] self.str_caller = 'You say, "' + self.emote + '"\n [%s]' - if self.caller.has_player: + if self.caller.has_account: self.str_other = '|c%s|n says, "' % self.caller.key + self.emote + '"\n [%s]' else: self.str_other = '%s says, "' % self.caller.key + self.emote + '"\n [%s]' @@ -766,7 +766,7 @@ class CmdTrade(Command): if ':' in self.args: self.args, emote = [part.strip() for part in self.args.rsplit(":", 1)] selfemote = 'You say, "%s"\n ' % emote - if self.caller.has_player: + if self.caller.has_account: theiremote = '|c%s|n says, "%s"\n ' % (self.caller.key, emote) else: theiremote = '%s says, "%s"\n ' % (self.caller.key, emote) diff --git a/evennia/contrib/chargen.py b/evennia/contrib/chargen.py index ea1cabb4df..6794003879 100644 --- a/evennia/contrib/chargen.py +++ b/evennia/contrib/chargen.py @@ -7,8 +7,8 @@ necessary anymore - the ooclook and @charcreate commands in that mode replaces this module with better functionality. This remains here for inspiration. -This is a simple character creation commandset for the Player level. -It shows some more info and gives the Player the option to create a +This is a simple character creation commandset for the Account level. +It shows some more info and gives the Account the option to create a character without any more customizations than their name (further options are unique for each game anyway). @@ -19,7 +19,7 @@ cmdset. Installation: Import this module to `mygame/commands/default_cmdsets.py` and -add `chargen.OOCCMdSetCharGen` to the `PlayerCmdSet` class +add `chargen.OOCCMdSetCharGen` to the `AccountCmdSet` class (it says where to add it). Reload. """ @@ -39,7 +39,7 @@ class CmdOOCLook(default_cmds.CmdLook): look look - This is an OOC version of the look command. Since a Player doesn't + This is an OOC version of the look command. Since an Account doesn't have an in-game existence, there is no concept of location or "self". @@ -56,24 +56,24 @@ class CmdOOCLook(default_cmds.CmdLook): """ Implements the ooc look command - We use an attribute _character_dbrefs on the player in order + We use an attribute _character_dbrefs on the account in order to figure out which characters are "theirs". A drawback of this is that only the CmdCharacterCreate command adds this attribute, - and thus e.g. player #1 will not be listed (although it will work). + and thus e.g. account #1 will not be listed (although it will work). Existence in this list does not depend on puppeting rights though, that is checked by the @ic command directly. """ - # making sure caller is really a player + # making sure caller is really an account self.character = None if utils.inherits_from(self.caller, "evennia.objects.objects.Object"): - # An object of some type is calling. Convert to player. + # An object of some type is calling. Convert to account. self.character = self.caller - if hasattr(self.caller, "player"): - self.caller = self.caller.player + if hasattr(self.caller, "account"): + self.caller = self.caller.account if not self.character: - # ooc mode, we are players + # ooc mode, we are accounts avail_chars = self.caller.db._character_dbrefs if self.args: @@ -139,13 +139,13 @@ class CmdOOCCharacterCreate(Command): attribute on ourselves to remember it. """ - # making sure caller is really a player + # making sure caller is really an account self.character = None if utils.inherits_from(self.caller, "evennia.objects.objects.Object"): - # An object of some type is calling. Convert to player. + # An object of some type is calling. Convert to account. self.character = self.caller - if hasattr(self.caller, "player"): - self.caller = self.caller.player + if hasattr(self.caller, "account"): + self.caller = self.caller.account if not self.args: self.caller.msg("Usage: create ") @@ -161,7 +161,7 @@ class CmdOOCCharacterCreate(Command): if not new_character: self.caller.msg("|rThe Character couldn't be created. This is a bug. Please contact an admin.") return - # make sure to lock the character to only be puppeted by this player + # make sure to lock the character to only be puppeted by this account new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" % (new_character.id, self.caller.id)) @@ -175,7 +175,7 @@ class CmdOOCCharacterCreate(Command): self.caller.msg("|gThe character |c%s|g was successfully created!" % charname) -class OOCCmdSetCharGen(default_cmds.PlayerCmdSet): +class OOCCmdSetCharGen(default_cmds.AccountCmdSet): """ Extends the default OOC cmdset. """ diff --git a/evennia/contrib/egi_client/client.py b/evennia/contrib/egi_client/client.py index caab2bea71..39c684f8eb 100644 --- a/evennia/contrib/egi_client/client.py +++ b/evennia/contrib/egi_client/client.py @@ -13,7 +13,7 @@ from twisted.web.http_headers import Headers from twisted.web.iweb import IBodyProducer from zope.interface import implements -from evennia.players.models import PlayerDB +from evennia.accounts.models import AccountDB from evennia.server.sessionhandler import SESSIONS from evennia.utils import get_evennia_version, logger @@ -97,8 +97,8 @@ class EvenniaGameIndexClient(object): 'web_client_url': egi_config.get('web_client_url') or '', # Game stats - 'connected_player_count': SESSIONS.player_count(), - 'total_player_count': PlayerDB.objects.num_total_players() or 0, + 'connected_account_count': SESSIONS.account_count(), + 'total_account_count': AccountDB.objects.num_total_accounts() or 0, # System info 'evennia_version': get_evennia_version(), diff --git a/evennia/contrib/email_login.py b/evennia/contrib/email_login.py index 488982e73e..01d08abab4 100644 --- a/evennia/contrib/email_login.py +++ b/evennia/contrib/email_login.py @@ -31,7 +31,7 @@ the module given by settings.CONNECTION_SCREEN_MODULE. """ import re from django.conf import settings -from evennia.players.models import PlayerDB +from evennia.accounts.models import AccountDB from evennia.objects.models import ObjectDB from evennia.server.models import ServerConfig @@ -77,7 +77,7 @@ class CmdUnconnectedConnect(MuxCommand): have a unique position in that their `func()` receives a session object instead of a `source_object` like all other types of logged-in commands (this is because - there is no object yet before the player has logged in) + there is no object yet before the account has logged in) """ session = self.caller @@ -90,22 +90,22 @@ class CmdUnconnectedConnect(MuxCommand): password = arglist[1] # Match an email address to an account. - player = PlayerDB.objects.get_player_from_email(email) - # No playername match - if not player: + account = AccountDB.objects.get_account_from_email(email) + # No accountname match + if not account: string = "The email '%s' does not match any accounts." % email string += "\n\r\n\rIf you are new you should first create a new account " string += "using the 'create' command." session.msg(string) return # We have at least one result, so we can check the password. - if not player[0].check_password(password): + if not account[0].check_password(password): session.msg("Incorrect password.") return # Check IP and/or name bans bans = ServerConfig.objects.conf("server_bans") - if bans and (any(tup[0] == player.name for tup in bans) or + if bans and (any(tup[0] == account.name for tup in bans) or any(tup[2].match(session.address[0]) for tup in bans if tup[2])): # this is a banned IP or name! string = "|rYou have been banned and cannot continue from here." @@ -115,7 +115,7 @@ class CmdUnconnectedConnect(MuxCommand): return # actually do the login. This will call all hooks. - session.sessionhandler.login(session, player) + session.sessionhandler.login(session, account) class CmdUnconnectedCreate(MuxCommand): @@ -123,9 +123,9 @@ class CmdUnconnectedCreate(MuxCommand): Create a new account. Usage (at login screen): - create \"playername\" + create \"accountname\" - This creates a new player account. + This creates a new account account. """ key = "create" @@ -134,36 +134,36 @@ class CmdUnconnectedCreate(MuxCommand): def parse(self): """ - The parser must handle the multiple-word player + The parser must handle the multiple-word account name enclosed in quotes: connect "Long name with many words" my@myserv.com mypassw """ super(CmdUnconnectedCreate, self).parse() - self.playerinfo = [] + self.accountinfo = [] if len(self.arglist) < 3: return if len(self.arglist) > 3: - # this means we have a multi_word playername. pop from the back. + # this means we have a multi_word accountname. pop from the back. password = self.arglist.pop() email = self.arglist.pop() - # what remains is the playername. - playername = " ".join(self.arglist) + # what remains is the accountname. + accountname = " ".join(self.arglist) else: - playername, email, password = self.arglist + accountname, email, password = self.arglist - playername = playername.replace('"', '') # remove " - playername = playername.replace("'", "") - self.playerinfo = (playername, email, password) + accountname = accountname.replace('"', '') # remove " + accountname = accountname.replace("'", "") + self.accountinfo = (accountname, email, password) def func(self): """Do checks and create account""" session = self.caller try: - playername, email, password = self.playerinfo + accountname, email, password = self.accountinfo except ValueError: - string = "\n\r Usage (without <>): create \"\" " + string = "\n\r Usage (without <>): create \"\" " session.msg(string) return if not email or not password: @@ -174,26 +174,26 @@ class CmdUnconnectedCreate(MuxCommand): session.msg("'%s' is not a valid e-mail address." % email) return # sanity checks - if not re.findall(r"^[\w. @+\-']+$", playername) or not (0 < len(playername) <= 30): + if not re.findall(r"^[\w. @+\-']+$", accountname) or not (0 < len(accountname) <= 30): # this echoes the restrictions made by django's auth # module (except not allowing spaces, for convenience of # logging in). - string = "\n\r Playername can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_/' only." + string = "\n\r Accountname can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_/' only." session.msg(string) return - # strip excessive spaces in playername - playername = re.sub(r"\s+", " ", playername).strip() - if PlayerDB.objects.filter(username__iexact=playername): - # player already exists (we also ignore capitalization here) - session.msg("Sorry, there is already a player with the name '%s'." % playername) + # strip excessive spaces in accountname + accountname = re.sub(r"\s+", " ", accountname).strip() + if AccountDB.objects.filter(username__iexact=accountname): + # account already exists (we also ignore capitalization here) + session.msg("Sorry, there is already an account with the name '%s'." % accountname) return - if PlayerDB.objects.get_player_from_email(email): - # email already set on a player - session.msg("Sorry, there is already a player with that email address.") + if AccountDB.objects.get_account_from_email(email): + # email already set on an account + session.msg("Sorry, there is already an account with that email address.") return - # Reserve playernames found in GUEST_LIST - if settings.GUEST_LIST and playername.lower() in (guest.lower() for guest in settings.GUEST_LIST): - string = "\n\r That name is reserved. Please choose another Playername." + # Reserve accountnames found in GUEST_LIST + if settings.GUEST_LIST and accountname.lower() in (guest.lower() for guest in settings.GUEST_LIST): + string = "\n\r That name is reserved. Please choose another Accountname." session.msg(string) return if not re.findall(r"^[\w. @+\-']+$", password) or not (3 < len(password)): @@ -205,7 +205,7 @@ class CmdUnconnectedCreate(MuxCommand): # Check IP and/or name bans bans = ServerConfig.objects.conf("server_bans") - if bans and (any(tup[0] == playername.lower() for tup in bans) or + if bans and (any(tup[0] == accountname.lower() for tup in bans) or any(tup[2].match(session.address) for tup in bans if tup[2])): # this is a banned IP or name! string = "|rYou have been banned and cannot continue from here." \ @@ -216,20 +216,20 @@ class CmdUnconnectedCreate(MuxCommand): # everything's ok. Create the new player account. try: - permissions = settings.PERMISSION_PLAYER_DEFAULT + permissions = settings.PERMISSION_ACCOUNT_DEFAULT typeclass = settings.BASE_CHARACTER_TYPECLASS - new_player = default_unloggedin._create_player(session, playername, password, permissions, email=email) - if new_player: + new_account = default_unloggedin._create_account(session, accountname, password, permissions, email=email) + if new_account: if MULTISESSION_MODE < 2: default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME) - default_unloggedin._create_character(session, new_player, typeclass, default_home, permissions) + default_unloggedin._create_character(session, new_account, typeclass, default_home, permissions) # tell the caller everything went well. string = "A new account '%s' was created. Welcome!" - if " " in playername: + if " " in accountname: string += "\n\nYou can now log in with the command 'connect \"%s\" '." else: string += "\n\nYou can now log with the command 'connect %s '." - session.msg(string % (playername, email)) + session.msg(string % (accountname, email)) except Exception: # We are in the middle between logged in and -not, so we have @@ -243,7 +243,7 @@ class CmdUnconnectedCreate(MuxCommand): class CmdUnconnectedQuit(MuxCommand): """ We maintain a different version of the `quit` command - here for unconnected players for the sake of simplicity. The logged in + here for unconnected accounts for the sake of simplicity. The logged in version is a bit more complicated. """ key = "quit" diff --git a/evennia/contrib/events/callbackhandler.py b/evennia/contrib/events/callbackhandler.py index 9d6c0b1111..cb53563003 100644 --- a/evennia/contrib/events/callbackhandler.py +++ b/evennia/contrib/events/callbackhandler.py @@ -88,7 +88,7 @@ class CallbackHandler(object): Args: callback_name (str): the name of the callback to add. code (str): the Python code associated with this callback. - author (Character or Player, optional): the author of the callback. + author (Character or Account, optional): the author of the callback. valid (bool, optional): should the callback be connected? parameters (str, optional): optional parameters. @@ -109,7 +109,7 @@ class CallbackHandler(object): callback_name (str): the name of the callback to edit. number (int): the callback number to be changed. code (str): the Python code associated with this callback. - author (Character or Player, optional): the author of the callback. + author (Character or Account, optional): the author of the callback. valid (bool, optional): should the callback be connected? Returns: diff --git a/evennia/contrib/events/commands.py b/evennia/contrib/events/commands.py index ddc42d42fa..bbbd07a14b 100644 --- a/evennia/contrib/events/commands.py +++ b/evennia/contrib/events/commands.py @@ -94,7 +94,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS): on user permission. Args: - caller (Object or Player): the caller asking for help on the command. + caller (Object or Account): the caller asking for help on the command. cmdset (CmdSet): the command set (if you need additional commands). Returns: diff --git a/evennia/contrib/events/scripts.py b/evennia/contrib/events/scripts.py index a61d308e59..50795abd99 100644 --- a/evennia/contrib/events/scripts.py +++ b/evennia/contrib/events/scripts.py @@ -200,7 +200,7 @@ class EventHandler(DefaultScript): obj (Object): the Evennia typeclassed object to be extended. callback_name (str): the name of the callback to add. code (str): the Python code associated with this callback. - author (Character or Player, optional): the author of the callback. + author (Character or Account, optional): the author of the callback. valid (bool, optional): should the callback be connected? parameters (str, optional): optional parameters. @@ -254,7 +254,7 @@ class EventHandler(DefaultScript): callback_name (str): the name of the callback to edit. number (int): the callback number to be changed. code (str): the Python code associated with this callback. - author (Character or Player, optional): the author of the callback. + author (Character or Account, optional): the author of the callback. valid (bool, optional): should the callback be connected? Raises: diff --git a/evennia/contrib/events/typeclasses.py b/evennia/contrib/events/typeclasses.py index e8b0bdb6fd..6f822d0778 100644 --- a/evennia/contrib/events/typeclasses.py +++ b/evennia/contrib/events/typeclasses.py @@ -81,7 +81,7 @@ This event is called when another character arrives in the location where the current character is. For instance, a puppeted character arrives in the shop of a shopkeeper (assuming the shopkeeper is a character). As its name suggests, this event can be very useful -to have NPC greeting one another, or players, who come to visit. +to have NPC greeting one another, or accounts, who come to visit. Variables you can use in this event: character: the character connected to this event. @@ -100,9 +100,9 @@ Variables you can use in this event: """ CHARACTER_PUPPETED = """ -When the character has been puppeted by a player. -This event is called when a player has just puppeted this character. -This can commonly happen when a player connects onto this character, +When the character has been puppeted by an account. +This event is called when an account has just puppeted this character. +This can commonly happen when an account connects onto this character, or when puppeting to a NPC or free character. Variables you can use in this event: @@ -151,8 +151,8 @@ Variables you can use in this event: CHARACTER_UNPUPPETED = """ When the character is about to be un-puppeted. -This event is called when a player is about to un-puppet the -character, which can happen if the player is disconnecting or +This event is called when an account is about to un-puppet the +character, which can happen if the account is disconnecting or changing puppets. Variables you can use in this event: @@ -244,8 +244,8 @@ class EventCharacter(DefaultCharacter): """ - if not source_location and self.location.has_player: - # This was created from nowhere and added to a player's + if not source_location and self.location.has_account: + # This was created from nowhere and added to an account's # inventory; it's probably the result of a create command. string = "You now have %s in your possession." % self.get_display_name(self.location) self.location.msg(string) @@ -357,11 +357,11 @@ class EventCharacter(DefaultCharacter): def at_post_puppet(self): """ Called just after puppeting has been completed and all - Player<->Object links have been established. + Account<->Object links have been established. Note: - You can use `self.player` and `self.sessions.get()` to get - player and sessions at this point; the last entry in the + You can use `self.account` and `self.sessions.get()` to get + account and sessions at this point; the last entry in the list from `self.sessions.get()` is the latest Session puppeting this Object. @@ -378,11 +378,11 @@ class EventCharacter(DefaultCharacter): def at_pre_unpuppet(self): """ Called just before beginning to un-connect a puppeting from - this Player. + this Account. Note: - You can use `self.player` and `self.sessions.get()` to get - player and sessions at this point; the last entry in the + You can use `self.account` and `self.sessions.get()` to get + account and sessions at this point; the last entry in the list from `self.sessions.get()` is the latest Session puppeting this Object. @@ -683,7 +683,7 @@ Variables you can use in this event: ROOM_PUPPETED_IN = """ After the character has been puppeted in this room. This event is called after a character has been puppeted in this -room. This can happen when a player, having connected, begins +room. This can happen when an account, having connected, begins to puppet a character. The character's location at this point, if it's a room, will see this event fire. @@ -733,7 +733,7 @@ Variables you can use in this event: ROOM_UNPUPPETED_IN = """ Before the character is un-puppeted in this room. This event is called before a character is un-puppeted in this -room. This can happen when a player, puppeting a character, is +room. This can happen when an account, puppeting a character, is disconnecting. The character's location at this point, if it's a room, will see this event fire. diff --git a/evennia/contrib/extended_room.py b/evennia/contrib/extended_room.py index ed0dc72de2..e4391ee531 100644 --- a/evennia/contrib/extended_room.py +++ b/evennia/contrib/extended_room.py @@ -277,7 +277,7 @@ class CmdExtendedLook(default_cmds.CmdLook): look look look - look * + look * Observes your location, details at your location or objects in your vicinity. """ @@ -315,7 +315,7 @@ class CmdExtendedLook(default_cmds.CmdLook): return if not hasattr(looking_at_obj, 'return_appearance'): - # this is likely due to us having a player instead + # this is likely due to us having an account instead looking_at_obj = looking_at_obj.character if not looking_at_obj.access(caller, "view"): caller.msg("Could not find '%s'." % args) diff --git a/evennia/contrib/mail.py b/evennia/contrib/mail.py index f071f44a7f..10d63b9155 100644 --- a/evennia/contrib/mail.py +++ b/evennia/contrib/mail.py @@ -7,12 +7,12 @@ A simple Brandymail style @mail system that uses the Msg class from Evennia Core Installation: import CmdMail from this module (from evennia.contrib.mail import CmdMail), - and add into the default Player or Character command set (self.add(CmdMail)). + and add into the default Account or Character command set (self.add(CmdMail)). """ import re -from evennia import ObjectDB, PlayerDB +from evennia import ObjectDB, AccountDB from evennia import default_cmds from evennia.utils import create, evtable, make_iter from evennia.comms.models import Msg @@ -27,17 +27,17 @@ class CmdMail(default_cmds.MuxCommand): Commands that allow either IC or OOC communications Usage: - @mail - Displays all the mail a player has in their mailbox + @mail - Displays all the mail an account has in their mailbox @mail <#> - Displays a specific message - @mail =/ - - Sends a message to the comma separated list of players. + @mail =/ + - Sends a message to the comma separated list of accounts. @mail/delete <#> - Deletes a specific message - @mail/forward =<#>[/] - - Forwards an existing message to the specified list of players, + @mail/forward =<#>[/] + - Forwards an existing message to the specified list of accounts, original message is delivered with optional Message prepended. @mail/reply <#>= @@ -65,7 +65,7 @@ class CmdMail(default_cmds.MuxCommand): Search a list of targets of the same type as caller. Args: - caller (Object or Player): The type of object to search. + caller (Object or Account): The type of object to search. namelist (list): List of strings for objects to search for. Returns: @@ -73,10 +73,10 @@ class CmdMail(default_cmds.MuxCommand): """ nameregex = r"|".join(r"^%s$" % re.escape(name) for name in make_iter(namelist)) - if hasattr(self.caller, "player") and self.caller.player: + if hasattr(self.caller, "account") and self.caller.account: matches = list(ObjectDB.objects.filter(db_key__iregex=nameregex)) else: - matches = list(PlayerDB.objects.filter(username__iregex=nameregex)) + matches = list(AccountDB.objects.filter(username__iregex=nameregex)) return matches def get_all_mail(self): @@ -89,10 +89,10 @@ class CmdMail(default_cmds.MuxCommand): # mail_messages = Msg.objects.get_by_tag(category="mail") # messages = [] try: - player = self.caller.player + account = self.caller.account except AttributeError: - player = self.caller - messages = Msg.objects.get_by_tag(category="mail").filter(db_receivers_players=player) + account = self.caller + messages = Msg.objects.get_by_tag(category="mail").filter(db_receivers_accounts=account) return messages def send_mail(self, recipients, subject, message, caller): @@ -100,10 +100,10 @@ class CmdMail(default_cmds.MuxCommand): Function for sending new mail. Also useful for sending notifications from objects or systems. Args: - recipients (list): list of Player or character objects to receive the newly created mails. + recipients (list): list of Account or character objects to receive the newly created mails. subject (str): The header or subject of the message to be delivered. message (str): The body of the message being sent. - caller (obj): The object (or Player or Character) that is sending the message. + caller (obj): The object (or Account or Character) that is sending the message. """ for recipient in recipients: recipient.msg("You have received a new @mail from %s" % caller) @@ -114,7 +114,7 @@ class CmdMail(default_cmds.MuxCommand): caller.msg("You sent your message.") return else: - caller.msg("No valid players found. Cannot send message.") + caller.msg("No valid accounts found. Cannot send message.") return def func(self): @@ -142,7 +142,7 @@ class CmdMail(default_cmds.MuxCommand): elif "forward" in self.switches: try: if not self.rhs: - self.caller.msg("Cannot forward a message without a player list. Please try again.") + self.caller.msg("Cannot forward a message without an account list. Please try again.") return elif not self.lhs: self.caller.msg("You must define a message to forward.") @@ -176,7 +176,7 @@ class CmdMail(default_cmds.MuxCommand): except IndexError: self.caller.msg("Message does not exixt.") except ValueError: - self.caller.msg("Usage: @mail/forward =<#>[/]") + self.caller.msg("Usage: @mail/forward =<#>[/]") elif "reply" in self.switches: try: if not self.rhs: diff --git a/evennia/contrib/mapbuilder.py b/evennia/contrib/mapbuilder.py index 71d883c671..2052223982 100644 --- a/evennia/contrib/mapbuilder.py +++ b/evennia/contrib/mapbuilder.py @@ -32,7 +32,7 @@ called and so on until the map is completed. Building instructions are passed the following arguments: x - The rooms position on the maps x axis y - The rooms position on the maps y axis - caller - The player calling the command + caller - The account calling the command iteration - The current iterations number (0, 1 or 2) room_dict - A dictionary containing room references returned by build functions where tuple coordinates are the keys (x, y). @@ -119,7 +119,7 @@ def example1_build_forest(x, y, **kwargs): room = create_object(rooms.Room, key="forest" + str(x) + str(y)) room.db.desc = "Basic forest room." - # Send a message to the player + # Send a message to the account kwargs["caller"].msg(room.key + " " + room.dbref) # This is generally mandatory. @@ -143,7 +143,7 @@ def example1_build_mountains(x, y, **kwargs): rock = create_object(key="Rock", location=room) rock.db.desc = "An ordinary rock." - # Send a message to the player + # Send a message to the account kwargs["caller"].msg(room.key + " " + room.dbref) # This is generally mandatory. @@ -167,7 +167,7 @@ def example1_build_temple(x, y, **kwargs): "keeping the sound level only just below thunderous. " "This is a rare spot of mirth on this dread moor.") - # Send a message to the player + # Send a message to the account kwargs["caller"].msg(room.key + " " + room.dbref) # This is generally mandatory. diff --git a/evennia/contrib/menu_login.py b/evennia/contrib/menu_login.py index 3e885ea112..d7dd455c15 100644 --- a/evennia/contrib/menu_login.py +++ b/evennia/contrib/menu_login.py @@ -18,8 +18,8 @@ CMDSET_UNLOGGEDIN = "contrib.menu_login.UnloggedinCmdSet" When you'll reload the server, new sessions will connect to the new login system, where they will be able to: -* Enter their username, assuming they have an existing player. -* Enter 'NEW' to create a new player. +* Enter their username, assuming they have an existing account. +* Enter 'NEW' to create a new account. The top-level functions in this file are menu nodes (as described in evennia.utils.evmenu.py). Each one of these functions is responsible @@ -61,7 +61,7 @@ def start(caller): a session has been created OR if an error occurs further down the menu tree. From there, users can either enter a username (if this username exists) or type NEW (capitalized - or not) to create a new player. + or not) to create a new account. """ text = random_string_from_module(CONNECTION_SCREEN_MODULE) @@ -79,7 +79,7 @@ def start(caller): def username(caller, string_input): - """Check that the username leads to an existing player. + """Check that the username leads to an existing account. Check that the specified username exists. If the username doesn't exist, display an error message and ask the user to try again. If @@ -88,8 +88,8 @@ def username(caller, string_input): """ string_input = string_input.strip() - player = managers.players.get_player_from_name(string_input) - if player is None: + account = managers.accounts.get_account_from_name(string_input) + if account is None: text = dedent(""" |rThe username '{}' doesn't exist. Have you created it?|n Try another name or leave empty to go back. @@ -100,8 +100,8 @@ def username(caller, string_input): {"key": "_default", "goto": "username"}) else: - caller.ndb._menutree.player = player - text = "Enter the password for the {} account.".format(player.name) + caller.ndb._menutree.account = account + text = "Enter the password for the {} account.".format(account.name) # Disables echo for the password caller.msg("", options={"echo": False}) options = ( @@ -115,7 +115,7 @@ def username(caller, string_input): def ask_password(caller, string_input): - """Ask the user to enter the password to this player. + """Ask the user to enter the password to this account. This is assuming the user exists (see 'create_username' and 'create_password'). This node "loops" if needed: if the @@ -129,14 +129,14 @@ def ask_password(caller, string_input): # Check the password and login is correct; also check for bans - player = menutree.player + account = menutree.account password_attempts = menutree.password_attempts \ if hasattr(menutree, "password_attempts") else 0 bans = ServerConfig.objects.conf("server_bans") - banned = bans and (any(tup[0] == player.name.lower() for tup in bans) or + banned = bans and (any(tup[0] == account.name.lower() for tup in bans) or any(tup[2].match(caller.address) for tup in bans if tup[2])) - if not player.check_password(string_input): + if not account.check_password(string_input): # Didn't enter a correct password password_attempts += 1 if password_attempts > 2: @@ -173,7 +173,7 @@ def ask_password(caller, string_input): text = "" options = {} caller.msg("", options={"echo": True}) - caller.sessionhandler.login(caller, player) + caller.sessionhandler.login(caller, account) return text, options @@ -201,10 +201,10 @@ def create_username(caller, string_input): """ menutree = caller.ndb._menutree string_input = string_input.strip() - player = managers.players.get_player_from_name(string_input) + account = managers.accounts.get_account_from_name(string_input) - # If a player with that name exists, a new one will not be created - if player: + # If an account with that name exists, a new one will not be created + if account: text = dedent(""" |rThe account {} already exists.|n Enter another username or leave blank to go back. @@ -229,7 +229,7 @@ def create_username(caller, string_input): "goto": "create_username"}) else: # a valid username - continue getting the password - menutree.playername = string_input + menutree.accountname = string_input # Disables echo for entering password caller.msg("", options={"echo": False}) # Redirects to the creation of a password @@ -259,7 +259,7 @@ def create_password(caller, string_input): "goto": "create_password"}) password = string_input.strip() - playername = menutree.playername + accountname = menutree.accountname if len(password) < LEN_PASSWD: # The password is too short @@ -273,15 +273,15 @@ def create_password(caller, string_input): from evennia.commands.default import unloggedin # We make use of the helper functions from the default set here. try: - permissions = settings.PERMISSION_PLAYER_DEFAULT + permissions = settings.PERMISSION_ACCOUNT_DEFAULT typeclass = settings.BASE_CHARACTER_TYPECLASS - new_player = unloggedin._create_player(caller, playername, + new_account = unloggedin._create_account(caller, accountname, password, permissions) - if new_player: + if new_account: if settings.MULTISESSION_MODE < 2: default_home = ObjectDB.objects.get_id( settings.DEFAULT_HOME) - unloggedin._create_character(caller, new_player, + unloggedin._create_character(caller, new_account, typeclass, default_home, permissions) except Exception: # We are in the middle between logged in and -not, so we have @@ -297,7 +297,7 @@ def create_password(caller, string_input): text = "" caller.msg("|gWelcome, your new account has been created!|n") caller.msg("", options={"echo": True}) - caller.sessionhandler.login(caller, new_player) + caller.sessionhandler.login(caller, new_account) return text, options @@ -335,7 +335,7 @@ class UnloggedinCmdSet(CmdSet): class CmdUnloggedinLook(Command): """ An unloggedin version of the look command. This is called by the server - when the player first connects. It sets up the menu before handing off + when the account first connects. It sets up the menu before handing off to the menu's own look command. """ key = syscmdkeys.CMD_LOGINSTART diff --git a/evennia/contrib/rpsystem.py b/evennia/contrib/rpsystem.py index 6bdf93d00d..e3d349a05f 100644 --- a/evennia/contrib/rpsystem.py +++ b/evennia/contrib/rpsystem.py @@ -1210,7 +1210,7 @@ class ContribRPObject(DefaultObject): otherwise it will return a list of 0, 1 or more matches. Notes: - To find Players, use eg. `evennia.player_search`. If + To find Accounts, use eg. `evennia.account_search`. If `quiet=False`, error messages will be handled by `settings.SEARCH_AT_RESULT` and echoed automatically (on error, return will be `None`). If `quiet=True`, the error @@ -1228,7 +1228,7 @@ class ContribRPObject(DefaultObject): if use_nicks: # do nick-replacement on search - searchdata = self.nicks.nickreplace(searchdata, categories=("object", "player"), include_player=True) + searchdata = self.nicks.nickreplace(searchdata, categories=("object", "account"), include_account=True) if(global_search or (is_string and searchdata.startswith("#") and len(searchdata) > 1 and searchdata[1:].isdigit())): @@ -1296,7 +1296,7 @@ class ContribRPObject(DefaultObject): Displays the name of the object in a viewer-aware manner. Args: - looker (TypedObject): The object or player that is looking + looker (TypedObject): The object or account that is looking at/getting inforamtion for this object. Kwargs: @@ -1342,7 +1342,7 @@ class ContribRPObject(DefaultObject): key = con.get_display_name(looker, pose=True) if con.destination: exits.append(key) - elif con.has_player: + elif con.has_account: users.append(key) else: things.append(key) @@ -1383,7 +1383,7 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): Displays the name of the object in a viewer-aware manner. Args: - looker (TypedObject): The object or player that is looking + looker (TypedObject): The object or account that is looking at/getting inforamtion for this object. Kwargs: diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 71cc105b81..c02accd4db 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -395,13 +395,13 @@ class TestWilderness(EvenniaTest): # Pretend that both char1 and char2 are connected... self.char1.sessions.add(1) self.char2.sessions.add(1) - self.assertTrue(self.char1.has_player) - self.assertTrue(self.char2.has_player) + self.assertTrue(self.char1.has_account) + self.assertTrue(self.char2.has_account) wilderness.create_wilderness() w = self.get_wilderness_script() - # We should have no unused room after moving the first player in. + # We should have no unused room after moving the first account in. self.assertEquals(len(w.db.unused_rooms), 0) w.move_obj(self.char1, (0, 0)) self.assertEquals(len(w.db.unused_rooms), 0) @@ -442,15 +442,15 @@ from evennia.contrib import chargen class TestChargen(CommandTest): def test_ooclook(self): - self.call(chargen.CmdOOCLook(), "foo", "You have no characters to look at", caller=self.player) - self.call(chargen.CmdOOCLook(), "", "You, TestPlayer, are an OOC ghost without form.", caller=self.player) + self.call(chargen.CmdOOCLook(), "foo", "You have no characters to look at", caller=self.account) + self.call(chargen.CmdOOCLook(), "", "You, TestAccount, are an OOC ghost without form.", caller=self.account) def test_charcreate(self): - self.call(chargen.CmdOOCCharacterCreate(), "testchar", "The character testchar was successfully created!", caller=self.player) - self.call(chargen.CmdOOCCharacterCreate(), "testchar", "Character testchar already exists.", caller=self.player) - self.assertTrue(self.player.db._character_dbrefs) - self.call(chargen.CmdOOCLook(), "", "You, TestPlayer, are an OOC ghost without form.",caller=self.player) - self.call(chargen.CmdOOCLook(), "testchar", "testchar(", caller=self.player) + self.call(chargen.CmdOOCCharacterCreate(), "testchar", "The character testchar was successfully created!", caller=self.account) + self.call(chargen.CmdOOCCharacterCreate(), "testchar", "Character testchar already exists.", caller=self.account) + self.assertTrue(self.account.db._character_dbrefs) + self.call(chargen.CmdOOCLook(), "", "You, TestAccount, are an OOC ghost without form.",caller=self.account) + self.call(chargen.CmdOOCLook(), "testchar", "testchar(", caller=self.account) # Testing clothing contrib from evennia.contrib import clothing @@ -600,9 +600,9 @@ class TestEmailLogin(CommandTest): def test_connect(self): self.call(email_login.CmdUnconnectedConnect(), "mytest@test.com test", "The email 'mytest@test.com' does not match any accounts.") self.call(email_login.CmdUnconnectedCreate(), '"mytest" mytest@test.com test11111', "A new account 'mytest' was created. Welcome!") - self.call(email_login.CmdUnconnectedConnect(), "mytest@test.com test11111", "", caller=self.player.sessions.get()[0]) + self.call(email_login.CmdUnconnectedConnect(), "mytest@test.com test11111", "", caller=self.account.sessions.get()[0]) def test_quit(self): - self.call(email_login.CmdUnconnectedQuit(), "", "", caller=self.player.sessions.get()[0]) + self.call(email_login.CmdUnconnectedQuit(), "", "", caller=self.account.sessions.get()[0]) def test_unconnectedlook(self): self.call(email_login.CmdUnconnectedLook(), "", "==========") def test_unconnectedhelp(self): @@ -628,19 +628,19 @@ from evennia.contrib import mail class TestMail(CommandTest): def test_mail(self): - self.call(mail.CmdMail(), "2", "'2' is not a valid mail id.", caller=self.player) - self.call(mail.CmdMail(), "", "There are no messages in your inbox.", caller=self.player) + self.call(mail.CmdMail(), "2", "'2' is not a valid mail id.", caller=self.account) + self.call(mail.CmdMail(), "", "There are no messages in your inbox.", caller=self.account) self.call(mail.CmdMail(), "Char=Message 1", "You have received a new @mail from Char|You sent your message.", caller=self.char1) self.call(mail.CmdMail(), "Char=Message 2", "You sent your message.", caller=self.char2) - self.call(mail.CmdMail(), "TestPlayer2=Message 2", - "You have received a new @mail from TestPlayer2(player 2)|You sent your message.", caller=self.player2) - self.call(mail.CmdMail(), "TestPlayer=Message 1", "You sent your message.", caller=self.player2) - self.call(mail.CmdMail(), "TestPlayer=Message 2", "You sent your message.", caller=self.player2) - self.call(mail.CmdMail(), "", "| ID: From: Subject:", caller=self.player) - self.call(mail.CmdMail(), "2", "From: TestPlayer2", caller=self.player) - self.call(mail.CmdMail(), "/forward TestPlayer2 = 1/Forward message", "You sent your message.|Message forwarded.", caller=self.player) - self.call(mail.CmdMail(), "/reply 2=Reply Message2", "You sent your message.", caller=self.player) - self.call(mail.CmdMail(), "/delete 2", "Message 2 deleted", caller=self.player) + self.call(mail.CmdMail(), "TestAccount2=Message 2", + "You have received a new @mail from TestAccount2(account 2)|You sent your message.", caller=self.account2) + self.call(mail.CmdMail(), "TestAccount=Message 1", "You sent your message.", caller=self.account2) + self.call(mail.CmdMail(), "TestAccount=Message 2", "You sent your message.", caller=self.account2) + self.call(mail.CmdMail(), "", "| ID: From: Subject:", caller=self.account) + self.call(mail.CmdMail(), "2", "From: TestAccount2", caller=self.account) + self.call(mail.CmdMail(), "/forward TestAccount2 = 1/Forward message", "You sent your message.|Message forwarded.", caller=self.account) + self.call(mail.CmdMail(), "/reply 2=Reply Message2", "You sent your message.", caller=self.account) + self.call(mail.CmdMail(), "/delete 2", "Message 2 deleted", caller=self.account) # test map builder contrib diff --git a/evennia/contrib/tutorial_examples/cmdset_red_button.py b/evennia/contrib/tutorial_examples/cmdset_red_button.py index 5f4c8d7fda..fce903195d 100644 --- a/evennia/contrib/tutorial_examples/cmdset_red_button.py +++ b/evennia/contrib/tutorial_examples/cmdset_red_button.py @@ -297,17 +297,17 @@ class LidOpenCmdSet(CmdSet): class BlindCmdSet(CmdSet): """ - This is the cmdset added to the *player* when + This is the cmdset added to the *account* when the button is pushed. """ key = "BlindCmdSet" # we want it to completely replace all normal commands # until the timed script removes it again. mergetype = "Replace" - # we want to stop the player from walking around + # we want to stop the account from walking around # in this blinded state, so we hide all exits too. # (channel commands will still work). - no_exits = True # keep player in the same room + no_exits = True # keep account in the same room no_objs = True # don't allow object commands def at_cmdset_creation(self): diff --git a/evennia/contrib/tutorial_examples/red_button_scripts.py b/evennia/contrib/tutorial_examples/red_button_scripts.py index ba369bcb69..43be358b29 100644 --- a/evennia/contrib/tutorial_examples/red_button_scripts.py +++ b/evennia/contrib/tutorial_examples/red_button_scripts.py @@ -100,17 +100,17 @@ class BlindedState(DefaultScript): """ This is a timed state. - This adds a (very limited) cmdset TO THE PLAYER, during a certain time, + This adds a (very limited) cmdset TO THE ACCOUNT, during a certain time, after which the script will close and all functions are restored. It's up to the function starting the script to actually - set it on the right player object. + set it on the right account object. """ def at_script_creation(self): """ We set up the script here. """ self.key = "temporary_blinder" - self.desc = "Temporarily blinds the player for a little while." + self.desc = "Temporarily blinds the account for a little while." self.interval = 20 # seconds self.start_delay = True # we don't want it to stop until after 20s. self.repeats = 1 # this will go away after interval seconds. @@ -123,7 +123,7 @@ class BlindedState(DefaultScript): Note that the RedButtonBlind cmdset is defined to completly replace the other cmdsets on the stack while it is active (this means that while blinded, only operations in this cmdset - will be possible for the player to perform). It is however + will be possible for the account to perform). It is however not persistent, so should there be a bug in it, we just need to restart the server to clear out of it during development. """ @@ -228,7 +228,7 @@ class DeactivateButtonEvent(DefaultScript): This deactivates the button for a short while (it won't blink, won't close its lid etc). It is meant to be called when the button is pushed and run as long as the blinded effect lasts. We cannot put these methods - in the AddBlindedCmdSet script since that script is defined on the *player* + in the AddBlindedCmdSet script since that script is defined on the *account* whereas this one must be defined on the *button*. """ def at_script_creation(self): @@ -250,7 +250,7 @@ class DeactivateButtonEvent(DefaultScript): """ # closing the lid will also add the ClosedState script self.obj.close_lid() - # lock the lid so other players can't access it until the + # lock the lid so other accounts can't access it until the # first one's effect has worn off. self.obj.db.lid_locked = True # breaking the lamp also sets a correct desc diff --git a/evennia/contrib/tutorial_world/mob.py b/evennia/contrib/tutorial_world/mob.py index acdb2f8be5..22bcbddb39 100644 --- a/evennia/contrib/tutorial_world/mob.py +++ b/evennia/contrib/tutorial_world/mob.py @@ -206,7 +206,7 @@ class Mob(tut_objects.TutorialObject): """ targets = [obj for obj in location.contents_get(exclude=self) - if obj.has_player and not obj.is_superuser] + if obj.has_account and not obj.is_superuser] return targets[0] if targets else None def set_alive(self, *args, **kwargs): @@ -290,9 +290,9 @@ class Mob(tut_objects.TutorialObject): """ Called repeatedly during patrolling mode. In this mode, the mob scans its surroundings and randomly chooses a viable exit. - One should lock exits with the traverse:has_player() lock in + One should lock exits with the traverse:has_account() lock in order to block the mob from moving outside its area while - allowing player-controlled characters to move normally. + allowing account-controlled characters to move normally. """ if random.random() < 0.01 and self.db.irregular_msgs: self.location.msg_contents(random.choice(self.db.irregular_msgs)) diff --git a/evennia/contrib/tutorial_world/objects.py b/evennia/contrib/tutorial_world/objects.py index 40e298595b..943bf3a915 100644 --- a/evennia/contrib/tutorial_world/objects.py +++ b/evennia/contrib/tutorial_world/objects.py @@ -582,7 +582,7 @@ class CrumblingWall(TutorialObject, DefaultExit): The CrumblingWall can be examined in various ways, but only if a lit light source is in the room. The traversal itself is blocked by a traverse: lock on the exit that only allows passage if a - certain attribute is set on the trying player. + certain attribute is set on the trying account. Important attribute destination - this property must be set to make this a valid exit @@ -701,7 +701,7 @@ class CrumblingWall(TutorialObject, DefaultExit): self.reset() def at_failed_traverse(self, traverser): - """This is called if the player fails to pass the Exit.""" + """This is called if the account fails to pass the Exit.""" traverser.msg("No matter how you try, you cannot force yourself through %s." % self.key) def reset(self): @@ -868,7 +868,7 @@ class Weapon(TutorialObject): When reset, the weapon is simply deleted, unless it has a place to return to. """ - if self.location.has_player and self.home == self.location: + if self.location.has_account and self.home == self.location: self.location.msg_contents("%s suddenly and magically fades into nothingness, as if it was never there ..." % self.key) self.delete() @@ -1032,7 +1032,7 @@ class WeaponRack(TutorialObject): Attributes to set on this object: available_weapons: list of prototype-keys from WEAPON_PROTOTYPES, the weapons available in this rack. - no_more_weapons_msg - error message to return to players + no_more_weapons_msg - error message to return to accounts who already got one weapon from the rack and tries to grab another one. diff --git a/evennia/contrib/tutorial_world/rooms.py b/evennia/contrib/tutorial_world/rooms.py index 9262044a2b..61f41c163e 100644 --- a/evennia/contrib/tutorial_world/rooms.py +++ b/evennia/contrib/tutorial_world/rooms.py @@ -126,7 +126,7 @@ class CmdTutorialLook(default_cmds.CmdLook): Usage: look look - look * + look * Observes your location, details at your location or objects in your vicinity. @@ -182,7 +182,7 @@ class CmdTutorialLook(default_cmds.CmdLook): return if not hasattr(looking_at_obj, 'return_appearance'): - # this is likely due to us having a player instead + # this is likely due to us having an account instead looking_at_obj = looking_at_obj.character if not looking_at_obj.access(caller, "view"): caller.msg("Could not find '%s'." % args) @@ -232,7 +232,7 @@ class TutorialRoom(DefaultRoom): source_location (Object): the previous location of new_arrival. """ - if new_arrival.has_player and not new_arrival.is_superuser: + if new_arrival.has_account and not new_arrival.is_superuser: # this is a character for obj in self.contents_get(exclude=new_arrival): if hasattr(obj, "at_new_arrival"): @@ -362,7 +362,7 @@ class IntroRoom(TutorialRoom): super(IntroRoom, self).at_object_creation() self.db.tutorial_info = "The first room of the tutorial. " \ "This assigns the health Attribute to "\ - "the player." + "the account." def at_object_receive(self, character, source_location): """ @@ -372,7 +372,7 @@ class IntroRoom(TutorialRoom): # setup character for the tutorial health = self.db.char_health or 20 - if character.has_player: + if character.has_account: character.db.health = health character.db.health_max = health @@ -388,7 +388,7 @@ class IntroRoom(TutorialRoom): # Defines a special west-eastward "bridge"-room, a large room that takes # several steps to cross. It is complete with custom commands and a # chance of falling off the bridge. This room has no regular exits, -# instead the exitings are handled by custom commands set on the player +# instead the exitings are handled by custom commands set on the account # upon first entering the room. # # Since one can enter the bridge room from both ends, it is @@ -537,7 +537,7 @@ class CmdLookBridge(Command): BRIDGE_POS_MESSAGES[bridge_position], random.choice(BRIDGE_MOODS)) - chars = [obj for obj in self.obj.contents_get(exclude=caller) if obj.has_player] + chars = [obj for obj in self.obj.contents_get(exclude=caller) if obj.has_account] if chars: # we create the You see: message manually here message += "\n You see: %s" % ", ".join("|c%s|n" % char.key for char in chars) @@ -606,7 +606,7 @@ class BridgeRoom(WeatherRoom): The bridge room implements an unsafe bridge. It also enters the player into a state where they get new commands so as to try to cross the bridge. - We want this to result in the player getting a special set of + We want this to result in the account getting a special set of commands related to crossing the bridge. The result is that it will take several steps to cross it, despite it being represented by only a single room. @@ -659,7 +659,7 @@ class BridgeRoom(WeatherRoom): This hook is called by the engine whenever the player is moved into this room. """ - if character.has_player: + if character.has_account: # we only run this if the entered object is indeed a player object. # check so our east/west exits are correctly defined. wexit = search_object(self.db.west_exit) @@ -682,7 +682,7 @@ class BridgeRoom(WeatherRoom): """ This is triggered when the player leaves the bridge room. """ - if character.has_player: + if character.has_account: # clean up the position attribute del character.db.tutorial_bridge_position @@ -876,7 +876,7 @@ class DarkRoom(TutorialRoom): self.locks.add("view:all()") self.cmdset.remove(DarkCmdSet) self.db.is_lit = True - for char in (obj for obj in self.contents if obj.has_player): + for char in (obj for obj in self.contents if obj.has_account): # this won't do anything if it is already removed char.msg("The room is lit up.") else: @@ -884,7 +884,7 @@ class DarkRoom(TutorialRoom): self.db.is_lit = False self.locks.add("view:false()") self.cmdset.add(DarkCmdSet, permanent=True) - for char in (obj for obj in self.contents if obj.has_player): + for char in (obj for obj in self.contents if obj.has_account): if char.is_superuser: char.msg("You are Superuser, so you are not affected by the dark state.") else: @@ -895,7 +895,7 @@ class DarkRoom(TutorialRoom): """ Called when an object enters the room. """ - if obj.has_player: + if obj.has_account: # a puppeted object, that is, a Character self._heal(obj) # in case the new guy carries light with them @@ -960,7 +960,7 @@ class TeleportRoom(TutorialRoom): This hook is called by the engine whenever the player is moved into this room. """ - if not character.has_player: + if not character.has_account: # only act on player characters. return # determine if the puzzle is a success or not @@ -1020,7 +1020,7 @@ class OutroRoom(TutorialRoom): """ Do cleanup. """ - if character.has_player: + if character.has_account: del character.db.health_max del character.db.health del character.db.last_climbed diff --git a/evennia/contrib/wilderness.py b/evennia/contrib/wilderness.py index 9e01a43211..07b2041d58 100644 --- a/evennia/contrib/wilderness.py +++ b/evennia/contrib/wilderness.py @@ -341,7 +341,7 @@ class WildernessScript(DefaultScript): old_room.wilderness.at_after_object_leave(obj) else: for item in old_room.contents: - if item.has_player: + if item.has_account: # There is still a player in the old room. # Let's create a new room and not touch that old # room. @@ -419,7 +419,7 @@ class WildernessScript(DefaultScript): return for item in room.contents: - if item.has_player: + if item.has_account: # There is still a character in that room. We can't get rid of # it just yet break @@ -457,7 +457,7 @@ class WildernessScript(DefaultScript): class WildernessRoom(DefaultRoom): """ This is a single room inside the wilderness. This room provides a "view" - into the wilderness map. When a player moves around, instead of going to + into the wilderness map. When an account moves around, instead of going to another room as with traditional rooms, they stay in the same room but the room itself changes to display another area of the wilderness. """ @@ -588,7 +588,7 @@ class WildernessRoom(DefaultRoom): Displays the name of the object in a viewer-aware manner. Args: - looker (TypedObject): The object or player that is looking + looker (TypedObject): The object or account that is looking at/getting inforamtion for this object. Returns: diff --git a/evennia/game_template/commands/command.py b/evennia/game_template/commands/command.py index b7308a3305..529a8450e4 100644 --- a/evennia/game_template/commands/command.py +++ b/evennia/game_template/commands/command.py @@ -1,7 +1,7 @@ """ Commands -Commands describe the input the player can do to the game. +Commands describe the input the account can do to the game. """ @@ -169,17 +169,17 @@ class Command(BaseCommand): # self.rhs = rhs # self.rhslist = rhslist # -# # if the class has the player_caller property set on itself, we make -# # sure that self.caller is always the player if possible. We also create +# # if the class has the account_caller property set on itself, we make +# # sure that self.caller is always the account if possible. We also create # # a special property "character" for the puppeted object, if any. This -# # is convenient for commands defined on the Player only. -# if hasattr(self, "player_caller") and self.player_caller: +# # is convenient for commands defined on the Account only. +# if hasattr(self, "account_caller") and self.account_caller: # if utils.inherits_from(self.caller, "evennia.objects.objects.DefaultObject"): # # caller is an Object/Character # self.character = self.caller -# self.caller = self.caller.player -# elif utils.inherits_from(self.caller, "evennia.players.players.DefaultPlayer"): -# # caller was already a Player +# self.caller = self.caller.account +# elif utils.inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount"): +# # caller was already an Account # self.character = self.caller.get_puppet(self.session) # else: # self.character = None diff --git a/evennia/game_template/commands/default_cmdsets.py b/evennia/game_template/commands/default_cmdsets.py index e46330c56f..0a599177c1 100644 --- a/evennia/game_template/commands/default_cmdsets.py +++ b/evennia/game_template/commands/default_cmdsets.py @@ -20,7 +20,7 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet): """ The `CharacterCmdSet` contains general in-game commands like `look`, `get`, etc available on in-game Character objects. It is merged with - the `PlayerCmdSet` when a Player puppets a Character. + the `AccountCmdSet` when an Account puppets a Character. """ key = "DefaultCharacter" @@ -34,20 +34,20 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet): # -class PlayerCmdSet(default_cmds.PlayerCmdSet): +class AccountCmdSet(default_cmds.AccountCmdSet): """ - This is the cmdset available to the Player at all times. It is - combined with the `CharacterCmdSet` when the Player puppets a + This is the cmdset available to the Account at all times. It is + combined with the `CharacterCmdSet` when the Account puppets a Character. It holds game-account-specific commands, channel commands, etc. """ - key = "DefaultPlayer" + key = "DefaultAccount" def at_cmdset_creation(self): """ Populates the cmdset """ - super(PlayerCmdSet, self).at_cmdset_creation() + super(AccountCmdSet, self).at_cmdset_creation() # # any commands you add below will overload the default ones. # diff --git a/evennia/game_template/server/conf/serversession.py b/evennia/game_template/server/conf/serversession.py index 050ff3e966..acaa6a9f25 100644 --- a/evennia/game_template/server/conf/serversession.py +++ b/evennia/game_template/server/conf/serversession.py @@ -5,7 +5,7 @@ The serversession is the Server-side in-memory representation of a user connecting to the game. Evennia manages one Session per connection to the game. So a user logged into the game with multiple clients (if Evennia is configured to allow that) will have multiple -sessions tied to one Player object. All communication between Evennia +sessions tied to one Account object. All communication between Evennia and the real-world user goes through the Session(s) associated with that user. It should be noted that modifying the Session object is not usually @@ -28,8 +28,8 @@ class ServerSession(BaseServerSession): This class represents a player's session and is a template for individual protocols to communicate with Evennia. - Each player gets one or more sessions assigned to them whenever they connect - to the game server. All communication between game and player goes + Each account gets one or more sessions assigned to them whenever they connect + to the game server. All communication between game and account goes through their session(s). """ pass diff --git a/evennia/game_template/typeclasses/channels.py b/evennia/game_template/typeclasses/channels.py index 3be9a6dde7..7a04746fe9 100644 --- a/evennia/game_template/typeclasses/channels.py +++ b/evennia/game_template/typeclasses/channels.py @@ -2,7 +2,7 @@ Channel The channel class represents the out-of-character chat-room usable by -Players in-game. It is mostly overloaded to change its appearance, but +Accounts in-game. It is mostly overloaded to change its appearance, but channels can be used to implement many different forms of message distribution systems. @@ -18,9 +18,9 @@ class Channel(DefaultChannel): """ Working methods: at_channel_creation() - called once, when the channel is created - has_connection(player) - check if the given player listens to this channel - connect(player) - connect player to this channel - disconnect(player) - disconnect player from channel + has_connection(account) - check if the given account listens to this channel + connect(account) - connect account to this channel + disconnect(account) - disconnect account from channel access(access_obj, access_type='listen', default=False) - check the access on this channel (default access_type is listen) delete() - delete this channel @@ -33,8 +33,8 @@ class Channel(DefaultChannel): tempmsg(msg, header=None, senders=None) - wrapper for sending non-persistent messages. distribute_message(msg, online=False) - send a message to all - connected players on channel, optionally sending only - to players that are currently online (optimized for very large sends) + connected accounts on channel, optionally sending only + to accounts that are currently online (optimized for very large sends) Useful hooks: channel_prefix(msg, emit=False) - how the channel should be diff --git a/evennia/game_template/typeclasses/characters.py b/evennia/game_template/typeclasses/characters.py index ff7d8bfb64..11d747e1ce 100644 --- a/evennia/game_template/typeclasses/characters.py +++ b/evennia/game_template/typeclasses/characters.py @@ -1,7 +1,7 @@ """ Characters -Characters are (by default) Objects setup to be puppeted by Players. +Characters are (by default) Objects setup to be puppeted by Accounts. They are what you "see" in game. The Character class in this module is setup to be the "default" character type created by the default creation commands. @@ -19,14 +19,14 @@ class Character(DefaultCharacter): and its commands only be called by itself, not anyone else. (to change things, use at_object_creation() instead). at_after_move(source_location) - Launches the "look" command after every move. - at_post_unpuppet(player) - when Player disconnects from the Character, we + at_post_unpuppet(account) - when Account disconnects from the Character, we store the current location in the pre_logout_location Attribute and move it to a None-location so the "unpuppeted" character - object does not need to stay on grid. Echoes "Player has disconnected" + object does not need to stay on grid. Echoes "Account has disconnected" to the room. - at_pre_puppet - Just before Player re-connects, retrieves the character's + at_pre_puppet - Just before Account re-connects, retrieves the character's pre_logout_location Attribute and move it back on the grid. - at_post_puppet - Echoes "PlayerName has entered the game" to the room. + at_post_puppet - Echoes "AccountName has entered the game" to the room. """ pass diff --git a/evennia/game_template/typeclasses/objects.py b/evennia/game_template/typeclasses/objects.py index 11e4f6a59e..3523689fe0 100644 --- a/evennia/game_template/typeclasses/objects.py +++ b/evennia/game_template/typeclasses/objects.py @@ -40,16 +40,16 @@ class Object(DefaultObject): date_created (string) - time stamp of object creation permissions (list of strings) - list of permission strings - player (Player) - controlling player (if any, only set together with + account (Account) - controlling account (if any, only set together with sessid below) sessid (int, read-only) - session id (if any, only set together with - player above). Use `sessions` handler to get the + account above). Use `sessions` handler to get the Sessions directly. location (Object) - current location. Is None if this is a room home (Object) - safety start-location sessions (list of Sessions, read-only) - returns all sessions connected to this object - has_player (bool, read-only)- will only return *connected* players + has_account (bool, read-only)- will only return *connected* accounts contents (list of Objects, read-only) - returns all objects inside this object (including exits) exits (list of Objects, read-only) - returns all exits from this @@ -73,7 +73,7 @@ class Object(DefaultObject): * Helper methods (see src.objects.objects.py for full headers) search(ostring, global_search=False, attribute_name=None, - use_nicks=False, location=None, ignore_errors=False, player=False) + use_nicks=False, location=None, ignore_errors=False, account=False) execute_cmd(raw_string) msg(text=None, **kwargs) msg_contents(message, exclude=None, from_obj=None, **kwargs) @@ -105,14 +105,14 @@ class Object(DefaultObject): requests a cmdset from this object. The kwargs are not normally used unless the cmdset is created dynamically (see e.g. Exits). - at_pre_puppet(player)- (player-controlled objects only) called just + at_pre_puppet(account)- (account-controlled objects only) called just before puppeting - at_post_puppet() - (player-controlled objects only) called just - after completing connection player<->object - at_pre_unpuppet() - (player-controlled objects only) called just + at_post_puppet() - (account-controlled objects only) called just + after completing connection account<->object + at_pre_unpuppet() - (account-controlled objects only) called just before un-puppeting - at_post_unpuppet(player) - (player-controlled objects only) called just - after disconnecting player<->object link + at_post_unpuppet(account) - (account-controlled objects only) called just + after disconnecting account<->object link at_server_reload() - called before server is reloaded at_server_shutdown() - called just before server is fully shut down diff --git a/evennia/game_template/typeclasses/players.py b/evennia/game_template/typeclasses/players.py deleted file mode 100644 index 2cdfc06fdb..0000000000 --- a/evennia/game_template/typeclasses/players.py +++ /dev/null @@ -1,102 +0,0 @@ -""" -Player - -The Player represents the game "account" and each login has only one -Player object. A Player is what chats on default channels but has no -other in-game-world existence. Rather the Player puppets Objects (such -as Characters) in order to actually participate in the game world. - - -Guest - -Guest players are simple low-level accounts that are created/deleted -on the fly and allows users to test the game without the commitment -of a full registration. Guest accounts are deactivated by default; to -activate them, add the following line to your settings file: - - GUEST_ENABLED = True - -You will also need to modify the connection screen to reflect the -possibility to connect with a guest account. The setting file accepts -several more options for customizing the Guest account system. - -""" - -from evennia import DefaultPlayer, DefaultGuest - -class Player(DefaultPlayer): - """ - This class describes the actual OOC player (i.e. the user connecting - to the MUD). It does NOT have visual appearance in the game world (that - is handled by the character which is connected to this). Comm channels - are attended/joined using this object. - - It can be useful e.g. for storing configuration options for your game, but - should generally not hold any character-related info (that's best handled - on the character level). - - Can be set using BASE_PLAYER_TYPECLASS. - - - * available properties - - key (string) - name of player - name (string)- wrapper for user.username - aliases (list of strings) - aliases to the object. Will be saved to database as AliasDB entries but returned as strings. - dbref (int, read-only) - unique #id-number. Also "id" can be used. - date_created (string) - time stamp of object creation - permissions (list of strings) - list of permission strings - - user (User, read-only) - django User authorization object - obj (Object) - game object controlled by player. 'character' can also be used. - sessions (list of Sessions) - sessions connected to this player - is_superuser (bool, read-only) - if the connected user is a superuser - - * Handlers - - locks - lock-handler: use locks.add() to add new lock strings - db - attribute-handler: store/retrieve database attributes on this self.db.myattr=val, val=self.db.myattr - ndb - non-persistent attribute handler: same as db but does not create a database entry when storing data - scripts - script-handler. Add new scripts to object with scripts.add() - cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object - nicks - nick-handler. New nicks with nicks.add(). - - * Helper methods - - msg(text=None, **kwargs) - swap_character(new_character, delete_old_character=False) - execute_cmd(raw_string, session=None) - search(ostring, global_search=False, attribute_name=None, use_nicks=False, location=None, ignore_errors=False, player=False) - is_typeclass(typeclass, exact=False) - swap_typeclass(new_typeclass, clean_attributes=False, no_default=True) - access(accessing_obj, access_type='read', default=False) - check_permstring(permstring) - - * Hook methods (when re-implementation, remember methods need to have self as first arg) - - basetype_setup() - at_player_creation() - - - note that the following hooks are also found on Objects and are - usually handled on the character level: - - at_init() - at_cmdset_get(**kwargs) - at_first_login() - at_post_login(session=None) - at_disconnect() - at_message_receive() - at_message_send() - at_server_reload() - at_server_shutdown() - - """ - pass - - -class Guest(DefaultGuest): - """ - This class is used for guest logins. Unlike Players, Guests and their - characters are deleted after disconnection. - """ - pass diff --git a/evennia/locks/lockfuncs.py b/evennia/locks/lockfuncs.py index bdba148cf6..48e0718ca8 100644 --- a/evennia/locks/lockfuncs.py +++ b/evennia/locks/lockfuncs.py @@ -28,38 +28,38 @@ MUX Name: Affects: Effect: DefaultLock: Exits: controls who may traverse the exit to its destination. Evennia: "traverse:" - Rooms: controls whether the player sees the + Rooms: controls whether the account sees the SUCC or FAIL message for the room following the room description when looking at the room. Evennia: Custom typeclass - Players/Things: controls who may GET the object. + Accounts/Things: controls who may GET the object. Evennia: "get:" ParentLock: All: controls who may make @parent links to the object. Evennia: Typeclasses and "puppet:" - ReceiveLock: Players/Things: controls who may give things to the + ReceiveLock: Accounts/Things: controls who may give things to the object. Evennia: SpeechLock: All but Exits: controls who may speak in that location @@ -95,11 +95,11 @@ from evennia.utils import utils _PERMISSION_HIERARCHY = [pe.lower() for pe in settings.PERMISSION_HIERARCHY] -def _to_player(accessing_obj): - "Helper function. Makes sure an accessing object is a player object" +def _to_account(accessing_obj): + "Helper function. Makes sure an accessing object is an account object" if utils.inherits_from(accessing_obj, "evennia.objects.objects.DefaultObject"): - # an object. Convert to player. - accessing_obj = accessing_obj.player + # an object. Convert to account. + accessing_obj = accessing_obj.account return accessing_obj @@ -149,11 +149,11 @@ def perm(accessing_obj, accessed_obj, *args, **kwargs): If the given permission is part of settings.PERMISSION_HIERARCHY, permission is also granted to all ranks higher up in the hierarchy. - If accessing_object is an Object controlled by a Player, the - permissions of the Player is used unless the Attribute _quell + If accessing_object is an Object controlled by an Account, the + permissions of the Account is used unless the Attribute _quell is set to True on the Object. In this case however, the - LOWEST hieararcy-permission of the Player/Object-pair will be used - (this is order to avoid Players potentially escalating their own permissions + LOWEST hieararcy-permission of the Account/Object-pair will be used + (this is order to avoid Accounts potentially escalating their own permissions by use of a higher-level Object) """ @@ -166,30 +166,30 @@ def perm(accessing_obj, accessed_obj, *args, **kwargs): except (AttributeError, IndexError): return False - if utils.inherits_from(accessing_obj, "evennia.objects.objects.DefaultObject") and accessing_obj.player: - player = accessing_obj.player + if utils.inherits_from(accessing_obj, "evennia.objects.objects.DefaultObject") and accessing_obj.account: + account = accessing_obj.account # we strip eventual plural forms, so Builders == Builder - perms_player = [p.lower().rstrip("s") for p in player.permissions.all()] - is_quell = player.attributes.get("_quell") + perms_account = [p.lower().rstrip("s") for p in account.permissions.all()] + is_quell = account.attributes.get("_quell") if permission in _PERMISSION_HIERARCHY: - # check hierarchy without allowing escalation obj->player + # check hierarchy without allowing escalation obj->account hpos_target = _PERMISSION_HIERARCHY.index(permission) - hpos_player = [hpos for hpos, hperm in enumerate(_PERMISSION_HIERARCHY) if hperm in perms_player] - hpos_player = hpos_player and hpos_player[-1] or -1 + hpos_account = [hpos for hpos, hperm in enumerate(_PERMISSION_HIERARCHY) if hperm in perms_account] + hpos_account = hpos_account and hpos_account[-1] or -1 if is_quell: hpos_object = [hpos for hpos, hperm in enumerate(_PERMISSION_HIERARCHY) if hperm in perms_object] hpos_object = hpos_object and hpos_object[-1] or -1 if gtmode: - return hpos_target < min(hpos_player, hpos_object) + return hpos_target < min(hpos_account, hpos_object) else: - return hpos_target <= min(hpos_player, hpos_object) + return hpos_target <= min(hpos_account, hpos_object) elif gtmode: - return hpos_target < hpos_player + return hpos_target < hpos_account else: - return hpos_target <= hpos_player - elif not is_quell and permission in perms_player: - # if we get here, check player perms first, otherwise + return hpos_target <= hpos_account + elif not is_quell and permission in perms_account: + # if we get here, check account perms first, otherwise # continue as normal return True @@ -217,7 +217,7 @@ def perm_above(accessing_obj, accessed_obj, *args, **kwargs): def pperm(accessing_obj, accessed_obj, *args, **kwargs): """ - The basic permission-checker only for Player objects. Ignores case. + The basic permission-checker only for Account objects. Ignores case. Usage: pperm() @@ -227,17 +227,17 @@ def pperm(accessing_obj, accessed_obj, *args, **kwargs): is part of _PERMISSION_HIERARCHY, permission is also granted to all ranks higher up in the hierarchy. """ - return perm(_to_player(accessing_obj), accessed_obj, *args, **kwargs) + return perm(_to_account(accessing_obj), accessed_obj, *args, **kwargs) def pperm_above(accessing_obj, accessed_obj, *args, **kwargs): """ - Only allow Player objects with a permission *higher* in the permission + Only allow Account objects with a permission *higher* in the permission hierarchy than the one given. If there is no such higher rank, it's assumed we refer to superuser. If no hierarchy is defined, this function has no meaning and returns False. """ - return perm_above(_to_player(accessing_obj), accessed_obj, *args, **kwargs) + return perm_above(_to_account(accessing_obj), accessed_obj, *args, **kwargs) def dbref(accessing_obj, accessed_obj, *args, **kwargs): @@ -263,9 +263,9 @@ def dbref(accessing_obj, accessed_obj, *args, **kwargs): def pdbref(accessing_obj, accessed_obj, *args, **kwargs): """ - Same as dbref, but making sure accessing_obj is a player. + Same as dbref, but making sure accessing_obj is an account. """ - return dbref(_to_player(accessing_obj), accessed_obj, *args, **kwargs) + return dbref(_to_account(accessing_obj), accessed_obj, *args, **kwargs) def id(accessing_obj, accessed_obj, *args, **kwargs): @@ -274,8 +274,8 @@ def id(accessing_obj, accessed_obj, *args, **kwargs): def pid(accessing_obj, accessed_obj, *args, **kwargs): - "Alias to dbref, for Players" - return dbref(_to_player(accessing_obj), accessed_obj, *args, **kwargs) + "Alias to dbref, for Accounts" + return dbref(_to_account(accessing_obj), accessed_obj, *args, **kwargs) # this is more efficient than multiple if ... elif statments @@ -566,15 +566,15 @@ def superuser(*args, **kwargs): """ return False -def has_player(accessing_obj, accessed_obj, *args, **kwargs): +def has_account(accessing_obj, accessed_obj, *args, **kwargs): """ - Only returns true if accessing_obj has_player is true, that is, - this is a player-controlled object. It fails on actual players! + Only returns true if accessing_obj has_account is true, that is, + this is an account-controlled object. It fails on actual accounts! This is a useful lock for traverse-locking Exits to restrain NPC mobiles from moving outside their areas. """ - return hasattr(accessing_obj, "has_player") and accessing_obj.has_player + return hasattr(accessing_obj, "has_account") and accessing_obj.has_account def serversetting(accessing_obj, accessed_obj, *args, **kwargs): """ diff --git a/evennia/locks/lockhandler.py b/evennia/locks/lockhandler.py index be35924efb..07240b6716 100644 --- a/evennia/locks/lockhandler.py +++ b/evennia/locks/lockhandler.py @@ -271,10 +271,10 @@ class LockHandler(object): def cache_lock_bypass(self, obj): """ We cache superuser bypass checks here for efficiency. This - needs to be re-run when a player is assigned to a character. + needs to be re-run when an account is assigned to a character. We need to grant access to superusers. We need to check both - directly on the object (players), through obj.player and using - the get_player() method (this sits on serversessions, in some + directly on the object (accounts), through obj.account and using + the get_account() method (this sits on serversessions, in some rare cases where a check is done before the login process has yet been fully finalized) @@ -450,8 +450,8 @@ class LockHandler(object): except AttributeError: # happens before session is initiated. if not no_superuser_bypass and ((hasattr(accessing_obj, 'is_superuser') and accessing_obj.is_superuser) - or (hasattr(accessing_obj, 'player') and hasattr(accessing_obj.player, 'is_superuser') and accessing_obj.player.is_superuser) - or (hasattr(accessing_obj, 'get_player') and (not accessing_obj.get_player() or accessing_obj.get_player().is_superuser))): + or (hasattr(accessing_obj, 'account') and hasattr(accessing_obj.account, 'is_superuser') and accessing_obj.account.is_superuser) + or (hasattr(accessing_obj, 'get_account') and (not accessing_obj.get_account() or accessing_obj.get_account().is_superuser))): return True # no superuser or bypass -> normal lock operation @@ -511,8 +511,8 @@ class LockHandler(object): return True except AttributeError: if no_superuser_bypass and ((hasattr(accessing_obj, 'is_superuser') and accessing_obj.is_superuser) - or (hasattr(accessing_obj, 'player') and hasattr(accessing_obj.player, 'is_superuser') and accessing_obj.player.is_superuser) - or (hasattr(accessing_obj, 'get_player') and (not accessing_obj.get_player() or accessing_obj.get_player().is_superuser))): + or (hasattr(accessing_obj, 'account') and hasattr(accessing_obj.account, 'is_superuser') and accessing_obj.account.is_superuser) + or (hasattr(accessing_obj, 'get_account') and (not accessing_obj.get_account() or accessing_obj.get_account().is_superuser))): return True if not ":" in lockstring: lockstring = "%s:%s" % ("_dummy", lockstring) diff --git a/evennia/objects/admin.py b/evennia/objects/admin.py index 396800e8ca..706ad331e7 100644 --- a/evennia/objects/admin.py +++ b/evennia/objects/admin.py @@ -81,9 +81,9 @@ class ObjectDBAdmin(admin.ModelAdmin): """ inlines = [ObjectTagInline, ObjectAttributeInline] - list_display = ('id', 'db_key', 'db_player', 'db_typeclass_path') + list_display = ('id', 'db_key', 'db_account', 'db_typeclass_path') list_display_links = ('id', 'db_key') - ordering = ['db_player', 'db_typeclass_path', 'id'] + ordering = ['db_account', 'db_typeclass_path', 'id'] search_fields = ['^db_key', 'db_typeclass_path'] raw_id_fields = ('db_destination', 'db_location', 'db_home') diff --git a/evennia/objects/manager.py b/evennia/objects/manager.py index ae70896d85..01012c2616 100644 --- a/evennia/objects/manager.py +++ b/evennia/objects/manager.py @@ -35,7 +35,7 @@ class ObjectDBManager(TypedObjectManager): get_dbref_range object_totals typeclass_search - get_object_with_player + get_object_with_account get_objs_with_key_and_typeclass get_objs_with_attr get_objs_with_attr_match @@ -53,18 +53,18 @@ class ObjectDBManager(TypedObjectManager): # ObjectManager Get methods # - # player related + # account related - def get_object_with_player(self, ostring, exact=True, candidates=None): + def get_object_with_account(self, ostring, exact=True, candidates=None): """ - Search for an object based on its player's name or dbref. + Search for an object based on its account's name or dbref. Args: ostring (str or int): Search criterion or dbref. Searching - for a player is sometimes initiated by appending an `*` to + for an account is sometimes initiated by appending an `*` to the beginning of the search criterion (e.g. in local_and_global_search). This is stripped here. - exact (bool, optional): Require an exact player match. + exact (bool, optional): Require an exact account match. candidates (list, optional): Only search among this list of possible object candidates. @@ -81,9 +81,9 @@ class ObjectDBManager(TypedObjectManager): cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q() if exact: - return self.filter(cand_restriction & Q(db_player__username__iexact=ostring)) + return self.filter(cand_restriction & Q(db_account__username__iexact=ostring)) else: # fuzzy matching - ply_cands = self.filter(cand_restriction & Q(playerdb__username__istartswith=ostring) + ply_cands = self.filter(cand_restriction & Q(accountdb__username__istartswith=ostring) ).values_list("db_key", flat=True) if candidates: index_matches = string_partial_matching(ply_cands, ostring, ret_index=True) @@ -503,7 +503,7 @@ class ObjectDBManager(TypedObjectManager): def clear_all_sessids(self): """ Clear the db_sessid field of all objects having also the - db_player field set. + db_account field set. """ self.filter(db_sessid__isnull=False).update(db_sessid=None) diff --git a/evennia/objects/models.py b/evennia/objects/models.py index 4b6606a71b..e06b04c315 100644 --- a/evennia/objects/models.py +++ b/evennia/objects/models.py @@ -141,16 +141,16 @@ class ObjectDB(TypedObject): The ObjectDB adds the following properties: - - player - optional connected player (always together with sessid) - - sessid - optional connection session id (always together with player) + - account - optional connected account (always together with sessid) + - sessid - optional connection session id (always together with account) - location - in-game location of object - home - safety location for object (handler) - scripts - scripts assigned to object (handler from typeclass) - cmdset - active cmdset on object (handler from typeclass) - aliases - aliases for this object (property) - nicks - nicknames for *other* things in Evennia (handler) - - sessions - sessions connected to this object (see also player) - - has_player - bool if an active player is currently connected + - sessions - sessions connected to this object (see also account) + - has_account - bool if an active account is currently connected - contents - other objects having this object as location - exits - exits from this object @@ -169,14 +169,14 @@ class ObjectDB(TypedObject): # self.key instead). The wrappers are created at the metaclass level and # will automatically save and cache the data more efficiently. - # If this is a character object, the player is connected here. + # If this is a character object, the account is connected here. db_account = models.ForeignKey("accounts.AccountDB", null=True, verbose_name='account', on_delete=models.SET_NULL, help_text='an Account connected to this object, if any.') - # the session id associated with this player, if any + # the session id associated with this account, if any db_sessid = models.CharField(null=True, max_length=32, validators=[validate_comma_separated_integer_list], verbose_name="session id", - help_text="csv list of session ids of connected Player, if any.") + help_text="csv list of session ids of connected Account, if any.") # The location in the game world. Since this one is likely # to change often, we set this with the 'location' property # to transparently handle Typeclassing. diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 40f5700db2..45588cf3a3 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -1,6 +1,6 @@ """ This module defines the basic `DefaultObject` and its children -`DefaultCharacter`, `DefaultPlayer`, `DefaultRoom` and `DefaultExit`. +`DefaultCharacter`, `DefaultAccount`, `DefaultRoom` and `DefaultExit`. These are the (default) starting points for all in-game visible entities. @@ -205,9 +205,9 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): return ObjectSessionHandler(self) @property - def has_player(self): + def has_account(self): """ - Convenience property for checking if an active player is + Convenience property for checking if an active account is currently connected to this object. """ @@ -216,11 +216,11 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): @property def is_superuser(self): """ - Check if user has a player, and if so, if it is a superuser. + Check if user has an account, and if so, if it is a superuser. """ - return self.db_player and self.db_player.is_superuser \ - and not self.db_player.attributes.get("_quell") + return self.db_account and self.db_account.is_superuser \ + and not self.db_account.attributes.get("_quell") def contents_get(self, exclude=None): """ @@ -259,7 +259,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): Displays the name of the object in a viewer-aware manner. Args: - looker (TypedObject): The object or player that is looking + looker (TypedObject): The object or account that is looking at/getting inforamtion for this object. Returns: @@ -349,7 +349,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): otherwise it will return a list of 0, 1 or more matches. Notes: - To find Players, use eg. `evennia.player_search`. If + To find Accounts, use eg. `evennia.account_search`. If `quiet=False`, error messages will be handled by `settings.SEARCH_AT_RESULT` and echoed automatically (on error, return will be `None`). If `quiet=True`, the error @@ -367,7 +367,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): if use_nicks: # do nick-replacement on search - searchdata = self.nicks.nickreplace(searchdata, categories=("object", "player"), include_player=True) + searchdata = self.nicks.nickreplace(searchdata, categories=("object", "account"), include_account=True) if (global_search or (is_string and searchdata.startswith("#") and len(searchdata) > 1 and searchdata[1:].isdigit())): @@ -405,19 +405,19 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): return _AT_SEARCH_RESULT(results, self, query=searchdata, nofound_string=nofound_string, multimatch_string=multimatch_string) - def search_player(self, searchdata, quiet=False): + def search_account(self, searchdata, quiet=False): """ - Simple shortcut wrapper to search for players, not characters. + Simple shortcut wrapper to search for accounts, not characters. Args: - searchdata (str): Search criterion - the key or dbref of the player + searchdata (str): Search criterion - the key or dbref of the account to search for. If this is "here" or "me", search - for the player connected to this object. + for the account connected to this object. quiet (bool): Returns the results as a list rather than echo eventual standard error messages. Default `False`. Returns: - result (Player, None or list): Just what is returned depends on + result (Account, None or list): Just what is returned depends on the `quiet` setting: - `quiet=True`: No match or multumatch auto-echoes errors to self.msg, then returns `None`. The esults are passed @@ -426,15 +426,15 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): unique match, this will be returned. - `quiet=True`: No automatic error messaging is done, and what is returned is always a list with 0, 1 or more - matching Players. + matching Accounts. """ if isinstance(searchdata, basestring): # searchdata is a string; wrap some common self-references if searchdata.lower() in ("me", "self",): - return [self.player] if quiet else self.player + return [self.account] if quiet else self.account - results = self.player.__class__.objects.player_search(searchdata) + results = self.account.__class__.objects.account_search(searchdata) if quiet: return results @@ -445,7 +445,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): Do something as this object. This is never called normally, it's only used when wanting specifically to let an object be the caller of a command. It makes use of nicks of eventual - connected players as well. + connected accounts as well. Args: raw_string (string): Raw command input @@ -473,7 +473,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): # nick replacement - we require full-word matching. # do text encoding conversion raw_string = to_unicode(raw_string) - raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_player=True) + raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_account=True) return cmdhandler.cmdhandler(self, raw_string, callertype="object", session=session, **kwargs) def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs): @@ -750,7 +750,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): def clear_contents(self): """ - Moves all objects (players/things) to their home location or + Moves all objects (accounts/things) to their home location or to default home. """ # Gather up everything that thinks this is its location. @@ -781,13 +781,13 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): logger.log_err(string % (obj.name, obj.dbid)) return - if obj.has_player: + if obj.has_account: if home: string = "Your current location has ceased to exist," string += " moving you to %s(#%d)." obj.msg(_(string) % (home.name, home.dbid)) else: - # Famous last words: The player should never see this. + # Famous last words: The account should never see this. string = "This place should not exist ... contact an admin." obj.msg(_(string)) obj.move_to(home) @@ -853,16 +853,16 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): self.delete_iter += 1 - # See if we need to kick the player off. + # See if we need to kick the account off. for session in self.sessions.all(): session.msg(_("Your character %s has been destroyed.") % self.key) - # no need to disconnect, Player just jumps to OOC mode. + # no need to disconnect, Account just jumps to OOC mode. # sever the connection (important!) - if self.player: + if self.account: for session in self.sessions.all(): - self.player.unpuppet_object(session) - self.player = None + self.account.unpuppet_object(session) + self.account = None for script in _ScriptDB.objects.get_all_scripts_on_obj(self): script.stop() @@ -1042,19 +1042,19 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): have no cmdsets. Kwargs: - caller (Session, Object or Player): The caller requesting + caller (Session, Object or Account): The caller requesting this cmdset. """ pass - def at_pre_puppet(self, player, session=None, **kwargs): + def at_pre_puppet(self, account, session=None, **kwargs): """ - Called just before a Player connects to this object to puppet + Called just before an Account connects to this object to puppet it. Args: - player (Player): This is the connecting player. + account (Account): This is the connecting account. session (Session): Session controlling the connection. **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default). @@ -1065,44 +1065,44 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): def at_post_puppet(self, **kwargs): """ Called just after puppeting has been completed and all - Player<->Object links have been established. + Account<->Object links have been established. Args: **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default). Note: - You can use `self.player` and `self.sessions.get()` to get - player and sessions at this point; the last entry in the + You can use `self.account` and `self.sessions.get()` to get + account and sessions at this point; the last entry in the list from `self.sessions.get()` is the latest Session puppeting this Object. """ - self.player.db._last_puppet = self + self.account.db._last_puppet = self def at_pre_unpuppet(self, **kwargs): """ Called just before beginning to un-connect a puppeting from - this Player. + this Account. Args: **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default). Note: - You can use `self.player` and `self.sessions.get()` to get - player and sessions at this point; the last entry in the + You can use `self.account` and `self.sessions.get()` to get + account and sessions at this point; the last entry in the list from `self.sessions.get()` is the latest Session puppeting this Object. """ pass - def at_post_unpuppet(self, player, session=None, **kwargs): + def at_post_unpuppet(self, account, session=None, **kwargs): """ - Called just after the Player successfully disconnected from + Called just after the Account successfully disconnected from this object, severing all connections. Args: - player (Player): The player object that just disconnected + account (Account): The account object that just disconnected from this object. session (Session): Session id controlling the connection that just disconnected. @@ -1139,7 +1139,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): Args: result (bool): The outcome of the access call. - accessing_obj (Object or Player): The entity trying to gain access. + accessing_obj (Object or Account): The entity trying to gain access. access_type (str): The type of access that was requested. Kwargs: @@ -1238,8 +1238,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ - if not source_location and self.location.has_player: - # This was created from nowhere and added to a player's + if not source_location and self.location.has_account: + # This was created from nowhere and added to an account's # inventory; it's probably the result of a create command. string = "You now have %s in your possession." % self.get_display_name(self.location) self.location.msg(string) @@ -1437,7 +1437,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): key = con.get_display_name(looker) if con.destination: exits.append(key) - elif con.has_player: + elif con.has_account: users.append("|c%s|n" % key) else: things.append(key) @@ -1576,7 +1576,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): class DefaultCharacter(DefaultObject): """ This implements an Object puppeted by a Session - that is, - a character avatar controlled by a player. + a character avatar controlled by an account. """ @@ -1604,11 +1604,11 @@ class DefaultCharacter(DefaultObject): if self.location.access(self, "view"): self.msg(self.at_look(self.location)) - def at_pre_puppet(self, player, session=None, **kwargs): + def at_pre_puppet(self, account, session=None, **kwargs): """ Return the character from storage in None location in `at_post_unpuppet`. Args: - player (Player): This is the connecting player. + account (Account): This is the connecting account. session (Session): Session controlling the connection. """ if self.location is None: # Make sure character's location is never None before being puppeted. @@ -1618,19 +1618,19 @@ class DefaultCharacter(DefaultObject): if self.location: # If the character is verified to be somewhere, self.db.prelogout_location = self.location # save location again to be sure. else: - player.msg("|r%s has no location and no home is set.|n" % self, session=session) # Note to set home. + account.msg("|r%s has no location and no home is set.|n" % self, session=session) # Note to set home. def at_post_puppet(self, **kwargs): """ Called just after puppeting has been completed and all - Player<->Object links have been established. + Account<->Object links have been established. Args: **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default). Note: - You can use `self.player` and `self.sessions.get()` to get - player and sessions at this point; the last entry in the + You can use `self.account` and `self.sessions.get()` to get + account and sessions at this point; the last entry in the list from `self.sessions.get()` is the latest Session puppeting this Object. @@ -1642,14 +1642,14 @@ class DefaultCharacter(DefaultObject): obj.msg("%s has entered the game." % self.get_display_name(obj), from_obj=from_obj) self.location.for_contents(message, exclude=[self], from_obj=self) - def at_post_unpuppet(self, player, session=None, **kwargs): + def at_post_unpuppet(self, account, session=None, **kwargs): """ - We stove away the character when the player goes ooc/logs off, + We stove away the character when the account goes ooc/logs off, otherwise the character object will remain in the room also - after the player logged off ("headless", so to say). + after the account logged off ("headless", so to say). Args: - player (Player): The player object that just disconnected + account (Account): The account object that just disconnected from this object. session (Session): Session controlling the connection that just disconnected. @@ -1782,7 +1782,7 @@ class DefaultExit(DefaultObject): Helper function for creating an exit command set + command. The command of this cmdset has the same name as the Exit - object and allows the exit to react when the player enter the + object and allows the exit to react when the account enter the exit's name, triggering the movement between rooms. Args: diff --git a/evennia/scripts/__init__.py b/evennia/scripts/__init__.py index bf8831d53e..fa038dd03b 100644 --- a/evennia/scripts/__init__.py +++ b/evennia/scripts/__init__.py @@ -1,6 +1,6 @@ """ This sub-package holds the Scripts system. Scripts are database -entities that can store data both in connection to Objects and Players +entities that can store data both in connection to Objects and Accounts or globally. They may also have a timer-component to execute various timed effects. diff --git a/evennia/scripts/manager.py b/evennia/scripts/manager.py index 46ae22e05c..c3753ed47f 100644 --- a/evennia/scripts/manager.py +++ b/evennia/scripts/manager.py @@ -49,20 +49,20 @@ class ScriptDBManager(TypedObjectManager): """ if not obj: return [] - player = _GA(_GA(obj, "__dbclass__"), "__name__") == "PlayerDB" + account = _GA(_GA(obj, "__dbclass__"), "__name__") == "AccountDB" if key: dbref = self.dbref(key) if dbref or dbref == 0: - if player: - return self.filter(db_player=obj, id=dbref) + if account: + return self.filter(db_account=obj, id=dbref) else: return self.filter(db_obj=obj, id=dbref) - elif player: - return self.filter(db_player=obj, db_key=key) + elif account: + return self.filter(db_account=obj, db_key=key) else: return self.filter(db_obj=obj, db_key=key) - elif player: - return self.filter(db_player=obj) + elif account: + return self.filter(db_account=obj) else: return self.filter(db_obj=obj) diff --git a/evennia/scripts/models.py b/evennia/scripts/models.py index fe08881918..a23eacb7fd 100644 --- a/evennia/scripts/models.py +++ b/evennia/scripts/models.py @@ -15,13 +15,13 @@ cleaning whatever effect they have had on the game object. Common examples of uses of Scripts: -- Load the default cmdset to the player object's cmdhandler +- Load the default cmdset to the account object's cmdhandler when logging in. - Switch to a different state, such as entering a text editor, start combat or enter a dark room. - Merge a new cmdset with the default one for changing which commands are available at a particular time -- Give the player/object a time-limited bonus/effect +- Give the account/object a time-limited bonus/effect """ from builtins import object @@ -62,7 +62,7 @@ class ScriptDB(TypedObject): The ScriptDB adds the following properties: desc - optional description of script obj - the object the script is linked to, if any - player - the player the script is linked to (exclusive with obj) + account - the account the script is linked to (exclusive with obj) interval - how often script should run start_delay - if the script should start repeating right away repeats - how many times the script should repeat @@ -122,18 +122,18 @@ class ScriptDB(TypedObject): def __get_obj(self): """ Property wrapper that homogenizes access to either the - db_player or db_obj field, using the same object property + db_account or db_obj field, using the same object property name. """ - obj = _GA(self, "db_player") + obj = _GA(self, "db_account") if not obj: obj = _GA(self, "db_obj") return obj def __set_obj(self, value): """ - Set player or obj to their right database field. If + Set account or obj to their right database field. If a dbref is given, assume ObjectDB. """ @@ -153,8 +153,8 @@ class ScriptDB(TypedObject): except ObjectDoesNotExist: # maybe it is just a name that happens to look like a dbid pass - if value.__class__.__name__ == "PlayerDB": - fname = "db_player" + if value.__class__.__name__ == "AccountDB": + fname = "db_account" _SA(self, fname, value) else: fname = "db_obj" diff --git a/evennia/scripts/scripthandler.py b/evennia/scripts/scripthandler.py index 1106d1a952..358a9853e2 100644 --- a/evennia/scripts/scripthandler.py +++ b/evennia/scripts/scripthandler.py @@ -66,9 +66,9 @@ class ScriptHandler(object): autostart (bool, optional): Start the script upon adding it. """ - if self.obj.__dbclass__.__name__ == "PlayerDB": - # we add to a Player, not an Object - script = create.create_script(scriptclass, key=key, player=self.obj, + if self.obj.__dbclass__.__name__ == "AccountDB": + # we add to an Account, not an Object + script = create.create_script(scriptclass, key=key, account=self.obj, autostart=autostart) else: # the normal - adding to an Object diff --git a/evennia/server/amp.py b/evennia/server/amp.py index 4b4435b318..c4a523674c 100644 --- a/evennia/server/amp.py +++ b/evennia/server/amp.py @@ -6,12 +6,12 @@ Both sides use this same protocol. The separation works like this: Portal - (AMP client) handles protocols. It contains a list of connected - sessions in a dictionary for identifying the respective player + sessions in a dictionary for identifying the respective account connected. If it loses the AMP connection it will automatically try to reconnect. Server - (AMP server) Handles all mud operations. The server holds its own list - of sessions tied to player objects. This is synced against the portal + of sessions tied to account objects. This is synced against the portal at startup and when a session connects/disconnects """ diff --git a/evennia/server/deprecations.py b/evennia/server/deprecations.py index 598e409d8d..7425ef7c5a 100644 --- a/evennia/server/deprecations.py +++ b/evennia/server/deprecations.py @@ -24,7 +24,7 @@ def check_errors(settings): raise DeprecationWarning(deprstring % ( "CMDSET_DEFAULT", "CMDSET_CHARACTER")) if hasattr(settings, "CMDSET_OOC"): - raise DeprecationWarning(deprstring % ("CMDSET_OOC", "CMDSET_PLAYER")) + raise DeprecationWarning(deprstring % ("CMDSET_OOC", "CMDSET_ACCOUNT")) if settings.WEBSERVER_ENABLED and not isinstance(settings.WEBSERVER_PORTS[0], tuple): raise DeprecationWarning( "settings.WEBSERVER_PORTS must be on the form " @@ -46,8 +46,8 @@ def check_errors(settings): raise DeprecationWarning(deprstring % "OBJECT_TYPECLASS_PATHS") if hasattr(settings, "SCRIPT_TYPECLASS_PATHS"): raise DeprecationWarning(deprstring % "SCRIPT_TYPECLASS_PATHS") - if hasattr(settings, "PLAYER_TYPECLASS_PATHS"): - raise DeprecationWarning(deprstring % "PLAYER_TYPECLASS_PATHS") + if hasattr(settings, "ACCOUNT_TYPECLASS_PATHS"): + raise DeprecationWarning(deprstring % "ACCOUNT_TYPECLASS_PATHS") if hasattr(settings, "CHANNEL_TYPECLASS_PATHS"): raise DeprecationWarning(deprstring % "CHANNEL_TYPECLASS_PATHS") if hasattr(settings, "SEARCH_MULTIMATCH_SEPARATOR"): diff --git a/evennia/server/evennia_launcher.py b/evennia/server/evennia_launcher.py index 28f6c60bde..a386d6051a 100644 --- a/evennia/server/evennia_launcher.py +++ b/evennia/server/evennia_launcher.py @@ -132,15 +132,15 @@ ERROR_NO_GAMEDIR = \ WARNING_MOVING_SUPERUSER = \ """ - WARNING: Evennia expects a Player superuser with id=1. No such - Player was found. However, another superuser ('{other_key}', + WARNING: Evennia expects an Account superuser with id=1. No such + Account was found. However, another superuser ('{other_key}', id={other_id}) was found in the database. If you just created this superuser and still see this text it is probably due to the database being flushed recently - in this case the database's internal auto-counter might just start from some value higher than one. - We will fix this by assigning the id 1 to Player '{other_key}'. + We will fix this by assigning the id 1 to Account '{other_key}'. Please confirm this is acceptable before continuing. """ @@ -182,7 +182,7 @@ RECREATED_SETTINGS = \ Note that if you were using an existing database, the password salt of this new settings file will be different from the old one. - This means that any existing players may not be able to log in to + This means that any existing accounts may not be able to log in to their accounts with their old passwords. """ @@ -270,7 +270,7 @@ HELP_ENTRY = \ adding new protocols or are debugging Evennia itself. Reload with (5) to update the server with your changes without - disconnecting any players. + disconnecting any accounts. Note: Reload and stop are sometimes poorly supported in Windows. If you have issues, log into the game to stop or restart the server instead. @@ -581,11 +581,11 @@ def create_game_directory(dirname): def create_superuser(): """ - Create the superuser player + Create the superuser account """ print( - "\nCreate a superuser below. The superuser is Player #1, the 'owner' " + "\nCreate a superuser below. The superuser is Account #1, the 'owner' " "account of the server.\n") django.core.management.call_command("createsuperuser", interactive=True) @@ -602,20 +602,20 @@ def check_database(): tables = connection.introspection.get_table_list(connection.cursor()) if not tables or not isinstance(tables[0], basestring): # django 1.8+ tables = [tableinfo.name for tableinfo in tables] - if tables and u'players_playerdb' in tables: + if tables and u'accounts_accountdb' in tables: # database exists and seems set up. Initialize evennia. evennia._init() - # Try to get Player#1 - from evennia.players.models import PlayerDB + # Try to get Account#1 + from evennia.accounts.models import AccountDB try: - PlayerDB.objects.get(id=1) + AccountDB.objects.get(id=1) except django.db.utils.OperationalError as e: print(ERROR_DATABASE.format(traceback=e)) sys.exit() - except PlayerDB.DoesNotExist: + except AccountDB.DoesNotExist: # no superuser yet. We need to create it. - other_superuser = PlayerDB.objects.filter(is_superuser=True) + other_superuser = AccountDB.objects.filter(is_superuser=True) if other_superuser: # Another superuser was found, but not with id=1. This may # happen if using flush (the auto-id starts at a higher @@ -811,10 +811,10 @@ def error_check_python_modules(): print("Warning: CMDSET_UNLOGGED failed to load!") if not cmdsethandler.import_cmdset(settings.CMDSET_CHARACTER, None): print("Warning: CMDSET_CHARACTER failed to load") - if not cmdsethandler.import_cmdset(settings.CMDSET_PLAYER, None): - print("Warning: CMDSET_PLAYER failed to load") + if not cmdsethandler.import_cmdset(settings.CMDSET_ACCOUNT, None): + print("Warning: CMDSET_ACCOUNT failed to load") # typeclasses - _imp(settings.BASE_PLAYER_TYPECLASS) + _imp(settings.BASE_ACCOUNT_TYPECLASS) _imp(settings.BASE_OBJECT_TYPECLASS) _imp(settings.BASE_CHARACTER_TYPECLASS) _imp(settings.BASE_ROOM_TYPECLASS) @@ -945,10 +945,10 @@ def run_dummyrunner(number_of_dummies): Start an instance of the dummyrunner Args: - number_of_dummies (int): The number of dummy players to start. + number_of_dummies (int): The number of dummy accounts to start. Notes: - The dummy players' behavior can be customized by adding a + The dummy accounts' behavior can be customized by adding a `dummyrunner_settings.py` config file in the game's conf/ directory. @@ -1215,7 +1215,7 @@ def main(): parser.add_argument( '--dummyrunner', nargs=1, action='store', dest='dummyrunner', metavar="N", - help="Test a running server by connecting N dummy players to it.") + help="Test a running server by connecting N dummy accounts to it.") parser.add_argument( '--settings', nargs=1, action='store', dest='altsettings', default=None, metavar="filename.py", diff --git a/evennia/server/initial_setup.py b/evennia/server/initial_setup.py index b124067953..985a54dc95 100644 --- a/evennia/server/initial_setup.py +++ b/evennia/server/initial_setup.py @@ -10,7 +10,7 @@ from __future__ import print_function import time from django.conf import settings from django.utils.translation import ugettext as _ -from evennia.players.models import PlayerDB +from evennia.accounts.models import AccountDB from evennia.server.models import ServerConfig from evennia.utils import create, logger @@ -28,7 +28,7 @@ ERROR_NO_SUPERUSER = """ LIMBO_DESC = _(""" Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if you need help, want to contribute, report issues or just join the community. -As Player #1 you can create a demo/tutorial area with |w@batchcommand tutorial_world.build|n. +As Account #1 you can create a demo/tutorial area with |w@batchcommand tutorial_world.build|n. """) @@ -36,55 +36,55 @@ WARNING_POSTGRESQL_FIX = """ PostgreSQL-psycopg2 compatibility fix: The in-game channels {chan1}, {chan2} and {chan3} were created, but the superuser was not yet connected to them. Please use in - game commands to connect Player #1 to those channels when first + game commands to connect Account #1 to those channels when first logging in. """ -def get_god_player(): +def get_god_account(): """ Creates the god user and don't take no for an answer. """ try: - god_player = PlayerDB.objects.get(id=1) - except PlayerDB.DoesNotExist: - raise PlayerDB.DoesNotExist(ERROR_NO_SUPERUSER) - return god_player + god_account = AccountDB.objects.get(id=1) + except AccountDB.DoesNotExist: + raise AccountDB.DoesNotExist(ERROR_NO_SUPERUSER) + return god_account def create_objects(): """ - Creates the #1 player and Limbo room. + Creates the #1 account and Limbo room. """ - logger.log_info("Creating objects (Player #1 and Limbo room) ...") + logger.log_info("Creating objects (Account #1 and Limbo room) ...") # Set the initial User's account object's username on the #1 object. # This object is pure django and only holds name, email and password. - god_player = get_god_player() + god_account = get_god_account() - # Create a Player 'user profile' object to hold eventual - # mud-specific settings for the PlayerDB object. - player_typeclass = settings.BASE_PLAYER_TYPECLASS + # Create an Account 'user profile' object to hold eventual + # mud-specific settings for the AccountDB object. + account_typeclass = settings.BASE_ACCOUNT_TYPECLASS - # run all creation hooks on god_player (we must do so manually + # run all creation hooks on god_account (we must do so manually # since the manage.py command does not) - god_player.swap_typeclass(player_typeclass, clean_attributes=True) - god_player.basetype_setup() - god_player.at_player_creation() - god_player.locks.add("examine:perm(Developer);edit:false();delete:false();boot:false();msg:all()") + god_account.swap_typeclass(account_typeclass, clean_attributes=True) + god_account.basetype_setup() + god_account.at_account_creation() + god_account.locks.add("examine:perm(Developer);edit:false();delete:false();boot:false();msg:all()") # this is necessary for quelling to work correctly. - god_player.permissions.add("Developer") + god_account.permissions.add("Developer") # Limbo is the default "nowhere" starting room - # Create the in-game god-character for player #1 and set + # Create the in-game god-character for account #1 and set # it to exist in Limbo. character_typeclass = settings.BASE_CHARACTER_TYPECLASS god_character = create.create_object(character_typeclass, - key=god_player.username, + key=god_account.username, nohome=True) god_character.id = 1 @@ -93,13 +93,13 @@ def create_objects(): god_character.locks.add("examine:perm(Developer);edit:false();delete:false();boot:false();msg:all();puppet:false()") god_character.permissions.add("Developer") - god_player.attributes.add("_first_login", True) - god_player.attributes.add("_last_puppet", god_character) + god_account.attributes.add("_first_login", True) + god_account.attributes.add("_last_puppet", god_character) try: - god_player.db._playable_characters.append(god_character) + god_account.db._playable_characters.append(god_character) except AttributeError: - god_player.db_playable_characters = [god_character] + god_account.db_playable_characters = [god_character] room_typeclass = settings.BASE_ROOM_TYPECLASS limbo_obj = create.create_object(room_typeclass, _('Limbo'), nohome=True) @@ -123,7 +123,7 @@ def create_channels(): """ logger.log_info("Creating default channels ...") - goduser = get_god_player() + goduser = get_god_account() for channeldict in settings.DEFAULT_CHANNELS: channel = create.create_channel(**channeldict) channel.connect(goduser) diff --git a/evennia/server/inputfuncs.py b/evennia/server/inputfuncs.py index c956db0870..3881b9fbec 100644 --- a/evennia/server/inputfuncs.py +++ b/evennia/server/inputfuncs.py @@ -23,7 +23,7 @@ from future.utils import viewkeys import importlib from django.conf import settings from evennia.commands.cmdhandler import cmdhandler -from evennia.players.models import PlayerDB +from evennia.accounts.models import AccountDB from evennia.utils.logger import log_err from evennia.utils.utils import to_str, to_unicode @@ -67,15 +67,15 @@ def text(session, *args, **kwargs): if txt.strip() in _IDLE_COMMAND: session.update_session_counters(idle=True) return - if session.player: + if session.account: # nick replacement puppet = session.puppet if puppet: txt = puppet.nicks.nickreplace(txt, - categories=("inputline", "channel"), include_player=True) + categories=("inputline", "channel"), include_account=True) else: - txt = session.player.nicks.nickreplace(txt, - categories=("inputline", "channel"), include_player=False) + txt = session.account.nicks.nickreplace(txt, + categories=("inputline", "channel"), include_account=False) kwargs.pop("options", None) cmdhandler(session, txt, callertype="session", session=session, **kwargs) session.update_session_counters() @@ -105,7 +105,7 @@ def bot_data_in(session, *args, **kwargs): return kwargs.pop("options", None) # Trigger the execute_cmd method of the corresponding bot. - session.player.execute_cmd(session=session, txt=txt, **kwargs) + session.account.execute_cmd(session=session, txt=txt, **kwargs) session.update_session_counters() @@ -153,10 +153,10 @@ def browser_sessid(session, *args, **kwargs): uid = browsersession.get("logged_in", None) if uid: try: - player = PlayerDB.objects.get(pk=uid) + account = AccountDB.objects.get(pk=uid) except Exception: return - session.sessionhandler.login(session, player) + session.sessionhandler.login(session, account) @@ -288,15 +288,15 @@ def login(session, *args, **kwargs): in. This will also automatically throttle too quick attempts. Kwargs: - name (str): Player name + name (str): Account name password (str): Plain-text password """ if not session.logged_in and "name" in kwargs and "password" in kwargs: - from evennia.commands.default.unloggedin import create_normal_player - player = create_normal_player(session, kwargs["name"], kwargs["password"]) - if player: - session.sessionhandler.login(session, player) + from evennia.commands.default.unloggedin import create_normal_account + account = create_normal_account(session, kwargs["name"], kwargs["password"]) + if account: + session.sessionhandler.login(session, account) _gettable = { "name": lambda obj: obj.key, @@ -308,7 +308,7 @@ _gettable = { def get_value(session, *args, **kwargs): """ Return the value of a given attribute or db_property on the - session's current player or character. + session's current account or character. Kwargs: name (str): Name of info value to return. Only names @@ -317,7 +317,7 @@ def get_value(session, *args, **kwargs): """ name = kwargs.get("name", "") - obj = session.puppet or session.player + obj = session.puppet or session.account if name in _gettable: session.msg(get_value={"name": name, "value": _gettable[name](obj)}) @@ -428,7 +428,7 @@ def unmonitor(session, *args, **kwargs): def _on_webclient_options_change(**kwargs): """ - Called when the webclient options stored on the player changes. + Called when the webclient options stored on the account changes. Inform the interested clients of this change. """ session = kwargs["session"] @@ -452,15 +452,15 @@ def webclient_options(session, *args, **kwargs): that changes. If kwargs is not empty, the key/values stored in there will be persisted - to the player object. + to the account object. Kwargs: