Merge branch 'main' into evadventure_work

This commit is contained in:
Griatch 2023-04-29 17:04:08 +02:00
commit 1f3d4ed840
23 changed files with 112 additions and 93 deletions

View file

@ -1,13 +1,18 @@
# Changelog # Changelog
## Main branch ## Evennia 1.3.0
- Feature: Better ANSI color fallbacks (InspectorCaracal) Apr 29, 2023
- Feature: Better ANSI color fallbacks (InspectorCaracal).
- Feature: Add support for saving `deque` with `maxlen` to Attributes (before - Feature: Add support for saving `deque` with `maxlen` to Attributes (before
`maxlen` was ignored). `maxlen` was ignored).
- Tools: More unit tests for scripts (Storsorken) - Fix: The username validator did not display errors correctly in web
registration form.
- Fix: Components contrib had issues with inherited typeclasses (ChrisLR) - Fix: Components contrib had issues with inherited typeclasses (ChrisLR)
- Fix: f-string fix in clothing contrib (aMiss-aWry) - Fix: f-string fix in clothing contrib (aMiss-aWry)
- Fix: Have `EvenniaTestCase` properly flush idmapper cache (bradleymarques)
- Tools: More unit tests for scripts (Storsorken)
- Docs: Made separate doc pages for Exits, Characters and Rooms. Expanded on how - Docs: Made separate doc pages for Exits, Characters and Rooms. Expanded on how
to change the description of an in-game object with templating. to change the description of an in-game object with templating.
- Docs: A multitude of doc issues and typos fixed. - Docs: A multitude of doc issues and typos fixed.

View file

@ -58,10 +58,10 @@ _multiversion-check-env:
@EVDIR=$(EVDIR) EVGAMEDIR=$(EVGAMEDIR) bash -e checkenv.sh multiversion @EVDIR=$(EVDIR) EVGAMEDIR=$(EVGAMEDIR) bash -e checkenv.sh multiversion
_clean_api_index: _clean_api_index:
rm source/api/* rm -f source/api/*
_clean_api_rsts: _clean_api_rsts:
rm source/api/*.rst rm -f source/api/*.rst
# remove superfluos 'module' and 'package' text from api headers # remove superfluos 'module' and 'package' text from api headers
_reformat_apidoc_headers: _reformat_apidoc_headers:

View file

@ -1,13 +1,21 @@
# Changelog # Changelog
## Main branch ## Evennia 1.3.0
Apr 29, 2023
- Feature: Better ANSI color fallbacks (InspectorCaracal).
- Feature: Add support for saving `deque` with `maxlen` to Attributes (before - Feature: Add support for saving `deque` with `maxlen` to Attributes (before
`maxlen` was ignored). `maxlen` was ignored).
- Fix: More unit tests for scripts (Storsorken) - Fix: The username validator did not display errors correctly in web
registration form.
- Fix: Components contrib had issues with inherited typeclasses (ChrisLR)
- Fix: f-string fix in clothing contrib (aMiss-aWry)
- Fix: Have `EvenniaTestCase` properly flush idmapper cache (bradleymarques)
- Tools: More unit tests for scripts (Storsorken)
- Docs: Made separate doc pages for Exits, Characters and Rooms. Expanded on how - Docs: Made separate doc pages for Exits, Characters and Rooms. Expanded on how
to change the description of an in-game object with templating. to change the description of an in-game object with templating.
- Docs: Fixed a multitude of doc issues. - Docs: A multitude of doc issues and typos fixed.
## Evennia 1.2.1 ## Evennia 1.2.1

View file

@ -106,7 +106,7 @@ To test this, run
to run the entire test module to run the entire test module
evennia test --settings setings.py world.tests evennia test --settings settings.py world.tests
or a specific class: or a specific class:

View file

@ -17,8 +17,8 @@ Channels can be used both for chats between [Accounts](./Accounts.md) and betwee
- Private guild channels for planning and organization (IC/OOC depending on game) - Private guild channels for planning and organization (IC/OOC depending on game)
- Cyberpunk-style retro chat rooms (IC) - Cyberpunk-style retro chat rooms (IC)
- In-game radio channels (IC) - In-game radio channels (IC)
- Group telephathy (IC) - Group telepathy (IC)
- Walkie talkies (IC) - Walkie-talkies (IC)
```{versionchanged} 1.0 ```{versionchanged} 1.0
@ -150,7 +150,7 @@ To create/destroy a new channel on the fly you can do
Aliases are optional but can be good for obvious shortcuts everyone may want to Aliases are optional but can be good for obvious shortcuts everyone may want to
use. The description is used in channel-listings. You will automatically join a use. The description is used in channel-listings. You will automatically join a
channel you created and will be controlling it. You can also use `channel/desc` to channel you created and will be controlling it. You can also use `channel/desc` to
change the description on a channel you wnn later. change the description on a channel you own later.
If you control a channel you can also kick people off it: If you control a channel you can also kick people off it:
@ -223,7 +223,7 @@ channels you could override the `help` command and change the lockstring to:
``` ```
Add this custom command to your default cmdset and regular users wil now get an Add this custom command to your default cmdset and regular users will now get an
access-denied error when trying to use use these switches. access-denied error when trying to use use these switches.
## Using channels in code ## Using channels in code
@ -263,7 +263,7 @@ below:
3. `channel.at_post_channel_msg(message, **kwargs)` 3. `channel.at_post_channel_msg(message, **kwargs)`
Note that `Accounts` and `Objects` both have their have separate sets of hooks. Note that `Accounts` and `Objects` both have their have separate sets of hooks.
So make sure you modify the set actually used by your subcribers (or both). So make sure you modify the set actually used by your subscribers (or both).
Default channels all use `Account` subscribers. Default channels all use `Account` subscribers.
### Channel class ### Channel class
@ -379,7 +379,7 @@ Notable `Channel` hooks:
a class-method that will happily remove found channel-aliases from the user linked to _any_ a class-method that will happily remove found channel-aliases from the user linked to _any_
channel, not only from the channel the method is called on. channel, not only from the channel the method is called on.
- `pre_join_channel(subscriber)` - if this returns `False`, connection will be refused. - `pre_join_channel(subscriber)` - if this returns `False`, connection will be refused.
- `post_join_channel(subscriber)` - by default this sets up a users's channel-nicks/aliases. - `post_join_channel(subscriber)` - by default this sets up a users' channel-nicks/aliases.
- `pre_leave_channel(subscriber)` - if this returns `False`, the user is not allowed to leave. - `pre_leave_channel(subscriber)` - if this returns `False`, the user is not allowed to leave.
- `post_leave_channel(subscriber)` - this will clean up any channel aliases/nicks of the user. - `post_leave_channel(subscriber)` - this will clean up any channel aliases/nicks of the user.
- `delete` the standard typeclass-delete mechanism will also automatically un-subscribe all - `delete` the standard typeclass-delete mechanism will also automatically un-subscribe all

View file

@ -19,6 +19,8 @@
*Exits* are in-game [Objects](./Objects.md) connecting other objects (usually [Rooms](./Rooms.md)) together. *Exits* are in-game [Objects](./Objects.md) connecting other objects (usually [Rooms](./Rooms.md)) together.
> Note that Exits are one-way objects, so in order for two Rooms to be linked bi-directionally, there will need to be two exits.
An object named `north` or `in` might be exits, as well as `door`, `portal` or `jump out the window`. An object named `north` or `in` might be exits, as well as `door`, `portal` or `jump out the window`.
An exit has two things that separate them from other objects. An exit has two things that separate them from other objects.
@ -29,7 +31,7 @@ The default exit functionality is all defined on the [DefaultExit](DefaultExit)
Exits are [locked](./Locks.md) using an `access_type` called *traverse* and also make use of a few hook methods for giving feedback if the traversal fails. See `evennia.DefaultExit` for more info. Exits are [locked](./Locks.md) using an `access_type` called *traverse* and also make use of a few hook methods for giving feedback if the traversal fails. See `evennia.DefaultExit` for more info.
Exits are normally overridden on a case-by-case basis, but if you want to change the default exit createad by rooms like `dig` , `tunnel` or `open` you can change it in settings: Exits are normally overridden on a case-by-case basis, but if you want to change the default exit created by rooms like `dig`, `tunnel` or `open` you can change it in settings:
BASE_EXIT_TYPECLASS = "typeclasses.exits.Exit" BASE_EXIT_TYPECLASS = "typeclasses.exits.Exit"
@ -53,3 +55,7 @@ The process of traversing an exit is as follows:
1. On the Exit object, `at_post_traverse(obj, source)` is triggered. 1. On the Exit object, `at_post_traverse(obj, source)` is triggered.
If the move fails for whatever reason, the Exit will look for an Attribute `err_traverse` on itself and display this as an error message. If this is not found, the Exit will instead call `at_failed_traverse(obj)` on itself. If the move fails for whatever reason, the Exit will look for an Attribute `err_traverse` on itself and display this as an error message. If this is not found, the Exit will instead call `at_failed_traverse(obj)` on itself.
### Creating Exits in code
For an example of how to create Exits programatically please see [this guide](../Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Creating-Things.md#linking-exits-and-rooms-in-code).

View file

@ -104,7 +104,7 @@ Below are the access_types checked by the default commandset.
- `search` - this controls if the object can be found with the - `search` - this controls if the object can be found with the
`DefaultObject.search` method (usually referred to with `caller.search` `DefaultObject.search` method (usually referred to with `caller.search`
in Commands). This is how to create entirely 'undetectable' in-game objects. in Commands). This is how to create entirely 'undetectable' in-game objects.
If not setting this lock excplicitly, all objects are assumed searchable. If not setting this lock explicitly, all objects are assumed searchable.
Note that if you are aiming to make some _permanently invisible game system, Note that if you are aiming to make some _permanently invisible game system,
using a [Script](./Scripts.md) is a better bet. using a [Script](./Scripts.md) is a better bet.
- `get`- who may pick up the object and carry it around. - `get`- who may pick up the object and carry it around.
@ -330,7 +330,7 @@ error message. Sounds good! Let's start by setting that on the box:
Next we need to craft a Lock of type *get* on our box. We want it to only be passed if the accessing Next we need to craft a Lock of type *get* on our box. We want it to only be passed if the accessing
object has the attribute *strength* of the right value. For this we would need to create a lock object has the attribute *strength* of the right value. For this we would need to create a lock
function that checks if attributes have a value greater than a given value. Luckily there is already function that checks if attributes have a value greater than a given value. Luckily there is already
such a one included in evennia (see `evennia/locks/lockfuncs.py`), called `attr_gt`. such a one included in Evennia (see `evennia/locks/lockfuncs.py`), called `attr_gt`.
So the lock string will look like this: `get:attr_gt(strength, 50)`. We put this on the box now: So the lock string will look like this: `get:attr_gt(strength, 50)`. We put this on the box now:

View file

@ -84,11 +84,11 @@ Translations are found in the core `evennia/` library, under
`evennia/evennia/locale/`. You must make sure to have cloned this repository `evennia/evennia/locale/`. You must make sure to have cloned this repository
from [Evennia's github](github:evennia) before you can proceed. from [Evennia's github](github:evennia) before you can proceed.
If you cannot find your language in `evennia/evennia/locale/` it's because noone If you cannot find your language in `evennia/evennia/locale/` it's because no one
has translated it yet. Alternatively you might have the language but find the has translated it yet. Alternatively you might have the language but find the
translation bad ... You are welcome to help improve the situation! translation bad ... You are welcome to help improve the situation!
To start a new translation you need to first have cloned the Evennia repositry To start a new translation you need to first have cloned the Evennia repository
with GIT and activated a python virtualenv as described on the with GIT and activated a python virtualenv as described on the
[Setup Quickstart](../Setup/Installation.md) page. [Setup Quickstart](../Setup/Installation.md) page.

View file

@ -1,24 +1,26 @@
# Components # Components
_Contrib by ChrisLR 2021_ Contrib by ChrisLR, 2021
# The Components Contrib Expand typeclasses using a components/composition approach.
## The Components Contrib
This contrib introduces Components and Composition to Evennia. This contrib introduces Components and Composition to Evennia.
Each 'Component' class represents a feature that will be 'enabled' on a typeclass instance. Each 'Component' class represents a feature that will be 'enabled' on a typeclass instance.
You can register these components on an entire typeclass or a single object at runtime. You can register these components on an entire typeclass or a single object at runtime.
It supports both persisted attributes and in-memory attributes by using Evennia's AttributeHandler. It supports both persisted attributes and in-memory attributes by using Evennia's AttributeHandler.
# Pros ## Pros
- You can reuse a feature across multiple typeclasses without inheritance - You can reuse a feature across multiple typeclasses without inheritance
- You can cleanly organize each feature into a self-contained class. - You can cleanly organize each feature into a self-contained class.
- You can check if your object supports a feature without checking its instance. - You can check if your object supports a feature without checking its instance.
# Cons ## Cons
- It introduces additional complexity. - It introduces additional complexity.
- A host typeclass instance is required. - A host typeclass instance is required.
# How to install ## How to install
To enable component support for a typeclass, To enable component support for a typeclass,
import and inherit the ComponentHolderMixin, similar to this import and inherit the ComponentHolderMixin, similar to this
@ -126,7 +128,7 @@ from typeclasses.components import health
``` ```
Both of the above examples will work. Both of the above examples will work.
# Full Example ## Full Example
```python ```python
from evennia.contrib.base_systems import components from evennia.contrib.base_systems import components

View file

@ -111,9 +111,9 @@ Additional color markup styles for Evennia (extending or replacing the default
### `components` ### `components`
__Contrib by ChrisLR 2021__ _Contrib by ChrisLR, 2021_
# The Components Contrib Expand typeclasses using a components/composition approach.
[Read the documentation](./Contrib-Components.md) - [Browse the Code](evennia.contrib.base_systems.components) [Read the documentation](./Contrib-Components.md) - [Browse the Code](evennia.contrib.base_systems.components)

View file

@ -100,7 +100,7 @@ If you try the `get` command, we will pick up the box. So far so good, but if we
lock box = get:false() lock box = get:false()
Locks represent a rather [big topic](../../../Components/Locks.md), but for now that will do what we want. This will lock the box so noone can lift it. The exception is superusers, they override all locks and will pick it Locks represent a rather [big topic](../../../Components/Locks.md), but for now that will do what we want. This will lock the box so no one can lift it. The exception is superusers, they override all locks and will pick it
up anyway. Make sure you are quelling your superuser powers and try to get the box now: up anyway. Make sure you are quelling your superuser powers and try to get the box now:
> get box > get box
@ -142,7 +142,7 @@ You create your own scripts in Python, outside the game; the path you give to `s
## Pushing Your Buttons ## Pushing Your Buttons
If we get back to the box we made, there is only so much fun you can have with it at this point. It's just a dumb generic object. If you renamed it to `stone` and changed its description, noone would be the wiser. However, with the combined use of custom [Typeclasses](../../../Components/Typeclasses.md), [Scripts](../../../Components/Scripts.md) If we get back to the box we made, there is only so much fun you can have with it at this point. It's just a dumb generic object. If you renamed it to `stone` and changed its description, no one would be the wiser. However, with the combined use of custom [Typeclasses](../../../Components/Typeclasses.md), [Scripts](../../../Components/Scripts.md)
and object-based [Commands](../../../Components/Commands.md), you could expand it and other items to be as unique, complex and object-based [Commands](../../../Components/Commands.md), you could expand it and other items to be as unique, complex
and interactive as you want. and interactive as you want.
@ -153,7 +153,7 @@ Let's make us one of _those_!
create/drop button:tutorials.red_button.RedButton create/drop button:tutorials.red_button.RedButton
The same way we did with the Script Earler, we specify a "Python-path" to the Python code we want Evennia to use for creating the object. There you go - one red button. The same way we did with the Script earlier, we specify a "Python-path" to the Python code we want Evennia to use for creating the object. There you go - one red button.
The RedButton is an example object intended to show off a few of Evennia's features. You will find that the [Typeclass](../../../Components/Typeclasses.md) and [Commands](../../../Components/Commands.md) controlling it are inside [evennia/contrib/tutorials/red_button](../../../api/evennia.contrib.tutorials.red_button.md) The RedButton is an example object intended to show off a few of Evennia's features. You will find that the [Typeclass](../../../Components/Typeclasses.md) and [Commands](../../../Components/Commands.md) controlling it are inside [evennia/contrib/tutorials/red_button](../../../api/evennia.contrib.tutorials.red_button.md)

View file

@ -320,7 +320,7 @@ class EvAdventureRollEngine:
defender_defense = getattr(defender, defense_type.value, 1) + 10 defender_defense = getattr(defender, defense_type.value, 1) + 10
result, quality = self.saving_throw(attacker, bonus_type=attack_type, result, quality = self.saving_throw(attacker, bonus_type=attack_type,
target=defender_defense, target=defender_defense,
advantage=advantave, disadvantage=disadvantage) advantage=advantage, disadvantage=disadvantage)
return result, quality return result, quality
``` ```
@ -584,8 +584,8 @@ class TestEvAdventureRuleEngine(BaseEvenniaTest):
@patch("evadventure.rules.randint") @patch("evadventure.rules.randint")
def test_roll(self, mock_randint): def test_roll(self, mock_randint):
mock_randint.return_value = 4 mock_randint.return_value = 4
self.assertEqual(self.roll_engine.roll("1d6", 4) self.assertEqual(self.roll_engine.roll("1d6", 4))
self.assertEqual(self.roll_engine.roll("2d6", 2 * 4) self.assertEqual(self.roll_engine.roll("2d6"), 2 * 4)
# test of the other rule methods below ... # test of the other rule methods below ...
``` ```

View file

@ -11,7 +11,7 @@ are very dedicated.
Many games, even the most roleplay-dedicated, thus tend to allow for players to mediate themselves Many games, even the most roleplay-dedicated, thus tend to allow for players to mediate themselves
to some extent. A common way to do this is to introduce *coded systems* - that is, to let the to some extent. A common way to do this is to introduce *coded systems* - that is, to let the
computer do some of the heavy lifting. A basic thing is to add an online dice-roller so everyone can computer do some of the heavy lifting. A basic thing is to add an online dice-roller so everyone can
make rolls and make sure noone is cheating. Somewhere at this level you find the most bare-bones make rolls and make sure no one is cheating. Somewhere at this level you find the most bare-bones
roleplaying MUSHes. roleplaying MUSHes.
The advantage of a coded system is that as long as the rules are fair the computer is too - it makes The advantage of a coded system is that as long as the rules are fair the computer is too - it makes

View file

@ -54,7 +54,7 @@ class NPCMerchant(Object):
def open_shop(self, shopper): def open_shop(self, shopper):
menunodes = {} # TODO! menunodes = {} # TODO!
shopname = self.db.shopname or "The shop" shopname = self.db.shopname or "The shop"
EvMenu(shopper, menunodes, startnode="shop_start", EvMenu(shopper, menunodes, startnode="shopfront",
shopname=shopname, shopkeeper=self, wares=self.contents) shopname=shopname, shopkeeper=self, wares=self.contents)
``` ```
@ -215,7 +215,7 @@ class NPCMerchant(Object):
"inspect_and_buy": node_inspect_and_buy "inspect_and_buy": node_inspect_and_buy
} }
shopname = self.db.shopname or "The shop" shopname = self.db.shopname or "The shop"
EvMenu(shopper, menunodes, startnode="shop_start", EvMenu(shopper, menunodes, startnode="shopfront",
shopname=shopname, shopkeeper=self, wares=self.contents) shopname=shopname, shopkeeper=self, wares=self.contents)
``` ```

View file

@ -78,6 +78,7 @@ If `localhost` doesn't work when trying to connect to your local game, try `127.
## Mac Troubleshooting ## Mac Troubleshooting
- Some Mac users have reported not being able to connect to `localhost` (i.e. your own computer). If so, try to connect to `127.0.0.1` instead, which is the same thing. Use port 4000 from mud clients and port 4001 from the web browser as usual. - Some Mac users have reported not being able to connect to `localhost` (i.e. your own computer). If so, try to connect to `127.0.0.1` instead, which is the same thing. Use port 4000 from mud clients and port 4001 from the web browser as usual.
- If you get a `MemoryError` when starting Evennia, or when looking at the log, this may be due to an sqlite versioning issue. [A user in our forums](https://github.com/evennia/evennia/discussions/2638#discussioncomment-3630761) found a working solution for this. [Here](https://github.com/evennia/evennia/issues/3120#issuecomment-1442540538) is another variation to solve it.
## Windows Troubleshooting ## Windows Troubleshooting

View file

@ -1 +1 @@
1.2.1 1.3.0

View file

@ -20,7 +20,6 @@ from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.utils import timezone from django.utils import timezone
from django.utils.module_loading import import_string from django.utils.module_loading import import_string
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from evennia.accounts.manager import AccountManager from evennia.accounts.manager import AccountManager
from evennia.accounts.models import AccountDB from evennia.accounts.models import AccountDB
from evennia.commands.cmdsethandler import CmdSetHandler from evennia.commands.cmdsethandler import CmdSetHandler
@ -38,13 +37,7 @@ from evennia.typeclasses.attributes import ModelAttributeBackend, NickHandler
from evennia.typeclasses.models import TypeclassBase from evennia.typeclasses.models import TypeclassBase
from evennia.utils import class_from_module, create, logger from evennia.utils import class_from_module, create, logger
from evennia.utils.optionhandler import OptionHandler from evennia.utils.optionhandler import OptionHandler
from evennia.utils.utils import ( from evennia.utils.utils import is_iter, lazy_property, make_iter, to_str, variable_from_module
is_iter,
lazy_property,
make_iter,
to_str,
variable_from_module,
)
__all__ = ("DefaultAccount", "DefaultGuest") __all__ = ("DefaultAccount", "DefaultGuest")
@ -509,7 +502,6 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
Returns: Returns:
validators (list): List of instantiated Validator objects. validators (list): List of instantiated Validator objects.
""" """
objs = [] objs = []
for validator in validator_config: for validator in validator_config:
try: try:

View file

@ -8,7 +8,6 @@ Communication commands:
""" """
from django.conf import settings from django.conf import settings
from evennia.accounts import bots from evennia.accounts import bots
from evennia.accounts.models import AccountDB from evennia.accounts.models import AccountDB
from evennia.comms.comms import DefaultChannel from evennia.comms.comms import DefaultChannel
@ -777,7 +776,6 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
maxwidth=_DEFAULT_WIDTH, maxwidth=_DEFAULT_WIDTH,
) )
for chan in subscribed: for chan in subscribed:
locks = "-" locks = "-"
chanid = "-" chanid = "-"
if chan.access(self.caller, "control"): if chan.access(self.caller, "control"):
@ -1158,7 +1156,6 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
reason = reason[0].strip() if reason else "" reason = reason[0].strip() if reason else ""
for chan in channels: for chan in channels:
if not chan.access(caller, "control"): if not chan.access(caller, "control"):
self.msg(f"You need 'control'-access to boot a user from {chan.key}.") self.msg(f"You need 'control'-access to boot a user from {chan.key}.")
return return
@ -1245,9 +1242,11 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
) )
ask_yes_no( ask_yes_no(
caller, caller,
f"Are you sure you want to ban user {target.key} from " (
f"channel(s) {channames} (make sure name/channels are correct{reasonwarn}) " f"Are you sure you want to ban user {target.key} from "
"{options}?", f"channel(s) {channames} (make sure name/channels are correct{reasonwarn}) "
"{options}?"
),
_ban_user, _ban_user,
"Aborted.", "Aborted.",
) )
@ -1360,7 +1359,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
targets.append(target_obj) targets.append(target_obj)
message = self.rhs.strip() message = self.rhs.strip()
else: else:
target, *message = self.args.split(" ", 4) target, *message = self.args.split(" ", 1)
if target and target.isnumeric(): if target and target.isnumeric():
# a number to specify a historic page # a number to specify a historic page
number = int(target) number = int(target)
@ -1970,7 +1969,8 @@ class CmdDiscord2Chan(COMMAND_DEFAULT_CLASS):
if not discord_bot.is_typeclass(settings.DISCORD_BOT_CLASS, exact=True): if not discord_bot.is_typeclass(settings.DISCORD_BOT_CLASS, exact=True):
self.msg( self.msg(
f"WARNING: The Discord bot's typeclass is '{discord_bot.typeclass_path}'. This does not match {settings.DISCORD_BOT_CLASS} in settings!" f"WARNING: The Discord bot's typeclass is '{discord_bot.typeclass_path}'. This does"
f" not match {settings.DISCORD_BOT_CLASS} in settings!"
) )
if "start" in self.switches: if "start" in self.switches:
@ -1984,13 +1984,15 @@ class CmdDiscord2Chan(COMMAND_DEFAULT_CLASS):
if "guild" in self.switches: if "guild" in self.switches:
discord_bot.db.tag_guild = not discord_bot.db.tag_guild discord_bot.db.tag_guild = not discord_bot.db.tag_guild
self.msg( self.msg(
f"Messages to Evennia |wwill {'' if discord_bot.db.tag_guild else 'not '}|ninclude the Discord server." f"Messages to Evennia |wwill {'' if discord_bot.db.tag_guild else 'not '}|ninclude"
" the Discord server."
) )
return return
if "channel" in self.switches: if "channel" in self.switches:
discord_bot.db.tag_channel = not discord_bot.db.tag_channel discord_bot.db.tag_channel = not discord_bot.db.tag_channel
self.msg( self.msg(
f"Relayed messages |wwill {'' if discord_bot.db.tag_channel else 'not '}|ninclude the originating channel." f"Relayed messages |wwill {'' if discord_bot.db.tag_channel else 'not '}|ninclude"
" the originating channel."
) )
return return
@ -2029,7 +2031,8 @@ class CmdDiscord2Chan(COMMAND_DEFAULT_CLASS):
dc_chan_names = discord_bot.attributes.get("discord_channels", {}) dc_chan_names = discord_bot.attributes.get("discord_channels", {})
dc_info = dc_chan_names.get(dc_chan, {"name": "unknown", "guild": "unknown"}) dc_info = dc_chan_names.get(dc_chan, {"name": "unknown", "guild": "unknown"})
self.msg( self.msg(
f"Removed link between {ev_chan} and #{dc_info.get('name','?')}@{dc_info.get('guild','?')}" f"Removed link between {ev_chan} and"
f" #{dc_info.get('name','?')}@{dc_info.get('guild','?')}"
) )
return return
else: else:

View file

@ -1,24 +1,26 @@
# Components # Components
_Contrib by ChrisLR 2021_ Contrib by ChrisLR, 2021
# The Components Contrib Expand typeclasses using a components/composition approach.
## The Components Contrib
This contrib introduces Components and Composition to Evennia. This contrib introduces Components and Composition to Evennia.
Each 'Component' class represents a feature that will be 'enabled' on a typeclass instance. Each 'Component' class represents a feature that will be 'enabled' on a typeclass instance.
You can register these components on an entire typeclass or a single object at runtime. You can register these components on an entire typeclass or a single object at runtime.
It supports both persisted attributes and in-memory attributes by using Evennia's AttributeHandler. It supports both persisted attributes and in-memory attributes by using Evennia's AttributeHandler.
# Pros ## Pros
- You can reuse a feature across multiple typeclasses without inheritance - You can reuse a feature across multiple typeclasses without inheritance
- You can cleanly organize each feature into a self-contained class. - You can cleanly organize each feature into a self-contained class.
- You can check if your object supports a feature without checking its instance. - You can check if your object supports a feature without checking its instance.
# Cons ## Cons
- It introduces additional complexity. - It introduces additional complexity.
- A host typeclass instance is required. - A host typeclass instance is required.
# How to install ## How to install
To enable component support for a typeclass, To enable component support for a typeclass,
import and inherit the ComponentHolderMixin, similar to this import and inherit the ComponentHolderMixin, similar to this
@ -126,7 +128,7 @@ from typeclasses.components import health
``` ```
Both of the above examples will work. Both of the above examples will work.
# Full Example ## Full Example
```python ```python
from evennia.contrib.base_systems import components from evennia.contrib.base_systems import components

View file

@ -3,7 +3,6 @@ import re
from django.conf import settings from django.conf import settings
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from evennia.accounts.models import AccountDB from evennia.accounts.models import AccountDB
@ -24,7 +23,6 @@ class EvenniaUsernameAvailabilityValidator:
raises ValidationError otherwise. raises ValidationError otherwise.
""" """
# Check guest list # Check guest list
if settings.GUEST_LIST and username.lower() in ( if settings.GUEST_LIST and username.lower() in (
guest.lower() for guest in settings.GUEST_LIST guest.lower() for guest in settings.GUEST_LIST
@ -45,8 +43,7 @@ class EvenniaPasswordValidator:
def __init__( def __init__(
self, self,
regex=r"^[\w. @+\-',]+$", regex=r"^[\w. @+\-',]+$",
policy="Password should contain a mix of letters, " policy="Password should contain a mix of letters, spaces, digits and @/./+/-/_/'/, only.",
"spaces, digits and @/./+/-/_/'/, only.",
): ):
""" """
Constructs a standard Django password validator. Constructs a standard Django password validator.

View file

@ -558,9 +558,19 @@ class EvenniaTestCase(TestCase):
""" """
For use with gamedir settings; Just like the normal test case, only for naming consistency. For use with gamedir settings; Just like the normal test case, only for naming consistency.
Notes:
- Inheriting from this class will bypass EvenniaTestMixin, and therefore
not setup some default objects. This can result in faster tests.
- If you do inherit from this class for your unit tests, and have
overridden the tearDown() method, please also call flush_cache(). Not
doing so will result in flakey and order-dependent tests due to the
Django ID cache not being flushed.
""" """
pass def tearDown(self) -> None:
flush_cache()
@override_settings(**DEFAULT_SETTINGS) @override_settings(**DEFAULT_SETTINGS)

View file

@ -8,7 +8,6 @@ from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from evennia.utils import class_from_module from evennia.utils import class_from_module
from evennia.web.website import forms from evennia.web.website import forms
@ -56,22 +55,16 @@ class AccountCreateView(AccountMixin, EvenniaCreateView):
password = form.cleaned_data["password1"] password = form.cleaned_data["password1"]
email = form.cleaned_data.get("email", "") email = form.cleaned_data.get("email", "")
# Create account # Create account. This also runs all validations on the username/password.
account, errs = self.typeclass.create(username=username, password=password, email=email) account, errs = self.typeclass.create(username=username, password=password, email=email)
# If unsuccessful, display error messages to user
if not account: if not account:
[messages.error(self.request, err) for err in errs] # password validation happens earlier, only username checks appear here.
form.add_error("username", ", ".join(errs))
# Call the Django "form failure" hook
return self.form_invalid(form) return self.form_invalid(form)
else:
# Inform user of success # Inform user of success
messages.success( messages.success(
self.request, self.request, f"Your account '{account.name}' was successfully created!"
"Your account '%s' was successfully created! " )
"You may log in using it now." % account.name, return HttpResponseRedirect(self.success_url)
)
# Redirect the user to the login page
return HttpResponseRedirect(self.success_url)

View file

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "evennia" name = "evennia"
version = "1.2.1" version = "1.3.0"
maintainers = [{ name = "Griatch", email = "griatch@gmail.com" }] maintainers = [{ name = "Griatch", email = "griatch@gmail.com" }]
description = "A full-featured toolkit and server for text-based multiplayer games (MUDs, MU*, etc)." description = "A full-featured toolkit and server for text-based multiplayer games (MUDs, MU*, etc)."
requires-python = ">=3.10" requires-python = ">=3.10"