Compare commits

...

39 Commits

Author SHA1 Message Date
Griatch
27931248ec GLOBAL_SCRIPTS.all() failed. Resolve #3788 2025-04-26 16:47:05 +02:00
Griatch
0779ec82b6 Apply black on code 2025-04-26 14:07:38 +02:00
Griatch
04ce4f6c5e Update changelog 2025-04-26 13:15:30 +02:00
Griatch
88550d086f
Merge pull request #3783 from jaborsh/ruff_request
Add exclusions and line-length for ruff lint support
2025-04-26 13:05:49 +02:00
Griatch
0460981e36
Merge pull request #3784 from BlaneWins/patch-1
Update Beginner-Tutorial-Creating-Things.md
2025-04-26 13:04:38 +02:00
Griatch
a00a7503fb
Merge pull request #3786 from Problematic/patch-1
Update Locks.md
2025-04-26 13:02:06 +02:00
Griatch
2d3a75702a
Merge pull request #3768 from jaborsh/copy_alias
Copied objects retain plural alias category.
2025-04-26 12:34:18 +02:00
Griatch
ea51a4d3ca
Merge pull request #3766 from jaborsh/typos
Resolving Syntax Error in Spawner/Prototypes Doc
2025-04-26 12:32:46 +02:00
Griatch
fd4ff02ddb
Merge pull request #3763 from 0xDEADFED5/typos
typo fixes
2025-04-26 12:31:45 +02:00
Griatch
bf06a41d36 Some minor rephrasing to i18n doc; also removing recommendation to use %s style 2025-04-26 12:30:56 +02:00
Griatch
e96878f538
Merge pull request #3761 from JohniFi/patch-1
Update Internationalization.md
2025-04-26 12:28:38 +02:00
Griatch
532ccea050
Merge pull request #3757 from JohniFi/add-internationalization
Add internationalization (i18n via gettext)
2025-04-26 12:11:55 +02:00
Griatch
5ae30b46d9
Merge pull request #3756 from JohniFi/update-german-translation
update german translation
2025-04-26 12:06:53 +02:00
Griatch
f98ee304aa Update Changelog 2025-04-26 12:06:05 +02:00
Griatch
f533a2737a
Merge pull request #3751 from EliasWatson/commands-without-account
Handle missing account in Command
2025-04-26 12:04:06 +02:00
Griatch
b7e239e138
Merge pull request #3753 from JohniFi/fix-init_evennia_properties-include-parent
Let init_evennia_properties() also init properties of parent classes
2025-04-26 11:55:29 +02:00
Griatch
a89c8f68cd Add unit test for TickerHandler store_key fix 2025-04-26 11:51:13 +02:00
Griatch
43d1a36203
Merge pull request #3765 from 0xDEADFED5/ticker2
rebuild TickerHandler store_key if it's been serialized
2025-04-26 11:39:05 +02:00
Derek Stobbe
761886a477
Update Locks.md
Include description of tag lockfuncs
2025-04-10 12:53:07 -06:00
Blane Winstead
0418e6076e
Update Beginner-Tutorial-Creating-Things.md
Fix typo and grammar for section about linking exits.
2025-04-09 18:21:30 -05:00
Jake
d74b4c4dee Add exclusions and line-length for ruff lint support 2025-04-09 14:31:02 -06:00
Jake
f0dbcbc461 Copied objects retain plural alias category. 2025-03-30 20:40:50 -07:00
Jake
8d7a19136d Resolving Syntax Error in Spawner/Prototypes Doc 2025-03-30 18:51:07 -07:00
0xDEADFED5
bad5817c44 rebuild TickerHandler store_key if it's been serialized 2025-03-30 02:50:36 -07:00
0xDEADFED5
27a25929e4 typo fixes 2025-03-30 01:18:11 -07:00
Elias Watson
491ece1a89
Merge branch 'evennia:main' into commands-without-account 2025-03-29 13:37:05 -04:00
JohniFi
c7981a7986
Update Internationalization.md 2025-03-29 15:03:55 +01:00
JohniFi
e147511938 fix typo 2025-03-29 14:15:03 +01:00
JohniFi
1f8e14fe7a fix endsep 2025-03-28 13:43:58 +01:00
JohniFi
9f8a893ead fix formatting 2025-03-28 12:37:07 +01:00
JohniFi
1aa6ff3562 add internationalization for typeclasses.models.py 2025-03-28 12:27:26 +01:00
JohniFi
6382727a6f add internationalization for objects.objects.py 2025-03-28 12:27:04 +01:00
JohniFi
f49f80a8f3 add internationalization for objects.objects.py 2025-03-28 11:18:41 +01:00
JohniFi
d787bcf19e revert header changes 2025-03-28 10:47:53 +01:00
JohniFi
8d33c9b0c1 revert header changes 2025-03-28 10:46:55 +01:00
JohniFi
cf051393f0 update german translation 2025-03-28 10:32:34 +01:00
JohniFi
a38b179e51
Update init_evennia_properties() to also init properties of parent classes 2025-03-27 11:11:50 +01:00
Elias Watson
c3697b44e8 Check if account exists before accessing 2025-03-22 20:09:35 -04:00
Elias Watson
14501db8de Handle missing account in Command 2025-03-22 19:19:23 -04:00
83 changed files with 990 additions and 354 deletions

View File

@ -14,6 +14,9 @@ This upgrade requires running `evennia migrate` on your existing database
- [Feat][pull3633]: Default object's default descs are now taken from a `default_description`
class variable instead of the `desc` Attribute always being set (count-infinity)
- [Feat][pull3718]: Remove twistd.bat creation for Windows, should not be needed anymore (0xDEADFED5)
- [Feat][pull3756]: Updated German translation (JohnFi)
- [Feat][pull3757]: Add more i18n strings to `DefaultObject` for easier translation (JohnFi)
- [Feat][pull3783]: Support users of `ruff` linter by adding compatible config in `pyproject.toml` (jaborsh)
- [Fix][pull3677]: Make sure that `DefaultAccount.create` normalizes to empty
strings instead of `None` if no name is provided, also enforce string type (InspectorCaracal)
- [Fix][pull3682]: Allow in-game help searching for commands natively starting
@ -40,6 +43,13 @@ This upgrade requires running `evennia migrate` on your existing database
- [Fix][pull3744]: Fix for format strings not getting picked up in i18n (JohnFi)
- [Fix][pull3743]: Log full stack trace on failed object creation (aMiss-aWry)
- [Fix][pull3747]: TutorialWorld bridge-room didn't correctly randomize weather effects (SpyrosRoum)
- [Fix][pull3765]: Storing TickerHandler `store_key` in a db attribute would not
work correctly (0xDEADFED5)
- [Fix][pull3753]: Make sure `AttributeProperty`s are initialized with default values also in parent class (JohnFi)
- [Fix][pull3751]: The `access` and `inventory` commands would traceback if run on a character without an Account (EliasWatson)
- [Fix][pull3768]: Make sure the `CmdCopy` command copies object categories,
since otherwise plurals were lost (jaborsh)
- [Fix][issue3788]: `GLOBAL_SCRIPTS.all()` raised error (Griatch)
- Fix: `options` setting `NOPROMPTGOAHEAD` was not possible to set (Griatch)
- Fix: Make `\\` properly preserve one backlash in funcparser (Griatch)
- Fix: The testing 'echo' inputfunc didn't work correctly; now returns both args/kwargs (Griatch)
@ -47,7 +57,7 @@ This upgrade requires running `evennia migrate` on your existing database
it caused an OnDemandHandler save error on reload. Will now clean up on save. (Griatch)
used as the task's category (Griatch)
- Fix: Correct aws contrib's use of legacy django string utils (Griatch)
- [Docs]: Fixes from InspectorCaracal, Griatch, ChrisLR
- [Docs]: Fixes from InspectorCaracal, Griatch, ChrisLR, JohnFi, 0xDEADFED5, jaborsh, Problematic, BlaneWins
[pull3633]: https://github.com/evennia/evennia/pull/3633
[pull3677]: https://github.com/evennia/evennia/pull/3677
@ -70,8 +80,16 @@ This upgrade requires running `evennia migrate` on your existing database
[pull3743]: https://github.com/evennia/evennia/pull/3743
[pull3744]: https://github.com/evennia/evennia/pull/3744
[pull3747]: https://github.com/evennia/evennia/pull/3747
[pull3765]: https://github.com/evennia/evennia/pull/3765
[pull3753]: https://github.com/evennia/evennia/pull/3753
[pull3751]: https://github.com/evennia/evennia/pull/3751
[pull3756]: https://github.com/evennia/evennia/pull/3756
[pull3757]: https://github.com/evennia/evennia/pull/3757
[pull3768]: https://github.com/evennia/evennia/pull/3768
[pull3783]: https://github.com/evennia/evennia/pull/3783
[issue3688]: https://github.com/evennia/evennia/issues/3688
[issue3687]: https://github.com/evennia/evennia/issues/3687
[issue3788]: https://github.com/evennia/evennia/issues/3788

View File

@ -182,6 +182,9 @@ Some useful default lockfuncs (see `src/locks/lockfuncs.py` for more):
- `attr(attrname, value)` - checks so an attribute exists on accessing_object *and* has the given value.
- `attr_gt(attrname, value)` - checks so accessing_object has a value larger (`>`) than the given value.
- `attr_ge, attr_lt, attr_le, attr_ne` - corresponding for `>=`, `<`, `<=` and `!=`.
- `tag(tagkey[, category])` - checks if the accessing_object has the specified tag and optional category.
- `objtag(tagkey[, category])` - checks if the *accessed_object* has the specified tag and optional category.
- `objloctag(tagkey[, category])` - checks if the *accessed_obj*'s location has the specified tag and optional category.
- `holds(objid)` - checks so the accessing objects contains an object of given name or dbref.
- `inside()` - checks so the accessing object is inside the accessed object (the inverse of `holds()`).
- `pperm(perm)`, `pid(num)/pdbref(num)` - same as `perm`, `id/dbref` but always looks for permissions and dbrefs of *Accounts*, not on Characters.

View File

@ -39,7 +39,7 @@ In dictionary form, a prototype can look something like this:
```
If you wanted to load it into the spawner in-game you could just put all on one line:
spawn {"prototype_key="house", "key": "Large house", ...}
spawn {"prototype_key"="house", "key": "Large house", ...}
> Note that the prototype dict as given on the command line must be a valid Python structure - so you need to put quotes around strings etc. For security reasons, a dict inserted from-in game cannot have any other advanced Python functionality, such as executable code, `lambda` etc. If builders are supposed to be able to use such features, you need to offer them through [$protfuncs](Spawner-and- Prototypes#protfuncs), embedded runnable functions that you have full control to check and vet before running.

View File

@ -117,21 +117,21 @@ find there. You can edit this with a normal text editor but it is easiest if
you use a special po-file editor from the web (search the web for "po editor"
for many free alternatives), for example:
- [gtranslator](https://wiki.gnome.org/Apps/Gtranslator)
- [poeditor](https://poeditor.com/)
- [gtranslator](https://wiki.gnome.org/Apps/Gtranslator)
- [poeditor](https://poeditor.com/)
The concept of translating is simple, it's just a matter of taking the english
strings you find in the `**.po` file and add your language's translation best
strings you find in the `django.po` file and add your language's translation best
you can. Once you are done, run
`evennia compilemessages`
evennia compilemessages
This will compile all languages. Check your language and also check back to your
`.po` file in case the process updated it - you may need to fill in some missing
header fields and should usually note who did the translation.
When you are done, make sure that everyone can benefit from your translation!
Make a PR against Evennia with the updated `**.po` file. Less ideally (if git is
Make a PR against Evennia with the updated `django.po` file. Less ideally (if git is
not your thing) you can also attach it to a new post in our forums.
### Hints on translation
@ -161,3 +161,31 @@ English anyway.
\n(Traceback was logged {timestamp})"
Swedish: "Fel medan cmdset laddades: Ingen cmdset-klass med namn '{classname}' i {path}.
\n(Traceback loggades {timestamp})"
## Marking Strings in Code for Translation
If you modify the Python module code, you can mark strings for translation by passing them to the `gettext()` method. In Evennia, this is usually imported as `_()` for convenience:
```python
from django.utils.translation import gettext as _
string = _("Text to translate")
```
### Formatting Considerations
When using formatted strings, ensure that you pass the "raw" string to `gettext` for translation first and then format the output. Otherwise, placeholders will be replaced before translation occurs, preventing the correct string from being found in the `.po` file. It's also recommended to use named placeholders (e.g., `{char}`) instead of positional ones (e.g., `{}`) for better readability and maintainability.
```python
# incorrect:
string2 = _("Hello {char}!".format(char=caller.name))
# correct:
string2 = _("Hello {char}!").format(char=caller.name)
```
This is also why f-strings don't work with `gettext`:
```python
# will not work
string = _(f"Hello {char}!")
```

View File

@ -43,7 +43,7 @@ For the `look`-command (and anything else written by the player), the `text` `co
### Inputfuncs
On the Evennia server side, a list of [inputfucs](Inputuncs) are registered. You can add your own by extending `settings.INPUT_FUNC_MODULES`.
On the Evennia server side, a list of [inputfuncs](Inputfuncs) are registered. You can add your own by extending `settings.INPUT_FUNC_MODULES`.
```python
inputfunc_commandname(session, *args, **kwargs)

View File

@ -80,7 +80,7 @@ The `east` exit has a `key` of `east`, a `location` of `Meadow` and a `destinati
Meadow -> east -> Forest
Forest -> west -> Meadow
In-game you do this with `tunnel` and `dig` commands, bit if you want to ever set up these links in code, you can do it like this:
In-game you do this with `tunnel` and `dig` commands, but if you want to set up these links in code, you can do it like this:
```python
from evennia import create_object

View File

@ -81,7 +81,7 @@ Common for the settings is that you generally will never import them directly vi
- `at_server_startstop.py` - This allows to inject code to execute every time the server starts, stops or reloads in different ways.
- `connection_screens.py` - This allows for changing the connection screen you see when you first connect to your game.
- `inlinefuncs.py` - [Inlinefuncs](../../../Concepts/Inline-Functions.md) are optional and limited 'functions' that can be embedded in any strings being sent to a player. They are written as `$funcname(args)` and are used to customize the output depending on the user receiving it. For example sending people the text `"Let's meet at $realtime(13:00, GMT)!` would show every player seeing that string the time given in their own time zone. The functions added to this module will become new inlinefuncs in the game. See also the [FuncParser](../../../Components/FuncParser.md).
- `inputfucs.py` - When a command like `look` is received by the server, it is handled by an [Inputfunc](InputFuncs) that redirects it to the cmdhandler system. But there could be other inputs coming from the clients, like button-presses or the request to update a health-bar. While most common cases are already covered, this is where one adds new functions to process new types of input.
- `inputfuncs.py` - When a command like `look` is received by the server, it is handled by an [Inputfunc](InputFuncs) that redirects it to the cmdhandler system. But there could be other inputs coming from the clients, like button-presses or the request to update a health-bar. While most common cases are already covered, this is where one adds new functions to process new types of input.
- `lockfuncs.py` - [Locks](../../../Components/Locks.md) and their component _LockFuncs_ restrict access to things in-game. Lock funcs are used in a mini-language to defined more complex locks. For example you could have a lockfunc that checks if the user is carrying a given item, is bleeding or has a certain skill value. New functions added in this modules will become available for use in lock definitions.
- `mssp.py` - Mud Server Status Protocol is a way for online MUD archives/listings (which you usually have to sign up for) to track which MUDs are currently online, how many players they have etc. While Evennia handles the dynamic information automatically, this is where you set up the meta-info about your game, such as its theme, if player-killing is allowed and so on. This is a more generic form of the Evennia Game directory.
- `portal_services_plugins.py` - If you want to add new external connection protocols to Evennia, this is the place to add them.

View File

@ -16,13 +16,14 @@ import time
import typing
from random import getrandbits
import evennia
from django.conf import settings
from django.contrib.auth import authenticate, password_validation
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.utils import timezone
from django.utils.module_loading import import_string
from django.utils.translation import gettext as _
import evennia
from evennia.accounts.manager import AccountManager
from evennia.accounts.models import AccountDB
from evennia.commands.cmdsethandler import CmdSetHandler
@ -41,7 +42,13 @@ from evennia.typeclasses.attributes import ModelAttributeBackend, NickHandler
from evennia.typeclasses.models import TypeclassBase
from evennia.utils import class_from_module, create, logger
from evennia.utils.optionhandler import OptionHandler
from evennia.utils.utils import is_iter, lazy_property, make_iter, to_str, variable_from_module
from evennia.utils.utils import (
is_iter,
lazy_property,
make_iter,
to_str,
variable_from_module,
)
__all__ = ("DefaultAccount", "DefaultGuest")

View File

@ -555,7 +555,7 @@ class DiscordBot(Bot):
"""
factory_path = "evennia.server.portal.discord.DiscordWebsocketServerFactory"
def _load_channels(self):
self.ndb.ev_channels = {}
if channel_links := self.db.channels:
@ -594,7 +594,9 @@ class DiscordBot(Bot):
if not channel.connect(self):
logger.log_warn(f"{self} could not connect to Evennia channel {channel}.")
if not channel.access(self, "send"):
logger.log_warn(f"{self} doesn't have permission to send messages to Evennia channel {channel}.")
logger.log_warn(
f"{self} doesn't have permission to send messages to Evennia channel {channel}."
)
# these will be made available as properties on the protocol factory
configdict = {"uid": self.dbid}

View File

@ -261,6 +261,7 @@ def _progressive_cmd_run(cmd, generator, response=None):
class NoCmdSets(Exception):
"No cmdsets found. Critical error."
pass

View File

@ -12,6 +12,7 @@ import re
from django.conf import settings
from django.urls import reverse
from django.utils.text import slugify
from evennia.locks.lockhandler import LockHandler
from evennia.utils.ansi import ANSIString
from evennia.utils.evtable import EvTable
@ -623,6 +624,22 @@ Command \"{cmdname}\" has no defined `func()` method. Available properties on th
)[0]
return settings.CLIENT_DEFAULT_WIDTH
def _get_account_option(self, option):
"""
Retrieve the value of a specified account option.
Args:
option (str): The name of the option to retrieve.
Returns:
The value of the specified account option if the account exists,
otherwise the default value from settings.OPTIONS_ACCOUNT_DEFAULT.
"""
if self.account:
return self.account.options.get(option)
return settings.OPTIONS_ACCOUNT_DEFAULT.get(option)
def styled_table(self, *args, **kwargs):
"""
Create an EvTable styled by on user preferences.
@ -638,8 +655,8 @@ Command \"{cmdname}\" has no defined `func()` method. Available properties on th
or incomplete and ready for use with `.add_row` or `.add_collumn`.
"""
border_color = self.account.options.get("border_color")
column_color = self.account.options.get("column_names_color")
border_color = self._get_account_option("border_color")
column_color = self._get_account_option("column_names_color")
colornames = ["|%s%s|n" % (column_color, col) for col in args]
@ -699,9 +716,9 @@ Command \"{cmdname}\" has no defined `func()` method. Available properties on th
"""
colors = dict()
colors["border"] = self.account.options.get("border_color")
colors["headertext"] = self.account.options.get("%s_text_color" % mode)
colors["headerstar"] = self.account.options.get("%s_star_color" % mode)
colors["border"] = self._get_account_option("border_color")
colors["headertext"] = self._get_account_option("%s_text_color" % mode)
colors["headerstar"] = self._get_account_option("%s_star_color" % mode)
width = width or self.client_width()
if edge_character:
@ -722,7 +739,7 @@ Command \"{cmdname}\" has no defined `func()` method. Available properties on th
else:
center_string = ""
fill_character = self.account.options.get("%s_fill" % mode)
fill_character = self._get_account_option("%s_fill" % mode)
remain_fill = width - len(center_string)
if remain_fill % 2 == 0:

View File

@ -22,8 +22,9 @@ method. Otherwise all text will be returned to all connected sessions.
import time
from codecs import lookup as codecs_lookup
import evennia
from django.conf import settings
import evennia
from evennia.utils import create, logger, search, utils
COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)

View File

@ -21,7 +21,6 @@ therefore always be limited to superusers only.
import re
from django.conf import settings
from evennia.commands.cmdset import CmdSet
from evennia.utils import logger, utils
from evennia.utils.batchprocessors import BATCHCMD, BATCHCODE
@ -412,7 +411,7 @@ class CmdStateLL(_COMMAND_DEFAULT_CLASS):
key = "ll"
help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)"
locks = "cmd:perm(batchcommands) or perm(Developer)"
def func(self):
show_curr(self.caller, showall=True)

View File

@ -5,10 +5,11 @@ Building and world design commands
import re
import typing
import evennia
from django.conf import settings
from django.core.paginator import Paginator
from django.db.models import Max, Min, Q
import evennia
from evennia import InterruptCommand
from evennia.commands.cmdhandler import generate_cmdset_providers, get_and_merge_cmdsets
from evennia.locks.lockhandler import LockException
@ -397,7 +398,10 @@ class CmdCopy(ObjManipCommand):
if not from_obj:
return
to_obj_name = "%s_copy" % from_obj_name
to_obj_aliases = ["%s_copy" % alias for alias in from_obj.aliases.all()]
to_obj_aliases = [
(f"{alias}_copy", category)
for alias, category in from_obj.aliases.all(return_key_and_category=True)
]
copiedobj = ObjectDB.objects.copy_object(
from_obj, new_key=to_obj_name, new_aliases=to_obj_aliases
)
@ -1484,7 +1488,7 @@ class CmdName(ObjManipCommand):
obj = None
if self.lhs_objs:
objname = self.lhs_objs[0]["name"]
if objname.startswith("*"):
if objname.startswith("*") and caller.account:
# account mode
obj = caller.account.search(objname.lstrip("*"))
if obj:

View File

@ -5,6 +5,7 @@ General Character commands usually available to all characters
import re
from django.conf import settings
from evennia.objects.objects import DefaultObject
from evennia.typeclasses.attributes import NickTemplateInvalid
from evennia.utils import utils
@ -189,7 +190,8 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
if "clearall" in switches:
caller.nicks.clear()
caller.account.nicks.clear()
if caller.account:
caller.account.nicks.clear()
caller.msg("Cleared all nicks.")
return
@ -789,15 +791,18 @@ class CmdAccess(COMMAND_DEFAULT_CLASS):
hierarchy_full = settings.PERMISSION_HIERARCHY
string = "\n|wPermission Hierarchy|n (climbing):\n %s" % ", ".join(hierarchy_full)
if self.caller.account.is_superuser:
if caller.account and caller.account.is_superuser:
cperms = "<Superuser>"
pperms = "<Superuser>"
else:
cperms = ", ".join(caller.permissions.all())
pperms = ", ".join(caller.account.permissions.all())
if caller.account:
pperms = ", ".join(caller.account.permissions.all())
else:
pperms = "<No account>"
string += "\n|wYour access|n:"
string += f"\nCharacter |c{caller.key}|n: {cperms}"
if utils.inherits_from(caller, DefaultObject):
if utils.inherits_from(caller, DefaultObject) and caller.account:
string += f"\nAccount |c{caller.account.key}|n: {pperms}"
caller.msg(string)

View File

@ -13,6 +13,7 @@ from dataclasses import dataclass
from itertools import chain
from django.conf import settings
from evennia.help.filehelp import FILE_HELP_ENTRIES
from evennia.help.models import HelpEntry
from evennia.help.utils import help_search_with_index, parse_entry_for_subcategories
@ -20,7 +21,13 @@ from evennia.locks.lockhandler import LockException
from evennia.utils import create, evmore
from evennia.utils.ansi import ANSIString
from evennia.utils.eveditor import EvEditor
from evennia.utils.utils import class_from_module, dedent, format_grid, inherits_from, pad
from evennia.utils.utils import (
class_from_module,
dedent,
format_grid,
inherits_from,
pad,
)
CMD_IGNORE_PREFIXES = settings.CMD_IGNORE_PREFIXES
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)

View File

@ -7,15 +7,16 @@ System commands
import code
import datetime
import os
import subprocess
import sys
import time
import traceback
import django
import evennia
import subprocess
import twisted
from django.conf import settings
import evennia
from evennia.accounts.models import AccountDB
from evennia.scripts.taskhandler import TaskHandlerTask
from evennia.utils import gametime, logger, search, utils

View File

@ -14,27 +14,39 @@ main test suite started with
import datetime
from unittest.mock import MagicMock, Mock, patch
import evennia
from anything import Anything
from django.conf import settings
from django.test import override_settings
from parameterized import parameterized
from twisted.internet import task
from evennia.objects.objects import DefaultCharacter, DefaultExit, DefaultObject, DefaultRoom
from evennia.objects.models import ObjectDB
from evennia.utils.search import search_object
import evennia
from evennia.commands import cmdparser
from evennia.commands.cmdset import CmdSet
from evennia.commands.command import Command, InterruptCommand
from evennia.commands.default import account, admin, batchprocess, building, comms, general
from evennia.commands.default import (
account,
admin,
batchprocess,
building,
comms,
general,
)
from evennia.commands.default import help as help_module
from evennia.commands.default import syscommands, system, unloggedin
from evennia.commands.default.cmdset_character import CharacterCmdSet
from evennia.commands.default.muxcommand import MuxCommand
from evennia.objects.models import ObjectDB
from evennia.objects.objects import (
DefaultCharacter,
DefaultExit,
DefaultObject,
DefaultRoom,
)
from evennia.prototypes import prototypes as protlib
from evennia.utils import create, gametime, utils
from evennia.utils.search import search_object
from evennia.utils.test_resources import BaseEvenniaCommandTest # noqa
from parameterized import parameterized
from twisted.internet import task
# ------------------------------------------------------------
# Command testing

View File

@ -4,6 +4,7 @@ Unit testing for the Command system itself.
"""
from django.test import override_settings
from evennia.commands import cmdparser
from evennia.commands.cmdset import CmdSet
from evennia.commands.command import Command
@ -990,9 +991,10 @@ class TestOptionTransferReplace(TestCase):
import sys
from evennia.commands import cmdhandler
from twisted.trial.unittest import TestCase as TwistedTestCase
from evennia.commands import cmdhandler
def _mockdelay(time, func, *args, **kwargs):
return func(*args, **kwargs)

View File

@ -9,9 +9,9 @@ from django.contrib.contenttypes.models import ContentType
from django.urls import reverse
from django.utils.text import slugify
from evennia.objects.objects import DefaultObject
from evennia.comms.managers import ChannelManager
from evennia.comms.models import ChannelDB
from evennia.objects.objects import DefaultObject
from evennia.typeclasses.models import TypeclassBase
from evennia.utils import create, logger
from evennia.utils.utils import inherits_from, make_iter

View File

@ -184,6 +184,7 @@ class Msg(SharedMemoryModel):
class Meta(object):
"Define Django meta options"
verbose_name = "Msg"
@lazy_property
@ -712,6 +713,7 @@ class ChannelDB(TypedObject):
class Meta:
"Define Django meta options"
verbose_name = "Channel"
verbose_name_plural = "Channels"

View File

@ -1,7 +1,7 @@
from django.test import SimpleTestCase
from evennia.comms.comms import DefaultChannel
from evennia.commands.default.comms import CmdChannel
from evennia.comms.comms import DefaultChannel
from evennia.utils.create import create_message
from evennia.utils.test_resources import BaseEvenniaTest

View File

@ -1,4 +1,4 @@
"""Tests for text2bbcode """
"""Tests for text2bbcode"""
import mock
from django.test import TestCase

View File

@ -65,6 +65,7 @@ class GodotWebSocketClient(webclient.WebSocketClient):
def start_plugin_services(portal):
class GodotWebsocket(WebSocketServerFactory):
"Only here for better naming in logs"
pass
factory = GodotWebsocket()

View File

@ -7,13 +7,18 @@ default ones in evennia core.
"""
from evennia.objects.objects import DefaultCharacter, DefaultExit, DefaultObject, DefaultRoom
from evennia.contrib.base_systems.ingame_python.callbackhandler import CallbackHandler
from evennia.contrib.base_systems.ingame_python.utils import (
phrase_event,
register_events,
time_event,
)
from evennia.objects.objects import (
DefaultCharacter,
DefaultExit,
DefaultObject,
DefaultRoom,
)
from evennia.utils.utils import inherits_from, lazy_property
# Character help

View File

@ -7,12 +7,15 @@ These functions are to be used by developers to customize events and callbacks.
from django.conf import settings
from evennia.scripts.models import ScriptDB
from evennia.utils import logger
from evennia.contrib.base_systems.custom_gametime import UNITS, gametime_to_realtime
from evennia.contrib.base_systems.custom_gametime import (
UNITS,
gametime_to_realtime,
)
from evennia.contrib.base_systems.custom_gametime import (
real_seconds_until as custom_rsu,
)
from evennia.scripts.models import ScriptDB
from evennia.utils import logger
from evennia.utils.create import create_script
from evennia.utils.gametime import real_seconds_until as standard_rsu
from evennia.utils.utils import class_from_module

View File

@ -32,6 +32,7 @@ The contrib can be further configured through two settings, `INGAME_REPORT_TYPES
"""
from django.conf import settings
from evennia import CmdSet
from evennia.commands.default.muxcommand import MuxCommand
from evennia.comms.models import Msg

View File

@ -1,6 +1,7 @@
from unittest.mock import Mock, patch, MagicMock
from evennia.utils import create
from unittest.mock import MagicMock, Mock, patch
from evennia.comms.models import TempMsg
from evennia.utils import create
from evennia.utils.test_resources import EvenniaCommandTest
from . import menu, reports

View File

@ -232,6 +232,7 @@ class MenuLoginEvMenu(EvMenu):
class UnloggedinCmdSet(CmdSet):
"Cmdset for the unloggedin state"
key = "DefaultUnloggedin"
priority = 0

View File

@ -29,8 +29,9 @@ Admin/development commands
import re
import evennia
from django.conf import settings
import evennia
from evennia import default_cmds, syscmdkeys
from evennia.commands.cmdset import CmdSet
from evennia.commands.command import Command, InterruptCommand

View File

@ -23,7 +23,7 @@ The recognized fields for an achievement are:
- name (str): The name of the achievement. This is not the key and does not need to be unique.
- desc (str): The longer description of the achievement. Common uses for this would be flavor text
or hints on how to complete it.
- category (str): The category of conditions which this achievement tracks. It will most likely be
- category (str): The category of conditions which this achievement tracks. It will most likely be
an action and you will most likely specify it based on where you're checking from.
e.g. killing 10 rats might have a category of "defeat", which you'd then check from your code
that runs when a player defeats something.

View File

@ -85,7 +85,9 @@ class TestClothingCmd(BaseEvenniaCommandTest):
)
# Test remove command.
self.call(clothing.CmdRemove(), "", "Usage: remove <worn clothing object>", caller=self.wearer)
self.call(
clothing.CmdRemove(), "", "Usage: remove <worn clothing object>", caller=self.wearer
)
self.call(
clothing.CmdRemove(),
"hat",

View File

@ -11,7 +11,7 @@ To install, import and add the `ContainerCmdSet` to `CharacterCmdSet` in your `d
class CharacterCmdSet(default_cmds.CharacterCmdSet):
# ...
def at_cmdset_creation(self):
# ...
self.add(ContainerCmdSet)

View File

@ -41,6 +41,7 @@ _RE_KEYS = re.compile(r"([\w\s]+)(?:\+*?)", re.U + re.I)
class DescValidateError(ValueError):
"Used for tracebacks from desc systems"
pass

View File

@ -1,7 +1,7 @@
from evennia import CmdSet
from evennia.commands.default.muxcommand import MuxCommand
from evennia.utils import list_to_string
from evennia.utils.search import search_object_by_tag
from evennia.commands.default.muxcommand import MuxCommand
SHARED_TAG_PREFIX = "shared"

View File

@ -33,7 +33,7 @@ class TestIngameMap(BaseEvenniaCommandTest):
create_object(
exits.Exit,
key="shopfront",
aliases=["w","west"],
aliases=["w", "west"],
location=self.east_room,
destination=self.west_room,
)

View File

@ -1,7 +1,7 @@
"""
Buffs - Tegiminis 2022
A buff is a timed object, attached to a game entity, that modifies values, triggers
A buff is a timed object, attached to a game entity, that modifies values, triggers
code, or both. It is a common design pattern in RPGs, particularly action games.
This contrib gives you a buff handler to apply to your objects, a buff class to extend them,
@ -25,7 +25,7 @@ To make use of the handler, you will need:
### Applying a Buff
Call the handler `add(BuffClass)` method. This requires a class reference, and also contains a number of
Call the handler `add(BuffClass)` method. This requires a class reference, and also contains a number of
optional arguments to customize the buff's duration, stacks, and so on.
```python
@ -36,8 +36,8 @@ self.buffs.add(ReflectBuff, to_cache={'reflect': 0.5}) # A single stack of Refl
### Modify
Call the handler `check(value, stat)` method wherever you want to see the modified value.
This will return the value, modified by and relevant buffs on the handler's owner (identified by
Call the handler `check(value, stat)` method wherever you want to see the modified value.
This will return the value, modified by and relevant buffs on the handler's owner (identified by
the `stat` string). For example:
```python
@ -49,7 +49,7 @@ def take_damage(self, source, damage):
### Trigger
Call the handler `trigger(triggerstring)` method wherever you want an event call. This
Call the handler `trigger(triggerstring)` method wherever you want an event call. This
will call the `at_trigger` hook method on all buffs with the relevant trigger.
```python

View File

@ -9,9 +9,10 @@ Unit test module for Trait classes.
from copy import copy
from anything import Something
from mock import MagicMock, patch
from evennia.objects.objects import DefaultCharacter
from evennia.utils.test_resources import BaseEvenniaTestCase, EvenniaTest
from mock import MagicMock, patch
from . import traits

View File

@ -456,9 +456,15 @@ from functools import total_ordering
from time import time
from django.conf import settings
from evennia.utils import logger
from evennia.utils.dbserialize import _SaverDict
from evennia.utils.utils import class_from_module, inherits_from, list_to_string, percent
from evennia.utils.utils import (
class_from_module,
inherits_from,
list_to_string,
percent,
)
# Available Trait classes.
# This way the user can easily supply their own. Each

View File

@ -123,6 +123,7 @@ class CmdTalk(default_cmds.MuxCommand):
class TalkingCmdSet(CmdSet):
"Stores the talk command."
key = "talkingcmdset"
def at_cmdset_creation(self):

View File

@ -109,6 +109,7 @@ class HelpEntry(SharedMemoryModel):
class Meta:
"Define Django meta options"
verbose_name = "Help Entry"
verbose_name_plural = "Help Entries"

View File

@ -5,6 +5,7 @@ command test-suite).
"""
from unittest import mock
from parameterized import parameterized
from evennia.help import filehelp

View File

@ -11,7 +11,6 @@ import re
from django.conf import settings
from lunr.stemmer import stemmer
_RE_HELP_SUBTOPICS_START = re.compile(r"^\s*?#\s*?subtopics\s*?$", re.I + re.M)
_RE_HELP_SUBTOPIC_SPLIT = re.compile(r"^\s*?(\#{2,6}\s*?\w+?[a-z0-9 \-\?!,\.]*?)$", re.M + re.I)
_RE_HELP_SUBTOPIC_PARSE = re.compile(r"^(?P<nesting>\#{2,6})\s*?(?P<name>.*?)$", re.I + re.M)
@ -80,12 +79,10 @@ class LunrSearch:
before twisted's logging has been set up
"""
# Lunr-related imports
from lunr import get_default_builder
from lunr import lunr
from lunr import stop_word_filter
from lunr import get_default_builder, lunr, stop_word_filter
from lunr.exceptions import QueryParseError
from lunr.stemmer import stemmer
from lunr.pipeline import Pipeline
from lunr.stemmer import stemmer
# Store imported modules as instance attributes
self.get_default_builder = get_default_builder

File diff suppressed because it is too large Load Diff

View File

@ -322,7 +322,7 @@ class ObjectDBManager(TypedObjectManager):
)
# convert search term to partial-match regex
search_regex = r".* ".join(r"\b" + re.escape(word) for word in ostring.split()) + r'.*'
search_regex = r".* ".join(r"\b" + re.escape(word) for word in ostring.split()) + r".*"
# do the fuzzy search and return whatever it matches
return (

View File

@ -20,6 +20,7 @@ from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.core.validators import validate_comma_separated_integer_list
from django.db import models
from evennia.objects.manager import ObjectDBManager
from evennia.typeclasses.models import TypedObject
from evennia.utils import logger

View File

@ -10,10 +10,11 @@ import time
import typing
from collections import defaultdict
import evennia
import inflect
from django.conf import settings
from django.utils.translation import gettext as _
import evennia
from evennia.commands import cmdset
from evennia.commands.cmdsethandler import CmdSetHandler
from evennia.objects.manager import ObjectManager
@ -1382,13 +1383,13 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
if obj.has_account:
if home:
string = "Your current location has ceased to exist,"
string += " moving you to (#{dbid})."
obj.msg(_(string).format(dbid=home.dbid))
string = _(
"Your current location has ceased to exist, moving you to (#{dbid})."
)
obj.msg(string.format(dbid=home.dbid))
else:
# Famous last words: The account should never see this.
string = "This place should not exist ... contact an admin."
obj.msg(_(string))
obj.msg(_("This place should not exist ... contact an admin."))
obj.move_to(home, move_type="teleport")
@classmethod
@ -1793,9 +1794,9 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
exits = self.filter_visible(self.contents_get(content_type="exit"), looker, **kwargs)
exit_names = (exi.get_display_name(looker, **kwargs) for exi in exits)
exit_names = iter_to_str(_sort_exit_names(exit_names))
return f"|wExits:|n {exit_names}" if exit_names else ""
exit_names = iter_to_str(_sort_exit_names(exit_names), endsep=_(", and"))
e = _("Exits")
return f"|w{e}:|n {exit_names}" if exit_names else ""
def get_display_characters(self, looker, **kwargs):
"""
@ -1812,10 +1813,10 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
self.contents_get(content_type="character"), looker, **kwargs
)
character_names = iter_to_str(
char.get_display_name(looker, **kwargs) for char in characters
(char.get_display_name(looker, **kwargs) for char in characters), endsep=_(", and")
)
return f"|wCharacters:|n {character_names}" if character_names else ""
c = _("Characters")
return f"|w{c}:|n {character_names}" if character_names else ""
def get_display_things(self, looker, **kwargs):
"""
@ -1841,8 +1842,9 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
thing = thinglist[0]
singular, plural = thing.get_numbered_name(nthings, looker, key=thingname)
thing_names.append(singular if nthings == 1 else plural)
thing_names = iter_to_str(thing_names)
return f"|wYou see:|n {thing_names}" if thing_names else ""
thing_names = iter_to_str(thing_names, endsep=_(", and"))
s = _("You see")
return f"|w{s}:|n {thing_names}" if thing_names else ""
def get_display_footer(self, looker, **kwargs):
"""
@ -2141,7 +2143,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
puppeting this Object.
"""
self.msg(f"You become |w{self.key}|n.")
self.msg(_("You become |w{key}|n.").format(key=self.key))
self.account.db._last_puppet = self
def at_pre_unpuppet(self, **kwargs):
@ -2320,7 +2322,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
if msg:
string = msg
else:
string = "{object} is leaving {origin}, heading for {destination}."
string = _("{object} is leaving {origin}, heading for {destination}.")
location = self.location
exits = [
@ -2332,9 +2334,9 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
mapping.update(
{
"object": self,
"exit": exits[0] if exits else "somewhere",
"origin": location or "nowhere",
"destination": destination or "nowhere",
"exit": exits[0] if exits else _("somewhere"),
"origin": location or _("nowhere"),
"destination": destination or _("nowhere"),
}
)
@ -2405,9 +2407,9 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
mapping.update(
{
"object": self,
"exit": exits[0] if exits else "somewhere",
"origin": origin or "nowhere",
"destination": destination or "nowhere",
"exit": exits[0] if exits else _("somewhere"),
"origin": origin or _("nowhere"),
"destination": destination or _("nowhere"),
}
)
@ -2671,9 +2673,11 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
"""
if not target.access(self, "view"):
try:
return "Could not view '%s'." % target.get_display_name(self, **kwargs)
return _("Could not view '{target_name}'.").format(
target_name=target.get_display_name(self, **kwargs)
)
except AttributeError:
return "Could not view '%s'." % target.key
return _("Could not view '{target_name}'.").format(target_name=target.key)
description = target.return_appearance(self, **kwargs)
@ -2798,7 +2802,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
# TODO: This if-statment will be removed in Evennia 1.0
return True
if not self.access(dropper, "drop", default=False):
dropper.msg(f"You cannot drop {self.get_display_name(dropper)}")
dropper.msg(_("You cannot drop {obj}").format(obj=self.get_display_name(dropper)))
return False
return True
@ -2910,15 +2914,15 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
# whisper mode
msg_type = "whisper"
msg_self = (
'{self} whisper to {all_receivers}, "|n{speech}|n"'
_('{self} whisper to {all_receivers}, "|n{speech}|n"')
if msg_self is True
else msg_self
)
msg_receivers = msg_receivers or '{object} whispers: "|n{speech}|n"'
msg_receivers = msg_receivers or _('{object} whispers: "|n{speech}|n"')
msg_location = None
else:
msg_self = '{self} say, "|n{speech}|n"' if msg_self is True else msg_self
msg_location = msg_location or '{object} says, "{speech}"'
msg_self = _('{self} say, "|n{speech}|n"') if msg_self is True else msg_self
msg_location = msg_location or _('{object} says, "{speech}"')
msg_receivers = msg_receivers or message
custom_mapping = kwargs.get("mapping", {})
@ -2927,7 +2931,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
if msg_self:
self_mapping = {
"self": "You",
"self": _("You"),
"object": self.get_display_name(self),
"location": location.get_display_name(self) if location else None,
"receiver": None,
@ -2943,7 +2947,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
if receivers and msg_receivers:
receiver_mapping = {
"self": "You",
"self": _("You"),
"object": None,
"location": None,
"receiver": None,
@ -2970,7 +2974,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
if self.location and msg_location:
location_mapping = {
"self": "You",
"self": _("You"),
"object": self,
"location": location,
"all_receivers": ", ".join(str(recv) for recv in receivers) if receivers else None,
@ -3195,7 +3199,7 @@ class DefaultCharacter(DefaultObject):
"""
if account and cls.objects.filter_family(db_key__iexact=name):
return f"|rA character named '|w{name}|r' already exists.|n"
return _("|rA character named '|w{name}|r' already exists.|n").format(name=name)
def basetype_setup(self):
"""
@ -3499,7 +3503,9 @@ class ExitCommand(_COMMAND_DEFAULT_CLASS):
"""
if self.obj.destination:
return " (exit to %s)" % self.obj.destination.get_display_name(caller, **kwargs)
return _(" (exit to {destination})").format(
destination=self.obj.destination.get_display_name(caller, **kwargs)
)
else:
return " (%s)" % self.obj.get_display_name(caller, **kwargs)

View File

@ -1,7 +1,12 @@
from unittest import skip
from evennia.objects.objects import DefaultCharacter, DefaultExit, DefaultObject, DefaultRoom
from evennia.objects.models import ObjectDB
from evennia.objects.objects import (
DefaultCharacter,
DefaultExit,
DefaultObject,
DefaultRoom,
)
from evennia.typeclasses.attributes import AttributeProperty
from evennia.typeclasses.tags import (
AliasProperty,

View File

@ -12,6 +12,7 @@ from django.conf import settings
from django.core.paginator import Paginator
from django.db.models import Q
from django.utils.translation import gettext as _
from evennia.locks.lockhandler import check_lockstring, validate_lockstring
from evennia.objects.models import ObjectDB
from evennia.scripts.scripts import DefaultScript

View File

@ -126,6 +126,7 @@ class ScriptDB(TypedObject):
class Meta(object):
"Define Django meta options"
verbose_name = "Script"
#

View File

@ -7,6 +7,7 @@ added to all game objects. You access it through the property
"""
from django.utils.translation import gettext as _
from evennia.scripts.models import ScriptDB
from evennia.utils import create, logger

View File

@ -6,12 +6,13 @@ ability to run timers.
"""
from django.utils.translation import gettext as _
from twisted.internet.defer import Deferred, maybeDeferred
from twisted.internet.task import LoopingCall
from evennia.scripts.manager import ScriptManager
from evennia.scripts.models import ScriptDB
from evennia.typeclasses.models import TypeclassBase
from evennia.utils import create, logger
from twisted.internet.defer import Deferred, maybeDeferred
from twisted.internet.task import LoopingCall
__all__ = ["DefaultScript", "DoNothing", "Store"]

View File

@ -5,13 +5,14 @@ Module containing the task handler for Evennia deferred tasks, persistent or not
from datetime import datetime, timedelta
from pickle import PickleError
from evennia.server.models import ServerConfig
from evennia.utils.dbserialize import dbserialize, dbunserialize
from evennia.utils.logger import log_err
from twisted.internet import reactor
from twisted.internet.defer import CancelledError as DefCancelledError
from twisted.internet.task import deferLater
from evennia.server.models import ServerConfig
from evennia.utils.dbserialize import dbserialize, dbunserialize
from evennia.utils.logger import log_err
TASK_HANDLER = None

View File

@ -6,6 +6,8 @@ Unit tests for the scripts package
from collections import defaultdict
from unittest import TestCase, mock
from parameterized import parameterized
from evennia import DefaultScript
from evennia.objects.objects import DefaultObject
from evennia.scripts.manager import ScriptDBManager
@ -17,7 +19,6 @@ from evennia.scripts.tickerhandler import TickerHandler
from evennia.utils.create import create_script
from evennia.utils.dbserialize import dbserialize
from evennia.utils.test_resources import BaseEvenniaTest, EvenniaTest
from parameterized import parameterized
class TestScript(BaseEvenniaTest):
@ -45,6 +46,20 @@ class TestTickerHandler(TestCase):
th = TickerHandler()
th.remove(callback=1)
def test_removing_ticker_using_store_key_in_attribute(self):
"""
Test adding a ticker, storing the store_key in an attribute, and then removing it
using that same store_key.
https://github.com/evennia/evennia/pull/3765
"""
obj = DefaultObject.create("test_object")[0]
th = TickerHandler()
obj.db.ticker = th.add(60, obj.msg, idstring="ticker_test", persistent=True)
self.assertTrue(len(th.all()), 1)
th.remove(store_key=obj.db.ticker)
self.assertTrue(len(th.all()), 0)
class TestScriptDBManager(TestCase):
"""Test the ScriptDBManger class"""

View File

@ -564,6 +564,14 @@ class TickerHandler(object):
if not store_key:
obj, path, callfunc = self._get_callback(callback)
store_key = self._store_key(obj, path, interval, callfunc, idstring, persistent)
else:
if isinstance(store_key, tuple) and not isinstance(store_key[0], tuple):
# this means the store-key was deserialized, which means we need to
# re-build the key anew (since the obj would already be unpacked otherwise)
obj, path, callfunc = self._get_callback(getattr(store_key[0], store_key[1]))
store_key = self._store_key(
obj, path, store_key[3], callfunc, store_key[4], store_key[5]
)
to_remove = self.ticker_storage.pop(store_key, None)
if to_remove:
self.ticker_pool.remove(store_key)

View File

@ -24,6 +24,7 @@ import importlib
from codecs import lookup as codecs_lookup
from django.conf import settings
from evennia.accounts.models import AccountDB
from evennia.commands.cmdhandler import cmdhandler
from evennia.utils.logger import log_err

View File

@ -110,6 +110,7 @@ class ServerConfig(WeakSharedMemoryModel):
class Meta:
"Define Django meta options"
verbose_name = "Server Config value"
verbose_name_plural = "Server Config values"

View File

@ -12,6 +12,7 @@ active players and so on.
"""
import weakref
from django.conf import settings
from evennia.utils import utils

View File

@ -25,7 +25,7 @@ URL_SUB = re.compile(r"\|lu(.*?)\|lt(.*?)\|le", re.DOTALL)
# MXP Telnet option
MXP = bytes([91]) # b"\x5b"
MXP_TEMPSECURE = "\x1B[4z"
MXP_TEMPSECURE = "\x1b[4z"
MXP_SEND = MXP_TEMPSECURE + '<SEND HREF="\\1">' + "\\2" + MXP_TEMPSECURE + "</SEND>"
MXP_URL = MXP_TEMPSECURE + '<A HREF="\\1">' + "\\2" + MXP_TEMPSECURE + "</A>"

View File

@ -10,8 +10,8 @@ client and update it when the size changes
"""
from codecs import encode as codecs_encode
import weakref
from codecs import encode as codecs_encode
from django.conf import settings
@ -86,4 +86,6 @@ class Naws:
width = options[0] + options[1]
self.protocol().protocol_flags["SCREENWIDTH"][0] = int(codecs_encode(width, "hex"), 16)
height = options[2] + options[3]
self.protocol().protocol_flags["SCREENHEIGHT"][0] = int(codecs_encode(height, "hex"), 16)
self.protocol().protocol_flags["SCREENHEIGHT"][0] = int(
codecs_encode(height, "hex"), 16
)

View File

@ -223,6 +223,7 @@ class EvenniaPortalService(MultiService):
class Websocket(WebSocketServerFactory):
"Only here for better naming in logs"
pass
factory = Websocket()

View File

@ -41,9 +41,9 @@ class SuppressGA:
self.protocol = weakref.ref(protocol)
self.protocol().protocol_flags["NOGOAHEAD"] = True
self.protocol().protocol_flags["NOPROMPTGOAHEAD"] = (
True # Used to send a GA after a prompt line only, set in TTYPE (per client)
)
self.protocol().protocol_flags[
"NOPROMPTGOAHEAD"
] = True # Used to send a GA after a prompt line only, set in TTYPE (per client)
# tell the client that we prefer to suppress GA ...
self.protocol().will(SUPPRESS_GA).addCallbacks(self.will_suppress_ga, self.wont_suppress_ga)

View File

@ -8,19 +8,20 @@ import time
import traceback
import django
import evennia
from django.conf import settings
from django.db import connection
from django.db.utils import OperationalError
from django.utils.translation import gettext as _
from evennia.utils import logger
from evennia.utils.utils import get_evennia_version, make_iter, mod_import
from twisted.application import internet
from twisted.application.service import MultiService
from twisted.internet import defer, reactor
from twisted.internet.defer import Deferred
from twisted.internet.task import LoopingCall
import evennia
from evennia.utils import logger
from evennia.utils.utils import get_evennia_version, make_iter, mod_import
_SA = object.__setattr__

View File

@ -17,6 +17,7 @@ from copy import copy
from django.conf import settings
from django.db import models
from django.utils.encoding import smart_str
from evennia.locks.lockhandler import LockHandler
from evennia.utils.dbserialize import from_pickle, to_pickle
from evennia.utils.idmapper.models import SharedMemoryModel
@ -62,7 +63,7 @@ class IAttribute:
return LockHandler(self)
key = property(lambda self: self.db_key)
strvalue = property(lambda self: getattr(self, 'db_strvalue', None))
strvalue = property(lambda self: getattr(self, "db_strvalue", None))
category = property(lambda self: self.db_category)
model = property(lambda self: self.db_model)
attrtype = property(lambda self: self.db_attrtype)
@ -411,6 +412,7 @@ class Attribute(IAttribute, SharedMemoryModel):
class Meta:
"Define Django meta options"
verbose_name = "Attribute"
# Wrapper properties to easily set database fields. These are

View File

@ -36,6 +36,7 @@ from django.urls import reverse
from django.utils import timezone
from django.utils.encoding import smart_str
from django.utils.text import slugify
from django.utils.translation import gettext as _
import evennia
from evennia.locks.lockhandler import LockHandler
@ -347,12 +348,21 @@ class TypedObject(SharedMemoryModel):
Called by creation methods; makes sure to initialize Attribute/TagProperties
by fetching them once.
"""
for propkey, prop in self.__class__.__dict__.items():
if isinstance(prop, (AttributeProperty, TagProperty, TagCategoryProperty)):
try:
getattr(self, propkey)
except Exception:
log_trace()
evennia_properties = set()
for base in type(self).__mro__:
evennia_properties.update(
{
propkey
for propkey, prop in vars(base).items()
if isinstance(prop, (AttributeProperty, TagProperty, TagCategoryProperty))
}
)
for propkey in evennia_properties:
try:
getattr(self, propkey)
except Exception:
log_trace()
# initialize all handlers in a lazy fashion
@lazy_property
@ -883,7 +893,7 @@ class TypedObject(SharedMemoryModel):
"""
if self.location == looker:
return " (carried)"
return _(" (carried)")
return ""
def at_rename(self, oldname, newname):

View File

@ -14,6 +14,7 @@ from collections import defaultdict
from django.conf import settings
from django.db import models
from evennia.locks.lockfuncs import perm as perm_lockfunc
from evennia.utils.utils import make_iter, to_str

View File

@ -67,6 +67,7 @@ import re
from collections import OrderedDict
from django.conf import settings
from evennia.utils import logger, utils
from evennia.utils.hex_colors import HexColors
from evennia.utils.utils import to_str

View File

@ -249,7 +249,7 @@ class GlobalScriptContainer(Container):
"""
if not self.loaded:
self.load_data()
managed_scripts = list(self.loaded_data.values())
managed_scripts = [self._load_script(key) for key in self.typeclass_storage.keys()]
unmanaged_scripts = list(
ScriptDB.objects.filter(db_obj__isnull=True).exclude(
id__in=[scr.id for scr in managed_scripts]

View File

@ -28,6 +28,8 @@ try:
except ImportError:
from pickle import dumps, loads
from enum import IntFlag
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.utils.safestring import SafeString
@ -35,7 +37,6 @@ from django.utils.safestring import SafeString
import evennia
from evennia.utils import logger
from evennia.utils.utils import is_iter, to_bytes, uses_database
from enum import IntFlag
__all__ = ("to_pickle", "from_pickle", "do_pickle", "do_unpickle", "dbserialize", "dbunserialize")

View File

@ -1421,14 +1421,20 @@ def list_node(option_generator, select=None, pagesize=10):
options.append(
{
"key": (_("|wp|Wrevious page|n"), "p"),
"goto": (lambda caller: None, kwargs | {"optionpage_index": page_index - 1}),
"goto": (
lambda caller: None,
kwargs | {"optionpage_index": page_index - 1},
),
}
)
if page_index < npages - 1:
options.append(
{
"key": (_("|wn|Wext page|n"), "n"),
"goto": (lambda caller: None, kwargs | {"optionpage_index": page_index + 1}),
"goto": (
lambda caller: None,
kwargs | {"optionpage_index": page_index + 1},
),
}
)

View File

@ -79,7 +79,7 @@ class CmdMore(Command):
Implement the command
"""
more = self.caller.ndb._more
if not more and hasattr(self.caller, 'account') and self.caller.account:
if not more and hasattr(self.caller, "account") and self.caller.account:
more = self.caller.account.ndb._more
if not more:
self.caller.msg("Error in loading the pager. Contact an admin.")
@ -113,7 +113,7 @@ class CmdMoreExit(Command):
Exit pager and re-fire the failed command.
"""
more = self.caller.ndb._more
if not more and hasattr(self.caller, 'account') and self.caller.account:
if not more and hasattr(self.caller, "account") and self.caller.account:
more = self.caller.account.ndb._more
if not more:
self.caller.msg("Error in exiting the pager. Contact an admin.")

View File

@ -8,12 +8,12 @@ total runtime of the server and the current uptime.
"""
import time
import evennia
from datetime import datetime, timedelta
from django.conf import settings
from django.db.utils import OperationalError
import evennia
from evennia.scripts.scripts import DefaultScript
from evennia.server.models import ServerConfig
from evennia.utils.create import create_script

View File

@ -1,4 +1,4 @@
"""Tests for batchprocessors """
"""Tests for batchprocessors"""
import codecs
import textwrap

View File

@ -2,7 +2,6 @@ import unittest
from django.conf import settings
from django.test import override_settings
from evennia import DefaultScript
from evennia.utils import containers
from evennia.utils.utils import class_from_module
@ -10,15 +9,16 @@ from evennia.utils.utils import class_from_module
_BASE_TYPECLASS = class_from_module(settings.BASE_SCRIPT_TYPECLASS)
class GoodScript(DefaultScript):
class UnittestGoodScript(DefaultScript):
pass
class InvalidScript:
class UnittestInvalidScript:
pass
class TestGlobalScriptContainer(unittest.TestCase):
def test_init_with_no_scripts(self):
gsc = containers.GlobalScriptContainer()
@ -60,7 +60,7 @@ class TestGlobalScriptContainer(unittest.TestCase):
@override_settings(
GLOBAL_SCRIPTS={
"script_name": {"typeclass": "evennia.utils.tests.test_containers.GoodScript"}
"script_name": {"typeclass": "evennia.utils.tests.test_containers.UnittestGoodScript"}
}
)
def test_start_with_valid_script(self):
@ -70,11 +70,13 @@ class TestGlobalScriptContainer(unittest.TestCase):
self.assertEqual(len(gsc.typeclass_storage), 1)
self.assertIn("script_name", gsc.typeclass_storage)
self.assertEqual(gsc.typeclass_storage["script_name"], GoodScript)
self.assertEqual(gsc.typeclass_storage["script_name"], UnittestGoodScript)
@override_settings(
GLOBAL_SCRIPTS={
"script_name": {"typeclass": "evennia.utils.tests.test_containers.InvalidScript"}
"script_name": {
"typeclass": "evennia.utils.tests.test_containers.UnittestInvalidScript"
}
}
)
def test_start_with_invalid_script(self):
@ -85,7 +87,7 @@ class TestGlobalScriptContainer(unittest.TestCase):
gsc.start()
# check for general attribute failure on the invalid class to preserve against future code-rder changes
self.assertTrue(
str(err.exception).startswith("type object 'InvalidScript' has no attribute"),
str(err.exception).startswith("type object 'UnittestInvalidScript' has no attribute"),
err.exception,
)
@ -105,3 +107,24 @@ class TestGlobalScriptContainer(unittest.TestCase):
str(err.exception).startswith("cannot import name 'nonexistent_module' from 'evennia'"),
err.exception,
)
@override_settings(
GLOBAL_SCRIPTS={
"script_name": {"typeclass": "evennia.utils.tests.test_containers.UnittestGoodScript"}
}
)
def test_using_global_script__all(self):
"""
Using the GlobalScriptContainer.all() to get all scripts
Tests https://github.com/evennia/evennia/issues/3788
"""
from evennia.scripts.models import ScriptDB
ScriptDB.objects.all().delete() # clean up any old scripts
gsc = containers.GlobalScriptContainer()
script = gsc.get("script_name")
result = gsc.all()
self.assertEqual(result, [script])

View File

@ -3,13 +3,13 @@ Tests for dbserialize module
"""
from collections import defaultdict, deque
from enum import IntFlag, auto
from django.test import TestCase
from parameterized import parameterized
from evennia.objects.objects import DefaultObject
from evennia.utils import dbserialize
from enum import IntFlag, auto
class TestDbSerialize(TestCase):
@ -24,6 +24,7 @@ class TestDbSerialize(TestCase):
def test_intflag(self):
class TestFlag(IntFlag):
foo = auto()
self.obj.db.test = TestFlag.foo
self.assertEqual(self.obj.db.test, TestFlag.foo)
self.obj.save()

View File

@ -29,6 +29,7 @@ from evennia.utils.test_resources import BaseEvenniaTest
class TestEvMenu(TestCase):
"Run the EvMenu testing."
menutree = {} # can also be the path to the menu tree
startnode = "start"
cmdset_mergetype = "Replace"

View File

@ -1,4 +1,4 @@
"""Tests for text2html """
"""Tests for text2html"""
import unittest

View File

@ -821,13 +821,13 @@ class TestAtSearchResult(TestCase):
class MockObject:
def __init__(self, key):
self.key = key
self.aliases = ''
self.aliases = ""
def get_display_name(self, looker, **kwargs):
return self.key
def get_extra_info(self, looker, **kwargs):
return ''
return ""
def __repr__(self):
return f"MockObject({self.key})"
@ -846,7 +846,7 @@ class TestAtSearchResult(TestCase):
def test_basic_multimatch(self):
"""multiple matches with the same name should return a message with incrementing indices"""
matches = [ self.MockObject("obj1") for _ in range(3) ]
matches = [self.MockObject("obj1") for _ in range(3)]
caller = mock.MagicMock()
self.assertIsNone(utils.at_search_result(matches, caller, "obj1"))
multimatch_msg = """\
@ -858,7 +858,9 @@ More than one match for 'obj1' (please narrow target):
def test_partial_multimatch(self):
"""multiple partial matches with different names should increment index by unique name"""
matches = [ self.MockObject("obj1") for _ in range(3) ] + [ self.MockObject("obj2") for _ in range(2) ]
matches = [self.MockObject("obj1") for _ in range(3)] + [
self.MockObject("obj2") for _ in range(2)
]
caller = mock.MagicMock()
self.assertIsNone(utils.at_search_result(matches, caller, "obj"))
multimatch_msg = """\

View File

@ -1,4 +1,4 @@
"""Tests for validatorfuncs """
"""Tests for validatorfuncs"""
import datetime

View File

@ -494,7 +494,7 @@ def compress_whitespace(text, max_linebreaks=1, max_spacing=2):
# this allows the blank-line compression to eliminate them if needed
text = re_empty.sub("\n\n", text)
# replace groups of extra spaces with the maximum number of spaces
text = re.sub(fr"(?<=\S) {{{max_spacing},}}", " " * max_spacing, text)
text = re.sub(rf"(?<=\S) {{{max_spacing},}}", " " * max_spacing, text)
# replace groups of extra newlines with the maximum number of newlines
text = re.sub(f"\n{{{max_linebreaks},}}", "\n" * max_linebreaks, text)
return text
@ -2401,10 +2401,8 @@ def at_search_result(matches, caller, query="", quiet=False, **kwargs):
grouped_matches = defaultdict(list)
for item in matches:
group_key = (
item.get_display_name(caller)
if hasattr(item, "get_display_name")
else query
)
item.get_display_name(caller) if hasattr(item, "get_display_name") else query
)
grouped_matches[group_key].append(item)
for key, match_list in grouped_matches.items():
@ -2415,7 +2413,9 @@ def at_search_result(matches, caller, query="", quiet=False, **kwargs):
# result is a typeclassed entity where `.aliases` is an AliasHandler.
aliases = result.aliases.all(return_objs=True)
# remove pluralization aliases
aliases = [alias.db_key for alias in aliases if alias.db_category != "plural_key"]
aliases = [
alias.db_key for alias in aliases if alias.db_category != "plural_key"
]
else:
# result is likely a Command, where `.aliases` is a list of strings.
aliases = result.aliases

View File

@ -39,6 +39,7 @@ class HelpEntryForm(forms.ModelForm):
@admin.register(HelpEntry)
class HelpEntryAdmin(admin.ModelAdmin):
"Sets up the admin manaager for help entries"
inlines = [HelpTagInline]
list_display = ("id", "db_key", "db_help_category", "db_lock_storage", "db_date_created")
list_display_links = ("id", "db_key")

View File

@ -170,3 +170,20 @@ omit = [
"*.pyc",
"*.service",
]
[tool.ruff]
exclude = [
"/.eggs/",
"/.git/",
"/.hg/",
"/.mypy_cache/",
"/.tox/",
"/.venv/",
"/_build/",
"/buck-out/",
"/build/",
"/dist/",
"migrations",
"docs",
]
line-length = 100