From 8c0dca25002378156d5748ee0b5c1d7d53d58885 Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 1 Oct 2018 18:29:21 +0200 Subject: [PATCH 01/15] Run collectstatic. Fix input autofocus in webclient --- evennia/web/webclient/static/webclient/js/plugins/default_in.js | 1 + 1 file changed, 1 insertion(+) diff --git a/evennia/web/webclient/static/webclient/js/plugins/default_in.js b/evennia/web/webclient/static/webclient/js/plugins/default_in.js index 28bfc9f315..02fd401706 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/default_in.js +++ b/evennia/web/webclient/static/webclient/js/plugins/default_in.js @@ -8,6 +8,7 @@ let defaultin_plugin = (function () { // // handle the default key triggering onSend() var onKeydown = function (event) { + $("#inputfield").focus(); if ( (event.which === 13) && (!event.shiftKey) ) { // Enter Key without shift var inputfield = $("#inputfield"); var outtext = inputfield.val(); From 33e54035a5f9a97ca088220912a5a618605d3385 Mon Sep 17 00:00:00 2001 From: Johnny Date: Wed, 3 Oct 2018 20:47:27 +0000 Subject: [PATCH 02/15] Adds logging to create/puppet/update/delete commands. --- evennia/commands/default/account.py | 7 ++++++- evennia/commands/default/admin.py | 15 +++++++++++++-- evennia/commands/default/comms.py | 4 +++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/evennia/commands/default/account.py b/evennia/commands/default/account.py index b50f55a8e0..7eda54e75c 100644 --- a/evennia/commands/default/account.py +++ b/evennia/commands/default/account.py @@ -23,7 +23,7 @@ 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 +from evennia.utils import utils, create, logger, search, evtable COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS) @@ -171,6 +171,7 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS): new_character.db.desc = "This is a character." self.msg("Created new character %s. Use |w@ic %s|n to enter the game as this character." % (new_character.key, new_character.key)) + logger.log_sec('Character Created: %s (Caller: %s, IP: %s).' % (new_character, account, self.session.address)) class CmdCharDelete(COMMAND_DEFAULT_CLASS): @@ -214,6 +215,7 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS): 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) + logger.log_sec('Character Deleted: %s (Caller: %s, IP: %s).' % (key, account, self.session.address)) else: self.msg("Deletion was aborted.") del caller.ndb._char_to_delete @@ -279,8 +281,10 @@ class CmdIC(COMMAND_DEFAULT_CLASS): try: account.puppet_object(session, new_character) account.db._last_puppet = new_character + logger.log_sec('Puppet Success: (Caller: %s, Target: %s, IP: %s).' % (account, new_character, self.session.address)) except RuntimeError as exc: self.msg("|rYou cannot become |C%s|n: %s" % (new_character.name, exc)) + logger.log_sec('Puppet Failed: %s (Caller: %s, Target: %s, IP: %s).' % (exc, account, new_character, self.session.address)) # note that this is inheriting from MuxAccountLookCommand, @@ -641,6 +645,7 @@ class CmdPassword(COMMAND_DEFAULT_CLASS): account.set_password(newpass) account.save() self.msg("Password changed.") + logger.log_sec('Password Changed: %s (Caller: %s, IP: %s).' % (account, account, self.session.address)) class CmdQuit(COMMAND_DEFAULT_CLASS): diff --git a/evennia/commands/default/admin.py b/evennia/commands/default/admin.py index fc90277127..917612c882 100644 --- a/evennia/commands/default/admin.py +++ b/evennia/commands/default/admin.py @@ -9,7 +9,7 @@ import re from django.conf import settings from evennia.server.sessionhandler import SESSIONS from evennia.server.models import ServerConfig -from evennia.utils import evtable, search, class_from_module +from evennia.utils import evtable, logger, search, class_from_module COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) @@ -95,6 +95,9 @@ class CmdBoot(COMMAND_DEFAULT_CLASS): for session in boot_list: session.msg(feedback) session.account.disconnect_session_from_account(session) + + if pobj and boot_list: + logger.log_sec('Booted: %s (Reason: %s, Caller: %s, IP: %s).' % (pobj, reason, caller, self.session.address)) # regex matching IP addresses with wildcards, eg. 233.122.4.* @@ -203,6 +206,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS): banlist.append(bantup) ServerConfig.objects.conf('server_bans', banlist) self.caller.msg("%s-Ban |w%s|n was added." % (typ, ban)) + logger.log_sec('Banned %s: %s (Caller: %s, IP: %s).' % (typ, ban.strip(), self.caller, self.session.address)) class CmdUnban(COMMAND_DEFAULT_CLASS): @@ -246,8 +250,10 @@ class CmdUnban(COMMAND_DEFAULT_CLASS): ban = banlist[num - 1] del banlist[num - 1] ServerConfig.objects.conf('server_bans', banlist) + value = " ".join([s for s in ban[:2]]) self.caller.msg("Cleared ban %s: %s" % - (num, " ".join([s for s in ban[:2]]))) + (num, value)) + logger.log_sec('Unbanned: %s (Caller: %s, IP: %s).' % (value.strip(), self.caller, self.session.address)) class CmdDelAccount(COMMAND_DEFAULT_CLASS): @@ -317,6 +323,7 @@ class CmdDelAccount(COMMAND_DEFAULT_CLASS): if reason: string += " Reason given:\n '%s'" % reason account.msg(string) + logger.log_sec('Account Deleted: %s (Reason: %s, Caller: %s, IP: %s).' % (account, reason, caller, self.session.address)) account.delete() self.msg("Account %s was successfully deleted." % uname) @@ -445,6 +452,7 @@ class CmdNewPassword(COMMAND_DEFAULT_CLASS): if account.character != caller: account.msg("%s has changed your password to '%s'." % (caller.name, newpass)) + logger.log_sec('Password Changed: %s (Caller: %s, IP: %s).' % (account, caller, self.session.address)) class CmdPerm(COMMAND_DEFAULT_CLASS): @@ -526,6 +534,7 @@ class CmdPerm(COMMAND_DEFAULT_CLASS): else: caller_result.append("\nPermission %s removed from %s (if they existed)." % (perm, obj.name)) target_result.append("\n%s revokes the permission(s) %s from you." % (caller.name, perm)) + logger.log_sec('Permissions Deleted: %s, %s (Caller: %s, IP: %s).' % (perm, obj, caller, self.session.address)) else: # add a new permission permissions = obj.permissions.all() @@ -547,6 +556,8 @@ class CmdPerm(COMMAND_DEFAULT_CLASS): caller_result.append("\nPermission '%s' given to %s (%s)." % (perm, obj.name, plystring)) target_result.append("\n%s gives you (%s, %s) the permission '%s'." % (caller.name, obj.name, plystring, perm)) + logger.log_sec('Permissions Added: %s, %s (Caller: %s, IP: %s).' % (obj, perm, caller, self.session.address)) + caller.msg("".join(caller_result).strip()) if target_result: obj.msg("".join(target_result).strip()) diff --git a/evennia/commands/default/comms.py b/evennia/commands/default/comms.py index d9fe0b0d20..90ef0a0086 100644 --- a/evennia/commands/default/comms.py +++ b/evennia/commands/default/comms.py @@ -14,7 +14,7 @@ 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 +from evennia.utils import create, logger, utils, evtable from evennia.utils.utils import make_iter, class_from_module COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) @@ -368,6 +368,7 @@ class CmdCdestroy(COMMAND_DEFAULT_CLASS): channel.delete() CHANNELHANDLER.update() self.msg("Channel '%s' was destroyed." % channel_key) + logger.log_sec('Channel Deleted: %s (Caller: %s, IP: %s).' % (channel_key, caller, self.session.address)) class CmdCBoot(COMMAND_DEFAULT_CLASS): @@ -433,6 +434,7 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS): # disconnect account channel.disconnect(account) CHANNELHANDLER.update() + logger.log_sec('Channel Boot: %s (Channel: %s, Reason: %s, Caller: %s, IP: %s).' % (account, channel, reason, caller, self.session.address)) class CmdCemit(COMMAND_DEFAULT_CLASS): From 0fd6b1beae507018599c05eb51bdeb1d60597b91 Mon Sep 17 00:00:00 2001 From: Brenden Tuck Date: Thu, 4 Oct 2018 19:59:30 -0400 Subject: [PATCH 03/15] Fix #1668 - up arrow key regression --- .../static/webclient/js/plugins/history.js | 30 ++++--------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/evennia/web/webclient/static/webclient/js/plugins/history.js b/evennia/web/webclient/static/webclient/js/plugins/history.js index 1bef6031cd..c33dbcabf9 100644 --- a/evennia/web/webclient/static/webclient/js/plugins/history.js +++ b/evennia/web/webclient/static/webclient/js/plugins/history.js @@ -43,14 +43,6 @@ let history_plugin = (function () { history_pos = 0; } - // - // Go to the last history line - var end = function () { - // move to the end of the history stack - history_pos = 0; - return history[history.length -1]; - } - // // Add input to the scratch line var scratch = function (input) { @@ -69,28 +61,17 @@ let history_plugin = (function () { var history_entry = null; var inputfield = $("#inputfield"); - if (inputfield[0].selectionStart == inputfield.val().length) { - // Only process up/down arrow if cursor is at the end of the line. - if (code === 38) { // Arrow up - history_entry = back(); - } - else if (code === 40) { // Arrow down - history_entry = fwd(); - } + if (code === 38) { // Arrow up + history_entry = back(); + } + else if (code === 40) { // Arrow down + history_entry = fwd(); } if (history_entry !== null) { // Doing a history navigation; replace the text in the input. inputfield.val(history_entry); } - else { - // Save the current contents of the input to the history scratch area. - setTimeout(function () { - // Need to wait until after the key-up to capture the value. - scratch(inputfield.val()); - end(); - }, 0); - } return false; } @@ -99,6 +80,7 @@ let history_plugin = (function () { // Listen for onSend lines to add to history var onSend = function (line) { add(line); + return null; // we are not returning an altered input line } // From 21a615250a603743a38af2c252bd50db241b27c8 Mon Sep 17 00:00:00 2001 From: Johnny Date: Fri, 5 Oct 2018 19:01:38 +0000 Subject: [PATCH 04/15] Fixes incorrect LOGIN_URL and LOGOUT_URL by means of reverse_lazy call. --- evennia/settings_default.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/evennia/settings_default.py b/evennia/settings_default.py index 9efbb6314b..9d6ad9e686 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -13,6 +13,7 @@ always be sure of what you have changed and what is default behaviour. """ from builtins import range +from django.urls import reverse_lazy import os import sys @@ -697,9 +698,9 @@ ROOT_URLCONF = 'web.urls' # Where users are redirected after logging in via contrib.auth.login. LOGIN_REDIRECT_URL = '/' # Where to redirect users when using the @login_required decorator. -LOGIN_URL = '/accounts/login' +LOGIN_URL = reverse_lazy('login') # Where to redirect users who wish to logout. -LOGOUT_URL = '/accounts/login' +LOGOUT_URL = reverse_lazy('logout') # URL that handles the media served from MEDIA_ROOT. # Example: "http://media.lawrence.com" MEDIA_URL = '/media/' From b49a3b8feee4538143ce3cffbfaf15052315b150 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 6 Oct 2018 14:21:24 +0200 Subject: [PATCH 05/15] [fix] Correct missing caller arg in security message --- evennia/commands/default/admin.py | 10 +++++----- evennia/commands/default/comms.py | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/evennia/commands/default/admin.py b/evennia/commands/default/admin.py index 917612c882..3bb4e7e512 100644 --- a/evennia/commands/default/admin.py +++ b/evennia/commands/default/admin.py @@ -95,7 +95,7 @@ class CmdBoot(COMMAND_DEFAULT_CLASS): for session in boot_list: session.msg(feedback) session.account.disconnect_session_from_account(session) - + if pobj and boot_list: logger.log_sec('Booted: %s (Reason: %s, Caller: %s, IP: %s).' % (pobj, reason, caller, self.session.address)) @@ -435,9 +435,9 @@ class CmdNewPassword(COMMAND_DEFAULT_CLASS): account = caller.search_account(self.lhs) if not account: return - + newpass = self.rhs - + # Validate password validated, error = account.validate_password(newpass) if not validated: @@ -445,7 +445,7 @@ class CmdNewPassword(COMMAND_DEFAULT_CLASS): string = "\n".join(errors) caller.msg(string) return - + account.set_password(newpass) account.save() self.msg("%s - new password set to '%s'." % (account.name, newpass)) @@ -557,7 +557,7 @@ class CmdPerm(COMMAND_DEFAULT_CLASS): target_result.append("\n%s gives you (%s, %s) the permission '%s'." % (caller.name, obj.name, plystring, perm)) logger.log_sec('Permissions Added: %s, %s (Caller: %s, IP: %s).' % (obj, perm, caller, self.session.address)) - + caller.msg("".join(caller_result).strip()) if target_result: obj.msg("".join(target_result).strip()) diff --git a/evennia/commands/default/comms.py b/evennia/commands/default/comms.py index 90ef0a0086..7abde75492 100644 --- a/evennia/commands/default/comms.py +++ b/evennia/commands/default/comms.py @@ -434,7 +434,8 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS): # disconnect account channel.disconnect(account) CHANNELHANDLER.update() - logger.log_sec('Channel Boot: %s (Channel: %s, Reason: %s, Caller: %s, IP: %s).' % (account, channel, reason, caller, self.session.address)) + logger.log_sec('Channel Boot: %s (Channel: %s, Reason: %s, Caller: %s, IP: %s).' % ( + account, channel, reason, self.caller, self.session.address)) class CmdCemit(COMMAND_DEFAULT_CLASS): From dd7ed220192361e6a61b6ccbd52c4b82b3fac734 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 6 Oct 2018 19:00:54 +0200 Subject: [PATCH 06/15] Be more lenient with spawning old, more free-form prototypes --- evennia/commands/default/building.py | 3 +- evennia/prototypes/prototypes.py | 51 ++++++++++++++++++++++------ evennia/prototypes/spawner.py | 3 ++ 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index bd4fb5e188..f0ae108f00 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -2889,7 +2889,8 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS): "use the 'exec' prototype key.") return None try: - protlib.validate_prototype(prototype) + # we homogenize first, to be more lenient + protlib.validate_prototype(protlib.homogenize_prototype(prototype)) except RuntimeError as err: self.caller.msg(str(err)) return diff --git a/evennia/prototypes/prototypes.py b/evennia/prototypes/prototypes.py index 67eccaafd0..a03cbc519f 100644 --- a/evennia/prototypes/prototypes.py +++ b/evennia/prototypes/prototypes.py @@ -6,6 +6,8 @@ Handling storage of prototypes, both database-based ones (DBPrototypes) and thos """ import re +import hashlib +import time from ast import literal_eval from django.conf import settings from evennia.scripts.scripts import DefaultScript @@ -13,7 +15,7 @@ from evennia.objects.models import ObjectDB from evennia.utils.create import create_script from evennia.utils.utils import ( all_from_module, make_iter, is_iter, dbid_to_obj, callables_from_module, - get_all_typeclasses, to_str, dbref, justify) + get_all_typeclasses, to_str, dbref, justify, class_from_module) from evennia.locks.lockhandler import validate_lockstring, check_lockstring from evennia.utils import logger from evennia.utils import inlinefuncs, dbserialize @@ -47,8 +49,8 @@ class ValidationError(RuntimeError): def homogenize_prototype(prototype, custom_keys=None): """ - Homogenize the more free-form prototype (where undefined keys are non-category attributes) - into the stricter form using `attrs` required by the system. + Homogenize the more free-form prototype supported pre Evennia 0.7 into the stricter form. + Args: prototype (dict): Prototype. @@ -56,18 +58,45 @@ def homogenize_prototype(prototype, custom_keys=None): the default reserved keys. Returns: - homogenized (dict): Prototype where all non-identified keys grouped as attributes. + homogenized (dict): Prototype where all non-identified keys grouped as attributes and other + homogenizations like adding missing prototype_keys and setting a default typeclass. + """ reserved = _PROTOTYPE_RESERVED_KEYS + (custom_keys or ()) + attrs = list(prototype.get('attrs', [])) # break reference + tags = make_iter(prototype.get('tags', [])) + homogenized_tags = [] + homogenized = {} for key, val in prototype.items(): if key in reserved: - homogenized[key] = val + if key == 'tags': + for tag in tags: + if not is_iter(tag): + homogenized_tags.append((tag, None, None)) + else: + homogenized_tags.append(tag) + else: + homogenized[key] = val else: + # unassigned keys -> attrs attrs.append((key, val, None, '')) if attrs: homogenized['attrs'] = attrs + if homogenized_tags: + homogenized['tags'] = homogenized_tags + + # add required missing parts that had defaults before + + if "prototype_key" not in prototype: + # assign a random hash as key + homogenized["prototype_key"] = "prototype-{}".format( + hashlib.md5(str(time.time())).hexdigest()[:7]) + + if "typeclass" not in prototype: + homogenized["typeclass"] = settings.BASE_OBJECT_TYPECLASS + return homogenized @@ -432,11 +461,13 @@ def validate_prototype(prototype, protkey=None, protparents=None, _flags['warnings'].append("Prototype {} can only be used as a mixin since it lacks " "a typeclass or a prototype_parent.".format(protkey)) - if (strict and typeclass and typeclass not - in get_all_typeclasses("evennia.objects.models.ObjectDB")): - _flags['errors'].append( - "Prototype {} is based on typeclass {}, which could not be imported!".format( - protkey, typeclass)) + if strict and typeclass: + try: + class_from_module(typeclass) + except ImportError as err: + _flags['errors'].append( + "{}: Prototype {} is based on typeclass {}, which could not be imported!".format( + err, protkey, typeclass)) # recursively traverese prototype_parent chain diff --git a/evennia/prototypes/spawner.py b/evennia/prototypes/spawner.py index 7d876cf580..1ca7229bea 100644 --- a/evennia/prototypes/spawner.py +++ b/evennia/prototypes/spawner.py @@ -659,6 +659,9 @@ def spawn(*prototypes, **kwargs): # get available protparents protparents = {prot['prototype_key'].lower(): prot for prot in protlib.search_prototype()} + if not kwargs.get("only_validate"): + prototypes = [protlib.homogenize_prototype(prot) for prot in prototypes] + # overload module's protparents with specifically given protparents # we allow prototype_key to be the key of the protparent dict, to allow for module-level # prototype imports. We need to insert prototype_key in this case From d444820036ca18e49c038deb799146a3f8f75fe0 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 6 Oct 2018 19:05:57 +0200 Subject: [PATCH 07/15] Fix bug in spawning with attributes --- evennia/prototypes/spawner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/evennia/prototypes/spawner.py b/evennia/prototypes/spawner.py index 1ca7229bea..b22770aa94 100644 --- a/evennia/prototypes/spawner.py +++ b/evennia/prototypes/spawner.py @@ -733,8 +733,9 @@ def spawn(*prototypes, **kwargs): val = make_iter(prot.pop("attrs", [])) attributes = [] for (attrname, value, category, locks) in val: - attributes.append((attrname, init_spawn_value(val), category, locks)) + attributes.append((attrname, init_spawn_value(value), category, locks)) + print("attributes to spawn: IN: {}, OUT: {}".format(val, attributes)) simple_attributes = [] for key, value in ((key, value) for key, value in prot.items() if not (key.startswith("ndb_"))): From 5091621f31cd0efa7320264622b9f824bd7d3421 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 6 Oct 2018 19:06:44 +0200 Subject: [PATCH 08/15] Remove debug info --- evennia/prototypes/spawner.py | 1 - 1 file changed, 1 deletion(-) diff --git a/evennia/prototypes/spawner.py b/evennia/prototypes/spawner.py index b22770aa94..ce08944139 100644 --- a/evennia/prototypes/spawner.py +++ b/evennia/prototypes/spawner.py @@ -735,7 +735,6 @@ def spawn(*prototypes, **kwargs): for (attrname, value, category, locks) in val: attributes.append((attrname, init_spawn_value(value), category, locks)) - print("attributes to spawn: IN: {}, OUT: {}".format(val, attributes)) simple_attributes = [] for key, value in ((key, value) for key, value in prot.items() if not (key.startswith("ndb_"))): From 6d95fa9d213e181393986c8451cbf6eaa51748f9 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 6 Oct 2018 21:00:31 +0200 Subject: [PATCH 09/15] Fix unittests. Implement #1675. --- evennia/prototypes/prototypes.py | 2 +- evennia/prototypes/tests.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/evennia/prototypes/prototypes.py b/evennia/prototypes/prototypes.py index a03cbc519f..550f1b2e7b 100644 --- a/evennia/prototypes/prototypes.py +++ b/evennia/prototypes/prototypes.py @@ -94,7 +94,7 @@ def homogenize_prototype(prototype, custom_keys=None): homogenized["prototype_key"] = "prototype-{}".format( hashlib.md5(str(time.time())).hexdigest()[:7]) - if "typeclass" not in prototype: + if "typeclass" not in prototype and "prototype_parent" not in prototype: homogenized["typeclass"] = settings.BASE_OBJECT_TYPECLASS return homogenized diff --git a/evennia/prototypes/tests.py b/evennia/prototypes/tests.py index 1ad1d9ac47..3cf2e38c7b 100644 --- a/evennia/prototypes/tests.py +++ b/evennia/prototypes/tests.py @@ -384,8 +384,9 @@ class TestPrototypeStorage(EvenniaTest): prot3 = protlib.create_prototype(**self.prot3) # partial match - self.assertEqual(list(protlib.search_prototype("prot")), [prot1b, prot2, prot3]) - self.assertEqual(list(protlib.search_prototype(tags="foo1")), [prot1b, prot2, prot3]) + with mock.patch("evennia.prototypes.prototypes._MODULE_PROTOTYPES", {}): + self.assertEqual(list(protlib.search_prototype("prot")), [prot1b, prot2, prot3]) + self.assertEqual(list(protlib.search_prototype(tags="foo1")), [prot1b, prot2, prot3]) self.assertTrue(str(unicode(protlib.list_prototypes(self.char1)))) From 7bdd3e8c35e403c8f95fc78f097a4337080f50f2 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 7 Oct 2018 10:50:35 +0200 Subject: [PATCH 10/15] Clarify prototype_key replacement in modules; address #1676. --- evennia/prototypes/prototypes.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/evennia/prototypes/prototypes.py b/evennia/prototypes/prototypes.py index 550f1b2e7b..eac53a6504 100644 --- a/evennia/prototypes/prototypes.py +++ b/evennia/prototypes/prototypes.py @@ -105,9 +105,11 @@ def homogenize_prototype(prototype, custom_keys=None): for mod in settings.PROTOTYPE_MODULES: # to remove a default prototype, override it with an empty dict. # internally we store as (key, desc, locks, tags, prototype_dict) - prots = [(prototype_key.lower(), homogenize_prototype(prot)) - for prototype_key, prot in all_from_module(mod).items() - if prot and isinstance(prot, dict)] + prots = [] + for variable_name, prot in all_from_module(mod).items(): + if "prototype_key" not in prot: + prot['prototype_key'] = variable_name.lower() + prots.append((prot['prototype_key'], homogenize_prototype(prot))) # assign module path to each prototype_key for easy reference _MODULE_PROTOTYPE_MODULES.update({prototype_key.lower(): mod for prototype_key, _ in prots}) # make sure the prototype contains all meta info From 5b24fd0ed17cc4437543e0f400dfb9e2f878e12b Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 7 Oct 2018 10:50:35 +0200 Subject: [PATCH 11/15] Clarify prototype_key replacement in modules; address #1676. --- evennia/prototypes/prototypes.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/evennia/prototypes/prototypes.py b/evennia/prototypes/prototypes.py index 550f1b2e7b..eac53a6504 100644 --- a/evennia/prototypes/prototypes.py +++ b/evennia/prototypes/prototypes.py @@ -105,9 +105,11 @@ def homogenize_prototype(prototype, custom_keys=None): for mod in settings.PROTOTYPE_MODULES: # to remove a default prototype, override it with an empty dict. # internally we store as (key, desc, locks, tags, prototype_dict) - prots = [(prototype_key.lower(), homogenize_prototype(prot)) - for prototype_key, prot in all_from_module(mod).items() - if prot and isinstance(prot, dict)] + prots = [] + for variable_name, prot in all_from_module(mod).items(): + if "prototype_key" not in prot: + prot['prototype_key'] = variable_name.lower() + prots.append((prot['prototype_key'], homogenize_prototype(prot))) # assign module path to each prototype_key for easy reference _MODULE_PROTOTYPE_MODULES.update({prototype_key.lower(): mod for prototype_key, _ in prots}) # make sure the prototype contains all meta info From 775c9085156833a1bc084d866ace4fc1ecd4056b Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 7 Oct 2018 12:31:43 +0200 Subject: [PATCH 12/15] Fix bug in unittest that would cause occational name collision --- evennia/accounts/tests.py | 18 +++++++++++++++--- evennia/prototypes/spawner.py | 7 ++++--- evennia/typeclasses/attributes.py | 2 +- evennia/typeclasses/tags.py | 5 ++++- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/evennia/accounts/tests.py b/evennia/accounts/tests.py index 2855dd0ca2..1175051d72 100644 --- a/evennia/accounts/tests.py +++ b/evennia/accounts/tests.py @@ -14,9 +14,15 @@ class TestAccountSessionHandler(TestCase): "Check AccountSessionHandler class" def setUp(self): - self.account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount) + self.account = create.create_account( + "TestAccount%s" % randint(0, 999999), email="test@test.com", + password="testpassword", typeclass=DefaultAccount) self.handler = AccountSessionHandler(self.account) + def tearDown(self): + if hasattr(self, 'account'): + self.account.delete() + def test_get(self): "Check get method" self.assertEqual(self.handler.get(), []) @@ -60,6 +66,10 @@ class TestDefaultAccount(TestCase): self.s1.puppet = None self.s1.sessid = 0 + def tearDown(self): + if hasattr(self, "account"): + self.account.delete() + def test_password_validation(self): "Check password validators deny bad passwords" @@ -71,7 +81,6 @@ class TestDefaultAccount(TestCase): "Check validators allow sufficiently complex passwords" for better in ('Mxyzptlk', "j0hn, i'M 0n1y d4nc1nG"): self.assertTrue(self.account.validate_password(better, account=self.account)[0]) - self.account.delete() def test_password_change(self): "Check password setting and validation is working as expected" @@ -109,7 +118,9 @@ class TestDefaultAccount(TestCase): import evennia.server.sessionhandler - account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount) + account = create.create_account( + "TestAccount%s" % randint(0, 999999), email="test@test.com", + password="testpassword", typeclass=DefaultAccount) self.s1.uid = account.uid evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1 @@ -171,6 +182,7 @@ class TestDefaultAccount(TestCase): import evennia.server.sessionhandler account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount) + self.account = account self.s1.uid = account.uid evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1 diff --git a/evennia/prototypes/spawner.py b/evennia/prototypes/spawner.py index ce08944139..ac6ad854b1 100644 --- a/evennia/prototypes/spawner.py +++ b/evennia/prototypes/spawner.py @@ -259,11 +259,11 @@ def prototype_from_object(obj): if aliases: prot['aliases'] = aliases tags = [(tag.db_key, tag.db_category, tag.db_data) - for tag in obj.tags.get(return_tagobj=True, return_list=True) if tag] + for tag in obj.tags.all(return_objs=True)] if tags: prot['tags'] = tags attrs = [(attr.key, attr.value, attr.category, ';'.join(attr.locks.all())) - for attr in obj.attributes.get(return_obj=True, return_list=True) if attr] + for attr in obj.attributes.all()] if attrs: prot['attrs'] = attrs @@ -660,6 +660,7 @@ def spawn(*prototypes, **kwargs): protparents = {prot['prototype_key'].lower(): prot for prot in protlib.search_prototype()} if not kwargs.get("only_validate"): + # homogenization to be more lenient about prototype format when entering the prototype manually prototypes = [protlib.homogenize_prototype(prot) for prot in prototypes] # overload module's protparents with specifically given protparents @@ -714,7 +715,7 @@ def spawn(*prototypes, **kwargs): val = prot.pop("tags", []) tags = [] - for (tag, category, data) in tags: + for (tag, category, data) in val: tags.append((init_spawn_value(val, str), category, data)) prototype_key = prototype.get('prototype_key', None) diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index 863628172a..1dc1902494 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -668,7 +668,7 @@ class AttributeHandler(object): def all(self, accessing_obj=None, default_access=True): """ - Return all Attribute objects on this object. + Return all Attribute objects on this object, regardless of category. Args: accessing_obj (object, optional): Check the `attrread` diff --git a/evennia/typeclasses/tags.py b/evennia/typeclasses/tags.py index 488dce0f85..ea675366fd 100644 --- a/evennia/typeclasses/tags.py +++ b/evennia/typeclasses/tags.py @@ -345,13 +345,14 @@ class TagHandler(object): self._catcache = {} self._cache_complete = False - def all(self, return_key_and_category=False): + def all(self, return_key_and_category=False, return_objs=False): """ Get all tags in this handler, regardless of category. Args: return_key_and_category (bool, optional): Return a list of tuples `[(key, category), ...]`. + return_objs (bool, optional): Return tag objects. Returns: tags (list): A list of tag keys `[tagkey, tagkey, ...]` or @@ -365,6 +366,8 @@ class TagHandler(object): if return_key_and_category: # return tuple (key, category) return [(to_str(tag.db_key), to_str(tag.db_category)) for tag in tags] + elif return_objs: + return tags else: return [to_str(tag.db_key) for tag in tags] From 1b00e103e7f68e3948b54713679a6fbc43553a90 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 7 Oct 2018 14:31:36 +0200 Subject: [PATCH 13/15] Cleanup of account tests with more mocking --- evennia/accounts/tests.py | 29 +++++++++++++++-------------- evennia/prototypes/spawner.py | 1 + 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/evennia/accounts/tests.py b/evennia/accounts/tests.py index 1175051d72..78ee87f37d 100644 --- a/evennia/accounts/tests.py +++ b/evennia/accounts/tests.py @@ -1,7 +1,8 @@ -from mock import Mock +from mock import Mock, MagicMock from random import randint from unittest import TestCase +from django.test import override_settings from evennia.accounts.accounts import AccountSessionHandler from evennia.accounts.accounts import DefaultAccount from evennia.server.session import Session @@ -30,24 +31,24 @@ class TestAccountSessionHandler(TestCase): import evennia.server.sessionhandler - s1 = Session() + s1 = MagicMock() s1.logged_in = True s1.uid = self.account.uid evennia.server.sessionhandler.SESSIONS[s1.uid] = s1 - s2 = Session() + s2 = MagicMock() s2.logged_in = True s2.uid = self.account.uid + 1 evennia.server.sessionhandler.SESSIONS[s2.uid] = s2 - s3 = Session() + s3 = MagicMock() s3.logged_in = False s3.uid = self.account.uid + 2 evennia.server.sessionhandler.SESSIONS[s3.uid] = s3 - self.assertEqual(self.handler.get(), [s1]) - self.assertEqual(self.handler.get(self.account.uid), [s1]) - self.assertEqual(self.handler.get(self.account.uid + 1), []) + self.assertEqual([s.uid for s in self.handler.get()], [s1.uid]) + self.assertEqual([s.uid for s in [self.handler.get(self.account.uid)]], [s1.uid]) + self.assertEqual([s.uid for s in self.handler.get(self.account.uid + 1)], []) def test_all(self): "Check all method" @@ -62,9 +63,10 @@ class TestDefaultAccount(TestCase): "Check DefaultAccount class" def setUp(self): - self.s1 = Session() + self.s1 = MagicMock() self.s1.puppet = None self.s1.sessid = 0 + self.s1.data_outj def tearDown(self): if hasattr(self, "account"): @@ -142,10 +144,7 @@ class TestDefaultAccount(TestCase): self.s1.uid = account.uid evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1 - self.s1.puppet = None - self.s1.logged_in = True - self.s1.data_out = Mock(return_value=None) - + self.s1.data_out = MagicMock() obj = Mock() obj.access = Mock(return_value=False) @@ -154,6 +153,7 @@ class TestDefaultAccount(TestCase): self.assertTrue(self.s1.data_out.call_args[1]['text'].startswith("You don't have permission to puppet")) self.assertIsNone(obj.at_post_puppet.call_args) + @override_settings(MULTISESSION_MODE=0) def test_puppet_object_joining_other_session(self): "Check puppet_object method called, joining other session" @@ -165,15 +165,16 @@ class TestDefaultAccount(TestCase): self.s1.puppet = None self.s1.logged_in = True - self.s1.data_out = Mock(return_value=None) + self.s1.data_out = MagicMock() obj = Mock() obj.access = Mock(return_value=True) obj.account = account + obj.sessions.all = MagicMock(return_value=[self.s1]) account.puppet_object(self.s1, obj) # works because django.conf.settings.MULTISESSION_MODE is not in (1, 3) - self.assertTrue(self.s1.data_out.call_args[1]['text'].endswith("from another of your sessions.")) + self.assertTrue(self.s1.data_out.call_args[1]['text'].endswith("from another of your sessions.|n")) self.assertTrue(obj.at_post_puppet.call_args[1] == {}) def test_puppet_object_already_puppeted(self): diff --git a/evennia/prototypes/spawner.py b/evennia/prototypes/spawner.py index ac6ad854b1..03bc6d2024 100644 --- a/evennia/prototypes/spawner.py +++ b/evennia/prototypes/spawner.py @@ -258,6 +258,7 @@ def prototype_from_object(obj): aliases = obj.aliases.get(return_list=True) if aliases: prot['aliases'] = aliases + from evennia import set_trace;set_trace() tags = [(tag.db_key, tag.db_category, tag.db_data) for tag in obj.tags.all(return_objs=True)] if tags: From bfc15b13a557c6fa70628912e1d271359892ecd7 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 7 Oct 2018 18:29:25 +0200 Subject: [PATCH 14/15] Update docker file to better handle starting without an existing game folder --- Dockerfile | 12 ++++++++++-- bin/unix/evennia-docker-start.sh | 16 ++++++++++++---- evennia/prototypes/spawner.py | 1 - 3 files changed, 22 insertions(+), 7 deletions(-) mode change 100644 => 100755 bin/unix/evennia-docker-start.sh diff --git a/Dockerfile b/Dockerfile index 381c83f925..961d3ad8ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ # Usage: # cd to a folder where you want your game data to be (or where it already is). # -# docker run -it -p 4000:4000 -p 4001:4001 -p 4005:4005 -v $PWD:/usr/src/game evennia/evennia +# docker run -it --rm -p 4000:4000 -p 4001:4001 -p 4005:4005 -v $PWD:/usr/src/game evennia/evennia # # (If your OS does not support $PWD, replace it with the full path to your current # folder). @@ -15,6 +15,14 @@ # You will end up in a shell where the `evennia` command is available. From here you # can install and run the game normally. Use Ctrl-D to exit the evennia docker container. # +# You can also start evennia directly by passing arguments to the folder: +# +# docker run -it --rm -p 4000:4000 -p 4001:4001 -p 4005:4005 -v $PWD:/usr/src/game evennia/evennia evennia start -l +# +# This will start Evennia running as the core process of the container. Note that you *must* use -l +# or one of the foreground modes (like evennia ipstart) since otherwise the container will immediately +# die since no foreground process keeps it up. +# # The evennia/evennia base image is found on DockerHub and can also be used # as a base for creating your own custom containerized Evennia game. For more # info, see https://github.com/evennia/evennia/wiki/Running%20Evennia%20in%20Docker . @@ -58,7 +66,7 @@ WORKDIR /usr/src/game ENV PS1 "evennia|docker \w $ " # startup a shell when we start the container -ENTRYPOINT bash -c "source /usr/src/evennia/bin/unix/evennia-docker-start.sh" +ENTRYPOINT ["/usr/src/evennia/bin/unix/evennia-docker-start.sh"] # expose the telnet, webserver and websocket client ports EXPOSE 4000 4001 4005 diff --git a/bin/unix/evennia-docker-start.sh b/bin/unix/evennia-docker-start.sh old mode 100644 new mode 100755 index d1333aaef6..5c87052da9 --- a/bin/unix/evennia-docker-start.sh +++ b/bin/unix/evennia-docker-start.sh @@ -1,10 +1,18 @@ -#! /bin/bash +#! /bin/sh # called by the Dockerfile to start the server in docker mode # remove leftover .pid files (such as from when dropping the container) rm /usr/src/game/server/*.pid >& /dev/null || true -# start evennia server; log to server.log but also output to stdout so it can -# be viewed with docker-compose logs -exec 3>&1; evennia start -l +PS1="evennia|docker \w $ " + +cmd="$@" +output="Docker starting with argument '$cmd' ..." +if test -z $cmd; then + cmd="bash" + output="No argument given, starting shell ..." +fi + +echo $output +exec 3>&1; $cmd diff --git a/evennia/prototypes/spawner.py b/evennia/prototypes/spawner.py index 03bc6d2024..ac6ad854b1 100644 --- a/evennia/prototypes/spawner.py +++ b/evennia/prototypes/spawner.py @@ -258,7 +258,6 @@ def prototype_from_object(obj): aliases = obj.aliases.get(return_list=True) if aliases: prot['aliases'] = aliases - from evennia import set_trace;set_trace() tags = [(tag.db_key, tag.db_category, tag.db_data) for tag in obj.tags.all(return_objs=True)] if tags: From 9b2b17d5e4d83943ba2979fc7170a314cadf4cd8 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 7 Oct 2018 18:45:28 +0200 Subject: [PATCH 15/15] Correct tag handling in prototype; fix unittests --- evennia/prototypes/tests.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/evennia/prototypes/tests.py b/evennia/prototypes/tests.py index 3cf2e38c7b..411bd45c27 100644 --- a/evennia/prototypes/tests.py +++ b/evennia/prototypes/tests.py @@ -134,6 +134,7 @@ class TestUtils(EvenniaTest): 'prototype_key': Something, 'prototype_locks': 'spawn:all();edit:all()', 'prototype_tags': [], + 'tags': [(u'footag', u'foocategory', None)], 'typeclass': 'evennia.objects.objects.DefaultObject'}) self.assertEqual(old_prot, @@ -182,6 +183,7 @@ class TestUtils(EvenniaTest): 'typeclass': ('evennia.objects.objects.DefaultObject', 'evennia.objects.objects.DefaultObject', 'KEEP'), 'aliases': {'foo': ('foo', None, 'REMOVE')}, + 'tags': {u'footag': ((u'footag', u'foocategory', None), None, 'REMOVE')}, 'prototype_desc': ('Built from Obj', 'New version of prototype', 'UPDATE'), 'permissions': {"Builder": (None, 'Builder', 'ADD')} @@ -200,6 +202,7 @@ class TestUtils(EvenniaTest): 'prototype_key': 'UPDATE', 'prototype_locks': 'KEEP', 'prototype_tags': 'KEEP', + 'tags': 'REMOVE', 'typeclass': 'KEEP'} )