From 1ab007a9768a5128d3828e8fdbdf2ddc89dfc60d Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 24 Feb 2021 22:32:54 +0100 Subject: [PATCH 01/33] Start adding chargen --- .../contrib/rules/openadventure/chargen.py | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 evennia/contrib/rules/openadventure/chargen.py diff --git a/evennia/contrib/rules/openadventure/chargen.py b/evennia/contrib/rules/openadventure/chargen.py new file mode 100644 index 0000000000..44f9770e36 --- /dev/null +++ b/evennia/contrib/rules/openadventure/chargen.py @@ -0,0 +1,193 @@ +""" +This is a convenient module for use by chargen implementations. It gives access +to ready-made calculations (using the various rule systems) to get options and +evaluate results during the character generation process. + +""" +from dataclasses import dataclass +from evennia.utils.utils import callables_from_module, inherits_from + + +@dataclass +class ChargenChoice: + """ + This temporarily stores the choices done during chargen. It could be + saved to an Attribute if persistence is required. + + """ + rulename = "" + data = {} + + +class ChargenStep: + """ + This represents the steps of a character generation. It assumes this is a + sequential process (if chargen allows jumping that's fine too, one could + just have a menu that points to each step). + + """ + step_name = "" + prev_step = None + next_step = None + + def get_help(self, *args, **kwargs): + """ + This returns help text for this choice. + + """ + return self.__doc__.strip() + + def options(self, *args, **kwargs): + """ + This presents the choices allowed by this step. + + Returns: + list: A list of all applicable choices, usually as rule-classes. + + """ + + def validate_input(self, inp, *args, **kwargs): + """ + This can be used to validate free-form inputs from the user. + + Args: + inp (str): Input from the user. + Returns: + bool: If input was valid or not. + + """ + + def user_choice(self, *args, **kwargs): + """ + This should be called with the user's choice. This must already be + determined to be a valid choice at this point. + + Args: + *args, **kwargs: Input on any form. + + Returns: + CharacterStore: Storing the current, valid choice. + + """ + + +class ArchetypeChoice(ChargenStep): + """ + Choose one archetype and record all of its characteristics–or–choose two + archetypes, halve all the characteristic's numbers, then combine their + values. + + """ + step_name = "Choose an Archetype" + prev_step = None + next_step = "Choose a Race" + + def get_help(self, *args, **kwargs): + return self.__doc__.strip() + + def options(self, *args, **kwargs): + """ + Returns a list of Archetypes. Each Archetype class has a `.modifiers` + dict on it with values keyed to enums for the bonuses/drawbacks of that + archetype. The archetype's name is the class name, retrieved with `.__name__`. + + """ + from . import archetypes + return callables_from_module(archetypes) + + def user_choice(self, choice1, choice2=None, **kwargs): + """ + We allow one or two choices - they must be passed into this method + together. + + Args: + choice1 (Archetype): The first archetype choice. + choice2 (Archetype, optional): Second choice (for dual archetype choice) + + Returns: + ChargenStore: Holds rule_name and data; data has an additional 'name' field + to identify the archetype (which may be a merger of two names). + + """ + data1 = choice1.modifiers + if choice2: + # dual-archetype - add with choice1 values and half, rounding down + data2 = choice2.modifiers + data = {} + for key, value1 in data1.items(): + value2 = data2.get(key, 0) + data[key] = int((value1 + value2) / 2) + # add values from choice2 that was not yet covered + data.update({key: int(value2 / 2) for key, value in data2.items() if key not in data}) + # create a new name for the dual-archetype by combining the names alphabetically + data['name'] = "-".join(sorted((choice1.__name__, choice2.__name__))) + else: + data = data1 + data['name'] = choice1.__name__ + + return ChargenChoice(rulename="archetype", data=data) + + +class RaceChoice(ChargenStep): + """ + Choose a race/creature type that best suits this character. + + """ + step_name = "Choose a Race" + prev_step = "Choose an Archetype" + next_step = "Choose a race Focus" + + + def options(self, *args, **kwargs): + """ + Returns a list of races. Each Race class has the following properties: + :: + size = enum for small, average, large + bodytype = enum for slim, average, stocky + modifiers = dict of enums and bonuses/drawbacks + foci = list of which Focus may be chosen for this race + feats = list of which Feats may be chosen for this race + + """ + from . import races + all_ = callables_from_module(races) + return [race for race in all_ if inherits_from(race, race.Race)] + + def user_choice(self, choice, *args, **kwargs): + """ + The choice should be a distinct race. + + Args: + choice (Race): The chosen race. + + Returns: + ChargenChoice: This will have the Race class as .data. + + """ + return ChargenChoice(rulename="race", data=choice) + + +class FocusChoice(ChargenStep): + """ + Each race has three 'subtypes' or Foci available to them. This represents + natural strengths of that race that come across more or less in different + individuals. Each Character may pick one race-Focus to emphasize. + + """ + def options(self, race, *args, **kwargs): + """ + To know which focus to offer, the chosen Race must be passed. + + Args: + race (Race): The race chosen on the previous step. + + """ + return list(race.foci) + + def user_choice(self, choice, *args, **kwargs): + """ + + """ + + + From 12e44167cbb02c4a3ff2c0255b3fe7f53f1390b9 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 24 Feb 2021 22:35:04 +0100 Subject: [PATCH 02/33] More tests for doc building --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 14ee089c06..dabdf3ffd0 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -176,7 +176,7 @@ ansi_clean = None if not _no_autodoc: # we must set up Evennia and its paths for autodocs to work - EV_ROOT = os.environ.get("EVDIR") + EV_ROOT = os.path.abspath(os.path.abspath(os.path.abspath(__file__))) # os.environ.get("EVDIR") GAME_DIR = os.environ.get("EVGAMEDIR") if not (EV_ROOT and GAME_DIR): From 034b237c25858a93bc787f55f69ff0b51f000c01 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 24 Feb 2021 22:41:15 +0100 Subject: [PATCH 03/33] Another doc adjustment --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index dabdf3ffd0..d786a6fc7d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -176,7 +176,7 @@ ansi_clean = None if not _no_autodoc: # we must set up Evennia and its paths for autodocs to work - EV_ROOT = os.path.abspath(os.path.abspath(os.path.abspath(__file__))) # os.environ.get("EVDIR") + EV_ROOT = os.path.abspath(os.path.abspath(os.path.abspath(os.path.abspath(__file__)))) # os.environ.get("EVDIR") GAME_DIR = os.environ.get("EVGAMEDIR") if not (EV_ROOT and GAME_DIR): From 72b3e18ee44b9207e549c178f5050ed1e7d343ae Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 24 Feb 2021 23:12:28 +0100 Subject: [PATCH 04/33] More debug output during doc build --- docs/source/conf.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index d786a6fc7d..cb4d92c48c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -187,7 +187,10 @@ if not _no_autodoc: ) raise RuntimeError(err) - print("Evennia root: {}, Game dir: {}".format(EV_ROOT, GAME_DIR)) + print("Evennia root: {}, Game dir: {}, branch:".format(EV_ROOT, GAME_DIR)), + import subprocess + subprocess.call(["git", "rev-parse", "--abbrev-ref", "HEAD"]) + subprocess.call("pwd") sys.path.insert(1, EV_ROOT) sys.path.insert(1, GAME_DIR) From 3e4b8f5a3ee266d01c5dee72ccdd840b21ef6f01 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 24 Feb 2021 23:16:41 +0100 Subject: [PATCH 05/33] Correct evpath variable --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index cb4d92c48c..8b3049040c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -176,7 +176,7 @@ ansi_clean = None if not _no_autodoc: # we must set up Evennia and its paths for autodocs to work - EV_ROOT = os.path.abspath(os.path.abspath(os.path.abspath(os.path.abspath(__file__)))) # os.environ.get("EVDIR") + EV_ROOT = os.path.dirpath(os.path.dirpath(os.path.dirpath(os.path.abspath(__file__)))) # os.environ.get("EVDIR") GAME_DIR = os.environ.get("EVGAMEDIR") if not (EV_ROOT and GAME_DIR): From 704c7958124dd00e9f33226905e1027bdf9432f4 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 24 Feb 2021 23:20:13 +0100 Subject: [PATCH 06/33] Fix typo in conf --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 8b3049040c..73d163daf9 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -176,7 +176,7 @@ ansi_clean = None if not _no_autodoc: # we must set up Evennia and its paths for autodocs to work - EV_ROOT = os.path.dirpath(os.path.dirpath(os.path.dirpath(os.path.abspath(__file__)))) # os.environ.get("EVDIR") + EV_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # os.environ.get("EVDIR") GAME_DIR = os.environ.get("EVGAMEDIR") if not (EV_ROOT and GAME_DIR): From 18be93f8258116546d13cdf9c36c22177252bbd4 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 24 Feb 2021 23:25:46 +0100 Subject: [PATCH 07/33] Update makefile from develop --- docs/Makefile | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index b23d27ae94..e7141b6b20 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -10,7 +10,7 @@ SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SPHINXMULTIVERSION ?= sphinx-multiversion SPHINXAPIDOC ?= sphinx-apidoc -SPHINXAPIDOCOPTS = --tocfile evennia-api --module-first --force --maxdepth 6 --separate --templatedir=$(SOURCEDIR)/_templates/ +SPHINXAPIDOCOPTS = --tocfile evennia-api --module-first --force -d 6 --separate --templatedir=$(SOURCEDIR)/_templates/ SPHINXAPIDOCENV = members,undoc-members,show-inheritance SPHINXAPIDOCEXCLUDE = ../*/migrations/* ../evennia/game_template/* ../evennia/*/tests/* ../evennia/*/tests.py @@ -68,10 +68,8 @@ _autodoc-index: make _reformat_apidoc_headers _multiversion-autodoc-index: - make _clean_api_index - @EVDIR=$(EVDIR) EVGAMEDIR=$(EVGAMEDIR) SPHINX_APIDOC_OPTIONS=$(SPHINXAPIDOCENV) $(SPHINXAPIDOC) $(SPHINXAPIDOCOPTS) -o $(SOURCEDIR)/api/ $(EVDIR) $(SPHINXAPIDOCEXCLUDE) - make _reformat_apidoc_headers - git diff-index --quiet HEAD || git commit -a -m "Updated API autodoc index." | : + make _autodoc-index + git diff-index --quiet HEAD || git commit -a -m "Updated API autodoc index." || : _html-build: @EVDIR=$(EVDIR) EVGAMEDIR=$(EVGAMEDIR) $(SPHINXBUILD) $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)/html" @@ -84,14 +82,11 @@ _multiversion-build: _multiversion-deploy: @bash -e deploy.sh - @EVDIR=$(EVDIR) EVGAMEDIR=$(EVGAMEDIR) $(SPHINXMULTIVERSION) $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) _latex-build: @NOAUTODOC=1 EVDIR=$(EVDIR) EVGAMEDIR=$(EVGAMEDIR) $(SPHINXBUILD) -M latexpdf "$(SOURCEDIR)" "$(BUILDDIR)/latex" $(QUICKFILES) -# main targets - -install: +# main target: @pip install -r requirements.txt clean: @@ -116,6 +111,7 @@ quick: quickstrict: SPHINXOPTS=-W make quick +# we build index directly for the current branch local: make _check-env make clean @@ -125,10 +121,14 @@ local: @echo "Documentation built (single version)." @echo "To see result, open evennia/docs/build/html/index.html in a browser." +# note that this should be done for each relevant multiversion branch. +mv-index: + make _multiversion-autodoc-index + @echo "(Re)Built and committed api rst files for this branch only." + mv-local: make _multiversion-check-env make clean - make _multiversion-autodoc-index make _multiversion-build @echo "" @echo "Documentation built (multiversion + autodocs)." From 23fa1862b29b041ee6df0a61614fd378d299bea1 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 27 Feb 2021 13:39:14 +0100 Subject: [PATCH 08/33] Resolve merge conflict --- CHANGELOG.md | 11 +++++++++++ evennia/__init__.py | 13 ------------- evennia/commands/default/comms.py | 3 ++- evennia/server/portal/portal.py | 5 +++-- evennia/settings_default.py | 12 ++++++++++++ evennia/utils/utils.py | 17 +++++++++++++++-- evennia/web/website/forms.py | 9 ++++++--- evennia/web/website/tests.py | 3 ++- evennia/web/website/views.py | 31 +++++++++++++++++++++---------- 9 files changed, 72 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d17b5c6d9a..24de2405fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ - New `drop:holds()` lock default to limit dropping nonsensical things. Access check defaults to True for backwards-compatibility in 0.9, will be False in 1.0 - Add `tags.has()` method for checking if an object has a tag or tags (PR by ChrisLR) +- Make IP throttle use Django-based cache system for optional persistence (PR by strikaco) +- Renamed Tutorial classes "Weapon" and "WeaponRack" to "TutorialWeapon" and + "TutorialWeaponRack" to prevent collisions with classes in mygame +- New `crafting` contrib, adding a full crafting subsystem (Griatch 2020) +- The `rplanguage` contrib now auto-capitalizes sentences and retains ellipsis (...). This + change means that proper nouns at the start of sentences will not be treated as nouns. +- Make MuxCommand `lhs/rhslist` always be lists, also if empty (used to be the empty string) +- Fix typo in UnixCommand contrib, where `help` was given as `--hel`. +- Latin (la) i18n translation (jamalainm) +- Made the `evennia` dir possible to use without gamedir for purpose of doc generation. ### Evennia 0.9.5 (Nov 2020) @@ -88,6 +98,7 @@ without arguments starts a full interactive Python console. - Include more Web-client info in `session.protocol_flags`. - Fixes in multi-match situations - don't allow finding/listing multimatches for 3-box when only two boxes in location. +- Made the `evennia` dir possible to use without gamedir for purpose of doc generation. ## Evennia 0.9 (2018-2019) diff --git a/evennia/__init__.py b/evennia/__init__.py index 828bec77fe..3b7c37f83f 100644 --- a/evennia/__init__.py +++ b/evennia/__init__.py @@ -379,19 +379,6 @@ def _init(): del SystemCmds del _EvContainer - # typeclases - from .utils.utils import class_from_module - - BASE_ACCOUNT_TYPECLASS = class_from_module(settings.BASE_ACCOUNT_TYPECLASS) - BASE_OBJECT_TYPECLASS = class_from_module(settings.BASE_OBJECT_TYPECLASS) - BASE_CHARACTER_TYPECLASS = class_from_module(settings.BASE_CHARACTER_TYPECLASS) - BASE_ROOM_TYPECLASS = class_from_module(settings.BASE_ROOM_TYPECLASS) - BASE_EXIT_TYPECLASS = class_from_module(settings.BASE_EXIT_TYPECLASS) - BASE_CHANNEL_TYPECLASS = class_from_module(settings.BASE_CHANNEL_TYPECLASS) - BASE_SCRIPT_TYPECLASS = class_from_module(settings.BASE_SCRIPT_TYPECLASS) - BASE_GUEST_TYPECLASS = class_from_module(settings.BASE_GUEST_TYPECLASS) - del class_from_module - # delayed starts - important so as to not back-access evennia before it has # finished initializing GLOBAL_SCRIPTS.start() diff --git a/evennia/commands/default/comms.py b/evennia/commands/default/comms.py index 81d966b31b..e6057cdc4b 100644 --- a/evennia/commands/default/comms.py +++ b/evennia/commands/default/comms.py @@ -19,7 +19,8 @@ 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) -CHANNEL_DEFAULT_TYPECLASS = class_from_module(settings.BASE_CHANNEL_TYPECLASS) +CHANNEL_DEFAULT_TYPECLASS = class_from_module( + settings.BASE_CHANNEL_TYPECLASS, fallback=settings.FALLBACK_CHANNEL_TYPECLASS) # limit symbol import for API diff --git a/evennia/server/portal/portal.py b/evennia/server/portal/portal.py index 6c47d6f82b..d1af41a143 100644 --- a/evennia/server/portal/portal.py +++ b/evennia/server/portal/portal.py @@ -31,7 +31,6 @@ from evennia.utils.utils import get_evennia_version, mod_import, make_iter from evennia.server.portal.portalsessionhandler import PORTAL_SESSIONS from evennia.utils import logger from evennia.server.webserver import EvenniaReverseProxyResource -from django.db import connection # we don't need a connection to the database so close it right away @@ -431,4 +430,6 @@ if WEBSERVER_ENABLED: for plugin_module in PORTAL_SERVICES_PLUGIN_MODULES: # external plugin services to start - plugin_module.start_plugin_services(PORTAL) + if plugin_module: + plugin_module.start_plugin_services(PORTAL) + diff --git a/evennia/settings_default.py b/evennia/settings_default.py index f44778face..8dd6f41374 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -517,6 +517,18 @@ START_LOCATION = "#2" # out of sync between the processes. Keep on unless you face such # issues. TYPECLASS_AGGRESSIVE_CACHE = True +# These are fallbacks for BASE typeclasses failing to load. Usually needed only +# during doc building. The system expects these to *always* load correctly, so +# only modify if you are making fundamental changes to how objects/accounts +# work and know what you are doing +FALLBACK_ACCOUNT_TYPECLASS = "evennia.accounts.accounts.DefaultAccount" +FALLBACK_OBJECT_TYPECLASS = "evennia.objects.objects.DefaultObject" +FALLBACK_CHARACTER_TYPECLASS = "evennia.objects.objects.DefaultCharacter" +FALLBACK_ROOM_TYPECLASS = "evennia.objects.objects.DefaultRoom" +FALLBACK_EXIT_TYPECLASS = "evennia.objects.objects.DefaultExit" +FALLBACK_CHANNEL_TYPECLASS = "evennia.comms.comms.DefaultChannel" +FALLBACK_SCRIPT_TYPECLASS = "evennia.scripts.scripts.DefaultScript" + ###################################################################### # Options and validators diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 8882c4a2b2..b0abe2b9c7 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -1322,6 +1322,9 @@ def variable_from_module(module, variable=None, default=None): mod = mod_import(module) + if not mod: + return default + if variable: result = [] for var in make_iter(variable): @@ -1413,7 +1416,7 @@ def fuzzy_import_from_module(path, variable, default=None, defaultpaths=None): return default -def class_from_module(path, defaultpaths=None): +def class_from_module(path, defaultpaths=None, fallback=None): """ Return a class from a module, given the module's path. This is primarily used to convert db_typeclass_path:s to classes. @@ -1422,6 +1425,10 @@ def class_from_module(path, defaultpaths=None): path (str): Full Python dot-path to module. defaultpaths (iterable, optional): If a direct import from `path` fails, try subsequent imports by prepending those paths to `path`. + fallback (str): If all other attempts fail, use this path as a fallback. + This is intended as a last-resport. In the example of Evennia + loading, this would be a path to a default parent class in the + evennia repo itself. Returns: class (Class): An uninstatiated class recovered from path. @@ -1475,7 +1482,13 @@ def class_from_module(path, defaultpaths=None): err += "\nPaths searched:\n %s" % "\n ".join(paths) else: err += "." - raise ImportError(err) + logger.log_err(err) + if fallback: + logger.log_warn(f"Falling back to {fallback}.") + return class_from_module(fallback) + else: + # even fallback fails + raise ImportError(err) return cls diff --git a/evennia/web/website/forms.py b/evennia/web/website/forms.py index 8a1585459f..505415f7cd 100644 --- a/evennia/web/website/forms.py +++ b/evennia/web/website/forms.py @@ -52,7 +52,8 @@ class AccountForm(UserCreationForm): """ # The model/typeclass this form creates - model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS) + model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS, + fallback=settings.FALLBACK_ACCOUNT_TYPECLASS) # The fields to display on the form, in the given order fields = ("username", "email") @@ -87,7 +88,8 @@ class ObjectForm(EvenniaForm, ModelForm): """ # The model/typeclass this form creates - model = class_from_module(settings.BASE_OBJECT_TYPECLASS) + model = class_from_module(settings.BASE_OBJECT_TYPECLASS, + fallback=settings.FALLBACK_OBJECT_TYPECLASS) # The fields to display on the form, in the given order fields = ("db_key",) @@ -140,7 +142,8 @@ class CharacterForm(ObjectForm): """ # Get the correct object model - model = class_from_module(settings.BASE_CHARACTER_TYPECLASS) + model = class_from_module(settings.BASE_CHARACTER_TYPECLASS, + fallback=settings.FALLBACK_CHARACTER_TYPECLASS) # Allow entry of the 'key' field fields = ("db_key",) diff --git a/evennia/web/website/tests.py b/evennia/web/website/tests.py index 0c6ae8bf48..a4633f51f4 100644 --- a/evennia/web/website/tests.py +++ b/evennia/web/website/tests.py @@ -125,7 +125,8 @@ class ChannelDetailTest(EvenniaWebTest): def setUp(self): super(ChannelDetailTest, self).setUp() - klass = class_from_module(self.channel_typeclass) + klass = class_from_module(self.channel_typeclass, + fallback=settings.FALLBACK_CHANNEL_TYPECLASS) # Create a channel klass.create("demo") diff --git a/evennia/web/website/views.py b/evennia/web/website/views.py index 5e22698ad9..fe65bc5c8b 100644 --- a/evennia/web/website/views.py +++ b/evennia/web/website/views.py @@ -32,6 +32,7 @@ from django.utils.text import slugify _BASE_CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS +# typeclass fallbacks def _gamestats(): # Some misc. configurable stuff. @@ -48,11 +49,14 @@ def _gamestats(): nobjs = ObjectDB.objects.count() nobjs = nobjs or 1 # fix zero-div error with empty database - Character = class_from_module(settings.BASE_CHARACTER_TYPECLASS) + Character = class_from_module(settings.BASE_CHARACTER_TYPECLASS, + fallback=settings.FALLBACK_CHARACTER_TYPECLASS) nchars = Character.objects.all_family().count() - Room = class_from_module(settings.BASE_ROOM_TYPECLASS) + Room = class_from_module(settings.BASE_ROOM_TYPECLASS, + fallback=settings.FALLBACK_ROOM_TYPECLASS) nrooms = Room.objects.all_family().count() - Exit = class_from_module(settings.BASE_EXIT_TYPECLASS) + Exit = class_from_module(settings.BASE_EXIT_TYPECLASS, + fallback=settings.FALLBACK_EXIT_TYPECLASS) nexits = Exit.objects.all_family().count() nothers = nobjs - nchars - nrooms - nexits @@ -269,7 +273,8 @@ class ObjectDetailView(EvenniaDetailView): # # So when you extend it, this line should look simple, like: # model = Object - model = class_from_module(settings.BASE_OBJECT_TYPECLASS) + model = class_from_module(settings.BASE_OBJECT_TYPECLASS, + fallback=settings.FALLBACK_OBJECT_TYPECLASS) # What HTML template you wish to use to display this page. template_name = "website/object_detail.html" @@ -372,7 +377,8 @@ class ObjectCreateView(LoginRequiredMixin, EvenniaCreateView): """ - model = class_from_module(settings.BASE_OBJECT_TYPECLASS) + model = class_from_module(settings.BASE_OBJECT_TYPECLASS, + fallback=settings.FALLBACK_OBJECT_TYPECLASS) class ObjectDeleteView(LoginRequiredMixin, ObjectDetailView, EvenniaDeleteView): @@ -387,7 +393,8 @@ class ObjectDeleteView(LoginRequiredMixin, ObjectDetailView, EvenniaDeleteView): """ # -- Django constructs -- - model = class_from_module(settings.BASE_OBJECT_TYPECLASS) + model = class_from_module(settings.BASE_OBJECT_TYPECLASS, + fallback=settings.FALLBACK_OBJECT_TYPECLASS) template_name = "website/object_confirm_delete.html" # -- Evennia constructs -- @@ -430,7 +437,8 @@ class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView): """ # -- Django constructs -- - model = class_from_module(settings.BASE_OBJECT_TYPECLASS) + model = class_from_module(settings.BASE_OBJECT_TYPECLASS, + fallback=settings.FALLBACK_OBJECT_TYPECLASS) # -- Evennia constructs -- access_type = "edit" @@ -513,7 +521,8 @@ class AccountMixin(TypeclassMixin): """ # -- Django constructs -- - model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS) + model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS, + fallback=settings.FALLBACK_ACCOUNT_TYPECLASS) form_class = website_forms.AccountForm @@ -578,7 +587,8 @@ class CharacterMixin(TypeclassMixin): """ # -- Django constructs -- - model = class_from_module(settings.BASE_CHARACTER_TYPECLASS) + model = class_from_module(settings.BASE_CHARACTER_TYPECLASS, + fallback=settings.FALLBACK_CHARACTER_TYPECLASS) form_class = website_forms.CharacterForm success_url = reverse_lazy("character-manage") @@ -817,7 +827,8 @@ class ChannelMixin(TypeclassMixin): """ # -- Django constructs -- - model = class_from_module(settings.BASE_CHANNEL_TYPECLASS) + model = class_from_module(settings.BASE_CHANNEL_TYPECLASS, + fallback=settings.FALLBACK_CHANNEL_TYPECLASS) # -- Evennia constructs -- page_title = "Channels" From 1a0ae184537c66a9a18ff1070b532d0bc2ba338c Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 27 Feb 2021 13:48:11 +0100 Subject: [PATCH 09/33] Fix doc conf build for multiversion --- docs/source/conf.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 73d163daf9..98c568e4ec 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -176,28 +176,17 @@ ansi_clean = None if not _no_autodoc: # we must set up Evennia and its paths for autodocs to work - EV_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # os.environ.get("EVDIR") - GAME_DIR = os.environ.get("EVGAMEDIR") + EV_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - if not (EV_ROOT and GAME_DIR): - err = ( - "The EVDIR and EVGAMEDIR environment variables must be set to " - "the absolute paths to the evennia/ repo and an initialized " - "evennia gamedir respectively." - ) - raise RuntimeError(err) - - print("Evennia root: {}, Game dir: {}, branch:".format(EV_ROOT, GAME_DIR)), import subprocess subprocess.call(["git", "rev-parse", "--abbrev-ref", "HEAD"]) subprocess.call("pwd") sys.path.insert(1, EV_ROOT) - sys.path.insert(1, GAME_DIR) - with cd(GAME_DIR): + with cd(EV_ROOT): # set up Evennia so its sources can be parsed - os.environ["DJANGO_SETTINGS_MODULE"] = "server.conf.settings" + os.environ["DJANGO_SETTINGS_MODULE"] = "evennia.settings.default" import django # noqa From 10b09e2623b63028957bcd9fe65e31891fd4def5 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 27 Feb 2021 13:53:53 +0100 Subject: [PATCH 10/33] Fix typo in doc conf --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 98c568e4ec..7b3ea3d02a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -186,7 +186,7 @@ if not _no_autodoc: with cd(EV_ROOT): # set up Evennia so its sources can be parsed - os.environ["DJANGO_SETTINGS_MODULE"] = "evennia.settings.default" + os.environ["DJANGO_SETTINGS_MODULE"] = "evennia.settings_default" import django # noqa From 773bbda113317e1799cefbbf043b2550ef8d7ff4 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 27 Feb 2021 13:57:58 +0100 Subject: [PATCH 11/33] Tweak search manager import for doc build --- evennia/utils/search.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/evennia/utils/search.py b/evennia/utils/search.py index 92778b77b8..8e4cdfaeb3 100644 --- a/evennia/utils/search.py +++ b/evennia/utils/search.py @@ -26,6 +26,7 @@ Example: To reach the search method 'get_object_with_account' # Import the manager methods to be wrapped +from django.db.utils impoort OperationalError from django.contrib.contenttypes.models import ContentType # limit symbol import from API @@ -43,14 +44,23 @@ __all__ = ( # import objects this way to avoid circular import problems -ObjectDB = ContentType.objects.get(app_label="objects", model="objectdb").model_class() -AccountDB = ContentType.objects.get(app_label="accounts", model="accountdb").model_class() -ScriptDB = ContentType.objects.get(app_label="scripts", model="scriptdb").model_class() -Msg = ContentType.objects.get(app_label="comms", model="msg").model_class() -Channel = ContentType.objects.get(app_label="comms", model="channeldb").model_class() -HelpEntry = ContentType.objects.get(app_label="help", model="helpentry").model_class() -Tag = ContentType.objects.get(app_label="typeclasses", model="tag").model_class() - +try: + ObjectDB = ContentType.objects.get(app_label="objects", model="objectdb").model_class() + AccountDB = ContentType.objects.get(app_label="accounts", model="accountdb").model_class() + ScriptDB = ContentType.objects.get(app_label="scripts", model="scriptdb").model_class() + Msg = ContentType.objects.get(app_label="comms", model="msg").model_class() + Channel = ContentType.objects.get(app_label="comms", model="channeldb").model_class() + HelpEntry = ContentType.objects.get(app_label="help", model="helpentry").model_class() + Tag = ContentType.objects.get(app_label="typeclasses", model="tag").model_class() +except OperationalError: + # this is a fallback used during tests/doc building + print("Couldn't initialize search managers - db not set up.") + from evennia.objects.models import ObjectDB + from evennia.accounts.models import AccountDB + from evennia.scripts.models import ScriptDB + from evennia.comms.models import Msg, ChannelDB + from evennia.help.models import HelpEntry + from evennia.typeclasses.tags import Tag # ------------------------------------------------------------------- # Search manager-wrappers From 96dfe9b27b97af4ae4cad439db2d7bb2bbb17702 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 27 Feb 2021 13:58:51 +0100 Subject: [PATCH 12/33] Fix additional things needed for stand-alone import of evennia lib --- evennia/utils/search.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/evennia/utils/search.py b/evennia/utils/search.py index 8e4cdfaeb3..622ce8a5ca 100644 --- a/evennia/utils/search.py +++ b/evennia/utils/search.py @@ -26,7 +26,7 @@ Example: To reach the search method 'get_object_with_account' # Import the manager methods to be wrapped -from django.db.utils impoort OperationalError +from django.db.utils import OperationalError from django.contrib.contenttypes.models import ContentType # limit symbol import from API @@ -49,11 +49,11 @@ try: AccountDB = ContentType.objects.get(app_label="accounts", model="accountdb").model_class() ScriptDB = ContentType.objects.get(app_label="scripts", model="scriptdb").model_class() Msg = ContentType.objects.get(app_label="comms", model="msg").model_class() - Channel = ContentType.objects.get(app_label="comms", model="channeldb").model_class() + ChannelDB = ContentType.objects.get(app_label="comms", model="channeldb").model_class() HelpEntry = ContentType.objects.get(app_label="help", model="helpentry").model_class() Tag = ContentType.objects.get(app_label="typeclasses", model="tag").model_class() except OperationalError: - # this is a fallback used during tests/doc building + # this is a fallback used during tests/doc building print("Couldn't initialize search managers - db not set up.") from evennia.objects.models import ObjectDB from evennia.accounts.models import AccountDB @@ -180,7 +180,7 @@ messages = search_messages # exact - requires an exact ostring match (not case sensitive) # -search_channel = Channel.objects.channel_search +search_channel = ChannelDB.objects.channel_search search_channels = search_channel channel_search = search_channel channels = search_channels @@ -241,7 +241,7 @@ def search_script_attribute( def search_channel_attribute( key=None, category=None, value=None, strvalue=None, attrtype=None, **kwargs ): - return Channel.objects.get_by_attribute( + return ChannelDB.objects.get_by_attribute( key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs ) @@ -356,7 +356,7 @@ def search_channel_tag(key=None, category=None, tagtype=None, **kwargs): matches were found. """ - return Channel.objects.get_by_tag(key=key, category=category, tagtype=tagtype, **kwargs) + return ChannelDB.objects.get_by_tag(key=key, category=category, tagtype=tagtype, **kwargs) # search for tag objects (not the objects they are attached to From 15c6e5c63072f9b679d2d1a033a985a8edf27c84 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 27 Feb 2021 14:13:42 +0100 Subject: [PATCH 13/33] More fixes --- docs/source/toc.md | 2 +- evennia/server/server.py | 17 ++++++++++++++--- evennia/utils/ansi.py | 17 +++++++---------- evennia/utils/create.py | 6 +++--- evennia/utils/gametime.py | 7 ++++++- 5 files changed, 31 insertions(+), 18 deletions(-) diff --git a/docs/source/toc.md b/docs/source/toc.md index e9cdc2363b..9e0e45f859 100644 --- a/docs/source/toc.md +++ b/docs/source/toc.md @@ -1,5 +1,5 @@ # Toc - +- [API root](api/evennia-api.rst) - [./A voice operated elevator using events](./A-voice-operated-elevator-using-events) - [./API refactoring](./API-refactoring) - [./Accounts](./Accounts) diff --git a/evennia/server/server.py b/evennia/server/server.py index bf3cbb333c..4d09d8fdb6 100644 --- a/evennia/server/server.py +++ b/evennia/server/server.py @@ -26,6 +26,7 @@ import evennia evennia._init() from django.db import connection +from django.db.utils import OperationalError from django.conf import settings from evennia.accounts.models import AccountDB @@ -205,7 +206,10 @@ class Evennia(object): self.start_time = time.time() # initialize channelhandler - channelhandler.CHANNELHANDLER.update() + try: + channelhandler.CHANNELHANDLER.update() + except OperationalError: + print("channelhandler couldn't update - db not set up") # wrap the SIGINT handler to make sure we empty the threadpool # even when we reload and we have long-running requests in queue. @@ -616,7 +620,11 @@ class Evennia(object): # Tell the system the server is starting up; some things are not available yet -ServerConfig.objects.conf("server_starting_mode", True) +try: + ServerConfig.objects.conf("server_starting_mode", True) +except OperationalError: + print("Server server_starting_mode couldn't be set - database not set up.") + # twistd requires us to define the variable 'application' so it knows # what to execute from. @@ -728,4 +736,7 @@ for plugin_module in SERVER_SERVICES_PLUGIN_MODULES: print(f"Could not load plugin module {plugin_module}") # clear server startup mode -ServerConfig.objects.conf("server_starting_mode", delete=True) +try: + ServerConfig.objects.conf("server_starting_mode", delete=True) +except OperationalError: + print("Server server_starting_mode couldn't unset - db not set up.") diff --git a/evennia/utils/ansi.py b/evennia/utils/ansi.py index e954590fbb..cd69b48821 100644 --- a/evennia/utils/ansi.py +++ b/evennia/utils/ansi.py @@ -1,8 +1,8 @@ """ ANSI - Gives colour to text. -Use the codes defined in ANSIPARSER in your text -to apply colour to text according to the ANSI standard. +Use the codes defined in ANSIPARSER in your text to apply colour to text +according to the ANSI standard. Examples: @@ -10,10 +10,9 @@ Examples: "This is |rRed text|n and this is normal again." ``` -Mostly you should not need to call `parse_ansi()` explicitly; -it is run by Evennia just before returning data to/from the -user. Depreciated example forms are available by extending -the ansi mapping. +Mostly you should not need to call `parse_ansi()` explicitly; it is run by +Evennia just before returning data to/from the user. Depreciated example forms +are available by extending the ansi mapping. """ import functools @@ -81,11 +80,9 @@ _COLOR_NO_DEFAULT = settings.COLOR_NO_DEFAULT class ANSIParser(object): """ - A class that parses ANSI markup - to ANSI command sequences + A class that parses ANSI markup to ANSI command sequences. - We also allow to escape colour codes - by prepending with an extra |. + We also allow to escape colour codes by prepending with an extra `|`. """ diff --git a/evennia/utils/create.py b/evennia/utils/create.py index e6066d1191..b8bda74460 100644 --- a/evennia/utils/create.py +++ b/evennia/utils/create.py @@ -96,16 +96,16 @@ def create_object( location itself or during unittests. attributes (list): Tuples on the form (key, value) or (key, value, category), (key, value, lockstring) or (key, value, lockstring, default_access). - to set as Attributes on the new object. + to set as Attributes on the new object. nattributes (list): Non-persistent tuples on the form (key, value). Note that - adding this rarely makes sense since this data will not survive a reload. + adding this rarely makes sense since this data will not survive a reload. Returns: object (Object): A newly created object of the given typeclass. Raises: ObjectDB.DoesNotExist: If trying to create an Object with - `location` or `home` that can't be found. + `location` or `home` that can't be found. """ global _ObjectDB diff --git a/evennia/utils/gametime.py b/evennia/utils/gametime.py index b280a83999..ed7901582a 100644 --- a/evennia/utils/gametime.py +++ b/evennia/utils/gametime.py @@ -10,6 +10,7 @@ import time from calendar import monthrange from datetime import datetime, timedelta +from django.db.utils import OperationalError from django.conf import settings from evennia import DefaultScript from evennia.server.models import ServerConfig @@ -23,7 +24,11 @@ IGNORE_DOWNTIMES = settings.TIME_IGNORE_DOWNTIMES # Only set if gametime_reset was called at some point. -GAME_TIME_OFFSET = ServerConfig.objects.conf("gametime_offset", default=0) +try: + GAME_TIME_OFFSET = ServerConfig.objects.conf("gametime_offset", default=0) +except OperationalError: + print("Gametime offset could not load - db not set up.") + GAME_TIME_OFFSET = 0 # Common real-life time measure, in seconds. # You should not change this. From 61e37799a59580f0dd930562c6e3c6b568022425 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 27 Feb 2021 14:25:17 +0100 Subject: [PATCH 14/33] Debug output for doc build --- docs/source/conf.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 7b3ea3d02a..a16fef27a7 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -197,6 +197,12 @@ if not _no_autodoc: evennia._init() from evennia.utils.ansi import strip_raw_ansi as ansi_clean + try: + from evennia.contrib import crafting # noqa + except ImportError: + print("--- NOT develop branch!") + else: + print("--- Develop branch!") if _no_autodoc: From 2b909ab923cc8082fe5e97635c34ee5aad73a00e Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 27 Feb 2021 14:33:29 +0100 Subject: [PATCH 15/33] More testing with importlib --- docs/source/conf.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index a16fef27a7..3f5c6892ac 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -7,6 +7,7 @@ import os import sys import re +import importlib from recommonmark.transform import AutoStructify from sphinx.util.osutil import cd @@ -189,10 +190,12 @@ if not _no_autodoc: os.environ["DJANGO_SETTINGS_MODULE"] = "evennia.settings_default" import django # noqa + importlib.reload(django) django.setup() import evennia # noqa + importlib.reload(evennia) evennia._init() From b5397c010217c940d998d540f3ed525f16ba712c Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 27 Feb 2021 17:31:30 +0100 Subject: [PATCH 16/33] More experimentation with module reloading --- docs/source/conf.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 3f5c6892ac..be04cb1943 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -190,12 +190,14 @@ if not _no_autodoc: os.environ["DJANGO_SETTINGS_MODULE"] = "evennia.settings_default" import django # noqa - importlib.reload(django) django.setup() + import sys + import evennia # noqa + del sys.modules['evennia'] + del evennia import evennia # noqa - importlib.reload(evennia) evennia._init() From 53e975f7d589d8eabad29754ad164a8fac120849 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 27 Feb 2021 18:04:02 +0100 Subject: [PATCH 17/33] More debug output --- docs/source/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index be04cb1943..e305282181 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -185,6 +185,8 @@ if not _no_autodoc: sys.path.insert(1, EV_ROOT) + print(f"sys.path: {sys.path}") + with cd(EV_ROOT): # set up Evennia so its sources can be parsed os.environ["DJANGO_SETTINGS_MODULE"] = "evennia.settings_default" From 3d335b26703392340e08b0f1d750bc611bd56080 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 27 Feb 2021 18:10:29 +0100 Subject: [PATCH 18/33] More testing --- docs/source/conf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index e305282181..462d33947f 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -183,7 +183,7 @@ if not _no_autodoc: subprocess.call(["git", "rev-parse", "--abbrev-ref", "HEAD"]) subprocess.call("pwd") - sys.path.insert(1, EV_ROOT) + sys.path.insert(0, EV_ROOT) print(f"sys.path: {sys.path}") @@ -203,6 +203,8 @@ if not _no_autodoc: evennia._init() + print(f"evennia location: {evennia.__file__}") + from evennia.utils.ansi import strip_raw_ansi as ansi_clean try: from evennia.contrib import crafting # noqa From 4d0e639735054fcb02a5e02bdb17bee4e3cec204 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 27 Feb 2021 18:21:12 +0100 Subject: [PATCH 19/33] More debugging --- docs/source/conf.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 462d33947f..8f912628ae 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -203,15 +203,16 @@ if not _no_autodoc: evennia._init() - print(f"evennia location: {evennia.__file__}") - from evennia.utils.ansi import strip_raw_ansi as ansi_clean try: from evennia.contrib import crafting # noqa except ImportError: print("--- NOT develop branch!") + print(f"evennia location: {evennia.__file__}") else: print("--- Develop branch!") + print(f"evennia location: {evennia.__file__}") + print(f"crafting location: {evennia.contrib.crafting.__file__}") if _no_autodoc: From b0bdc72cb1642efb0d5ed1a522c331cc24d2f13d Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 27 Feb 2021 18:38:47 +0100 Subject: [PATCH 20/33] More testing --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 8f912628ae..a94c146514 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -212,7 +212,7 @@ if not _no_autodoc: else: print("--- Develop branch!") print(f"evennia location: {evennia.__file__}") - print(f"crafting location: {evennia.contrib.crafting.__file__}") + print(f"crafting location: {crafting.__file__}") if _no_autodoc: From 49731fcd8f235ce865ab5f010bdf8638b7cbdb38 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 27 Feb 2021 18:45:04 +0100 Subject: [PATCH 21/33] Testing with new cleanup mechanism --- docs/source/conf.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index a94c146514..f920b70b09 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -195,10 +195,10 @@ if not _no_autodoc: django.setup() - import sys - import evennia # noqa - del sys.modules['evennia'] - del evennia + for modname in sys.modules: + if modname.startswith("evennia"): + del sys.modules[modname] + import evennia # noqa evennia._init() From 8286e577981d6fb004230b28785dfa270a5f1953 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 27 Feb 2021 18:48:18 +0100 Subject: [PATCH 22/33] Fixing looping bug --- docs/source/conf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index f920b70b09..826a950ebe 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -195,9 +195,9 @@ if not _no_autodoc: django.setup() - for modname in sys.modules: - if modname.startswith("evennia"): - del sys.modules[modname] + modnames = [mname for mname in sys.modules if mname.startswith("evennia")] + for modname in modnames: + del sys.modules[modname] import evennia # noqa From c27b5c33e1e517536a82ff0670b8a803d0390607 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 27 Feb 2021 19:04:55 +0100 Subject: [PATCH 23/33] Fix to go with fix for build --- docs/source/conf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 826a950ebe..9d4478d9d6 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -195,9 +195,9 @@ if not _no_autodoc: django.setup() - modnames = [mname for mname in sys.modules if mname.startswith("evennia")] - for modname in modnames: - del sys.modules[modname] + # modnames = [mname for mname in sys.modules if mname.startswith("evennia")] + # for modname in modnames: + # del sys.modules[modname] import evennia # noqa From 135e1ba14de4ed8b3dd5a26dc57ebbb79eebf99a Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 27 Feb 2021 19:52:46 +0100 Subject: [PATCH 24/33] Hopefully final solution to the doc-build issue --- docs/source/conf.py | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 9d4478d9d6..7aa9aea03f 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -179,13 +179,7 @@ if not _no_autodoc: EV_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - import subprocess - subprocess.call(["git", "rev-parse", "--abbrev-ref", "HEAD"]) - subprocess.call("pwd") - - sys.path.insert(0, EV_ROOT) - - print(f"sys.path: {sys.path}") + sys.path.insert(1, EV_ROOT) with cd(EV_ROOT): # set up Evennia so its sources can be parsed @@ -195,25 +189,11 @@ if not _no_autodoc: django.setup() - # modnames = [mname for mname in sys.modules if mname.startswith("evennia")] - # for modname in modnames: - # del sys.modules[modname] - import evennia # noqa evennia._init() from evennia.utils.ansi import strip_raw_ansi as ansi_clean - try: - from evennia.contrib import crafting # noqa - except ImportError: - print("--- NOT develop branch!") - print(f"evennia location: {evennia.__file__}") - else: - print("--- Develop branch!") - print(f"evennia location: {evennia.__file__}") - print(f"crafting location: {crafting.__file__}") - if _no_autodoc: exclude_patterns = ["api/*"] From b51c086ed069b544919cc4da08a8cbe640a87293 Mon Sep 17 00:00:00 2001 From: llzzies <67440021+llzzies@users.noreply.github.com> Date: Mon, 1 Mar 2021 04:11:48 -0500 Subject: [PATCH 25/33] Update Objects.md at_before_leave(obj, destination) was changed to at_object_leave --- docs/source/Objects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/Objects.md b/docs/source/Objects.md index e2f21477e9..905dc86a80 100644 --- a/docs/source/Objects.md +++ b/docs/source/Objects.md @@ -172,7 +172,7 @@ object). 1. In `at_traverse`, `object.move_to(destination)` is triggered. This triggers the following hooks, in order: 1. `obj.at_before_move(destination)` - if this returns False, move is aborted. - 1. `origin.at_before_leave(obj, destination)` + 1. `origin.at_object_leave(obj, destination)` 1. `obj.announce_move_from(destination)` 1. Move is performed by changing `obj.location` from source location to `destination`. 1. `obj.announce_move_to(source)` From f9721b99b66f422d1bd5f3548a8c5e41a9dc72df Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 6 Mar 2021 01:15:28 +0100 Subject: [PATCH 26/33] Add link sidebar to docs --- docs/source/_templates/links.html | 11 +++++++++++ docs/source/conf.py | 1 + 2 files changed, 12 insertions(+) create mode 100644 docs/source/_templates/links.html diff --git a/docs/source/_templates/links.html b/docs/source/_templates/links.html new file mode 100644 index 0000000000..376806491f --- /dev/null +++ b/docs/source/_templates/links.html @@ -0,0 +1,11 @@ +

Links

+ diff --git a/docs/source/conf.py b/docs/source/conf.py index 7aa9aea03f..d81450fcf3 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -73,6 +73,7 @@ html_sidebars = { "relations.html", "sourcelink.html", "versioning.html", + "links.html", ] } html_favicon = "_static/images/favicon.ico" From 1da065e6df39b036a65a1015798d361b4e154f76 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 6 Mar 2021 01:32:23 +0100 Subject: [PATCH 27/33] Change link order in doc sidebar --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index d81450fcf3..bb3379c754 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -72,8 +72,8 @@ html_sidebars = { # "globaltoc.html", "relations.html", "sourcelink.html", - "versioning.html", "links.html", + "versioning.html", ] } html_favicon = "_static/images/favicon.ico" From fdffe4b78c8013f508ca0bb0c13752f087cce52f Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 7 Mar 2021 10:31:16 +0100 Subject: [PATCH 28/33] Remove `BASE_*_TYPECLASS` from flat API, break circular imports of cmdhandler in accounts/objects. Resolve #2330. --- evennia/__init__.py | 15 --------------- evennia/accounts/accounts.py | 8 ++++++-- evennia/objects/objects.py | 9 +++++++-- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/evennia/__init__.py b/evennia/__init__.py index 3b7c37f83f..703d0bcafc 100644 --- a/evennia/__init__.py +++ b/evennia/__init__.py @@ -101,17 +101,6 @@ CHANNEL_HANDLER = None GLOBAL_SCRIPTS = None OPTION_CLASSES = None -# typeclasses -BASE_ACCOUNT_TYPECLASS = None -BASE_OBJECT_TYPECLASS = None -BASE_CHARACTER_TYPECLASS = None -BASE_ROOM_TYPECLASS = None -BASE_EXIT_TYPECLASS = None -BASE_CHANNEL_TYPECLASS = None -BASE_SCRIPT_TYPECLASS = None -BASE_GUEST_TYPECLASS = None - - def _create_version(): """ Helper function for building the version string @@ -165,10 +154,6 @@ def _init(): global EvMenu, EvTable, EvForm, EvMore, EvEditor global ANSIString - global BASE_ACCOUNT_TYPECLASS, BASE_OBJECT_TYPECLASS, BASE_CHARACTER_TYPECLASS - global BASE_ROOM_TYPECLASS, BASE_EXIT_TYPECLASS, BASE_CHANNEL_TYPECLASS - global BASE_SCRIPT_TYPECLASS, BASE_GUEST_TYPECLASS - # Parent typeclasses from .accounts.accounts import DefaultAccount from .accounts.accounts import DefaultGuest diff --git a/evennia/accounts/accounts.py b/evennia/accounts/accounts.py index 9bb5acbf29..4cb1255dfa 100644 --- a/evennia/accounts/accounts.py +++ b/evennia/accounts/accounts.py @@ -22,7 +22,6 @@ 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.server.models import ServerConfig from evennia.server.throttle import Throttle from evennia.utils import class_from_module, create, logger @@ -49,6 +48,7 @@ _MULTISESSION_MODE = settings.MULTISESSION_MODE _MAX_NR_CHARACTERS = settings.MAX_NR_CHARACTERS _CMDSET_ACCOUNT = settings.CMDSET_ACCOUNT _MUDINFO_CHANNEL = None +_CMDHANDLER = None # Create throttles for too many account-creations and login attempts CREATION_THROTTLE = Throttle( @@ -931,6 +931,10 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): commands at run-time. """ + # break circular import issues + global _CMDHANDLER + if not _CMDHANDLER: + from evennia.commands.cmdhandler import cmdhandler as _CMDHANDLER raw_string = self.nicks.nickreplace( raw_string, categories=("inputline", "channel"), include_account=False ) @@ -939,7 +943,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): sessions = self.sessions.get() session = sessions[0] if sessions else None - return cmdhandler.cmdhandler( + return _CMDHANDLER( self, raw_string, callertype="account", session=session, **kwargs ) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 2461fb8234..746d02a47a 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -18,7 +18,6 @@ from evennia.objects.models import ObjectDB from evennia.scripts.scripthandler import ScriptHandler from evennia.commands import cmdset, command from evennia.commands.cmdsethandler import CmdSetHandler -from evennia.commands import cmdhandler from evennia.utils import create from evennia.utils import search from evennia.utils import logger @@ -39,6 +38,7 @@ _MULTISESSION_MODE = settings.MULTISESSION_MODE _ScriptDB = None _SESSIONS = None +_CMDHANDLER = None _AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit(".", 1)) _COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) @@ -576,12 +576,17 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): command structure. """ + # break circular import issues + global _CMDHANDLER + if not _CMDHANDLER: + from evennia.commands.cmdhandler import cmdhandler as _CMDHANDLER + # nick replacement - we require full-word matching. # do text encoding conversion raw_string = self.nicks.nickreplace( raw_string, categories=("inputline", "channel"), include_account=True ) - return cmdhandler.cmdhandler( + return _CMDHANDLER( self, raw_string, callertype="object", session=session, **kwargs ) From b3ac55b58a342c09639bf172f229449ff8472c5e Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 11 Mar 2021 09:43:02 +0100 Subject: [PATCH 29/33] Remove spurious code accidentally added from other branch --- .../contrib/rules/openadventure/chargen.py | 193 ------------------ 1 file changed, 193 deletions(-) delete mode 100644 evennia/contrib/rules/openadventure/chargen.py diff --git a/evennia/contrib/rules/openadventure/chargen.py b/evennia/contrib/rules/openadventure/chargen.py deleted file mode 100644 index 44f9770e36..0000000000 --- a/evennia/contrib/rules/openadventure/chargen.py +++ /dev/null @@ -1,193 +0,0 @@ -""" -This is a convenient module for use by chargen implementations. It gives access -to ready-made calculations (using the various rule systems) to get options and -evaluate results during the character generation process. - -""" -from dataclasses import dataclass -from evennia.utils.utils import callables_from_module, inherits_from - - -@dataclass -class ChargenChoice: - """ - This temporarily stores the choices done during chargen. It could be - saved to an Attribute if persistence is required. - - """ - rulename = "" - data = {} - - -class ChargenStep: - """ - This represents the steps of a character generation. It assumes this is a - sequential process (if chargen allows jumping that's fine too, one could - just have a menu that points to each step). - - """ - step_name = "" - prev_step = None - next_step = None - - def get_help(self, *args, **kwargs): - """ - This returns help text for this choice. - - """ - return self.__doc__.strip() - - def options(self, *args, **kwargs): - """ - This presents the choices allowed by this step. - - Returns: - list: A list of all applicable choices, usually as rule-classes. - - """ - - def validate_input(self, inp, *args, **kwargs): - """ - This can be used to validate free-form inputs from the user. - - Args: - inp (str): Input from the user. - Returns: - bool: If input was valid or not. - - """ - - def user_choice(self, *args, **kwargs): - """ - This should be called with the user's choice. This must already be - determined to be a valid choice at this point. - - Args: - *args, **kwargs: Input on any form. - - Returns: - CharacterStore: Storing the current, valid choice. - - """ - - -class ArchetypeChoice(ChargenStep): - """ - Choose one archetype and record all of its characteristics–or–choose two - archetypes, halve all the characteristic's numbers, then combine their - values. - - """ - step_name = "Choose an Archetype" - prev_step = None - next_step = "Choose a Race" - - def get_help(self, *args, **kwargs): - return self.__doc__.strip() - - def options(self, *args, **kwargs): - """ - Returns a list of Archetypes. Each Archetype class has a `.modifiers` - dict on it with values keyed to enums for the bonuses/drawbacks of that - archetype. The archetype's name is the class name, retrieved with `.__name__`. - - """ - from . import archetypes - return callables_from_module(archetypes) - - def user_choice(self, choice1, choice2=None, **kwargs): - """ - We allow one or two choices - they must be passed into this method - together. - - Args: - choice1 (Archetype): The first archetype choice. - choice2 (Archetype, optional): Second choice (for dual archetype choice) - - Returns: - ChargenStore: Holds rule_name and data; data has an additional 'name' field - to identify the archetype (which may be a merger of two names). - - """ - data1 = choice1.modifiers - if choice2: - # dual-archetype - add with choice1 values and half, rounding down - data2 = choice2.modifiers - data = {} - for key, value1 in data1.items(): - value2 = data2.get(key, 0) - data[key] = int((value1 + value2) / 2) - # add values from choice2 that was not yet covered - data.update({key: int(value2 / 2) for key, value in data2.items() if key not in data}) - # create a new name for the dual-archetype by combining the names alphabetically - data['name'] = "-".join(sorted((choice1.__name__, choice2.__name__))) - else: - data = data1 - data['name'] = choice1.__name__ - - return ChargenChoice(rulename="archetype", data=data) - - -class RaceChoice(ChargenStep): - """ - Choose a race/creature type that best suits this character. - - """ - step_name = "Choose a Race" - prev_step = "Choose an Archetype" - next_step = "Choose a race Focus" - - - def options(self, *args, **kwargs): - """ - Returns a list of races. Each Race class has the following properties: - :: - size = enum for small, average, large - bodytype = enum for slim, average, stocky - modifiers = dict of enums and bonuses/drawbacks - foci = list of which Focus may be chosen for this race - feats = list of which Feats may be chosen for this race - - """ - from . import races - all_ = callables_from_module(races) - return [race for race in all_ if inherits_from(race, race.Race)] - - def user_choice(self, choice, *args, **kwargs): - """ - The choice should be a distinct race. - - Args: - choice (Race): The chosen race. - - Returns: - ChargenChoice: This will have the Race class as .data. - - """ - return ChargenChoice(rulename="race", data=choice) - - -class FocusChoice(ChargenStep): - """ - Each race has three 'subtypes' or Foci available to them. This represents - natural strengths of that race that come across more or less in different - individuals. Each Character may pick one race-Focus to emphasize. - - """ - def options(self, race, *args, **kwargs): - """ - To know which focus to offer, the chosen Race must be passed. - - Args: - race (Race): The race chosen on the previous step. - - """ - return list(race.foci) - - def user_choice(self, choice, *args, **kwargs): - """ - - """ - - - From 28d558e4f06e0003cac2c621a1f3e2a7f6f108ac Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 20 Mar 2021 09:43:29 +0100 Subject: [PATCH 30/33] Fix mention of defunct unban command. Resolves #2337 --- docs/source/Banning.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/source/Banning.md b/docs/source/Banning.md index 5234bbfeae..afb17874d8 100644 --- a/docs/source/Banning.md +++ b/docs/source/Banning.md @@ -3,7 +3,7 @@ Whether due to abuse, blatant breaking of your rules, or some other reason, you will eventually find no other recourse but to kick out a particularly troublesome player. The default command set has -admin tools to handle this, primarily `@ban`, `@unban`, and `@boot`. +admin tools to handle this, primarily `ban`, `unban`, and `boot`. ## Creating a ban @@ -16,7 +16,7 @@ have tried to be nice. Now you just want this troll gone. The easiest recourse is to block the account YouSuck from ever connecting again. - @ban YouSuck + ban YouSuck This will lock the name YouSuck (as well as 'yousuck' and any other capitalization combination), and next time they try to log in with this name the server will not let them! @@ -24,12 +24,12 @@ next time they try to log in with this name the server will not let them! You can also give a reason so you remember later why this was a good thing (the banned account will never see this) - @ban YouSuck:This is just a troll. + ban YouSuck:This is just a troll. If you are sure this is just a spam account, you might even consider deleting the player account outright: - @delaccount YouSuck + account/delete YouSuck Generally, banning the name is the easier and safer way to stop the use of an account -- if you change your mind you can always remove the block later whereas a deletion is permanent. @@ -49,7 +49,7 @@ the `who` command, which will show you something like this: The "Host" bit is the IP address from which the account is connecting. Use this to define the ban instead of the name: - @ban 237.333.0.223 + ban 237.333.0.223 This will stop YouSuckMore connecting from their computer. Note however that IP address might change easily - either due to how the player's Internet Service Provider operates or by the user simply @@ -58,7 +58,7 @@ groups of three digits in the address. So if you figure out that !YouSuckMore ma 237.333.0.223, 237.333.0.225, and 237.333.0.256 (only changes in their subnet), it might be an idea to put down a ban like this to include any number in that subnet: - @ban 237.333.0.* + ban 237.333.0.* You should combine the IP ban with a name-ban too of course, so the account YouSuckMore is truly locked regardless of where they connect from. @@ -71,16 +71,16 @@ blocking out innocent players who just happen to connect from the same subnet as YouSuck is not really noticing all this banning yet though - and won't until having logged out and trying to log back in again. Let's help the troll along. - @boot YouSuck + boot YouSuck Good riddance. You can give a reason for booting too (to be echoed to the player before getting kicked out). - @boot YouSuck:Go troll somewhere else. + boot YouSuck:Go troll somewhere else. ### Lifting a ban -Use the `@unban` (or `@ban`) command without any arguments and you will see a list of all currently +Use the `unban` (or `ban`) command without any arguments and you will see a list of all currently active bans: Active bans @@ -90,7 +90,7 @@ active bans: Use the `id` from this list to find out which ban to lift. - @unban 2 + unban 2 Cleared ban 2: 237.333.0.* @@ -132,7 +132,7 @@ case) the lock to fail. - **type thomas = FlowerPot** -- Turn an annoying player into a flower pot (assuming you have a `FlowerPot` typeclass ready) - **userpassword thomas = fooBarFoo** -- Change a user's password -- **delaccount thomas** -- Delete a player account (not recommended, use **ban** instead) +- **account/delete thomas** -- Delete a player account (not recommended, use **ban** instead) - **server** -- Show server statistics, such as CPU load, memory usage, and how many objects are cached From 63e50af160cd3b840b4145a9a366c99db6700735 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 20 Mar 2021 10:49:06 +0100 Subject: [PATCH 31/33] Fix doc build makefile --- .github/workflows/github_action_build_docs.yml | 14 +++++++------- docs/Makefile | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/github_action_build_docs.yml b/.github/workflows/github_action_build_docs.yml index 5b085b9f3b..5ad86c2a9c 100644 --- a/.github/workflows/github_action_build_docs.yml +++ b/.github/workflows/github_action_build_docs.yml @@ -41,13 +41,13 @@ jobs: make quickstrict # full game dir needed for mv-local - - name: Set up evennia game dir - run: | - pip install -e . - cd .. - evennia --init gamedir - cd gamedir - evennia migrate + # - name: Set up evennia game dir + # run: | + # pip install -e . + # cd .. + # evennia --init gamedir + # cd gamedir + # evennia migrate - name: Deploy docs (only from master/develop branch) if: ${{ github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master'}} diff --git a/docs/Makefile b/docs/Makefile index e7141b6b20..0cc01b83ed 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -86,7 +86,7 @@ _multiversion-deploy: _latex-build: @NOAUTODOC=1 EVDIR=$(EVDIR) EVGAMEDIR=$(EVGAMEDIR) $(SPHINXBUILD) -M latexpdf "$(SOURCEDIR)" "$(BUILDDIR)/latex" $(QUICKFILES) -# main target: +install: @pip install -r requirements.txt clean: From f18afae6ae2a4bb5ed4aa6e72bcf4ef4d38681b1 Mon Sep 17 00:00:00 2001 From: duysqubix Date: Tue, 23 Mar 2021 04:32:21 +0000 Subject: [PATCH 32/33] need to add twistd location to environment --- bin/unix/evennia.service | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/unix/evennia.service b/bin/unix/evennia.service index a312bb8b4e..5b636f202b 100644 --- a/bin/unix/evennia.service +++ b/bin/unix/evennia.service @@ -26,6 +26,11 @@ User=your-user # ExecStart=/your/path/to/pyenv/bin/python /your/path/to/evennia/bin/unix/evennia ipstart --gamedir /your/path/to/mygame +# +# Service needs to know the path to twistd binary +# Replace /your/path/to/venv/bin with whatever is appropriate +Environment="PATH=/your/path/to/pyenv/bin:$PATH" + # restart on all failures, wait 3 seconds before doing so. Restart=on-failure RestartSec=3 From a07def36aa34d135803792c7afb00d1215a9bd35 Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 25 Mar 2021 23:18:39 +0100 Subject: [PATCH 33/33] Fix links to docs --- evennia/contrib/tutorial_world/intro_menu.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/evennia/contrib/tutorial_world/intro_menu.py b/evennia/contrib/tutorial_world/intro_menu.py index e482eec453..98559452fa 100644 --- a/evennia/contrib/tutorial_world/intro_menu.py +++ b/evennia/contrib/tutorial_world/intro_menu.py @@ -694,27 +694,27 @@ If you want there is also some |wextra|n info for where to go beyond that. After playing through the tutorial-world quest, if you aim to make a game with Evennia you are wise to take a look at the |wEvennia documentation|n at - |yhttps://github.com/evennia/evennia/wiki|n + |yhttps://www.evennia.com/docs/latest - You can start by trying to build some stuff by following the |wBuilder quick-start|n: - |yhttps://github.com/evennia/evennia/wiki/Building-Quickstart|n + |yhttps://www.evennia.com/docs/latest/Building-Quickstart|n - The tutorial-world may or may not be your cup of tea, but it does show off several |wuseful tools|n of Evennia. You may want to check out how it works: - |yhttps://github.com/evennia/evennia/wiki/Tutorial-World-Introduction|n + |yhttps://www.evennia.com/docs/latest/Tutorial-World-Introduction|n - You can then continue looking through the |wTutorials|n and pick one that fits your level of understanding. - |yhttps://github.com/evennia/evennia/wiki/Tutorials|n + |yhttps://www.evennia.com/docs/latest/Tutorials|n - Make sure to |wjoin our forum|n and connect to our |wsupport chat|n! The Evennia community is very active and friendly and no question is too simple. You will often quickly get help. You can everything you need linked from - |yhttp://www.evennia.com|n + |yhttps://www.evennia.com|n # ---------------------------------------------------------------------------------