mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Resolve merge conflict
This commit is contained in:
commit
1153433a2c
55 changed files with 590 additions and 348 deletions
|
|
@ -40,7 +40,7 @@ SERVERNAME = "testing_mygame"
|
|||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql_psycopg2",
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"NAME": "evennia",
|
||||
"USER": "evennia",
|
||||
"PASSWORD": "password",
|
||||
|
|
|
|||
|
|
@ -48,6 +48,10 @@ without arguments starts a full interactive Python console.
|
|||
`.get_command_info()` method for easier overloading and access. (Volund)
|
||||
- Removed unused `CYCLE_LOGFILES` setting. Added `SERVER_LOG_DAY_ROTATION`
|
||||
and `SERVER_LOG_MAX_SIZE` (and equivalent for PORTAL) to control log rotation.
|
||||
- Addded `inside_rec` lockfunc - if room is locked, the normal `inside()` lockfunc will
|
||||
fail e.g. for your inventory objs (since their loc is you), whereas this will pass.
|
||||
- RPSystem contrib's CmdRecog will now list all recogs if no arg is given. Also multiple
|
||||
bugfixes.
|
||||
|
||||
|
||||
## Evennia 0.9 (2018-2019)
|
||||
|
|
|
|||
136
INSTALL.md
136
INSTALL.md
|
|
@ -1,137 +1,5 @@
|
|||
|
||||
# Evennia installation
|
||||
|
||||
The latest and more detailed installation instructions can be found
|
||||
[here](https://github.com/evennia/evennia/wiki/Getting-Started).
|
||||
|
||||
## Installing Python
|
||||
|
||||
First install [Python](https://www.python.org/). Linux users should
|
||||
have it in their repositories, Windows/Mac users can get it from the
|
||||
Python homepage. You need the 2.7.x version (Python 3 is not yet
|
||||
supported). Windows users, make sure to select the option to make
|
||||
Python available in your path - this is so you can call it everywhere
|
||||
as `python`. Python 2.7.9 and later also includes the
|
||||
[pip](https://pypi.python.org/pypi/pip/) installer out of the box,
|
||||
otherwise install this separately (in linux it's usually found as the
|
||||
`python-pip` package).
|
||||
|
||||
### installing virtualenv
|
||||
|
||||
This step is optional, but *highly* recommended. For installing
|
||||
up-to-date Python packages we recommend using
|
||||
[virtualenv](https://pypi.python.org/pypi/virtualenv), this makes it
|
||||
easy to keep your Python packages up-to-date without interfering with
|
||||
the defaults for your system.
|
||||
|
||||
```
|
||||
pip install virtualenv
|
||||
```
|
||||
|
||||
Go to the place where you want to make your virtual python library
|
||||
storage. This does not need to be near where you plan to install
|
||||
Evennia. Then do
|
||||
|
||||
```
|
||||
virtualenv vienv
|
||||
```
|
||||
|
||||
A new folder `vienv` will be created (you could also name it something
|
||||
else if you prefer). Activate the virtual environment like this:
|
||||
|
||||
```
|
||||
# for Linux/Unix/Mac:
|
||||
source vienv/bin/activate
|
||||
# for Windows:
|
||||
vienv\Scripts\activate.bat
|
||||
```
|
||||
|
||||
You should see `(vienv)` next to your prompt to show you the
|
||||
environment is active. You need to activate it whenever you open a new
|
||||
terminal, but you *don't* have to be inside the `vienv` folder henceforth.
|
||||
|
||||
|
||||
## Get the developer's version of Evennia
|
||||
|
||||
This is currently the only Evennia version available. First download
|
||||
and install [Git](http://git-scm.com/) from the homepage or via the
|
||||
package manager in Linux. Next, go to the place where you want the
|
||||
`evennia` folder to be created and run
|
||||
|
||||
```
|
||||
git clone https://github.com/evennia/evennia.git
|
||||
```
|
||||
|
||||
If you have a github account and have [set up SSH
|
||||
keys](https://help.github.com/articles/generating-ssh-keys/), you want
|
||||
to use this instead:
|
||||
|
||||
```
|
||||
git clone git@github.com:evennia/evennia.git
|
||||
```
|
||||
|
||||
In the future you just enter the new `evennia` folder and do
|
||||
|
||||
```
|
||||
git pull
|
||||
```
|
||||
|
||||
to get the latest Evennia updates.
|
||||
|
||||
## Evennia package install
|
||||
|
||||
Stand at the root of your new `evennia` directory and run
|
||||
|
||||
```
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
(note the period "." at the end, this tells pip to install from the
|
||||
current directory). This will install Evennia and all its dependencies
|
||||
(into your virtualenv if you are using that) and make the `evennia`
|
||||
command available on the command line. You can find Evennia's
|
||||
dependencies in `evennia/requirements.txt`.
|
||||
|
||||
## Creating your game project
|
||||
|
||||
To create your new game you need to initialize a new game project.
|
||||
This should be done somewhere *outside* of your `evennia` folder.
|
||||
|
||||
|
||||
```
|
||||
evennia --init mygame
|
||||
```
|
||||
|
||||
This will create a new game project named "mygame" in a folder of the
|
||||
same name. If you want to change the settings for your project, you
|
||||
will need to edit `mygame/server/conf/settings.py`.
|
||||
|
||||
|
||||
## Starting Evennia
|
||||
|
||||
Enter your new game directory and run
|
||||
|
||||
```
|
||||
evennia migrate
|
||||
evennia start
|
||||
```
|
||||
|
||||
Follow the instructions to create your superuser account. A lot of
|
||||
information will scroll past as the database is created and the server
|
||||
initializes. After this Evennia will be running. Use
|
||||
|
||||
```
|
||||
evennia -h
|
||||
```
|
||||
|
||||
for help with starting, stopping and other operations.
|
||||
|
||||
Start up your MUD client of choice and point it to your server and
|
||||
port *4000*. If you are just running locally the server name is
|
||||
*localhost*.
|
||||
|
||||
Alternatively, you can find the web interface and webclient by
|
||||
pointing your web browser to *http://localhost:4001*.
|
||||
|
||||
Finally, login with the superuser account and password you provided
|
||||
earlier. Welcome to Evennia!
|
||||
You can find the latest updated installation instructions and
|
||||
requirements [here](https://github.com/evennia/evennia/wiki/Getting-Started).
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ from evennia.scripts.scripthandler import ScriptHandler
|
|||
from evennia.commands.cmdsethandler import CmdSetHandler
|
||||
from evennia.utils.optionhandler import OptionHandler
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
from random import getrandbits
|
||||
|
||||
__all__ = ("DefaultAccount",)
|
||||
|
|
@ -828,7 +828,10 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
server.
|
||||
|
||||
Args:
|
||||
text (str, optional): text data to send
|
||||
text (str or tuple, optional): The message to send. This
|
||||
is treated internally like any send-command, so its
|
||||
value can be a tuple if sending multiple arguments to
|
||||
the `text` oob command.
|
||||
from_obj (Object or Account or list, optional): Object sending. If given, its
|
||||
at_msg_send() hook will be called. If iterable, call on all entities.
|
||||
session (Session or list, optional): Session object or a list of
|
||||
|
|
@ -859,7 +862,13 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
kwargs["options"] = options
|
||||
|
||||
if text is not None:
|
||||
kwargs["text"] = to_str(text)
|
||||
if not (isinstance(text, str) or isinstance(text, tuple)):
|
||||
# sanitize text before sending across the wire
|
||||
try:
|
||||
text = to_str(text)
|
||||
except Exception:
|
||||
text = repr(text)
|
||||
kwargs["text"] = text
|
||||
|
||||
# session relay
|
||||
sessions = make_iter(session) if session else self.sessions.all()
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ from evennia.comms.channelhandler import CHANNELHANDLER
|
|||
from evennia.utils import logger, utils
|
||||
from evennia.utils.utils import string_suggestions
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
_IN_GAME_ERRORS = settings.IN_GAME_ERRORS
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ Set theory.
|
|||
|
||||
"""
|
||||
from weakref import WeakKeyDictionary
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
from evennia.utils.utils import inherits_from, is_iter
|
||||
|
||||
__all__ = ("CmdSet",)
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ from evennia.utils import logger, utils
|
|||
from evennia.commands.cmdset import CmdSet
|
||||
from evennia.server.models import ServerConfig
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
__all__ = ("import_cmdset", "CmdSetHandler")
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ All commands in Evennia inherit from the 'Command' class in this module.
|
|||
"""
|
||||
import re
|
||||
import math
|
||||
import inspect
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
|
|
@ -74,6 +75,13 @@ def _init_command(cls, **kwargs):
|
|||
cls.is_exit = False
|
||||
if not hasattr(cls, "help_category"):
|
||||
cls.help_category = "general"
|
||||
# make sure to pick up the parent's docstring if the child class is
|
||||
# missing one (important for auto-help)
|
||||
if cls.__doc__ is None:
|
||||
for parent_class in inspect.getmro(cls):
|
||||
if parent_class.__doc__ is not None:
|
||||
cls.__doc__ = parent_class.__doc__
|
||||
break
|
||||
cls.help_category = cls.help_category.lower()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ from evennia.utils.eveditor import EvEditor
|
|||
from evennia.utils.evmore import EvMore
|
||||
from evennia.prototypes import spawner, prototypes as protlib, menus as olc_menus
|
||||
from evennia.utils.ansi import raw
|
||||
from evennia.prototypes.menus import _format_diff_text_and_options
|
||||
|
||||
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||
|
||||
|
|
@ -1912,8 +1913,8 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
Usage:
|
||||
typeclass[/switch] <object> [= typeclass.path]
|
||||
type ''
|
||||
parent ''
|
||||
typeclass/prototype <object> = prototype_key
|
||||
|
||||
typeclass/list/show [typeclass.path]
|
||||
swap - this is a shorthand for using /force/reset flags.
|
||||
update - this is a shorthand for using the /force/reload flag.
|
||||
|
|
@ -1930,9 +1931,12 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
|||
list - show available typeclasses. Only typeclasses in modules actually
|
||||
imported or used from somewhere in the code will show up here
|
||||
(those typeclasses are still available if you know the path)
|
||||
prototype - clean and overwrite the object with the specified
|
||||
prototype key - effectively making a whole new object.
|
||||
|
||||
Example:
|
||||
type button = examples.red_button.RedButton
|
||||
type/prototype button=a red button
|
||||
|
||||
If the typeclass_path is not given, the current object's typeclass is
|
||||
assumed.
|
||||
|
|
@ -1954,7 +1958,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
key = "typeclass"
|
||||
aliases = ["type", "parent", "swap", "update"]
|
||||
switch_options = ("show", "examine", "update", "reset", "force", "list")
|
||||
switch_options = ("show", "examine", "update", "reset", "force", "list", "prototype")
|
||||
locks = "cmd:perm(typeclass) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
|
|
@ -2038,6 +2042,27 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
new_typeclass = self.rhs or obj.path
|
||||
|
||||
prototype = None
|
||||
if "prototype" in self.switches:
|
||||
key = self.rhs
|
||||
prototype = protlib.search_prototype(key=key)
|
||||
if len(prototype) > 1:
|
||||
caller.msg(
|
||||
"More than one match for {}:\n{}".format(
|
||||
key, "\n".join(proto.get("prototype_key", "") for proto in prototype)
|
||||
)
|
||||
)
|
||||
return
|
||||
elif prototype:
|
||||
# one match
|
||||
prototype = prototype[0]
|
||||
else:
|
||||
# no match
|
||||
caller.msg("No prototype '{}' was found.".format(key))
|
||||
return
|
||||
new_typeclass = prototype["typeclass"]
|
||||
self.switches.append("force")
|
||||
|
||||
if "show" in self.switches or "examine" in self.switches:
|
||||
string = "%s's current typeclass is %s." % (obj.name, obj.__class__)
|
||||
caller.msg(string)
|
||||
|
|
@ -2070,11 +2095,34 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
|||
hooks = "at_object_creation" if update else "all"
|
||||
old_typeclass_path = obj.typeclass_path
|
||||
|
||||
# special prompt for the user in cases where we want
|
||||
# to confirm changes.
|
||||
if "prototype" in self.switches:
|
||||
diff, _ = spawner.prototype_diff_from_object(prototype, obj)
|
||||
txt, options = _format_diff_text_and_options(diff, objects=[obj])
|
||||
prompt = (
|
||||
"Applying prototype '%s' over '%s' will cause the follow changes:\n%s\n"
|
||||
% (prototype["key"], obj.name, "\n".join(txt))
|
||||
)
|
||||
if not reset:
|
||||
prompt += "\n|yWARNING:|n Use the /reset switch to apply the prototype over a blank state."
|
||||
prompt += "\nAre you sure you want to apply these changes [yes]/no?"
|
||||
answer = yield (prompt)
|
||||
if answer and answer in ("no", "n"):
|
||||
caller.msg("Canceled: No changes were applied.")
|
||||
return
|
||||
|
||||
# we let this raise exception if needed
|
||||
obj.swap_typeclass(
|
||||
new_typeclass, clean_attributes=reset, clean_cmdsets=reset, run_start_hooks=hooks
|
||||
)
|
||||
|
||||
if "prototype" in self.switches:
|
||||
modified = spawner.batch_update_objects_with_prototype(prototype, objects=[obj])
|
||||
prototype_success = modified > 0
|
||||
if not prototype_success:
|
||||
caller.msg("Prototype %s failed to apply." % prototype["key"])
|
||||
|
||||
if is_same:
|
||||
string = "%s updated its existing typeclass (%s).\n" % (obj.name, obj.path)
|
||||
else:
|
||||
|
|
@ -2091,6 +2139,11 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
|||
string += " All old attributes where deleted before the swap."
|
||||
else:
|
||||
string += " Attributes set before swap were not removed."
|
||||
if "prototype" in self.switches and prototype_success:
|
||||
string += (
|
||||
" Prototype '%s' was successfully applied over the object type."
|
||||
% prototype["key"]
|
||||
)
|
||||
|
||||
caller.msg(string)
|
||||
|
||||
|
|
@ -2832,8 +2885,8 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
|||
reference. A puppeted object cannot be moved to None.
|
||||
loc - teleport object to the target's location instead of its contents
|
||||
|
||||
Teleports an object somewhere. If no object is given, you yourself
|
||||
is teleported to the target location.
|
||||
Teleports an object somewhere. If no object is given, you yourself are
|
||||
teleported to the target location.
|
||||
"""
|
||||
|
||||
key = "tel"
|
||||
|
|
@ -2998,7 +3051,8 @@ class CmdScript(COMMAND_DEFAULT_CLASS):
|
|||
ok = obj.scripts.add(self.rhs, autostart=True)
|
||||
if not ok:
|
||||
result.append(
|
||||
"\nScript %s could not be added and/or started on %s."
|
||||
"\nScript %s could not be added and/or started on %s "
|
||||
"(or it started and immediately shut down)."
|
||||
% (self.rhs, obj.get_display_name(caller))
|
||||
)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ class CmdHelp(Command):
|
|||
evmore.msg(self.caller, text, session=self.session)
|
||||
return
|
||||
|
||||
self.msg((text, {"type": "help"}))
|
||||
self.msg(text=(text, {"type": "help"}))
|
||||
|
||||
@staticmethod
|
||||
def format_help_entry(title, help_text, aliases=None, suggested=None):
|
||||
|
|
|
|||
|
|
@ -991,6 +991,34 @@ class TestBuilding(CommandTest):
|
|||
"All object creation hooks were run. All old attributes where deleted before the swap.",
|
||||
)
|
||||
|
||||
from evennia.prototypes.prototypes import homogenize_prototype
|
||||
|
||||
test_prototype = [
|
||||
homogenize_prototype(
|
||||
{
|
||||
"prototype_key": "testkey",
|
||||
"prototype_tags": [],
|
||||
"typeclass": "typeclasses.objects.Object",
|
||||
"key": "replaced_obj",
|
||||
"attrs": [("foo", "bar", None, ""), ("desc", "protdesc", None, "")],
|
||||
}
|
||||
)
|
||||
]
|
||||
with mock.patch(
|
||||
"evennia.commands.default.building.protlib.search_prototype",
|
||||
new=mock.MagicMock(return_value=test_prototype),
|
||||
) as mprot:
|
||||
self.call(
|
||||
building.CmdTypeclass(),
|
||||
"/prototype Obj=testkey",
|
||||
"replaced_obj changed typeclass from "
|
||||
"evennia.objects.objects.DefaultObject to "
|
||||
"typeclasses.objects.Object.\nAll object creation hooks were "
|
||||
"run. Attributes set before swap were not removed. Prototype "
|
||||
"'replaced_obj' was successfully applied over the object type.",
|
||||
)
|
||||
assert self.obj1.db.desc == "protdesc"
|
||||
|
||||
def test_lock(self):
|
||||
self.call(building.CmdLock(), "", "Usage: ")
|
||||
self.call(building.CmdLock(), "Obj = test:all()", "Added lock 'test:all()' to Obj.")
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ from django.conf import settings
|
|||
from evennia.commands import cmdset, command
|
||||
from evennia.utils.logger import tail_log_file
|
||||
from evennia.utils.utils import class_from_module
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
# we must late-import these since any overloads are likely to
|
||||
# themselves be using these classes leading to a circular import.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ Comm system components.
|
|||
from django.db.models import Q
|
||||
from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager
|
||||
from evennia.utils import logger
|
||||
from evennia.utils.utils import dbref
|
||||
|
||||
_GA = object.__getattribute__
|
||||
_AccountDB = None
|
||||
|
|
@ -31,32 +32,6 @@ class CommError(Exception):
|
|||
#
|
||||
|
||||
|
||||
def dbref(inp, reqhash=True):
|
||||
"""
|
||||
Valid forms of dbref (database reference number) are either a
|
||||
string '#N' or an integer N.
|
||||
|
||||
Args:
|
||||
inp (int or str): A possible dbref to check syntactically.
|
||||
reqhash (bool): Require an initial hash `#` to accept.
|
||||
|
||||
Returns:
|
||||
is_dbref (int or None): The dbref integer part if a valid
|
||||
dbref, otherwise `None`.
|
||||
|
||||
"""
|
||||
if reqhash and not (isinstance(inp, str) and inp.startswith("#")):
|
||||
return None
|
||||
if isinstance(inp, str):
|
||||
inp = inp.lstrip("#")
|
||||
try:
|
||||
if int(inp) < 0:
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
return inp
|
||||
|
||||
|
||||
def identify_object(inp):
|
||||
"""
|
||||
Helper function. Identifies if an object is an account or an object;
|
||||
|
|
|
|||
|
|
@ -8,26 +8,39 @@ insert custom markers in their text to indicate gender-aware
|
|||
messaging. It relies on a modified msg() and is meant as an
|
||||
inspiration and starting point to how to do stuff like this.
|
||||
|
||||
When in use, all messages being sent to the character will make use of
|
||||
the character's gender, for example the echo
|
||||
An object can have the following genders:
|
||||
- male (he/his)
|
||||
- female (her/hers)
|
||||
- neutral (it/its)
|
||||
- ambiguous (they/them/their/theirs)
|
||||
|
||||
When in use, messages can contain special tags to indicate pronouns gendered
|
||||
based on the one being addressed. Capitalization will be retained.
|
||||
|
||||
- `|s`, `|S`: Subjective form: he, she, it, He, She, It, They
|
||||
- `|o`, `|O`: Objective form: him, her, it, Him, Her, It, Them
|
||||
- `|p`, `|P`: Possessive form: his, her, its, His, Her, Its, Their
|
||||
- `|a`, `|A`: Absolute Possessive form: his, hers, its, His, Hers, Its, Theirs
|
||||
|
||||
For example,
|
||||
|
||||
```
|
||||
char.msg("%s falls on |p face with a thud." % char.key)
|
||||
"Tom falls on his face with a thud"
|
||||
```
|
||||
|
||||
will result in "Tom falls on his|her|its|their face with a thud"
|
||||
depending on the gender of the object being messaged. Default gender
|
||||
is "ambiguous" (they).
|
||||
The default gender is "ambiguous" (they/them/their/theirs).
|
||||
|
||||
To use, have DefaultCharacter inherit from this, or change
|
||||
setting.DEFAULT_CHARACTER to point to this class.
|
||||
|
||||
The `@gender` command needs to be added to the default cmdset before
|
||||
it becomes available.
|
||||
The `@gender` command is used to set the gender. It needs to be added to the
|
||||
default cmdset before it becomes available.
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
from evennia.utils import logger
|
||||
from evennia import DefaultCharacter
|
||||
from evennia import Command
|
||||
|
||||
|
|
@ -114,7 +127,10 @@ class GenderCharacter(DefaultCharacter):
|
|||
gender-aware markers in output.
|
||||
|
||||
Args:
|
||||
text (str, optional): The message to send
|
||||
text (str or tuple, optional): The message to send. This
|
||||
is treated internally like any send-command, so its
|
||||
value can be a tuple if sending multiple arguments to
|
||||
the `text` oob command.
|
||||
from_obj (obj, optional): object that is sending. If
|
||||
given, at_msg_send will be called
|
||||
session (Session or list, optional): session or list of
|
||||
|
|
@ -125,9 +141,13 @@ class GenderCharacter(DefaultCharacter):
|
|||
All extra kwargs will be passed on to the protocol.
|
||||
|
||||
"""
|
||||
# pre-process the text before continuing
|
||||
try:
|
||||
text = _RE_GENDER_PRONOUN.sub(self._get_pronoun, text)
|
||||
if text and isinstance(text, tuple):
|
||||
text = (self._RE_GENDER_PRONOUN.sub(self._get_pronoun, text[0]), *text[1:])
|
||||
else:
|
||||
text = self._RE_GENDER_PRONOUN.sub(self._get_pronoun, text)
|
||||
except TypeError:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.log_trace(e)
|
||||
super().msg(text, from_obj=from_obj, session=session, **kwargs)
|
||||
|
|
|
|||
|
|
@ -798,6 +798,16 @@ class RecogHandler(object):
|
|||
# recog_mask log not passed, disable recog
|
||||
return obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key
|
||||
|
||||
def all(self):
|
||||
"""
|
||||
Get a mapping of the recogs stored in handler.
|
||||
|
||||
Returns:
|
||||
recogs (dict): A mapping of {recog: obj} stored in handler.
|
||||
|
||||
"""
|
||||
return {self.obj2recog[obj]: obj for obj in self.obj2recog.keys()}
|
||||
|
||||
def remove(self, obj):
|
||||
"""
|
||||
Clear recog for a given object.
|
||||
|
|
@ -896,10 +906,9 @@ class CmdSay(RPCommand): # replaces standard say
|
|||
caller.msg("Say what?")
|
||||
return
|
||||
|
||||
# calling the speech hook on the location
|
||||
speech = caller.location.at_before_say(self.args)
|
||||
# calling the speech modifying hook
|
||||
speech = caller.at_before_say(self.args)
|
||||
# preparing the speech with sdesc/speech parsing.
|
||||
speech = '/me says, "{speech}"'.format(speech=speech)
|
||||
targets = self.caller.location.contents
|
||||
send_emote(self.caller, targets, speech, anonymous_add=None)
|
||||
|
||||
|
|
@ -932,6 +941,9 @@ class CmdSdesc(RPCommand): # set/look at own sdesc
|
|||
except SdescError as err:
|
||||
caller.msg(err)
|
||||
return
|
||||
except AttributeError:
|
||||
caller.msg(f"Cannot set sdesc on {caller.key}.")
|
||||
return
|
||||
caller.msg("%s's sdesc was set to '%s'." % (caller.key, sdesc))
|
||||
|
||||
|
||||
|
|
@ -1041,6 +1053,7 @@ class CmdRecog(RPCommand): # assign personal alias to object in room
|
|||
Recognize another person in the same room.
|
||||
|
||||
Usage:
|
||||
recog
|
||||
recog sdesc as alias
|
||||
forget alias
|
||||
|
||||
|
|
@ -1048,8 +1061,8 @@ class CmdRecog(RPCommand): # assign personal alias to object in room
|
|||
recog tall man as Griatch
|
||||
forget griatch
|
||||
|
||||
This will assign a personal alias for a person, or
|
||||
forget said alias.
|
||||
This will assign a personal alias for a person, or forget said alias.
|
||||
Using the command without arguments will list all current recogs.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -1058,6 +1071,7 @@ class CmdRecog(RPCommand): # assign personal alias to object in room
|
|||
|
||||
def parse(self):
|
||||
"Parse for the sdesc as alias structure"
|
||||
self.sdesc, self.alias = "", ""
|
||||
if " as " in self.args:
|
||||
self.sdesc, self.alias = [part.strip() for part in self.args.split(" as ", 2)]
|
||||
elif self.args:
|
||||
|
|
@ -1070,22 +1084,47 @@ class CmdRecog(RPCommand): # assign personal alias to object in room
|
|||
def func(self):
|
||||
"Assign the recog"
|
||||
caller = self.caller
|
||||
if not self.args:
|
||||
caller.msg("Usage: recog <sdesc> as <alias> or forget <alias>")
|
||||
return
|
||||
sdesc = self.sdesc
|
||||
alias = self.alias.rstrip(".?!")
|
||||
sdesc = self.sdesc
|
||||
|
||||
recog_mode = self.cmdstring != "forget" and alias and sdesc
|
||||
forget_mode = self.cmdstring == "forget" and sdesc
|
||||
list_mode = not self.args
|
||||
|
||||
if not (recog_mode or forget_mode or list_mode):
|
||||
caller.msg("Usage: recog, recog <sdesc> as <alias> or forget <alias>")
|
||||
return
|
||||
|
||||
if list_mode:
|
||||
# list all previously set recogs
|
||||
all_recogs = caller.recog.all()
|
||||
if not all_recogs:
|
||||
caller.msg(
|
||||
"You recognize no-one. " "(Use 'recog <sdesc> as <alias>' to recognize people."
|
||||
)
|
||||
else:
|
||||
# note that we don't skip those failing enable_recog lock here,
|
||||
# because that would actually reveal more than we want.
|
||||
lst = "\n".join(
|
||||
" {} ({})".format(key, obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key)
|
||||
for key, obj in all_recogs.items()
|
||||
)
|
||||
caller.msg(
|
||||
f"Currently recognized (use 'recog <sdesc> as <alias>' to add "
|
||||
f"new and 'forget <alias>' to remove):\n{lst}"
|
||||
)
|
||||
return
|
||||
|
||||
prefixed_sdesc = sdesc if sdesc.startswith(_PREFIX) else _PREFIX + sdesc
|
||||
candidates = caller.location.contents
|
||||
matches = parse_sdescs_and_recogs(caller, candidates, prefixed_sdesc, search_mode=True)
|
||||
nmatches = len(matches)
|
||||
# handle 0, 1 and >1 matches
|
||||
# handle 0 and >1 matches
|
||||
if nmatches == 0:
|
||||
caller.msg(_EMOTE_NOMATCH_ERROR.format(ref=sdesc))
|
||||
elif nmatches > 1:
|
||||
reflist = [
|
||||
"%s%s%s (%s%s)"
|
||||
% (
|
||||
"{}{}{} ({}{})".format(
|
||||
inum + 1,
|
||||
_NUM_SEP,
|
||||
_RE_PREFIX.sub("", sdesc),
|
||||
|
|
@ -1095,17 +1134,20 @@ class CmdRecog(RPCommand): # assign personal alias to object in room
|
|||
for inum, obj in enumerate(matches)
|
||||
]
|
||||
caller.msg(_EMOTE_MULTIMATCH_ERROR.format(ref=sdesc, reflist="\n ".join(reflist)))
|
||||
|
||||
else:
|
||||
# one single match
|
||||
obj = matches[0]
|
||||
if not obj.access(self.obj, "enable_recog", default=True):
|
||||
# don't apply recog if object doesn't allow it (e.g. by being masked).
|
||||
caller.msg("Can't recognize someone who is masked.")
|
||||
caller.msg("It's impossible to recognize them.")
|
||||
return
|
||||
if self.cmdstring == "forget":
|
||||
if forget_mode:
|
||||
# remove existing recog
|
||||
caller.recog.remove(obj)
|
||||
caller.msg("%s will now know only '%s'." % (caller.key, obj.recog.get(obj)))
|
||||
caller.msg("%s will now know them only as '%s'." % (caller.key, obj.recog.get(obj)))
|
||||
else:
|
||||
# set recog
|
||||
sdesc = obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key
|
||||
try:
|
||||
alias = caller.recog.add(obj, alias)
|
||||
|
|
@ -1509,6 +1551,20 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject):
|
|||
# initializing sdesc
|
||||
self.sdesc.add("A normal person")
|
||||
|
||||
def at_before_say(self, message, **kwargs):
|
||||
"""
|
||||
Called before the object says or whispers anything, return modified message.
|
||||
|
||||
Args:
|
||||
message (str): The suggested say/whisper text spoken by self.
|
||||
Kwargs:
|
||||
whisper (bool): If True, this is a whisper rather than a say.
|
||||
|
||||
"""
|
||||
if kwargs.get("whisper"):
|
||||
return f'/me whispers "{message}"'
|
||||
return f'/me says, "{message}"'
|
||||
|
||||
def process_sdesc(self, sdesc, obj, **kwargs):
|
||||
"""
|
||||
Allows to customize how your sdesc is displayed (primarily by
|
||||
|
|
|
|||
|
|
@ -169,6 +169,8 @@ class TestRPSystem(EvenniaTest):
|
|||
self.speaker.recog.remove(self.receiver1)
|
||||
self.assertEqual(self.speaker.recog.get(self.receiver1), sdesc1)
|
||||
|
||||
self.assertEqual(self.speaker.recog.all(), {"Mr Receiver2": self.receiver2})
|
||||
|
||||
def test_parse_language(self):
|
||||
self.assertEqual(
|
||||
rpsystem.parse_language(self.speaker, emote),
|
||||
|
|
@ -233,6 +235,49 @@ class TestRPSystem(EvenniaTest):
|
|||
self.assertEqual(self.speaker.search("colliding"), self.receiver2)
|
||||
|
||||
|
||||
class TestRPSystemCommands(CommandTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.char1.swap_typeclass(rpsystem.ContribRPCharacter)
|
||||
self.char2.swap_typeclass(rpsystem.ContribRPCharacter)
|
||||
|
||||
def test_commands(self):
|
||||
|
||||
self.call(
|
||||
rpsystem.CmdSdesc(), "Foobar Character", "Char's sdesc was set to 'Foobar Character'."
|
||||
)
|
||||
self.call(
|
||||
rpsystem.CmdSdesc(),
|
||||
"BarFoo Character",
|
||||
"Char2's sdesc was set to 'BarFoo Character'.",
|
||||
caller=self.char2,
|
||||
)
|
||||
self.call(rpsystem.CmdSay(), "Hello!", 'Char says, "Hello!"')
|
||||
self.call(rpsystem.CmdEmote(), "/me smiles to /barfoo.", "Char smiles to BarFoo Character")
|
||||
self.call(
|
||||
rpsystem.CmdPose(),
|
||||
"stands by the bar",
|
||||
"Pose will read 'Foobar Character stands by the bar.'.",
|
||||
)
|
||||
self.call(
|
||||
rpsystem.CmdRecog(),
|
||||
"barfoo as friend",
|
||||
"Char will now remember BarFoo Character as friend.",
|
||||
)
|
||||
self.call(
|
||||
rpsystem.CmdRecog(),
|
||||
"",
|
||||
"Currently recognized (use 'recog <sdesc> as <alias>' to add new "
|
||||
"and 'forget <alias>' to remove):\n friend (BarFoo Character)",
|
||||
)
|
||||
self.call(
|
||||
rpsystem.CmdRecog(),
|
||||
"friend",
|
||||
"Char will now know them only as 'BarFoo Character'",
|
||||
cmdstring="forget",
|
||||
)
|
||||
|
||||
|
||||
# Testing of ExtendedRoom contrib
|
||||
|
||||
from django.conf import settings
|
||||
|
|
|
|||
|
|
@ -26,3 +26,16 @@ def at_webserver_root_creation(web_root):
|
|||
|
||||
"""
|
||||
return web_root
|
||||
|
||||
|
||||
def at_webproxy_root_creation(web_root):
|
||||
"""
|
||||
This function can modify the portal proxy service.
|
||||
Args:
|
||||
web_root (evennia.server.webserver.Website): The Evennia
|
||||
Website application. Use .putChild() to add new
|
||||
subdomains that are Portal-accessible over TCP;
|
||||
primarily for new protocol development, but suitable
|
||||
for other shenanigans.
|
||||
"""
|
||||
return web_root
|
||||
|
|
|
|||
|
|
@ -547,11 +547,39 @@ def inside(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
Usage:
|
||||
inside()
|
||||
|
||||
Only true if accessing_obj is "inside" accessed_obj
|
||||
True if accessing_obj is 'inside' accessing_obj. Note that this only checks
|
||||
one level down. So if if the lock is on a room, you will pass but not your
|
||||
inventory (since their location is you, not the locked object). If you
|
||||
want also nested objects to pass the lock, use the `insiderecursive`
|
||||
lockfunc.
|
||||
"""
|
||||
return accessing_obj.location == accessed_obj
|
||||
|
||||
|
||||
def inside_rec(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Usage:
|
||||
inside_rec()
|
||||
|
||||
True if accessing_obj is inside the accessed obj, at up to 10 levels
|
||||
of recursion (so if this lock is on a room, then an object inside a box
|
||||
in your inventory will also pass the lock).
|
||||
"""
|
||||
|
||||
def _recursive_inside(obj, accessed_obj, lvl=1):
|
||||
if obj.location:
|
||||
if obj.location == accessed_obj:
|
||||
return True
|
||||
elif lvl >= 10:
|
||||
# avoid infinite recursions
|
||||
return False
|
||||
else:
|
||||
return _recursive_inside(obj.location, accessed_obj, lvl + 1)
|
||||
return False
|
||||
|
||||
return _recursive_inside(accessing_obj, accessed_obj)
|
||||
|
||||
|
||||
def holds(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Usage:
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ to any other identifier you can use.
|
|||
import re
|
||||
from django.conf import settings
|
||||
from evennia.utils import logger, utils
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
__all__ = ("LockHandler", "LockException")
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ except ImportError:
|
|||
|
||||
from evennia import settings_default
|
||||
from evennia.locks import lockfuncs
|
||||
from evennia.utils.create import create_object
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Lock testing
|
||||
|
|
@ -179,6 +180,13 @@ class TestLockfuncs(EvenniaTest):
|
|||
self.assertEqual(False, lockfuncs.inside(self.char1, self.room2))
|
||||
self.assertEqual(True, lockfuncs.holds(self.room1, self.char1))
|
||||
self.assertEqual(False, lockfuncs.holds(self.room2, self.char1))
|
||||
# test recursively
|
||||
self.assertEqual(True, lockfuncs.inside_rec(self.char1, self.room1))
|
||||
self.assertEqual(False, lockfuncs.inside_rec(self.char1, self.room2))
|
||||
inventory_item = create_object(key="InsideTester", location=self.char1)
|
||||
self.assertEqual(True, lockfuncs.inside_rec(inventory_item, self.room1))
|
||||
self.assertEqual(False, lockfuncs.inside_rec(inventory_item, self.room2))
|
||||
inventory_item.delete()
|
||||
|
||||
def test_has_account(self):
|
||||
self.assertEqual(True, lockfuncs.has_account(self.char1, None))
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ from evennia.utils.utils import (
|
|||
list_to_string,
|
||||
to_str,
|
||||
)
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
_INFLECT = inflect.engine()
|
||||
_MULTISESSION_MODE = settings.MULTISESSION_MODE
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from evennia.scripts.models import ScriptDB
|
|||
from evennia.utils import create
|
||||
from evennia.utils import logger
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
|
||||
class ScriptHandler(object):
|
||||
|
|
@ -78,11 +78,20 @@ class ScriptHandler(object):
|
|||
scriptclass, key=key, account=self.obj, autostart=autostart
|
||||
)
|
||||
else:
|
||||
# the normal - adding to an Object
|
||||
script = create.create_script(scriptclass, key=key, obj=self.obj, autostart=autostart)
|
||||
# the normal - adding to an Object. We wait to autostart so we can differentiate
|
||||
# a failing creation from a script that immediately starts/stops.
|
||||
script = create.create_script(scriptclass, key=key, obj=self.obj, autostart=False)
|
||||
if not script:
|
||||
logger.log_err("Script %s could not be created and/or started." % scriptclass)
|
||||
logger.log_err("Script %s failed to be created/started." % scriptclass)
|
||||
return False
|
||||
if autostart:
|
||||
script.start()
|
||||
if not script.id:
|
||||
# this can happen if the script has repeats=1 or calls stop() in at_repeat.
|
||||
logger.log_info(
|
||||
"Script %s started and then immediately stopped; "
|
||||
"it could probably be a normal function." % scriptclass
|
||||
)
|
||||
return True
|
||||
|
||||
def start(self, key):
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ ability to run timers.
|
|||
from twisted.internet.defer import Deferred, maybeDeferred
|
||||
from twisted.internet.task import LoopingCall
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
from evennia.typeclasses.models import TypeclassBase
|
||||
from evennia.scripts.models import ScriptDB
|
||||
from evennia.scripts.manager import ScriptManager
|
||||
|
|
@ -209,6 +209,9 @@ class ScriptBase(ScriptDB, metaclass=TypeclassBase):
|
|||
Step task runner. No try..except needed due to defer wrap.
|
||||
|
||||
"""
|
||||
if not self.ndb._task:
|
||||
# if there is no task, we have no business using this method
|
||||
return
|
||||
|
||||
if not self.is_valid():
|
||||
self.stop()
|
||||
|
|
@ -218,10 +221,13 @@ class ScriptBase(ScriptDB, metaclass=TypeclassBase):
|
|||
self.at_repeat()
|
||||
|
||||
# check repeats
|
||||
callcount = self.ndb._task.callcount
|
||||
maxcount = self.db_repeats
|
||||
if maxcount > 0 and maxcount <= callcount:
|
||||
self.stop()
|
||||
if self.ndb._task:
|
||||
# we need to check for the task in case stop() was called
|
||||
# inside at_repeat() and it already went away.
|
||||
callcount = self.ndb._task.callcount
|
||||
maxcount = self.db_repeats
|
||||
if maxcount > 0 and maxcount <= callcount:
|
||||
self.stop()
|
||||
|
||||
def _step_task(self):
|
||||
"""
|
||||
|
|
@ -339,9 +345,9 @@ class DefaultScript(ScriptBase):
|
|||
|
||||
try:
|
||||
obj = create.create_script(**kwargs)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
errors.append("The script '%s' encountered errors and could not be created." % key)
|
||||
logger.log_err(e)
|
||||
|
||||
return obj, errors
|
||||
|
||||
|
|
|
|||
|
|
@ -115,3 +115,10 @@ def check_warnings(settings):
|
|||
print(" [Devel: settings.IN_GAME_ERRORS is True. Turn off in production.]")
|
||||
if settings.ALLOWED_HOSTS == ["*"]:
|
||||
print(" [Devel: settings.ALLOWED_HOSTS set to '*' (all). Limit in production.]")
|
||||
for dbentry in settings.DATABASES.values():
|
||||
if "psycopg" in dbentry.get("ENGINE", ""):
|
||||
print(
|
||||
'Deprecation: postgresql_psycopg2 backend is deprecated". '
|
||||
"Switch settings.DATABASES to use "
|
||||
'"ENGINE": "django.db.backends.postgresql instead"'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ SRESET = chr(19) # shutdown server in reset mode
|
|||
PYTHON_MIN = "3.7"
|
||||
TWISTED_MIN = "18.0.0"
|
||||
DJANGO_MIN = "2.1"
|
||||
DJANGO_REC = "2.2.9"
|
||||
DJANGO_REC = "2.2"
|
||||
|
||||
try:
|
||||
sys.path[1] = EVENNIA_ROOT
|
||||
|
|
@ -1281,7 +1281,7 @@ def check_main_evennia_dependencies():
|
|||
try:
|
||||
dversion = ".".join(str(num) for num in django.VERSION if isinstance(num, int))
|
||||
# only the main version (1.5, not 1.5.4.0)
|
||||
dversion_main = ".".join(dversion.split(".")[:3])
|
||||
dversion_main = ".".join(dversion.split(".")[:2])
|
||||
if LooseVersion(dversion) < LooseVersion(DJANGO_MIN):
|
||||
print(ERROR_DJANGO_MIN.format(dversion=dversion_main, django_min=DJANGO_MIN))
|
||||
error = True
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# Evennia Game Index Client
|
||||
|
||||
Greg Taylor 2016
|
||||
Greg Taylor 2016, Griatch 2020
|
||||
|
||||
This contrib features a client for the [Evennia Game Index]
|
||||
This is a client for the [Evennia Game Index]
|
||||
(http://evennia-game-index.appspot.com/), a listing of games built on
|
||||
Evennia. By listing your game on the index, you make it easy for other
|
||||
people in the community to discover your creation.
|
||||
|
|
@ -14,74 +14,24 @@ on remedying this.*
|
|||
|
||||
## Listing your Game
|
||||
|
||||
To list your game, you'll need to enable the Evennia Game Index client.
|
||||
Start by `cd`'ing to your game directory. From there, open up
|
||||
`server/conf/server_services_plugins.py`. It might look something like this
|
||||
if you don't have any other optional add-ons enabled:
|
||||
To list your game, go to your game dir and run
|
||||
|
||||
```python
|
||||
"""
|
||||
Server plugin services
|
||||
evennia connections
|
||||
|
||||
This plugin module can define user-created services for the Server to
|
||||
start.
|
||||
Follow the prompts to add details to the listing. Use `evennia reload`. In your log (visible with `evennia --log`
|
||||
you should see a note that info has been sent to the game index.
|
||||
|
||||
This module must handle all imports and setups required to start a
|
||||
twisted service (see examples in evennia.server.server). It must also
|
||||
contain a function start_plugin_services(application). Evennia will
|
||||
call this function with the main Server application (so your services
|
||||
can be added to it). The function should not return anything. Plugin
|
||||
services are started last in the Server startup process.
|
||||
"""
|
||||
## Detailed settings
|
||||
|
||||
|
||||
def start_plugin_services(server):
|
||||
"""
|
||||
This hook is called by Evennia, last in the Server startup process.
|
||||
|
||||
server - a reference to the main server application.
|
||||
"""
|
||||
pass
|
||||
```
|
||||
|
||||
To enable the client, import `EvenniaGameIndexService` and fire it up after the
|
||||
Evennia server has finished starting:
|
||||
|
||||
```python
|
||||
"""
|
||||
Server plugin services
|
||||
|
||||
This plugin module can define user-created services for the Server to
|
||||
start.
|
||||
|
||||
This module must handle all imports and setups required to start a
|
||||
twisted service (see examples in evennia.server.server). It must also
|
||||
contain a function start_plugin_services(application). Evennia will
|
||||
call this function with the main Server application (so your services
|
||||
can be added to it). The function should not return anything. Plugin
|
||||
services are started last in the Server startup process.
|
||||
"""
|
||||
|
||||
from evennia.contrib.egi_client import EvenniaGameIndexService
|
||||
|
||||
def start_plugin_services(server):
|
||||
"""
|
||||
This hook is called by Evennia, last in the Server startup process.
|
||||
|
||||
server - a reference to the main server application.
|
||||
"""
|
||||
egi_service = EvenniaGameIndexService()
|
||||
server.services.addService(egi_service)
|
||||
```
|
||||
|
||||
Next, configure your game listing by opening up `server/conf/settings.py` and
|
||||
If you don't want to use the wizard you can configure your game listing by opening up `server/conf/settings.py` and
|
||||
using the following as a starting point:
|
||||
|
||||
```python
|
||||
######################################################################
|
||||
# Contrib config
|
||||
# Game index
|
||||
######################################################################
|
||||
|
||||
GAME_INDEX_ENABLED = True
|
||||
GAME_INDEX_LISTING = {
|
||||
'game_status': 'pre-alpha',
|
||||
# Optional, comment out or remove if N/A
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Everything starts at handle_setup()
|
|||
|
||||
import time
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
from evennia.accounts.models import AccountDB
|
||||
from evennia.server.models import ServerConfig
|
||||
from evennia.utils import create, logger
|
||||
|
|
|
|||
|
|
@ -576,8 +576,7 @@ def msdp_list(session, *args, **kwargs):
|
|||
fieldnames = [tup[1] for tup in monitor_infos]
|
||||
session.msg(reported_variables=(fieldnames, {}))
|
||||
if "sendable_variables" in args_lower:
|
||||
# no default sendable variables
|
||||
session.msg(sendable_variables=([], {}))
|
||||
session.msg(sendable_variables=(_monitorable, {}))
|
||||
|
||||
|
||||
def msdp_report(session, *args, **kwargs):
|
||||
|
|
@ -597,6 +596,17 @@ def msdp_unreport(session, *args, **kwargs):
|
|||
unmonitor(session, *args, **kwargs)
|
||||
|
||||
|
||||
def msdp_send(session, *args, **kwargs):
|
||||
"""
|
||||
MSDP SEND command
|
||||
"""
|
||||
out = {}
|
||||
for varname in args:
|
||||
if varname.lower() in _monitorable:
|
||||
out[varname] = _monitorable[varname.lower()]
|
||||
session.msg(send=((), out))
|
||||
|
||||
|
||||
# client specific
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -314,7 +314,9 @@ class AMPMultiConnectionProtocol(amp.AMP):
|
|||
try:
|
||||
super(AMPMultiConnectionProtocol, self).dataReceived(data)
|
||||
except KeyError:
|
||||
_get_logger().log_trace("Discarded incoming partial data: {}".format(to_str(data)))
|
||||
_get_logger().log_trace(
|
||||
"Discarded incoming partial (packed) data (len {})".format(len(data))
|
||||
)
|
||||
elif self.multibatches:
|
||||
# invalid AMP, but we have a pending multi-batch that is not yet complete
|
||||
if data[-2:] == NULNUL:
|
||||
|
|
@ -323,7 +325,9 @@ class AMPMultiConnectionProtocol(amp.AMP):
|
|||
try:
|
||||
super(AMPMultiConnectionProtocol, self).dataReceived(data)
|
||||
except KeyError:
|
||||
_get_logger().log_trace("Discarded incoming multi-batch data:".format(to_str(data)))
|
||||
_get_logger().log_trace(
|
||||
"Discarded incoming multi-batch (packed) data (len {})".format(len(data))
|
||||
)
|
||||
else:
|
||||
# not an AMP communication, return warning
|
||||
self.transport.write(_HTTP_WARNING)
|
||||
|
|
|
|||
|
|
@ -15,9 +15,10 @@ This protocol is implemented by the telnet protocol importing
|
|||
mccp_compress and calling it from its write methods.
|
||||
"""
|
||||
import zlib
|
||||
from twisted.python.compat import _bytesChr as chr
|
||||
|
||||
# negotiations for v1 and v2 of the protocol
|
||||
MCCP = b"\x56"
|
||||
MCCP = chr(86) # b"\x56"
|
||||
FLUSH = zlib.Z_SYNC_FLUSH
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,10 +12,11 @@ active players and so on.
|
|||
"""
|
||||
from django.conf import settings
|
||||
from evennia.utils import utils
|
||||
from twisted.python.compat import _bytesChr as bchr
|
||||
|
||||
MSSP = b"\x46"
|
||||
MSSP_VAR = b"\x01"
|
||||
MSSP_VAL = b"\x02"
|
||||
MSSP = bchr(70) # b"\x46"
|
||||
MSSP_VAR = bchr(1) # b"\x01"
|
||||
MSSP_VAL = bchr(2) # b"\x02"
|
||||
|
||||
# try to get the customized mssp info, if it exists.
|
||||
MSSPTable_CUSTOM = utils.variable_from_module(settings.MSSP_META_MODULE, "MSSPTable", default={})
|
||||
|
|
@ -86,7 +87,7 @@ class Mssp(object):
|
|||
"PLAYERS": self.get_player_count,
|
||||
"UPTIME": self.get_uptime,
|
||||
"PORT": list(
|
||||
reversed(settings.TELNET_PORTS)
|
||||
str(port) for port in reversed(settings.TELNET_PORTS)
|
||||
), # most important port should be last in list
|
||||
# Evennia auto-filled
|
||||
"CRAWL DELAY": "-1",
|
||||
|
|
@ -119,10 +120,15 @@ class Mssp(object):
|
|||
if utils.is_iter(value):
|
||||
for partval in value:
|
||||
varlist += (
|
||||
MSSP_VAR + bytes(variable, "utf-8") + MSSP_VAL + bytes(partval, "utf-8")
|
||||
MSSP_VAR
|
||||
+ bytes(str(variable), "utf-8")
|
||||
+ MSSP_VAL
|
||||
+ bytes(str(partval), "utf-8")
|
||||
)
|
||||
else:
|
||||
varlist += MSSP_VAR + bytes(variable, "utf-8") + MSSP_VAL + bytes(value, "utf-8")
|
||||
varlist += (
|
||||
MSSP_VAR + bytes(str(variable), "utf-8") + MSSP_VAL + bytes(str(value), "utf-8")
|
||||
)
|
||||
|
||||
# send to crawler by subnegotiation
|
||||
self.protocol.requestNegotiation(MSSP, varlist)
|
||||
|
|
|
|||
|
|
@ -14,11 +14,12 @@ http://www.gammon.com.au/mushclient/addingservermxp.htm
|
|||
|
||||
"""
|
||||
import re
|
||||
from twisted.python.compat import _bytesChr as bchr
|
||||
|
||||
LINKS_SUB = re.compile(r"\|lc(.*?)\|lt(.*?)\|le", re.DOTALL)
|
||||
|
||||
# MXP Telnet option
|
||||
MXP = b"\x5b"
|
||||
MXP = bchr(91) # b"\x5b"
|
||||
|
||||
MXP_TEMPSECURE = "\x1B[4z"
|
||||
MXP_SEND = MXP_TEMPSECURE + '<SEND HREF="\\1">' + "\\2" + MXP_TEMPSECURE + "</SEND>"
|
||||
|
|
|
|||
|
|
@ -11,9 +11,10 @@ client and update it when the size changes
|
|||
"""
|
||||
from codecs import encode as codecs_encode
|
||||
from django.conf import settings
|
||||
from twisted.python.compat import _bytesChr as bchr
|
||||
|
||||
NAWS = b"\x1f"
|
||||
IS = b"\x00"
|
||||
NAWS = bchr(31) # b"\x1f"
|
||||
IS = bchr(0) # b"\x00"
|
||||
# default taken from telnet specification
|
||||
DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
|
||||
DEFAULT_HEIGHT = settings.CLIENT_DEFAULT_HEIGHT
|
||||
|
|
|
|||
|
|
@ -95,6 +95,16 @@ INFO_DICT = {
|
|||
"webserver_internal": [],
|
||||
}
|
||||
|
||||
try:
|
||||
WEB_PLUGINS_MODULE = mod_import(settings.WEB_PLUGINS_MODULE)
|
||||
except ImportError:
|
||||
WEB_PLUGINS_MODULE = None
|
||||
INFO_DICT["errors"] = (
|
||||
"WARNING: settings.WEB_PLUGINS_MODULE not found - "
|
||||
"copy 'evennia/game_template/server/conf/web_plugins.py to mygame/server/conf."
|
||||
)
|
||||
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Portal Service object
|
||||
# -------------------------------------------------------------
|
||||
|
|
@ -190,7 +200,6 @@ class Portal(object):
|
|||
self.sessions.disconnect_all()
|
||||
if _stop_server:
|
||||
self.amp_protocol.stop_server(mode="shutdown")
|
||||
|
||||
if not _reactor_stopping:
|
||||
# shutting down the reactor will trigger another signal. We set
|
||||
# a flag to avoid loops.
|
||||
|
|
@ -379,6 +388,14 @@ if WEBSERVER_ENABLED:
|
|||
webclientstr = "webclient-websocket%s: %s" % (w_ifacestr, port)
|
||||
INFO_DICT["webclient"].append(webclientstr)
|
||||
|
||||
if WEB_PLUGINS_MODULE:
|
||||
try:
|
||||
web_root = WEB_PLUGINS_MODULE.at_webproxy_root_creation(web_root)
|
||||
except Exception as e: # Legacy user has not added an at_webproxy_root_creation function in existing web plugins file
|
||||
INFO_DICT["errors"] = (
|
||||
"WARNING: WEB_PLUGINS_MODULE is enabled but at_webproxy_root_creation() not found - "
|
||||
"copy 'evennia/game_template/server/conf/web_plugins.py to mygame/server/conf."
|
||||
)
|
||||
web_root = Website(web_root, logPath=settings.HTTP_LOG_FILE)
|
||||
web_root.is_portal = True
|
||||
proxy_service = internet.TCPServer(proxyport, web_root, interface=interface)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ It is set as the NOGOAHEAD protocol_flag option.
|
|||
http://www.faqs.org/rfcs/rfc858.html
|
||||
|
||||
"""
|
||||
SUPPRESS_GA = b"\x03"
|
||||
from twisted.python.compat import _bytesChr as bchr
|
||||
|
||||
SUPPRESS_GA = bchr(3) # b"\x03"
|
||||
|
||||
# default taken from telnet specification
|
||||
|
||||
|
|
|
|||
|
|
@ -75,13 +75,21 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
self.protocol_key = "telnet"
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def dataReceived(self, data):
|
||||
"""
|
||||
Unused by default, but a good place to put debug printouts
|
||||
of incoming data.
|
||||
"""
|
||||
# print(f"telnet dataReceived: {data}")
|
||||
super().dataReceived(data)
|
||||
|
||||
def connectionMade(self):
|
||||
"""
|
||||
This is called when the connection is first established.
|
||||
|
||||
"""
|
||||
# important in order to work normally with standard telnet
|
||||
self.do(LINEMODE)
|
||||
self.do(LINEMODE).addErrback(self._wont_linemode)
|
||||
# initialize the session
|
||||
self.line_buffer = b""
|
||||
client_address = self.transport.client
|
||||
|
|
@ -126,6 +134,14 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
self.nop_keep_alive = None
|
||||
self.toggle_nop_keepalive()
|
||||
|
||||
def _wont_linemode(self, *args):
|
||||
"""
|
||||
Client refuses do(linemode). This is common for MUD-specific
|
||||
clients, but we must ask for the sake of raw telnet. We ignore
|
||||
this error.
|
||||
"""
|
||||
pass
|
||||
|
||||
def _send_nop_keepalive(self):
|
||||
"""Send NOP keepalive unless flag is set"""
|
||||
if self.protocol_flags.get("NOPKEEPALIVE"):
|
||||
|
|
@ -193,6 +209,16 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
or option == suppress_ga.SUPPRESS_GA
|
||||
)
|
||||
|
||||
def disableRemote(self, option):
|
||||
return (
|
||||
option == LINEMODE
|
||||
or option == ttype.TTYPE
|
||||
or option == naws.NAWS
|
||||
or option == MCCP
|
||||
or option == mssp.MSSP
|
||||
or option == suppress_ga.SUPPRESS_GA
|
||||
)
|
||||
|
||||
def enableLocal(self, option):
|
||||
"""
|
||||
Call to allow the activation of options for this protocol
|
||||
|
|
@ -219,13 +245,20 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
option (char): The telnet option to disable locally.
|
||||
|
||||
"""
|
||||
if option == LINEMODE:
|
||||
return True
|
||||
if option == ECHO:
|
||||
return True
|
||||
if option == MCCP:
|
||||
self.mccp.no_mccp(option)
|
||||
return True
|
||||
else:
|
||||
return super().disableLocal(option)
|
||||
try:
|
||||
return super().disableLocal(option)
|
||||
except Exception:
|
||||
from evennia.utils import logger
|
||||
|
||||
logger.log_trace()
|
||||
|
||||
def connectionLost(self, reason):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -28,22 +28,24 @@ header where applicable.
|
|||
import re
|
||||
import json
|
||||
from evennia.utils.utils import is_iter
|
||||
|
||||
# MSDP-relevant telnet cmd/opt-codes
|
||||
MSDP = b"\x45"
|
||||
MSDP_VAR = b"\x01" # ^A
|
||||
MSDP_VAL = b"\x02" # ^B
|
||||
MSDP_TABLE_OPEN = b"\x03" # ^C
|
||||
MSDP_TABLE_CLOSE = b"\x04" # ^D
|
||||
MSDP_ARRAY_OPEN = b"\x05" # ^E
|
||||
MSDP_ARRAY_CLOSE = b"\x06" # ^F
|
||||
|
||||
# GMCP
|
||||
GMCP = b"\xc9"
|
||||
from twisted.python.compat import _bytesChr as bchr
|
||||
|
||||
# General Telnet
|
||||
from twisted.conch.telnet import IAC, SB, SE
|
||||
|
||||
# MSDP-relevant telnet cmd/opt-codes
|
||||
MSDP = bchr(69)
|
||||
MSDP_VAR = bchr(1)
|
||||
MSDP_VAL = bchr(2)
|
||||
MSDP_TABLE_OPEN = bchr(3)
|
||||
MSDP_TABLE_CLOSE = bchr(4)
|
||||
|
||||
MSDP_ARRAY_OPEN = bchr(5)
|
||||
MSDP_ARRAY_CLOSE = bchr(6)
|
||||
|
||||
# GMCP
|
||||
GMCP = bchr(201)
|
||||
|
||||
|
||||
# pre-compiled regexes
|
||||
# returns 2-tuple
|
||||
|
|
@ -168,7 +170,7 @@ class TelnetOOB(object):
|
|||
|
||||
"""
|
||||
msdp_cmdname = "{msdp_var}{msdp_cmdname}{msdp_val}".format(
|
||||
msdp_var=MSDP_VAR, msdp_cmdname=cmdname, msdp_val=MSDP_VAL
|
||||
msdp_var=MSDP_VAR.decode(), msdp_cmdname=cmdname, msdp_val=MSDP_VAL.decode()
|
||||
)
|
||||
|
||||
if not (args or kwargs):
|
||||
|
|
@ -186,9 +188,9 @@ class TelnetOOB(object):
|
|||
"{msdp_array_open}"
|
||||
"{msdp_args}"
|
||||
"{msdp_array_close}".format(
|
||||
msdp_array_open=MSDP_ARRAY_OPEN,
|
||||
msdp_array_close=MSDP_ARRAY_CLOSE,
|
||||
msdp_args="".join("%s%s" % (MSDP_VAL, json.dumps(val)) for val in args),
|
||||
msdp_array_open=MSDP_ARRAY_OPEN.decode(),
|
||||
msdp_array_close=MSDP_ARRAY_CLOSE.decode(),
|
||||
msdp_args="".join("%s%s" % (MSDP_VAL.decode(), val) for val in args),
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -199,10 +201,10 @@ class TelnetOOB(object):
|
|||
"{msdp_table_open}"
|
||||
"{msdp_kwargs}"
|
||||
"{msdp_table_close}".format(
|
||||
msdp_table_open=MSDP_TABLE_OPEN,
|
||||
msdp_table_close=MSDP_TABLE_CLOSE,
|
||||
msdp_table_open=MSDP_TABLE_OPEN.decode(),
|
||||
msdp_table_close=MSDP_TABLE_CLOSE.decode(),
|
||||
msdp_kwargs="".join(
|
||||
"%s%s%s%s" % (MSDP_VAR, key, MSDP_VAL, json.dumps(val))
|
||||
"%s%s%s%s" % (MSDP_VAR.decode(), key, MSDP_VAL.decode(), val)
|
||||
for key, val in kwargs.items()
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -100,11 +100,11 @@ def verify_or_create_SSL_key_and_cert(keyfile, certfile):
|
|||
keypair.generate_key(crypto.TYPE_RSA, _PRIVATE_KEY_LENGTH)
|
||||
|
||||
with open(_PRIVATE_KEY_FILE, "wt") as pfile:
|
||||
pfile.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, keypair))
|
||||
pfile.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, keypair).decode("utf-8"))
|
||||
print("Created SSL private key in '{}'.".format(_PRIVATE_KEY_FILE))
|
||||
|
||||
with open(_PUBLIC_KEY_FILE, "wt") as pfile:
|
||||
pfile.write(crypto.dump_publickey(crypto.FILETYPE_PEM, keypair))
|
||||
pfile.write(crypto.dump_publickey(crypto.FILETYPE_PEM, keypair).decode("utf-8"))
|
||||
print("Created SSL public key in '{}'.".format(_PUBLIC_KEY_FILE))
|
||||
|
||||
except Exception as err:
|
||||
|
|
@ -128,7 +128,7 @@ def verify_or_create_SSL_key_and_cert(keyfile, certfile):
|
|||
cert.sign(keypair, "sha1")
|
||||
|
||||
with open(_CERTIFICATE_FILE, "wt") as cfile:
|
||||
cfile.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
|
||||
cfile.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode("utf-8"))
|
||||
print("Created SSL certificate in '{}'.".format(_CERTIFICATE_FILE))
|
||||
|
||||
except Exception as err:
|
||||
|
|
|
|||
|
|
@ -10,11 +10,12 @@ etc. If the client does not support TTYPE, this will be ignored.
|
|||
All data will be stored on the protocol's protocol_flags dictionary,
|
||||
under the 'TTYPE' key.
|
||||
"""
|
||||
from twisted.python.compat import _bytesChr as bchr
|
||||
|
||||
# telnet option codes
|
||||
TTYPE = b"\x18"
|
||||
IS = b"\x00"
|
||||
SEND = b"\x01"
|
||||
TTYPE = bchr(24) # b"\x18"
|
||||
IS = bchr(0) # b"\x00"
|
||||
SEND = bchr(1) # b"\x01"
|
||||
|
||||
# terminal capabilities and their codes
|
||||
MTTS = [
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ from evennia.utils import logger
|
|||
from evennia.comms import channelhandler
|
||||
from evennia.server.sessionhandler import SESSIONS
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
_SA = object.__setattr__
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ _ObjectDB = None
|
|||
_ANSI = None
|
||||
|
||||
# i18n
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
# Handlers for Session.db/ndb operation
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ PSTATUS = chr(18) # ping server or portal status
|
|||
SRESET = chr(19) # server shutdown in reset mode
|
||||
|
||||
# i18n
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
_SERVERNAME = settings.SERVERNAME
|
||||
_MULTISESSION_MODE = settings.MULTISESSION_MODE
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ def _drop_table(db_cursor, table_name):
|
|||
db_cursor.execute("SET FOREIGN_KEY_CHECKS=0;")
|
||||
db_cursor.execute("DROP TABLE {table};".format(table=table_name))
|
||||
db_cursor.execute("SET FOREIGN_KEY_CHECKS=1;")
|
||||
elif _ENGINE == "postgresql_psycopg2":
|
||||
elif _ENGINE == "postgresql":
|
||||
db_cursor.execute("ALTER TABLE {table} DISABLE TRIGGER ALL;".format(table=table_name))
|
||||
db_cursor.execute("DROP TABLE {table};".format(table=table_name))
|
||||
db_cursor.execute("ALTER TABLE {table} ENABLE TRIGGER ALL;".format(table=table_name))
|
||||
|
|
|
|||
|
|
@ -674,6 +674,13 @@ class ANSIString(str, metaclass=ANSIMeta):
|
|||
|
||||
"""
|
||||
|
||||
# A compiled Regex for the format mini-language: https://docs.python.org/3/library/string.html#formatspec
|
||||
re_format = re.compile(
|
||||
r"(?i)(?P<just>(?P<fill>.)?(?P<align>\<|\>|\=|\^))?(?P<sign>\+|\-| )?(?P<alt>\#)?"
|
||||
r"(?P<zero>0)?(?P<width>\d+)?(?P<grouping>\_|\,)?(?:\.(?P<precision>\d+))?"
|
||||
r"(?P<type>b|c|d|e|E|f|F|g|G|n|o|s|x|X|%)?"
|
||||
)
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
"""
|
||||
When creating a new ANSIString, you may use a custom parser that has
|
||||
|
|
@ -733,6 +740,47 @@ class ANSIString(str, metaclass=ANSIMeta):
|
|||
def __str__(self):
|
||||
return self._raw_string
|
||||
|
||||
def __format__(self, format_spec):
|
||||
"""
|
||||
This magic method covers ANSIString's behavior within a str.format() or f-string.
|
||||
|
||||
Current features supported: fill, align, width.
|
||||
|
||||
Args:
|
||||
format_spec (str): The format specification passed by f-string or str.format(). This is a string such as
|
||||
"0<30" which would mean "left justify to 30, filling with zeros". The full specification can be found
|
||||
at https://docs.python.org/3/library/string.html#formatspec
|
||||
|
||||
Returns:
|
||||
ansi_str (str): The formatted ANSIString's .raw() form, for display.
|
||||
"""
|
||||
# This calls the compiled regex stored on ANSIString's class to analyze the format spec.
|
||||
# It returns a dictionary.
|
||||
format_data = self.re_format.match(format_spec).groupdict()
|
||||
clean = self.clean()
|
||||
base_output = ANSIString(self.raw())
|
||||
align = format_data.get("align", "<")
|
||||
fill = format_data.get("fill", " ")
|
||||
|
||||
# Need to coerce width into an integer. We can be certain that it's numeric thanks to regex.
|
||||
width = format_data.get("width", None)
|
||||
if width is None:
|
||||
width = len(clean)
|
||||
else:
|
||||
width = int(width)
|
||||
|
||||
if align == "<":
|
||||
base_output = self.ljust(width, fill)
|
||||
elif align == ">":
|
||||
base_output = self.rjust(width, fill)
|
||||
elif align == "^":
|
||||
base_output = self.center(width, fill)
|
||||
elif align == "=":
|
||||
pass
|
||||
|
||||
# Return the raw string with ANSI markup, ready to be displayed.
|
||||
return base_output.raw()
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Let's make the repr the command that would actually be used to
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ except ImportError:
|
|||
from pickle import dumps, loads
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.safestring import SafeString, SafeBytes
|
||||
from django.utils.safestring import SafeString
|
||||
from evennia.utils.utils import uses_database, is_iter, to_str, to_bytes
|
||||
from evennia.utils import logger
|
||||
|
||||
|
|
@ -549,7 +549,7 @@ def to_pickle(data):
|
|||
def process_item(item):
|
||||
"""Recursive processor and identification of data"""
|
||||
dtype = type(item)
|
||||
if dtype in (str, int, float, bool, bytes, SafeString, SafeBytes):
|
||||
if dtype in (str, int, float, bool, bytes, SafeString):
|
||||
return item
|
||||
elif dtype == tuple:
|
||||
return tuple(process_item(val) for val in item)
|
||||
|
|
@ -577,7 +577,7 @@ def to_pickle(data):
|
|||
except TypeError:
|
||||
return item
|
||||
except Exception:
|
||||
logger.log_error(f"The object {item} of type {type(item)} could not be stored.")
|
||||
logger.log_err(f"The object {item} of type {type(item)} could not be stored.")
|
||||
raise
|
||||
|
||||
return process_item(data)
|
||||
|
|
@ -609,7 +609,7 @@ def from_pickle(data, db_obj=None):
|
|||
def process_item(item):
|
||||
"""Recursive processor and identification of data"""
|
||||
dtype = type(item)
|
||||
if dtype in (str, int, float, bool, bytes, SafeString, SafeBytes):
|
||||
if dtype in (str, int, float, bool, bytes, SafeString):
|
||||
return item
|
||||
elif _IS_PACKED_DBOBJ(item):
|
||||
# this must be checked before tuple
|
||||
|
|
@ -638,7 +638,7 @@ def from_pickle(data, db_obj=None):
|
|||
def process_tree(item, parent):
|
||||
"""Recursive processor, building a parent-tree from iterable data"""
|
||||
dtype = type(item)
|
||||
if dtype in (str, int, float, bool, bytes, SafeString, SafeBytes):
|
||||
if dtype in (str, int, float, bool, bytes, SafeString):
|
||||
return item
|
||||
elif _IS_PACKED_DBOBJ(item):
|
||||
# this must be checked before tuple
|
||||
|
|
@ -716,7 +716,7 @@ def do_pickle(data):
|
|||
try:
|
||||
return dumps(data, protocol=PICKLE_PROTOCOL)
|
||||
except Exception:
|
||||
logger.log_error(f"Could not pickle data for storage: {data}")
|
||||
logger.log_err(f"Could not pickle data for storage: {data}")
|
||||
raise
|
||||
|
||||
|
||||
|
|
@ -725,7 +725,7 @@ def do_unpickle(data):
|
|||
try:
|
||||
return loads(to_bytes(data))
|
||||
except Exception:
|
||||
logger.log_error(f"Could not unpickle data from storage: {data}")
|
||||
logger.log_err(f"Could not unpickle data from storage: {data}")
|
||||
raise
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ _CMD_NOINPUT = cmdhandler.CMD_NOINPUT
|
|||
# Return messages
|
||||
|
||||
# i18n
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
_ERR_NOT_IMPLEMENTED = _(
|
||||
"Menu node '{nodename}' is either not implemented or " "caused an error. Make another choice."
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ from django.forms.fields import CharField
|
|||
from django.forms.widgets import Textarea
|
||||
|
||||
from pickle import loads, dumps
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
|
||||
DEFAULT_PROTOCOL = 4
|
||||
|
|
@ -210,10 +210,10 @@ class PickledObjectField(models.Field):
|
|||
"""
|
||||
Returns the default value for this field.
|
||||
|
||||
The default implementation on models.Field calls force_text
|
||||
The default implementation on models.Field calls force_str
|
||||
on the default, which means you can't set arbitrary Python
|
||||
objects as the default. To fix this, we just return the value
|
||||
without calling force_text on it. Note that if you set a
|
||||
without calling force_str on it. Note that if you set a
|
||||
callable as a default, the field will still call it. It will
|
||||
*not* try to pickle and encode it.
|
||||
|
||||
|
|
@ -267,13 +267,13 @@ class PickledObjectField(models.Field):
|
|||
|
||||
"""
|
||||
if value is not None and not isinstance(value, PickledObject):
|
||||
# We call force_text here explicitly, so that the encoded string
|
||||
# isn't rejected by the postgresql_psycopg2 backend. Alternatively,
|
||||
# We call force_str here explicitly, so that the encoded string
|
||||
# isn't rejected by the postgresql backend. Alternatively,
|
||||
# we could have just registered PickledObject with the psycopg
|
||||
# marshaller (telling it to store it like it would a string), but
|
||||
# since both of these methods result in the same value being stored,
|
||||
# doing things this way is much easier.
|
||||
value = force_text(dbsafe_encode(value, self.compress, self.protocol))
|
||||
value = force_str(dbsafe_encode(value, self.compress, self.protocol))
|
||||
return value
|
||||
|
||||
def value_to_string(self, obj):
|
||||
|
|
|
|||
|
|
@ -98,6 +98,30 @@ class TestMLen(TestCase):
|
|||
self.assertEqual(utils.m_len({"hello": True, "Goodbye": False}), 2)
|
||||
|
||||
|
||||
class TestANSIString(TestCase):
|
||||
"""
|
||||
Verifies that ANSIString's string-API works as intended.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.example_raw = "|relectric |cboogaloo|n"
|
||||
self.example_ansi = ANSIString(self.example_raw)
|
||||
self.example_str = "electric boogaloo"
|
||||
self.example_output = "\x1b[1m\x1b[31melectric \x1b[1m\x1b[36mboogaloo\x1b[0m"
|
||||
|
||||
def test_length(self):
|
||||
self.assertEqual(len(self.example_ansi), 17)
|
||||
|
||||
def test_clean(self):
|
||||
self.assertEqual(self.example_ansi.clean(), self.example_str)
|
||||
|
||||
def test_raw(self):
|
||||
self.assertEqual(self.example_ansi.raw(), self.example_output)
|
||||
|
||||
def test_format(self):
|
||||
self.assertEqual(f"{self.example_ansi:0<20}", self.example_output + "000")
|
||||
|
||||
|
||||
class TestTimeformat(TestCase):
|
||||
"""
|
||||
Default function header from utils.py:
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ from collections import defaultdict, OrderedDict
|
|||
from twisted.internet import threads, reactor
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
from django.apps import apps
|
||||
from evennia.utils import logger
|
||||
|
||||
|
|
@ -1036,7 +1036,7 @@ def uses_database(name="sqlite3"):
|
|||
shortcut to having to use the full backend name.
|
||||
|
||||
Args:
|
||||
name (str): One of 'sqlite3', 'mysql', 'postgresql_psycopg2'
|
||||
name (str): One of 'sqlite3', 'mysql', 'postgresql'
|
||||
or 'oracle'.
|
||||
|
||||
Returns:
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@
|
|||
# http://diveintopython.org/regular_expressions/street_addresses.html#re.matching.2.3
|
||||
#
|
||||
|
||||
from django.urls import path
|
||||
from django.conf.urls import url, include
|
||||
from django.conf.urls import url
|
||||
from django.conf import settings
|
||||
from django.urls import path, include
|
||||
from django.views.generic import RedirectView
|
||||
|
||||
# Setup the root url tree from /
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ with evennia set up automatically and get the Evennia JS lib and
|
|||
JQuery available.
|
||||
-->
|
||||
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
<html dir="ltr" lang="en">
|
||||
<head>
|
||||
<title> {{game_name}} </title>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
This structures the (simple) structure of the
|
||||
webpage 'application'.
|
||||
"""
|
||||
from django.conf.urls import *
|
||||
from django.urls import path
|
||||
from evennia.web.webclient import views as webclient_views
|
||||
|
||||
app_name = "webclient"
|
||||
urlpatterns = [url(r"^$", webclient_views.webclient, name="index")]
|
||||
|
||||
urlpatterns = [path("", webclient_views.webclient, name="index")]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ Allow to customize the menu that appears at the top of every Evennia
|
|||
webpage. Copy this file to your game dir's web/template_overrides/website
|
||||
folder and edit it to add/remove links to the menu.
|
||||
{% endcomment %}
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
<nav class="navbar navbar-dark font-weight-bold navbar-expand-md">
|
||||
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#menu-content" aria-controls="menu-content" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{% load staticfiles sekizai_tags %}
|
||||
{% load static sekizai_tags %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
|
|
|||
|
|
@ -10,9 +10,12 @@ django-filter >= 2.2.0, < 2.3
|
|||
django-sekizai
|
||||
inflect
|
||||
autobahn >= 17.9.3
|
||||
model_mommy
|
||||
|
||||
# try to resolve dependency issue in py3.7
|
||||
attrs >= 19.2.0
|
||||
|
||||
# testing and development
|
||||
model_mommy
|
||||
mock >= 1.0.1
|
||||
anything
|
||||
black
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue