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 = {
|
DATABASES = {
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.postgresql_psycopg2",
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
"NAME": "evennia",
|
"NAME": "evennia",
|
||||||
"USER": "evennia",
|
"USER": "evennia",
|
||||||
"PASSWORD": "password",
|
"PASSWORD": "password",
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,10 @@ without arguments starts a full interactive Python console.
|
||||||
`.get_command_info()` method for easier overloading and access. (Volund)
|
`.get_command_info()` method for easier overloading and access. (Volund)
|
||||||
- Removed unused `CYCLE_LOGFILES` setting. Added `SERVER_LOG_DAY_ROTATION`
|
- Removed unused `CYCLE_LOGFILES` setting. Added `SERVER_LOG_DAY_ROTATION`
|
||||||
and `SERVER_LOG_MAX_SIZE` (and equivalent for PORTAL) to control log 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)
|
## Evennia 0.9 (2018-2019)
|
||||||
|
|
|
||||||
136
INSTALL.md
136
INSTALL.md
|
|
@ -1,137 +1,5 @@
|
||||||
|
|
||||||
# Evennia installation
|
# Evennia installation
|
||||||
|
|
||||||
The latest and more detailed installation instructions can be found
|
You can find the latest updated installation instructions and
|
||||||
[here](https://github.com/evennia/evennia/wiki/Getting-Started).
|
requirements [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!
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ from evennia.scripts.scripthandler import ScriptHandler
|
||||||
from evennia.commands.cmdsethandler import CmdSetHandler
|
from evennia.commands.cmdsethandler import CmdSetHandler
|
||||||
from evennia.utils.optionhandler import OptionHandler
|
from evennia.utils.optionhandler import OptionHandler
|
||||||
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import gettext as _
|
||||||
from random import getrandbits
|
from random import getrandbits
|
||||||
|
|
||||||
__all__ = ("DefaultAccount",)
|
__all__ = ("DefaultAccount",)
|
||||||
|
|
@ -828,7 +828,10 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
server.
|
server.
|
||||||
|
|
||||||
Args:
|
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
|
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.
|
at_msg_send() hook will be called. If iterable, call on all entities.
|
||||||
session (Session or list, optional): Session object or a list of
|
session (Session or list, optional): Session object or a list of
|
||||||
|
|
@ -859,7 +862,13 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
kwargs["options"] = options
|
kwargs["options"] = options
|
||||||
|
|
||||||
if text is not None:
|
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
|
# session relay
|
||||||
sessions = make_iter(session) if session else self.sessions.all()
|
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 import logger, utils
|
||||||
from evennia.utils.utils import string_suggestions
|
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
|
_IN_GAME_ERRORS = settings.IN_GAME_ERRORS
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ Set theory.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from weakref import WeakKeyDictionary
|
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
|
from evennia.utils.utils import inherits_from, is_iter
|
||||||
|
|
||||||
__all__ = ("CmdSet",)
|
__all__ = ("CmdSet",)
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ from evennia.utils import logger, utils
|
||||||
from evennia.commands.cmdset import CmdSet
|
from evennia.commands.cmdset import CmdSet
|
||||||
from evennia.server.models import ServerConfig
|
from evennia.server.models import ServerConfig
|
||||||
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
__all__ = ("import_cmdset", "CmdSetHandler")
|
__all__ = ("import_cmdset", "CmdSetHandler")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ All commands in Evennia inherit from the 'Command' class in this module.
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
import math
|
import math
|
||||||
|
import inspect
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
@ -74,6 +75,13 @@ def _init_command(cls, **kwargs):
|
||||||
cls.is_exit = False
|
cls.is_exit = False
|
||||||
if not hasattr(cls, "help_category"):
|
if not hasattr(cls, "help_category"):
|
||||||
cls.help_category = "general"
|
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()
|
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.utils.evmore import EvMore
|
||||||
from evennia.prototypes import spawner, prototypes as protlib, menus as olc_menus
|
from evennia.prototypes import spawner, prototypes as protlib, menus as olc_menus
|
||||||
from evennia.utils.ansi import raw
|
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)
|
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||||
|
|
||||||
|
|
@ -1912,8 +1913,8 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
typeclass[/switch] <object> [= typeclass.path]
|
typeclass[/switch] <object> [= typeclass.path]
|
||||||
type ''
|
typeclass/prototype <object> = prototype_key
|
||||||
parent ''
|
|
||||||
typeclass/list/show [typeclass.path]
|
typeclass/list/show [typeclass.path]
|
||||||
swap - this is a shorthand for using /force/reset flags.
|
swap - this is a shorthand for using /force/reset flags.
|
||||||
update - this is a shorthand for using the /force/reload flag.
|
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
|
list - show available typeclasses. Only typeclasses in modules actually
|
||||||
imported or used from somewhere in the code will show up here
|
imported or used from somewhere in the code will show up here
|
||||||
(those typeclasses are still available if you know the path)
|
(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:
|
Example:
|
||||||
type button = examples.red_button.RedButton
|
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
|
If the typeclass_path is not given, the current object's typeclass is
|
||||||
assumed.
|
assumed.
|
||||||
|
|
@ -1954,7 +1958,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
key = "typeclass"
|
key = "typeclass"
|
||||||
aliases = ["type", "parent", "swap", "update"]
|
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)"
|
locks = "cmd:perm(typeclass) or perm(Builder)"
|
||||||
help_category = "Building"
|
help_category = "Building"
|
||||||
|
|
||||||
|
|
@ -2038,6 +2042,27 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
new_typeclass = self.rhs or obj.path
|
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:
|
if "show" in self.switches or "examine" in self.switches:
|
||||||
string = "%s's current typeclass is %s." % (obj.name, obj.__class__)
|
string = "%s's current typeclass is %s." % (obj.name, obj.__class__)
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
|
|
@ -2070,11 +2095,34 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||||
hooks = "at_object_creation" if update else "all"
|
hooks = "at_object_creation" if update else "all"
|
||||||
old_typeclass_path = obj.typeclass_path
|
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
|
# we let this raise exception if needed
|
||||||
obj.swap_typeclass(
|
obj.swap_typeclass(
|
||||||
new_typeclass, clean_attributes=reset, clean_cmdsets=reset, run_start_hooks=hooks
|
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:
|
if is_same:
|
||||||
string = "%s updated its existing typeclass (%s).\n" % (obj.name, obj.path)
|
string = "%s updated its existing typeclass (%s).\n" % (obj.name, obj.path)
|
||||||
else:
|
else:
|
||||||
|
|
@ -2091,6 +2139,11 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||||
string += " All old attributes where deleted before the swap."
|
string += " All old attributes where deleted before the swap."
|
||||||
else:
|
else:
|
||||||
string += " Attributes set before swap were not removed."
|
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)
|
caller.msg(string)
|
||||||
|
|
||||||
|
|
@ -2832,8 +2885,8 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
||||||
reference. A puppeted object cannot be moved to None.
|
reference. A puppeted object cannot be moved to None.
|
||||||
loc - teleport object to the target's location instead of its contents
|
loc - teleport object to the target's location instead of its contents
|
||||||
|
|
||||||
Teleports an object somewhere. If no object is given, you yourself
|
Teleports an object somewhere. If no object is given, you yourself are
|
||||||
is teleported to the target location.
|
teleported to the target location.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "tel"
|
key = "tel"
|
||||||
|
|
@ -2998,7 +3051,8 @@ class CmdScript(COMMAND_DEFAULT_CLASS):
|
||||||
ok = obj.scripts.add(self.rhs, autostart=True)
|
ok = obj.scripts.add(self.rhs, autostart=True)
|
||||||
if not ok:
|
if not ok:
|
||||||
result.append(
|
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))
|
% (self.rhs, obj.get_display_name(caller))
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ class CmdHelp(Command):
|
||||||
evmore.msg(self.caller, text, session=self.session)
|
evmore.msg(self.caller, text, session=self.session)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.msg((text, {"type": "help"}))
|
self.msg(text=(text, {"type": "help"}))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_help_entry(title, help_text, aliases=None, suggested=None):
|
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.",
|
"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):
|
def test_lock(self):
|
||||||
self.call(building.CmdLock(), "", "Usage: ")
|
self.call(building.CmdLock(), "", "Usage: ")
|
||||||
self.call(building.CmdLock(), "Obj = test:all()", "Added lock 'test:all()' to Obj.")
|
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.commands import cmdset, command
|
||||||
from evennia.utils.logger import tail_log_file
|
from evennia.utils.logger import tail_log_file
|
||||||
from evennia.utils.utils import class_from_module
|
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
|
# we must late-import these since any overloads are likely to
|
||||||
# themselves be using these classes leading to a circular import.
|
# themselves be using these classes leading to a circular import.
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ Comm system components.
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager
|
from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
|
from evennia.utils.utils import dbref
|
||||||
|
|
||||||
_GA = object.__getattribute__
|
_GA = object.__getattribute__
|
||||||
_AccountDB = None
|
_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):
|
def identify_object(inp):
|
||||||
"""
|
"""
|
||||||
Helper function. Identifies if an object is an account or an object;
|
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
|
messaging. It relies on a modified msg() and is meant as an
|
||||||
inspiration and starting point to how to do stuff like this.
|
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
|
An object can have the following genders:
|
||||||
the character's gender, for example the echo
|
- 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)
|
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"
|
The default gender is "ambiguous" (they/them/their/theirs).
|
||||||
depending on the gender of the object being messaged. Default gender
|
|
||||||
is "ambiguous" (they).
|
|
||||||
|
|
||||||
To use, have DefaultCharacter inherit from this, or change
|
To use, have DefaultCharacter inherit from this, or change
|
||||||
setting.DEFAULT_CHARACTER to point to this class.
|
setting.DEFAULT_CHARACTER to point to this class.
|
||||||
|
|
||||||
The `@gender` command needs to be added to the default cmdset before
|
The `@gender` command is used to set the gender. It needs to be added to the
|
||||||
it becomes available.
|
default cmdset before it becomes available.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
from evennia.utils import logger
|
||||||
from evennia import DefaultCharacter
|
from evennia import DefaultCharacter
|
||||||
from evennia import Command
|
from evennia import Command
|
||||||
|
|
||||||
|
|
@ -114,7 +127,10 @@ class GenderCharacter(DefaultCharacter):
|
||||||
gender-aware markers in output.
|
gender-aware markers in output.
|
||||||
|
|
||||||
Args:
|
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
|
from_obj (obj, optional): object that is sending. If
|
||||||
given, at_msg_send will be called
|
given, at_msg_send will be called
|
||||||
session (Session or list, optional): session or list of
|
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.
|
All extra kwargs will be passed on to the protocol.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# pre-process the text before continuing
|
|
||||||
try:
|
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:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
logger.log_trace(e)
|
||||||
super().msg(text, from_obj=from_obj, session=session, **kwargs)
|
super().msg(text, from_obj=from_obj, session=session, **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -798,6 +798,16 @@ class RecogHandler(object):
|
||||||
# recog_mask log not passed, disable recog
|
# recog_mask log not passed, disable recog
|
||||||
return obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key
|
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):
|
def remove(self, obj):
|
||||||
"""
|
"""
|
||||||
Clear recog for a given object.
|
Clear recog for a given object.
|
||||||
|
|
@ -896,10 +906,9 @@ class CmdSay(RPCommand): # replaces standard say
|
||||||
caller.msg("Say what?")
|
caller.msg("Say what?")
|
||||||
return
|
return
|
||||||
|
|
||||||
# calling the speech hook on the location
|
# calling the speech modifying hook
|
||||||
speech = caller.location.at_before_say(self.args)
|
speech = caller.at_before_say(self.args)
|
||||||
# preparing the speech with sdesc/speech parsing.
|
# preparing the speech with sdesc/speech parsing.
|
||||||
speech = '/me says, "{speech}"'.format(speech=speech)
|
|
||||||
targets = self.caller.location.contents
|
targets = self.caller.location.contents
|
||||||
send_emote(self.caller, targets, speech, anonymous_add=None)
|
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:
|
except SdescError as err:
|
||||||
caller.msg(err)
|
caller.msg(err)
|
||||||
return
|
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))
|
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.
|
Recognize another person in the same room.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
recog
|
||||||
recog sdesc as alias
|
recog sdesc as alias
|
||||||
forget alias
|
forget alias
|
||||||
|
|
||||||
|
|
@ -1048,8 +1061,8 @@ class CmdRecog(RPCommand): # assign personal alias to object in room
|
||||||
recog tall man as Griatch
|
recog tall man as Griatch
|
||||||
forget griatch
|
forget griatch
|
||||||
|
|
||||||
This will assign a personal alias for a person, or
|
This will assign a personal alias for a person, or forget said alias.
|
||||||
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):
|
def parse(self):
|
||||||
"Parse for the sdesc as alias structure"
|
"Parse for the sdesc as alias structure"
|
||||||
|
self.sdesc, self.alias = "", ""
|
||||||
if " as " in self.args:
|
if " as " in self.args:
|
||||||
self.sdesc, self.alias = [part.strip() for part in self.args.split(" as ", 2)]
|
self.sdesc, self.alias = [part.strip() for part in self.args.split(" as ", 2)]
|
||||||
elif self.args:
|
elif self.args:
|
||||||
|
|
@ -1070,22 +1084,47 @@ class CmdRecog(RPCommand): # assign personal alias to object in room
|
||||||
def func(self):
|
def func(self):
|
||||||
"Assign the recog"
|
"Assign the recog"
|
||||||
caller = self.caller
|
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(".?!")
|
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
|
prefixed_sdesc = sdesc if sdesc.startswith(_PREFIX) else _PREFIX + sdesc
|
||||||
candidates = caller.location.contents
|
candidates = caller.location.contents
|
||||||
matches = parse_sdescs_and_recogs(caller, candidates, prefixed_sdesc, search_mode=True)
|
matches = parse_sdescs_and_recogs(caller, candidates, prefixed_sdesc, search_mode=True)
|
||||||
nmatches = len(matches)
|
nmatches = len(matches)
|
||||||
# handle 0, 1 and >1 matches
|
# handle 0 and >1 matches
|
||||||
if nmatches == 0:
|
if nmatches == 0:
|
||||||
caller.msg(_EMOTE_NOMATCH_ERROR.format(ref=sdesc))
|
caller.msg(_EMOTE_NOMATCH_ERROR.format(ref=sdesc))
|
||||||
elif nmatches > 1:
|
elif nmatches > 1:
|
||||||
reflist = [
|
reflist = [
|
||||||
"%s%s%s (%s%s)"
|
"{}{}{} ({}{})".format(
|
||||||
% (
|
|
||||||
inum + 1,
|
inum + 1,
|
||||||
_NUM_SEP,
|
_NUM_SEP,
|
||||||
_RE_PREFIX.sub("", sdesc),
|
_RE_PREFIX.sub("", sdesc),
|
||||||
|
|
@ -1095,17 +1134,20 @@ class CmdRecog(RPCommand): # assign personal alias to object in room
|
||||||
for inum, obj in enumerate(matches)
|
for inum, obj in enumerate(matches)
|
||||||
]
|
]
|
||||||
caller.msg(_EMOTE_MULTIMATCH_ERROR.format(ref=sdesc, reflist="\n ".join(reflist)))
|
caller.msg(_EMOTE_MULTIMATCH_ERROR.format(ref=sdesc, reflist="\n ".join(reflist)))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
# one single match
|
||||||
obj = matches[0]
|
obj = matches[0]
|
||||||
if not obj.access(self.obj, "enable_recog", default=True):
|
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).
|
# 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
|
return
|
||||||
if self.cmdstring == "forget":
|
if forget_mode:
|
||||||
# remove existing recog
|
# remove existing recog
|
||||||
caller.recog.remove(obj)
|
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:
|
else:
|
||||||
|
# set recog
|
||||||
sdesc = obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key
|
sdesc = obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key
|
||||||
try:
|
try:
|
||||||
alias = caller.recog.add(obj, alias)
|
alias = caller.recog.add(obj, alias)
|
||||||
|
|
@ -1509,6 +1551,20 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject):
|
||||||
# initializing sdesc
|
# initializing sdesc
|
||||||
self.sdesc.add("A normal person")
|
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):
|
def process_sdesc(self, sdesc, obj, **kwargs):
|
||||||
"""
|
"""
|
||||||
Allows to customize how your sdesc is displayed (primarily by
|
Allows to customize how your sdesc is displayed (primarily by
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,8 @@ class TestRPSystem(EvenniaTest):
|
||||||
self.speaker.recog.remove(self.receiver1)
|
self.speaker.recog.remove(self.receiver1)
|
||||||
self.assertEqual(self.speaker.recog.get(self.receiver1), sdesc1)
|
self.assertEqual(self.speaker.recog.get(self.receiver1), sdesc1)
|
||||||
|
|
||||||
|
self.assertEqual(self.speaker.recog.all(), {"Mr Receiver2": self.receiver2})
|
||||||
|
|
||||||
def test_parse_language(self):
|
def test_parse_language(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
rpsystem.parse_language(self.speaker, emote),
|
rpsystem.parse_language(self.speaker, emote),
|
||||||
|
|
@ -233,6 +235,49 @@ class TestRPSystem(EvenniaTest):
|
||||||
self.assertEqual(self.speaker.search("colliding"), self.receiver2)
|
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
|
# Testing of ExtendedRoom contrib
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
|
||||||
|
|
@ -26,3 +26,16 @@ def at_webserver_root_creation(web_root):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return 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:
|
Usage:
|
||||||
inside()
|
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
|
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):
|
def holds(accessing_obj, accessed_obj, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Usage:
|
Usage:
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ to any other identifier you can use.
|
||||||
import re
|
import re
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia.utils import logger, utils
|
from evennia.utils import logger, utils
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
__all__ = ("LockHandler", "LockException")
|
__all__ = ("LockHandler", "LockException")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ except ImportError:
|
||||||
|
|
||||||
from evennia import settings_default
|
from evennia import settings_default
|
||||||
from evennia.locks import lockfuncs
|
from evennia.locks import lockfuncs
|
||||||
|
from evennia.utils.create import create_object
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
# Lock testing
|
# Lock testing
|
||||||
|
|
@ -179,6 +180,13 @@ class TestLockfuncs(EvenniaTest):
|
||||||
self.assertEqual(False, lockfuncs.inside(self.char1, self.room2))
|
self.assertEqual(False, lockfuncs.inside(self.char1, self.room2))
|
||||||
self.assertEqual(True, lockfuncs.holds(self.room1, self.char1))
|
self.assertEqual(True, lockfuncs.holds(self.room1, self.char1))
|
||||||
self.assertEqual(False, lockfuncs.holds(self.room2, 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):
|
def test_has_account(self):
|
||||||
self.assertEqual(True, lockfuncs.has_account(self.char1, None))
|
self.assertEqual(True, lockfuncs.has_account(self.char1, None))
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ from evennia.utils.utils import (
|
||||||
list_to_string,
|
list_to_string,
|
||||||
to_str,
|
to_str,
|
||||||
)
|
)
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
_INFLECT = inflect.engine()
|
_INFLECT = inflect.engine()
|
||||||
_MULTISESSION_MODE = settings.MULTISESSION_MODE
|
_MULTISESSION_MODE = settings.MULTISESSION_MODE
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from evennia.scripts.models import ScriptDB
|
||||||
from evennia.utils import create
|
from evennia.utils import create
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
|
||||||
class ScriptHandler(object):
|
class ScriptHandler(object):
|
||||||
|
|
@ -78,11 +78,20 @@ class ScriptHandler(object):
|
||||||
scriptclass, key=key, account=self.obj, autostart=autostart
|
scriptclass, key=key, account=self.obj, autostart=autostart
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# the normal - adding to an Object
|
# the normal - adding to an Object. We wait to autostart so we can differentiate
|
||||||
script = create.create_script(scriptclass, key=key, obj=self.obj, autostart=autostart)
|
# 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:
|
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
|
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
|
return True
|
||||||
|
|
||||||
def start(self, key):
|
def start(self, key):
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ ability to run timers.
|
||||||
from twisted.internet.defer import Deferred, maybeDeferred
|
from twisted.internet.defer import Deferred, maybeDeferred
|
||||||
from twisted.internet.task import LoopingCall
|
from twisted.internet.task import LoopingCall
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
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.typeclasses.models import TypeclassBase
|
||||||
from evennia.scripts.models import ScriptDB
|
from evennia.scripts.models import ScriptDB
|
||||||
from evennia.scripts.manager import ScriptManager
|
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.
|
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():
|
if not self.is_valid():
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|
@ -218,10 +221,13 @@ class ScriptBase(ScriptDB, metaclass=TypeclassBase):
|
||||||
self.at_repeat()
|
self.at_repeat()
|
||||||
|
|
||||||
# check repeats
|
# check repeats
|
||||||
callcount = self.ndb._task.callcount
|
if self.ndb._task:
|
||||||
maxcount = self.db_repeats
|
# we need to check for the task in case stop() was called
|
||||||
if maxcount > 0 and maxcount <= callcount:
|
# inside at_repeat() and it already went away.
|
||||||
self.stop()
|
callcount = self.ndb._task.callcount
|
||||||
|
maxcount = self.db_repeats
|
||||||
|
if maxcount > 0 and maxcount <= callcount:
|
||||||
|
self.stop()
|
||||||
|
|
||||||
def _step_task(self):
|
def _step_task(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -339,9 +345,9 @@ class DefaultScript(ScriptBase):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
obj = create.create_script(**kwargs)
|
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)
|
errors.append("The script '%s' encountered errors and could not be created." % key)
|
||||||
logger.log_err(e)
|
|
||||||
|
|
||||||
return obj, errors
|
return obj, errors
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -115,3 +115,10 @@ def check_warnings(settings):
|
||||||
print(" [Devel: settings.IN_GAME_ERRORS is True. Turn off in production.]")
|
print(" [Devel: settings.IN_GAME_ERRORS is True. Turn off in production.]")
|
||||||
if settings.ALLOWED_HOSTS == ["*"]:
|
if settings.ALLOWED_HOSTS == ["*"]:
|
||||||
print(" [Devel: settings.ALLOWED_HOSTS set to '*' (all). Limit in production.]")
|
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"
|
PYTHON_MIN = "3.7"
|
||||||
TWISTED_MIN = "18.0.0"
|
TWISTED_MIN = "18.0.0"
|
||||||
DJANGO_MIN = "2.1"
|
DJANGO_MIN = "2.1"
|
||||||
DJANGO_REC = "2.2.9"
|
DJANGO_REC = "2.2"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sys.path[1] = EVENNIA_ROOT
|
sys.path[1] = EVENNIA_ROOT
|
||||||
|
|
@ -1281,7 +1281,7 @@ def check_main_evennia_dependencies():
|
||||||
try:
|
try:
|
||||||
dversion = ".".join(str(num) for num in django.VERSION if isinstance(num, int))
|
dversion = ".".join(str(num) for num in django.VERSION if isinstance(num, int))
|
||||||
# only the main version (1.5, not 1.5.4.0)
|
# 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):
|
if LooseVersion(dversion) < LooseVersion(DJANGO_MIN):
|
||||||
print(ERROR_DJANGO_MIN.format(dversion=dversion_main, django_min=DJANGO_MIN))
|
print(ERROR_DJANGO_MIN.format(dversion=dversion_main, django_min=DJANGO_MIN))
|
||||||
error = True
|
error = True
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# Evennia Game Index Client
|
# 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
|
(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
|
Evennia. By listing your game on the index, you make it easy for other
|
||||||
people in the community to discover your creation.
|
people in the community to discover your creation.
|
||||||
|
|
@ -14,74 +14,24 @@ on remedying this.*
|
||||||
|
|
||||||
## Listing your Game
|
## Listing your Game
|
||||||
|
|
||||||
To list your game, you'll need to enable the Evennia Game Index client.
|
To list your game, go to your game dir and run
|
||||||
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:
|
|
||||||
|
|
||||||
```python
|
evennia connections
|
||||||
"""
|
|
||||||
Server plugin services
|
|
||||||
|
|
||||||
This plugin module can define user-created services for the Server to
|
Follow the prompts to add details to the listing. Use `evennia reload`. In your log (visible with `evennia --log`
|
||||||
start.
|
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
|
## Detailed settings
|
||||||
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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
If you don't want to use the wizard you can configure your game listing by opening up `server/conf/settings.py` and
|
||||||
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
|
|
||||||
using the following as a starting point:
|
using the following as a starting point:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
######################################################################
|
######################################################################
|
||||||
# Contrib config
|
# Game index
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
|
GAME_INDEX_ENABLED = True
|
||||||
GAME_INDEX_LISTING = {
|
GAME_INDEX_LISTING = {
|
||||||
'game_status': 'pre-alpha',
|
'game_status': 'pre-alpha',
|
||||||
# Optional, comment out or remove if N/A
|
# Optional, comment out or remove if N/A
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ Everything starts at handle_setup()
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from django.conf import settings
|
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.accounts.models import AccountDB
|
||||||
from evennia.server.models import ServerConfig
|
from evennia.server.models import ServerConfig
|
||||||
from evennia.utils import create, logger
|
from evennia.utils import create, logger
|
||||||
|
|
|
||||||
|
|
@ -576,8 +576,7 @@ def msdp_list(session, *args, **kwargs):
|
||||||
fieldnames = [tup[1] for tup in monitor_infos]
|
fieldnames = [tup[1] for tup in monitor_infos]
|
||||||
session.msg(reported_variables=(fieldnames, {}))
|
session.msg(reported_variables=(fieldnames, {}))
|
||||||
if "sendable_variables" in args_lower:
|
if "sendable_variables" in args_lower:
|
||||||
# no default sendable variables
|
session.msg(sendable_variables=(_monitorable, {}))
|
||||||
session.msg(sendable_variables=([], {}))
|
|
||||||
|
|
||||||
|
|
||||||
def msdp_report(session, *args, **kwargs):
|
def msdp_report(session, *args, **kwargs):
|
||||||
|
|
@ -597,6 +596,17 @@ def msdp_unreport(session, *args, **kwargs):
|
||||||
unmonitor(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
|
# client specific
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -314,7 +314,9 @@ class AMPMultiConnectionProtocol(amp.AMP):
|
||||||
try:
|
try:
|
||||||
super(AMPMultiConnectionProtocol, self).dataReceived(data)
|
super(AMPMultiConnectionProtocol, self).dataReceived(data)
|
||||||
except KeyError:
|
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:
|
elif self.multibatches:
|
||||||
# invalid AMP, but we have a pending multi-batch that is not yet complete
|
# invalid AMP, but we have a pending multi-batch that is not yet complete
|
||||||
if data[-2:] == NULNUL:
|
if data[-2:] == NULNUL:
|
||||||
|
|
@ -323,7 +325,9 @@ class AMPMultiConnectionProtocol(amp.AMP):
|
||||||
try:
|
try:
|
||||||
super(AMPMultiConnectionProtocol, self).dataReceived(data)
|
super(AMPMultiConnectionProtocol, self).dataReceived(data)
|
||||||
except KeyError:
|
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:
|
else:
|
||||||
# not an AMP communication, return warning
|
# not an AMP communication, return warning
|
||||||
self.transport.write(_HTTP_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.
|
mccp_compress and calling it from its write methods.
|
||||||
"""
|
"""
|
||||||
import zlib
|
import zlib
|
||||||
|
from twisted.python.compat import _bytesChr as chr
|
||||||
|
|
||||||
# negotiations for v1 and v2 of the protocol
|
# negotiations for v1 and v2 of the protocol
|
||||||
MCCP = b"\x56"
|
MCCP = chr(86) # b"\x56"
|
||||||
FLUSH = zlib.Z_SYNC_FLUSH
|
FLUSH = zlib.Z_SYNC_FLUSH
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,11 @@ active players and so on.
|
||||||
"""
|
"""
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia.utils import utils
|
from evennia.utils import utils
|
||||||
|
from twisted.python.compat import _bytesChr as bchr
|
||||||
|
|
||||||
MSSP = b"\x46"
|
MSSP = bchr(70) # b"\x46"
|
||||||
MSSP_VAR = b"\x01"
|
MSSP_VAR = bchr(1) # b"\x01"
|
||||||
MSSP_VAL = b"\x02"
|
MSSP_VAL = bchr(2) # b"\x02"
|
||||||
|
|
||||||
# try to get the customized mssp info, if it exists.
|
# try to get the customized mssp info, if it exists.
|
||||||
MSSPTable_CUSTOM = utils.variable_from_module(settings.MSSP_META_MODULE, "MSSPTable", default={})
|
MSSPTable_CUSTOM = utils.variable_from_module(settings.MSSP_META_MODULE, "MSSPTable", default={})
|
||||||
|
|
@ -86,7 +87,7 @@ class Mssp(object):
|
||||||
"PLAYERS": self.get_player_count,
|
"PLAYERS": self.get_player_count,
|
||||||
"UPTIME": self.get_uptime,
|
"UPTIME": self.get_uptime,
|
||||||
"PORT": list(
|
"PORT": list(
|
||||||
reversed(settings.TELNET_PORTS)
|
str(port) for port in reversed(settings.TELNET_PORTS)
|
||||||
), # most important port should be last in list
|
), # most important port should be last in list
|
||||||
# Evennia auto-filled
|
# Evennia auto-filled
|
||||||
"CRAWL DELAY": "-1",
|
"CRAWL DELAY": "-1",
|
||||||
|
|
@ -119,10 +120,15 @@ class Mssp(object):
|
||||||
if utils.is_iter(value):
|
if utils.is_iter(value):
|
||||||
for partval in value:
|
for partval in value:
|
||||||
varlist += (
|
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:
|
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
|
# send to crawler by subnegotiation
|
||||||
self.protocol.requestNegotiation(MSSP, varlist)
|
self.protocol.requestNegotiation(MSSP, varlist)
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,12 @@ http://www.gammon.com.au/mushclient/addingservermxp.htm
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
from twisted.python.compat import _bytesChr as bchr
|
||||||
|
|
||||||
LINKS_SUB = re.compile(r"\|lc(.*?)\|lt(.*?)\|le", re.DOTALL)
|
LINKS_SUB = re.compile(r"\|lc(.*?)\|lt(.*?)\|le", re.DOTALL)
|
||||||
|
|
||||||
# MXP Telnet option
|
# MXP Telnet option
|
||||||
MXP = b"\x5b"
|
MXP = bchr(91) # b"\x5b"
|
||||||
|
|
||||||
MXP_TEMPSECURE = "\x1B[4z"
|
MXP_TEMPSECURE = "\x1B[4z"
|
||||||
MXP_SEND = MXP_TEMPSECURE + '<SEND HREF="\\1">' + "\\2" + MXP_TEMPSECURE + "</SEND>"
|
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 codecs import encode as codecs_encode
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from twisted.python.compat import _bytesChr as bchr
|
||||||
|
|
||||||
NAWS = b"\x1f"
|
NAWS = bchr(31) # b"\x1f"
|
||||||
IS = b"\x00"
|
IS = bchr(0) # b"\x00"
|
||||||
# default taken from telnet specification
|
# default taken from telnet specification
|
||||||
DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
|
DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
|
||||||
DEFAULT_HEIGHT = settings.CLIENT_DEFAULT_HEIGHT
|
DEFAULT_HEIGHT = settings.CLIENT_DEFAULT_HEIGHT
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,16 @@ INFO_DICT = {
|
||||||
"webserver_internal": [],
|
"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
|
# Portal Service object
|
||||||
# -------------------------------------------------------------
|
# -------------------------------------------------------------
|
||||||
|
|
@ -190,7 +200,6 @@ class Portal(object):
|
||||||
self.sessions.disconnect_all()
|
self.sessions.disconnect_all()
|
||||||
if _stop_server:
|
if _stop_server:
|
||||||
self.amp_protocol.stop_server(mode="shutdown")
|
self.amp_protocol.stop_server(mode="shutdown")
|
||||||
|
|
||||||
if not _reactor_stopping:
|
if not _reactor_stopping:
|
||||||
# shutting down the reactor will trigger another signal. We set
|
# shutting down the reactor will trigger another signal. We set
|
||||||
# a flag to avoid loops.
|
# a flag to avoid loops.
|
||||||
|
|
@ -379,6 +388,14 @@ if WEBSERVER_ENABLED:
|
||||||
webclientstr = "webclient-websocket%s: %s" % (w_ifacestr, port)
|
webclientstr = "webclient-websocket%s: %s" % (w_ifacestr, port)
|
||||||
INFO_DICT["webclient"].append(webclientstr)
|
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 = Website(web_root, logPath=settings.HTTP_LOG_FILE)
|
||||||
web_root.is_portal = True
|
web_root.is_portal = True
|
||||||
proxy_service = internet.TCPServer(proxyport, web_root, interface=interface)
|
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
|
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
|
# default taken from telnet specification
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,13 +75,21 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
||||||
self.protocol_key = "telnet"
|
self.protocol_key = "telnet"
|
||||||
super().__init__(*args, **kwargs)
|
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):
|
def connectionMade(self):
|
||||||
"""
|
"""
|
||||||
This is called when the connection is first established.
|
This is called when the connection is first established.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# important in order to work normally with standard telnet
|
# important in order to work normally with standard telnet
|
||||||
self.do(LINEMODE)
|
self.do(LINEMODE).addErrback(self._wont_linemode)
|
||||||
# initialize the session
|
# initialize the session
|
||||||
self.line_buffer = b""
|
self.line_buffer = b""
|
||||||
client_address = self.transport.client
|
client_address = self.transport.client
|
||||||
|
|
@ -126,6 +134,14 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
||||||
self.nop_keep_alive = None
|
self.nop_keep_alive = None
|
||||||
self.toggle_nop_keepalive()
|
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):
|
def _send_nop_keepalive(self):
|
||||||
"""Send NOP keepalive unless flag is set"""
|
"""Send NOP keepalive unless flag is set"""
|
||||||
if self.protocol_flags.get("NOPKEEPALIVE"):
|
if self.protocol_flags.get("NOPKEEPALIVE"):
|
||||||
|
|
@ -193,6 +209,16 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
||||||
or option == suppress_ga.SUPPRESS_GA
|
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):
|
def enableLocal(self, option):
|
||||||
"""
|
"""
|
||||||
Call to allow the activation of options for this protocol
|
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.
|
option (char): The telnet option to disable locally.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if option == LINEMODE:
|
||||||
|
return True
|
||||||
if option == ECHO:
|
if option == ECHO:
|
||||||
return True
|
return True
|
||||||
if option == MCCP:
|
if option == MCCP:
|
||||||
self.mccp.no_mccp(option)
|
self.mccp.no_mccp(option)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return super().disableLocal(option)
|
try:
|
||||||
|
return super().disableLocal(option)
|
||||||
|
except Exception:
|
||||||
|
from evennia.utils import logger
|
||||||
|
|
||||||
|
logger.log_trace()
|
||||||
|
|
||||||
def connectionLost(self, reason):
|
def connectionLost(self, reason):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -28,22 +28,24 @@ header where applicable.
|
||||||
import re
|
import re
|
||||||
import json
|
import json
|
||||||
from evennia.utils.utils import is_iter
|
from evennia.utils.utils import is_iter
|
||||||
|
from twisted.python.compat import _bytesChr as bchr
|
||||||
# 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"
|
|
||||||
|
|
||||||
# General Telnet
|
# General Telnet
|
||||||
from twisted.conch.telnet import IAC, SB, SE
|
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
|
# pre-compiled regexes
|
||||||
# returns 2-tuple
|
# returns 2-tuple
|
||||||
|
|
@ -168,7 +170,7 @@ class TelnetOOB(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
msdp_cmdname = "{msdp_var}{msdp_cmdname}{msdp_val}".format(
|
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):
|
if not (args or kwargs):
|
||||||
|
|
@ -186,9 +188,9 @@ class TelnetOOB(object):
|
||||||
"{msdp_array_open}"
|
"{msdp_array_open}"
|
||||||
"{msdp_args}"
|
"{msdp_args}"
|
||||||
"{msdp_array_close}".format(
|
"{msdp_array_close}".format(
|
||||||
msdp_array_open=MSDP_ARRAY_OPEN,
|
msdp_array_open=MSDP_ARRAY_OPEN.decode(),
|
||||||
msdp_array_close=MSDP_ARRAY_CLOSE,
|
msdp_array_close=MSDP_ARRAY_CLOSE.decode(),
|
||||||
msdp_args="".join("%s%s" % (MSDP_VAL, json.dumps(val)) for val in args),
|
msdp_args="".join("%s%s" % (MSDP_VAL.decode(), val) for val in args),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -199,10 +201,10 @@ class TelnetOOB(object):
|
||||||
"{msdp_table_open}"
|
"{msdp_table_open}"
|
||||||
"{msdp_kwargs}"
|
"{msdp_kwargs}"
|
||||||
"{msdp_table_close}".format(
|
"{msdp_table_close}".format(
|
||||||
msdp_table_open=MSDP_TABLE_OPEN,
|
msdp_table_open=MSDP_TABLE_OPEN.decode(),
|
||||||
msdp_table_close=MSDP_TABLE_CLOSE,
|
msdp_table_close=MSDP_TABLE_CLOSE.decode(),
|
||||||
msdp_kwargs="".join(
|
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()
|
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)
|
keypair.generate_key(crypto.TYPE_RSA, _PRIVATE_KEY_LENGTH)
|
||||||
|
|
||||||
with open(_PRIVATE_KEY_FILE, "wt") as pfile:
|
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))
|
print("Created SSL private key in '{}'.".format(_PRIVATE_KEY_FILE))
|
||||||
|
|
||||||
with open(_PUBLIC_KEY_FILE, "wt") as pfile:
|
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))
|
print("Created SSL public key in '{}'.".format(_PUBLIC_KEY_FILE))
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
|
|
@ -128,7 +128,7 @@ def verify_or_create_SSL_key_and_cert(keyfile, certfile):
|
||||||
cert.sign(keypair, "sha1")
|
cert.sign(keypair, "sha1")
|
||||||
|
|
||||||
with open(_CERTIFICATE_FILE, "wt") as cfile:
|
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))
|
print("Created SSL certificate in '{}'.".format(_CERTIFICATE_FILE))
|
||||||
|
|
||||||
except Exception as err:
|
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,
|
All data will be stored on the protocol's protocol_flags dictionary,
|
||||||
under the 'TTYPE' key.
|
under the 'TTYPE' key.
|
||||||
"""
|
"""
|
||||||
|
from twisted.python.compat import _bytesChr as bchr
|
||||||
|
|
||||||
# telnet option codes
|
# telnet option codes
|
||||||
TTYPE = b"\x18"
|
TTYPE = bchr(24) # b"\x18"
|
||||||
IS = b"\x00"
|
IS = bchr(0) # b"\x00"
|
||||||
SEND = b"\x01"
|
SEND = bchr(1) # b"\x01"
|
||||||
|
|
||||||
# terminal capabilities and their codes
|
# terminal capabilities and their codes
|
||||||
MTTS = [
|
MTTS = [
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ from evennia.utils import logger
|
||||||
from evennia.comms import channelhandler
|
from evennia.comms import channelhandler
|
||||||
from evennia.server.sessionhandler import SESSIONS
|
from evennia.server.sessionhandler import SESSIONS
|
||||||
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
_SA = object.__setattr__
|
_SA = object.__setattr__
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ _ObjectDB = None
|
||||||
_ANSI = None
|
_ANSI = None
|
||||||
|
|
||||||
# i18n
|
# i18n
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
# Handlers for Session.db/ndb operation
|
# 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
|
SRESET = chr(19) # server shutdown in reset mode
|
||||||
|
|
||||||
# i18n
|
# i18n
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
_SERVERNAME = settings.SERVERNAME
|
_SERVERNAME = settings.SERVERNAME
|
||||||
_MULTISESSION_MODE = settings.MULTISESSION_MODE
|
_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("SET FOREIGN_KEY_CHECKS=0;")
|
||||||
db_cursor.execute("DROP TABLE {table};".format(table=table_name))
|
db_cursor.execute("DROP TABLE {table};".format(table=table_name))
|
||||||
db_cursor.execute("SET FOREIGN_KEY_CHECKS=1;")
|
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("ALTER TABLE {table} DISABLE TRIGGER ALL;".format(table=table_name))
|
||||||
db_cursor.execute("DROP TABLE {table};".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))
|
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):
|
def __new__(cls, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
When creating a new ANSIString, you may use a custom parser that has
|
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):
|
def __str__(self):
|
||||||
return self._raw_string
|
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):
|
def __repr__(self):
|
||||||
"""
|
"""
|
||||||
Let's make the repr the command that would actually be used to
|
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 pickle import dumps, loads
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.contrib.contenttypes.models import ContentType
|
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.utils import uses_database, is_iter, to_str, to_bytes
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
|
|
||||||
|
|
@ -549,7 +549,7 @@ def to_pickle(data):
|
||||||
def process_item(item):
|
def process_item(item):
|
||||||
"""Recursive processor and identification of data"""
|
"""Recursive processor and identification of data"""
|
||||||
dtype = type(item)
|
dtype = type(item)
|
||||||
if dtype in (str, int, float, bool, bytes, SafeString, SafeBytes):
|
if dtype in (str, int, float, bool, bytes, SafeString):
|
||||||
return item
|
return item
|
||||||
elif dtype == tuple:
|
elif dtype == tuple:
|
||||||
return tuple(process_item(val) for val in item)
|
return tuple(process_item(val) for val in item)
|
||||||
|
|
@ -577,7 +577,7 @@ def to_pickle(data):
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return item
|
return item
|
||||||
except Exception:
|
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
|
raise
|
||||||
|
|
||||||
return process_item(data)
|
return process_item(data)
|
||||||
|
|
@ -609,7 +609,7 @@ def from_pickle(data, db_obj=None):
|
||||||
def process_item(item):
|
def process_item(item):
|
||||||
"""Recursive processor and identification of data"""
|
"""Recursive processor and identification of data"""
|
||||||
dtype = type(item)
|
dtype = type(item)
|
||||||
if dtype in (str, int, float, bool, bytes, SafeString, SafeBytes):
|
if dtype in (str, int, float, bool, bytes, SafeString):
|
||||||
return item
|
return item
|
||||||
elif _IS_PACKED_DBOBJ(item):
|
elif _IS_PACKED_DBOBJ(item):
|
||||||
# this must be checked before tuple
|
# this must be checked before tuple
|
||||||
|
|
@ -638,7 +638,7 @@ def from_pickle(data, db_obj=None):
|
||||||
def process_tree(item, parent):
|
def process_tree(item, parent):
|
||||||
"""Recursive processor, building a parent-tree from iterable data"""
|
"""Recursive processor, building a parent-tree from iterable data"""
|
||||||
dtype = type(item)
|
dtype = type(item)
|
||||||
if dtype in (str, int, float, bool, bytes, SafeString, SafeBytes):
|
if dtype in (str, int, float, bool, bytes, SafeString):
|
||||||
return item
|
return item
|
||||||
elif _IS_PACKED_DBOBJ(item):
|
elif _IS_PACKED_DBOBJ(item):
|
||||||
# this must be checked before tuple
|
# this must be checked before tuple
|
||||||
|
|
@ -716,7 +716,7 @@ def do_pickle(data):
|
||||||
try:
|
try:
|
||||||
return dumps(data, protocol=PICKLE_PROTOCOL)
|
return dumps(data, protocol=PICKLE_PROTOCOL)
|
||||||
except Exception:
|
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
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -725,7 +725,7 @@ def do_unpickle(data):
|
||||||
try:
|
try:
|
||||||
return loads(to_bytes(data))
|
return loads(to_bytes(data))
|
||||||
except Exception:
|
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
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -187,7 +187,7 @@ _CMD_NOINPUT = cmdhandler.CMD_NOINPUT
|
||||||
# Return messages
|
# Return messages
|
||||||
|
|
||||||
# i18n
|
# i18n
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
_ERR_NOT_IMPLEMENTED = _(
|
_ERR_NOT_IMPLEMENTED = _(
|
||||||
"Menu node '{nodename}' is either not implemented or " "caused an error. Make another choice."
|
"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 django.forms.widgets import Textarea
|
||||||
|
|
||||||
from pickle import loads, dumps
|
from pickle import loads, dumps
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_PROTOCOL = 4
|
DEFAULT_PROTOCOL = 4
|
||||||
|
|
@ -210,10 +210,10 @@ class PickledObjectField(models.Field):
|
||||||
"""
|
"""
|
||||||
Returns the default value for this 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
|
on the default, which means you can't set arbitrary Python
|
||||||
objects as the default. To fix this, we just return the value
|
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
|
callable as a default, the field will still call it. It will
|
||||||
*not* try to pickle and encode it.
|
*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):
|
if value is not None and not isinstance(value, PickledObject):
|
||||||
# We call force_text here explicitly, so that the encoded string
|
# We call force_str here explicitly, so that the encoded string
|
||||||
# isn't rejected by the postgresql_psycopg2 backend. Alternatively,
|
# isn't rejected by the postgresql backend. Alternatively,
|
||||||
# we could have just registered PickledObject with the psycopg
|
# we could have just registered PickledObject with the psycopg
|
||||||
# marshaller (telling it to store it like it would a string), but
|
# marshaller (telling it to store it like it would a string), but
|
||||||
# since both of these methods result in the same value being stored,
|
# since both of these methods result in the same value being stored,
|
||||||
# doing things this way is much easier.
|
# 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
|
return value
|
||||||
|
|
||||||
def value_to_string(self, obj):
|
def value_to_string(self, obj):
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,30 @@ class TestMLen(TestCase):
|
||||||
self.assertEqual(utils.m_len({"hello": True, "Goodbye": False}), 2)
|
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):
|
class TestTimeformat(TestCase):
|
||||||
"""
|
"""
|
||||||
Default function header from utils.py:
|
Default function header from utils.py:
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ from collections import defaultdict, OrderedDict
|
||||||
from twisted.internet import threads, reactor
|
from twisted.internet import threads, reactor
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
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 django.apps import apps
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
|
|
||||||
|
|
@ -1036,7 +1036,7 @@ def uses_database(name="sqlite3"):
|
||||||
shortcut to having to use the full backend name.
|
shortcut to having to use the full backend name.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name (str): One of 'sqlite3', 'mysql', 'postgresql_psycopg2'
|
name (str): One of 'sqlite3', 'mysql', 'postgresql'
|
||||||
or 'oracle'.
|
or 'oracle'.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@
|
||||||
# http://diveintopython.org/regular_expressions/street_addresses.html#re.matching.2.3
|
# http://diveintopython.org/regular_expressions/street_addresses.html#re.matching.2.3
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import path
|
from django.conf.urls import url
|
||||||
from django.conf.urls import url, include
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.urls import path, include
|
||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
|
|
||||||
# Setup the root url tree from /
|
# Setup the root url tree from /
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ with evennia set up automatically and get the Evennia JS lib and
|
||||||
JQuery available.
|
JQuery available.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
<html dir="ltr" lang="en">
|
<html dir="ltr" lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title> {{game_name}} </title>
|
<title> {{game_name}} </title>
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@
|
||||||
This structures the (simple) structure of the
|
This structures the (simple) structure of the
|
||||||
webpage 'application'.
|
webpage 'application'.
|
||||||
"""
|
"""
|
||||||
from django.conf.urls import *
|
from django.urls import path
|
||||||
from evennia.web.webclient import views as webclient_views
|
from evennia.web.webclient import views as webclient_views
|
||||||
|
|
||||||
app_name = "webclient"
|
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
|
webpage. Copy this file to your game dir's web/template_overrides/website
|
||||||
folder and edit it to add/remove links to the menu.
|
folder and edit it to add/remove links to the menu.
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
<nav class="navbar navbar-dark font-weight-bold navbar-expand-md">
|
<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">
|
<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>
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{% load staticfiles sekizai_tags %}
|
{% load static sekizai_tags %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,12 @@ django-filter >= 2.2.0, < 2.3
|
||||||
django-sekizai
|
django-sekizai
|
||||||
inflect
|
inflect
|
||||||
autobahn >= 17.9.3
|
autobahn >= 17.9.3
|
||||||
model_mommy
|
|
||||||
|
# try to resolve dependency issue in py3.7
|
||||||
|
attrs >= 19.2.0
|
||||||
|
|
||||||
# testing and development
|
# testing and development
|
||||||
|
model_mommy
|
||||||
mock >= 1.0.1
|
mock >= 1.0.1
|
||||||
anything
|
anything
|
||||||
black
|
black
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue