Resolve merge conflicts
This commit is contained in:
commit
daeaf6381a
287 changed files with 17246 additions and 9214 deletions
4
.github/CONTRIBUTING.md
vendored
4
.github/CONTRIBUTING.md
vendored
|
|
@ -19,6 +19,10 @@ happens to be named "command"!
|
||||||
|
|
||||||
A PR allows you to request that your custom fixes/additions/changes be pulled into the main Evennia repository. To make a PR you must first [fork Evennia on GitHub][8]. Read the [Contribution][3] page for more help.
|
A PR allows you to request that your custom fixes/additions/changes be pulled into the main Evennia repository. To make a PR you must first [fork Evennia on GitHub][8]. Read the [Contribution][3] page for more help.
|
||||||
|
|
||||||
|
If you are working to solve an Issue in the issue tracker, note which branch you should make the PR
|
||||||
|
against (`master` or `develop`). If you are making a PR for a new feature or contrib, do so against
|
||||||
|
the `develop' branch.
|
||||||
|
|
||||||
- All contributions should abide by Evennia's [style guide](https://github.com/evennia/evennia/blob/master/CODING_STYLE.md).
|
- All contributions should abide by Evennia's [style guide](https://github.com/evennia/evennia/blob/master/CODING_STYLE.md).
|
||||||
- For your own sanity and ours, separate unrelated contributions into their own branches and make a new PR for each. You can still update the branch after the PR is up - the PR will update automatically.
|
- For your own sanity and ours, separate unrelated contributions into their own branches and make a new PR for each. You can still update the branch after the PR is up - the PR will update automatically.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
# Evennia Changelog
|
# Evennia Changelog
|
||||||
|
|
||||||
|
# Sept 2017:
|
||||||
|
Release of Evennia 0.7; upgrade to Django 1.11, change 'Player' to
|
||||||
|
'Account', rework the website template and a slew of other updates.
|
||||||
|
Info on what changed and how to migrate is found here:
|
||||||
|
https://groups.google.com/forum/#!msg/evennia/0JYYNGY-NfE/cDFaIwmPBAAJ
|
||||||
|
|
||||||
## Feb 2017:
|
## Feb 2017:
|
||||||
New devel branch created, to lead up to Evennia 0.7.
|
New devel branch created, to lead up to Evennia 0.7.
|
||||||
|
|
||||||
|
|
|
||||||
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting griatch AT gmail DOT com or Griatch in the #evennia channel on irc.freenode.net. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||||
|
|
||||||
|
[homepage]: http://contributor-covenant.org
|
||||||
|
[version]: http://contributor-covenant.org/version/1/4/
|
||||||
50
Dockerfile
50
Dockerfile
|
|
@ -1,40 +1,54 @@
|
||||||
#####
|
#####
|
||||||
# Base docker image for running Evennia-based games in a container.
|
# Base docker image for running Evennia-based games in a container.
|
||||||
#
|
#
|
||||||
# This Dockerfile creates the evennia/evennia docker image
|
# Install:
|
||||||
# on DockerHub, which can be used as the basis for creating
|
# install `docker` (http://docker.com)
|
||||||
# an Evennia game within a container. This base image can be
|
|
||||||
# found in DockerHub at https://hub.docker.com/r/evennia/evennia/
|
|
||||||
#
|
|
||||||
# For more information on using it to build a container to run your game, see
|
|
||||||
#
|
#
|
||||||
# https://github.com/evennia/evennia/wiki/Running%20Evennia%20in%20Docker
|
# Usage:
|
||||||
|
# cd to a folder where you want your game data to be (or where it already is).
|
||||||
#
|
#
|
||||||
FROM python:2.7-alpine
|
# docker run -it -p 4000:4000 -p 4001:4001 -p 4005:4005 -v $PWD:/usr/src/game evennia/evennia
|
||||||
MAINTAINER Dan Feeney "feend78@gmail.com"
|
#
|
||||||
|
# (If your OS does not support $PWD, replace it with the full path to your current
|
||||||
|
# folder).
|
||||||
|
#
|
||||||
|
# You will end up in a shell where the `evennia` command is available. From here you
|
||||||
|
# can install and run the game normally. Use Ctrl-D to exit the evennia docker container.
|
||||||
|
#
|
||||||
|
# The evennia/evennia base image is found on DockerHub and can also be used
|
||||||
|
# as a base for creating your own custom containerized Evennia game. For more
|
||||||
|
# info, see https://github.com/evennia/evennia/wiki/Running%20Evennia%20in%20Docker .
|
||||||
|
#
|
||||||
|
FROM alpine
|
||||||
|
|
||||||
|
MAINTAINER www.evennia.com
|
||||||
|
|
||||||
# install compilation environment
|
# install compilation environment
|
||||||
RUN apk update && apk add gcc musl-dev
|
RUN apk update && apk add python py-pip python-dev py-setuptools gcc musl-dev jpeg-dev zlib-dev bash
|
||||||
|
|
||||||
# add the project source
|
# add the project source
|
||||||
ADD . /usr/src/evennia
|
ADD . /usr/src/evennia
|
||||||
|
|
||||||
# install dependencies
|
# install dependencies
|
||||||
RUN pip install -e /usr/src/evennia
|
RUN pip install --upgrade pip && pip install /usr/src/evennia --trusted-host pypi.python.org
|
||||||
|
|
||||||
# add the game source during game builds
|
# add the game source when rebuilding a new docker image from inside
|
||||||
|
# a game dir
|
||||||
ONBUILD ADD . /usr/src/game
|
ONBUILD ADD . /usr/src/game
|
||||||
|
|
||||||
# make the game source hierarchy persistent with a named volume.
|
# make the game source hierarchy persistent with a named volume.
|
||||||
# during development this is typically superceded by directives in
|
# mount on-disk game location here when using the container
|
||||||
# docker-compose.yml or the CLI to mount a local directory.
|
# to just get an evennia environment.
|
||||||
VOLUME /usr/src/game
|
VOLUME /usr/src/game
|
||||||
|
|
||||||
# set the working directory
|
# set the working directory
|
||||||
WORKDIR /usr/src/game
|
WORKDIR /usr/src/game
|
||||||
|
|
||||||
# startup command
|
# set bash prompt
|
||||||
CMD ["evennia", "-i", "start"]
|
ENV PS1 "evennia|docker \w $ "
|
||||||
|
|
||||||
# expose the default ports
|
# startup a shell when we start the container
|
||||||
EXPOSE 8000 8001 4000
|
ENTRYPOINT bash -c "source /usr/src/evennia/bin/unix/evennia-docker-start.sh"
|
||||||
|
|
||||||
|
# expose the telnet, webserver and websocket client ports
|
||||||
|
EXPOSE 4000 4001 4005
|
||||||
|
|
|
||||||
20
INSTALL.md
20
INSTALL.md
|
|
@ -2,7 +2,7 @@
|
||||||
# Evennia installation
|
# Evennia installation
|
||||||
|
|
||||||
The latest and more detailed installation instructions can be found
|
The latest and more detailed installation instructions can be found
|
||||||
[here](https://github.com/evennia/evennia/wiki/Getting-Started).
|
[here](https://github.com/evennia/evennia/wiki/Getting-Started).
|
||||||
|
|
||||||
## Installing Python
|
## Installing Python
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@ virtualenv vienv
|
||||||
```
|
```
|
||||||
|
|
||||||
A new folder `vienv` will be created (you could also name it something
|
A new folder `vienv` will be created (you could also name it something
|
||||||
else if you prefer). Activate the virtual environment like this:
|
else if you prefer). Activate the virtual environment like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
# for Linux/Unix/Mac:
|
# for Linux/Unix/Mac:
|
||||||
|
|
@ -64,13 +64,13 @@ git clone https://github.com/evennia/evennia.git
|
||||||
|
|
||||||
If you have a github account and have [set up SSH
|
If you have a github account and have [set up SSH
|
||||||
keys](https://help.github.com/articles/generating-ssh-keys/), you want
|
keys](https://help.github.com/articles/generating-ssh-keys/), you want
|
||||||
to use this instead:
|
to use this instead:
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone git@github.com:evennia/evennia.git
|
git clone git@github.com:evennia/evennia.git
|
||||||
```
|
```
|
||||||
|
|
||||||
In the future you just enter the new `evennia` folder and do
|
In the future you just enter the new `evennia` folder and do
|
||||||
|
|
||||||
```
|
```
|
||||||
git pull
|
git pull
|
||||||
|
|
@ -78,7 +78,7 @@ git pull
|
||||||
|
|
||||||
to get the latest Evennia updates.
|
to get the latest Evennia updates.
|
||||||
|
|
||||||
## Evennia package install
|
## Evennia package install
|
||||||
|
|
||||||
Stand at the root of your new `evennia` directory and run
|
Stand at the root of your new `evennia` directory and run
|
||||||
|
|
||||||
|
|
@ -90,12 +90,12 @@ pip install -e .
|
||||||
current directory). This will install Evennia and all its dependencies
|
current directory). This will install Evennia and all its dependencies
|
||||||
(into your virtualenv if you are using that) and make the `evennia`
|
(into your virtualenv if you are using that) and make the `evennia`
|
||||||
command available on the command line. You can find Evennia's
|
command available on the command line. You can find Evennia's
|
||||||
dependencies in `evennia/requirements.txt`.
|
dependencies in `evennia/requirements.txt`.
|
||||||
|
|
||||||
## Creating your game project
|
## Creating your game project
|
||||||
|
|
||||||
To create your new game you need to initialize a new 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.
|
This should be done somewhere *outside* of your `evennia` folder.
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
@ -118,10 +118,10 @@ evennia start
|
||||||
|
|
||||||
Follow the instructions to create your superuser account. A lot of
|
Follow the instructions to create your superuser account. A lot of
|
||||||
information will scroll past as the database is created and the server
|
information will scroll past as the database is created and the server
|
||||||
initializes. After this Evennia will be running. Use
|
initializes. After this Evennia will be running. Use
|
||||||
|
|
||||||
```
|
```
|
||||||
evennia -h
|
evennia -h
|
||||||
```
|
```
|
||||||
|
|
||||||
for help with starting, stopping and other operations.
|
for help with starting, stopping and other operations.
|
||||||
|
|
@ -131,7 +131,7 @@ port *4000*. If you are just running locally the server name is
|
||||||
*localhost*.
|
*localhost*.
|
||||||
|
|
||||||
Alternatively, you can find the web interface and webclient by
|
Alternatively, you can find the web interface and webclient by
|
||||||
pointing your web browser to *http://localhost:8000*.
|
pointing your web browser to *http://localhost:4001*.
|
||||||
|
|
||||||
Finally, login with the superuser account and password you provided
|
Finally, login with the superuser account and password you provided
|
||||||
earlier. Welcome to Evennia!
|
earlier. Welcome to Evennia!
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ Welcome!
|
||||||
[homepage]: http://www.evennia.com
|
[homepage]: http://www.evennia.com
|
||||||
[gettingstarted]: http://github.com/evennia/evennia/wiki/Getting-Started
|
[gettingstarted]: http://github.com/evennia/evennia/wiki/Getting-Started
|
||||||
[wiki]: https://github.com/evennia/evennia/wiki
|
[wiki]: https://github.com/evennia/evennia/wiki
|
||||||
[screenshot]: https://raw.githubusercontent.com/wiki/evennia/evennia/images/evennia_screenshot3.png
|
[screenshot]: https://user-images.githubusercontent.com/294267/30773728-ea45afb6-a076-11e7-8820-49be2168a6b8.png
|
||||||
[logo]: https://github.com/evennia/evennia/blob/master/evennia/web/website/static/website/images/evennia_logo.png
|
[logo]: https://github.com/evennia/evennia/blob/master/evennia/web/website/static/website/images/evennia_logo.png
|
||||||
[travisimg]: https://travis-ci.org/evennia/evennia.svg?branch=master
|
[travisimg]: https://travis-ci.org/evennia/evennia.svg?branch=master
|
||||||
[travislink]: https://travis-ci.org/evennia/evennia
|
[travislink]: https://travis-ci.org/evennia/evennia
|
||||||
|
|
|
||||||
223
bin/player-account-step1.patch
Normal file
223
bin/player-account-step1.patch
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
diff --git a/evennia/comms/migrations/0015_auto_20170706_2041.py b/evennia/comms/migrations/0015_auto_20170706_2041.py
|
||||||
|
index ec5fc29..62b7936 100644
|
||||||
|
--- a/evennia/comms/migrations/0015_auto_20170706_2041.py
|
||||||
|
+++ b/evennia/comms/migrations/0015_auto_20170706_2041.py
|
||||||
|
@@ -2,7 +2,12 @@
|
||||||
|
# Generated by Django 1.11.2 on 2017-07-06 20:41
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
-from django.db import migrations
|
||||||
|
+from django.db import migrations, connection
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+def _table_exists(db_cursor, tablename):
|
||||||
|
+ "Returns bool if table exists or not"
|
||||||
|
+ return tablename in connection.introspection.table_names()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
@@ -11,17 +16,23 @@ class Migration(migrations.Migration):
|
||||||
|
('comms', '0014_auto_20170705_1736'),
|
||||||
|
]
|
||||||
|
|
||||||
|
- operations = [
|
||||||
|
- migrations.RemoveField(
|
||||||
|
- model_name='channeldb',
|
||||||
|
- name='db_subscriptions',
|
||||||
|
- ),
|
||||||
|
- migrations.RemoveField(
|
||||||
|
- model_name='msg',
|
||||||
|
- name='db_receivers_players',
|
||||||
|
- ),
|
||||||
|
- migrations.RemoveField(
|
||||||
|
- model_name='msg',
|
||||||
|
- name='db_sender_players',
|
||||||
|
- ),
|
||||||
|
- ]
|
||||||
|
+ db_cursor = connection.cursor()
|
||||||
|
+
|
||||||
|
+ if not _table_exists(db_cursor, "channels.channeldb_db_receivers_players"):
|
||||||
|
+ # OBS - this is run BEFORE migrations are run!
|
||||||
|
+ operations = []
|
||||||
|
+ else:
|
||||||
|
+ operations = [
|
||||||
|
+ migrations.RemoveField(
|
||||||
|
+ model_name='channeldb',
|
||||||
|
+ name='db_subscriptions', # this is now db_account_subscriptions
|
||||||
|
+ ),
|
||||||
|
+ migrations.RemoveField(
|
||||||
|
+ model_name='msg',
|
||||||
|
+ name='db_receivers_players',
|
||||||
|
+ ),
|
||||||
|
+ migrations.RemoveField(
|
||||||
|
+ model_name='msg',
|
||||||
|
+ name='db_sender_players',
|
||||||
|
+ ),
|
||||||
|
+ ]
|
||||||
|
diff --git a/evennia/objects/migrations/0007_objectdb_db_account.py b/evennia/objects/migrations/0007_objectdb_db_account.py
|
||||||
|
index b27c75c..6e40252 100644
|
||||||
|
--- a/evennia/objects/migrations/0007_objectdb_db_account.py
|
||||||
|
+++ b/evennia/objects/migrations/0007_objectdb_db_account.py
|
||||||
|
@@ -2,21 +2,31 @@
|
||||||
|
# Generated by Django 1.11.2 on 2017-07-05 17:27
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
-from django.db import migrations, models
|
||||||
|
+from django.db import migrations, models, connection
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
+def _table_exists(db_cursor, tablename):
|
||||||
|
+ "Returns bool if table exists or not"
|
||||||
|
+ return tablename in connection.introspection.table_names()
|
||||||
|
+
|
||||||
|
+
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0002_copy_player_to_account'),
|
||||||
|
('objects', '0006_auto_20170606_1731'),
|
||||||
|
]
|
||||||
|
|
||||||
|
- operations = [
|
||||||
|
- migrations.AddField(
|
||||||
|
- model_name='objectdb',
|
||||||
|
- name='db_account',
|
||||||
|
- field=models.ForeignKey(help_text=b'an Account connected to this object, if any.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.AccountDB', verbose_name=b'account'),
|
||||||
|
- ),
|
||||||
|
- ]
|
||||||
|
+ db_cursor = connection.cursor()
|
||||||
|
+ operations = []
|
||||||
|
+ if _table_exists(db_cursor, "players_playerdb"):
|
||||||
|
+ # OBS - this is run BEFORE migrations even start, so if we have a player table
|
||||||
|
+ # here we are not starting from scratch.
|
||||||
|
+ operations = [
|
||||||
|
+ migrations.AddField(
|
||||||
|
+ model_name='objectdb',
|
||||||
|
+ name='db_account',
|
||||||
|
+ field=models.ForeignKey(help_text=b'an Account connected to this object, if any.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.AccountDB', verbose_name=b'account'),
|
||||||
|
+ ),
|
||||||
|
+ ]
|
||||||
|
diff --git a/evennia/objects/migrations/0009_remove_objectdb_db_player.py b/evennia/objects/migrations/0009_remove_objectdb_db_player.py
|
||||||
|
index 80161a1..10fb225 100644
|
||||||
|
--- a/evennia/objects/migrations/0009_remove_objectdb_db_player.py
|
||||||
|
+++ b/evennia/objects/migrations/0009_remove_objectdb_db_player.py
|
||||||
|
@@ -2,7 +2,12 @@
|
||||||
|
# Generated by Django 1.11.2 on 2017-07-06 20:41
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
-from django.db import migrations
|
||||||
|
+from django.db import migrations, connection
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+def _table_exists(db_cursor, tablename):
|
||||||
|
+ "Returns bool if table exists or not"
|
||||||
|
+ return tablename in connection.introspection.table_names()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
@@ -11,9 +16,15 @@ class Migration(migrations.Migration):
|
||||||
|
('objects', '0008_auto_20170705_1736'),
|
||||||
|
]
|
||||||
|
|
||||||
|
- operations = [
|
||||||
|
- migrations.RemoveField(
|
||||||
|
- model_name='objectdb',
|
||||||
|
- name='db_player',
|
||||||
|
- ),
|
||||||
|
- ]
|
||||||
|
+ db_cursor = connection.cursor()
|
||||||
|
+
|
||||||
|
+ if not _table_exists(db_cursor, "objectdb_db_player"):
|
||||||
|
+ # OBS - this is run BEFORE migrations are run!
|
||||||
|
+ operations = []
|
||||||
|
+ else:
|
||||||
|
+ operations = [
|
||||||
|
+ migrations.RemoveField(
|
||||||
|
+ model_name='objectdb',
|
||||||
|
+ name='db_player',
|
||||||
|
+ ),
|
||||||
|
+ ]
|
||||||
|
diff --git a/evennia/scripts/migrations/0009_scriptdb_db_account.py b/evennia/scripts/migrations/0009_scriptdb_db_account.py
|
||||||
|
index 99baf70..23f6df9 100644
|
||||||
|
--- a/evennia/scripts/migrations/0009_scriptdb_db_account.py
|
||||||
|
+++ b/evennia/scripts/migrations/0009_scriptdb_db_account.py
|
||||||
|
@@ -2,21 +2,31 @@
|
||||||
|
# Generated by Django 1.11.2 on 2017-07-05 17:27
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
-from django.db import migrations, models
|
||||||
|
+from django.db import migrations, models, connection
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
+def _table_exists(db_cursor, tablename):
|
||||||
|
+ "Returns bool if table exists or not"
|
||||||
|
+ return tablename in connection.introspection.table_names()
|
||||||
|
+
|
||||||
|
+
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0002_copy_player_to_account'),
|
||||||
|
('scripts', '0008_auto_20170606_1731'),
|
||||||
|
]
|
||||||
|
|
||||||
|
- operations = [
|
||||||
|
- migrations.AddField(
|
||||||
|
- model_name='scriptdb',
|
||||||
|
- name='db_account',
|
||||||
|
- field=models.ForeignKey(blank=True, help_text=b'the account to store this script on (should not be set if db_obj is set)', null=True, on_delete=django.db.models.deletion.CASCADE, to='accounts.AccountDB', verbose_name=b'scripted account'),
|
||||||
|
- ),
|
||||||
|
- ]
|
||||||
|
+ db_cursor = connection.cursor()
|
||||||
|
+ operations = []
|
||||||
|
+ if _table_exists(db_cursor, "players_playerdb"):
|
||||||
|
+ # OBS - this is run BEFORE migrations even start, so if we have a player table
|
||||||
|
+ # here we are not starting from scratch.
|
||||||
|
+ operations = [
|
||||||
|
+ migrations.AddField(
|
||||||
|
+ model_name='scriptdb',
|
||||||
|
+ name='db_account',
|
||||||
|
+ field=models.ForeignKey(blank=True, help_text=b'the account to store this script on (should not be set if db_obj is set)', null=True, on_delete=django.db.models.deletion.CASCADE, to='accounts.AccountDB', verbose_name=b'scripted account'),
|
||||||
|
+ ),
|
||||||
|
+ ]
|
||||||
|
diff --git a/evennia/scripts/migrations/0011_remove_scriptdb_db_player.py b/evennia/scripts/migrations/0011_remove_scriptdb_db_player.py
|
||||||
|
index d3746a5..20fa63f 100644
|
||||||
|
--- a/evennia/scripts/migrations/0011_remove_scriptdb_db_player.py
|
||||||
|
+++ b/evennia/scripts/migrations/0011_remove_scriptdb_db_player.py
|
||||||
|
@@ -2,7 +2,12 @@
|
||||||
|
# Generated by Django 1.11.2 on 2017-07-06 20:41
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
-from django.db import migrations
|
||||||
|
+from django.db import migrations, connection
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+def _table_exists(db_cursor, tablename):
|
||||||
|
+ "Returns bool if table exists or not"
|
||||||
|
+ return tablename in connection.introspection.table_names()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
@@ -11,9 +16,14 @@ class Migration(migrations.Migration):
|
||||||
|
('scripts', '0010_auto_20170705_1736'),
|
||||||
|
]
|
||||||
|
|
||||||
|
- operations = [
|
||||||
|
- migrations.RemoveField(
|
||||||
|
- model_name='scriptdb',
|
||||||
|
- name='db_player',
|
||||||
|
- ),
|
||||||
|
- ]
|
||||||
|
+ db_cursor = connection.cursor()
|
||||||
|
+
|
||||||
|
+ if not _table_exists(db_cursor, 'scripts_scriptdb_db_player'):
|
||||||
|
+ operations = []
|
||||||
|
+ else:
|
||||||
|
+ operations = [
|
||||||
|
+ migrations.RemoveField(
|
||||||
|
+ model_name='scriptdb',
|
||||||
|
+ name='db_player',
|
||||||
|
+ ),
|
||||||
|
+ ]
|
||||||
337
bin/project_rename.py
Normal file
337
bin/project_rename.py
Normal file
|
|
@ -0,0 +1,337 @@
|
||||||
|
"""
|
||||||
|
Project rename utility
|
||||||
|
|
||||||
|
Created for the Player->Account renaming
|
||||||
|
|
||||||
|
Griatch 2017, released under the BSD license.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import fnmatch
|
||||||
|
|
||||||
|
ANSI_HILITE = "\033[1m"
|
||||||
|
ANSI_RED = "\033[31m"
|
||||||
|
ANSI_GREEN = "\033[32m"
|
||||||
|
ANSI_YELLOW = "\033[33m"
|
||||||
|
ANSI_NORMAL = "\033[0m"
|
||||||
|
|
||||||
|
USE_COLOR = True
|
||||||
|
FAKE_MODE = False
|
||||||
|
|
||||||
|
# if these words are longer than output word, retain given case
|
||||||
|
CASE_WORD_EXCEPTIONS = ('an', )
|
||||||
|
|
||||||
|
_HELP_TEXT = """This program interactively renames words in all files of your project. It's
|
||||||
|
currently renaming {sources} to {targets}.
|
||||||
|
|
||||||
|
If it wants to replace text in a file, it will show all lines (and line numbers) it wants to
|
||||||
|
replace, each directly followed by the suggested replacement.
|
||||||
|
|
||||||
|
If a rename is not okay, you can de-select it by entering 'i' followed by one or more
|
||||||
|
comma-separated line numbers. You cannot ignore partial lines, those you need to remember to change
|
||||||
|
manually later.
|
||||||
|
|
||||||
|
[q]uit - exits the program immediately.
|
||||||
|
[h]elp - this help.
|
||||||
|
[s]kip file - make no changes at all in this file, continue on to the next.
|
||||||
|
[i]ignore lines - specify line numbers to not change.
|
||||||
|
[c]lear ignores - this reverts all your ignores if you make a mistake.
|
||||||
|
[a]accept/save file - apply all accepted renames and continue on to the next file.
|
||||||
|
|
||||||
|
(return to continue)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Helper functions
|
||||||
|
|
||||||
|
|
||||||
|
def _green(string):
|
||||||
|
if USE_COLOR:
|
||||||
|
return "%s%s%s" % (ANSI_GREEN, string, ANSI_NORMAL)
|
||||||
|
return string
|
||||||
|
|
||||||
|
|
||||||
|
def _yellow(string):
|
||||||
|
if USE_COLOR:
|
||||||
|
return "%s%s%s" % (ANSI_YELLOW, string, ANSI_NORMAL)
|
||||||
|
return string
|
||||||
|
|
||||||
|
|
||||||
|
def _red(string):
|
||||||
|
if USE_COLOR:
|
||||||
|
return "%s%s%s" % (ANSI_HILITE + ANSI_RED, string, ANSI_NORMAL)
|
||||||
|
return string
|
||||||
|
|
||||||
|
|
||||||
|
def _case_sensitive_replace(string, old, new):
|
||||||
|
"""
|
||||||
|
Replace text, retaining exact case.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
string (str): String in which to perform replacement.
|
||||||
|
old (str): Word or substring to replace.
|
||||||
|
new (str): What to replace `old` with.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
repl_string (str): Version of string where instances of
|
||||||
|
`old` has been replaced with `new`, retaining case.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def repl(match):
|
||||||
|
current = match.group()
|
||||||
|
# treat multi-word sentences word-by-word
|
||||||
|
old_words = current.split(" ")
|
||||||
|
new_words = new.split(" ")
|
||||||
|
out = []
|
||||||
|
for old_word, new_word in zip(old_words, new_words):
|
||||||
|
result = []
|
||||||
|
all_upper = True
|
||||||
|
for ind, chr in enumerate(old_word):
|
||||||
|
if ind >= len(new_word):
|
||||||
|
break
|
||||||
|
if chr.isupper():
|
||||||
|
result.append(new_word[ind].upper())
|
||||||
|
else:
|
||||||
|
result.append(new_word[ind].lower())
|
||||||
|
all_upper = False
|
||||||
|
# special cases - keep remaing case)
|
||||||
|
if new_word.lower() in CASE_WORD_EXCEPTIONS:
|
||||||
|
result.append(new_word[ind + 1:])
|
||||||
|
# append any remaining characters from new
|
||||||
|
elif all_upper:
|
||||||
|
result.append(new_word[ind + 1:].upper())
|
||||||
|
else:
|
||||||
|
result.append(new_word[ind + 1:].lower())
|
||||||
|
out.append("".join(result))
|
||||||
|
# if we have more new words than old ones, just add them verbatim
|
||||||
|
out.extend([new_word for ind, new_word in enumerate(new_words) if ind >= len(old_words)])
|
||||||
|
return " ".join(out)
|
||||||
|
|
||||||
|
regex = re.compile(re.escape(old), re.I)
|
||||||
|
return regex.sub(repl, string)
|
||||||
|
|
||||||
|
|
||||||
|
def rename_in_tree(path, in_list, out_list, excl_list, fileend_list, is_interactive):
|
||||||
|
"""
|
||||||
|
Rename across a recursive directory structure.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (str): Root directory to traverse. All subdirectories
|
||||||
|
will be visited.
|
||||||
|
in_list (list): List of src words to replace.
|
||||||
|
out_list (list): Matching list of words to replace with.
|
||||||
|
excl_list (list): List of paths to exclude.
|
||||||
|
fileend_list (list): List of file endings to accept. If
|
||||||
|
not given, accept all file endings.
|
||||||
|
is_interactive (bool): If we should stop to ask about the
|
||||||
|
replacements in each file.
|
||||||
|
|
||||||
|
"""
|
||||||
|
repl_mapping = zip(in_list, out_list)
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(path):
|
||||||
|
|
||||||
|
print("\ndir: %s\n" % root)
|
||||||
|
|
||||||
|
if any(fnmatch.fnmatch(root, excl) for excl in excl_list):
|
||||||
|
print("%s skipped (excluded)." % root)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
|
||||||
|
full_path = os.path.join(root, file)
|
||||||
|
if any(fnmatch.fnmatch(full_path, excl) for excl in excl_list):
|
||||||
|
print("%s skipped (excluded)." % full_path)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not fileend_list or any(file.endswith(ending) for ending in fileend_list):
|
||||||
|
rename_in_file(full_path, in_list, out_list, is_interactive)
|
||||||
|
|
||||||
|
# rename file - always ask
|
||||||
|
new_file = file
|
||||||
|
for src, dst in repl_mapping:
|
||||||
|
new_file = _case_sensitive_replace(new_file, src, dst)
|
||||||
|
if new_file != file:
|
||||||
|
inp = raw_input(_green("Rename %s\n -> %s\n Y/[N]? > " % (file, new_file)))
|
||||||
|
if inp.upper() == 'Y':
|
||||||
|
new_full_path = os.path.join(root, new_file)
|
||||||
|
try:
|
||||||
|
os.rename(full_path, new_full_path)
|
||||||
|
except OSError as err:
|
||||||
|
raw_input(_red("Could not rename - %s (return to skip)" % err))
|
||||||
|
else:
|
||||||
|
print("... Renamed.")
|
||||||
|
else:
|
||||||
|
print("... Skipped.")
|
||||||
|
# rename the dir
|
||||||
|
new_root = root
|
||||||
|
for src, dst in repl_mapping:
|
||||||
|
new_root = _case_sensitive_replace(new_root, src, dst)
|
||||||
|
if new_root != root:
|
||||||
|
inp = raw_input(_green("Dir Rename %s\n -> %s\n Y/[N]? > " % (root, new_root)))
|
||||||
|
if inp.upper() == 'Y':
|
||||||
|
try:
|
||||||
|
os.rename(root, new_root)
|
||||||
|
except OSError as err:
|
||||||
|
raw_input(_red("Could not rename - %s (return to skip)" % err))
|
||||||
|
else:
|
||||||
|
print("... Renamed.")
|
||||||
|
else:
|
||||||
|
print("... Skipped.")
|
||||||
|
|
||||||
|
|
||||||
|
def rename_in_file(path, in_list, out_list, is_interactive):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
path (str): Path to file in which to perform renaming.
|
||||||
|
in_list (list): List of src words to replace.
|
||||||
|
out_list (list): Matching list of words to replace with.
|
||||||
|
is_interactive (bool): If we should stop to ask about the
|
||||||
|
replacements in each file.
|
||||||
|
|
||||||
|
"""
|
||||||
|
print("-- %s" % path)
|
||||||
|
|
||||||
|
org_text = ""
|
||||||
|
new_text = None
|
||||||
|
if os.path.isdir(path):
|
||||||
|
print("%s is a directory. You should use the --recursive option." % path)
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
with open(path, 'r') as fil:
|
||||||
|
org_text = fil.read()
|
||||||
|
|
||||||
|
repl_mapping = zip(in_list, out_list)
|
||||||
|
|
||||||
|
if not is_interactive:
|
||||||
|
# just replace everything immediately
|
||||||
|
new_text = org_text
|
||||||
|
for src, dst in repl_mapping:
|
||||||
|
new_text = _case_sensitive_replace(new_text, src, dst)
|
||||||
|
if new_text != org_text:
|
||||||
|
if FAKE_MODE:
|
||||||
|
print(" ... Saved changes to %s. (faked)" % path)
|
||||||
|
else:
|
||||||
|
with open(path, 'w') as fil:
|
||||||
|
fil.write(new_text)
|
||||||
|
print(" ... Saved changes to %s." % path)
|
||||||
|
else:
|
||||||
|
# interactive mode
|
||||||
|
while True:
|
||||||
|
renamed = {}
|
||||||
|
|
||||||
|
org_lines = org_text.split("\n")
|
||||||
|
|
||||||
|
for iline, old_line in enumerate(org_lines):
|
||||||
|
new_line = old_line
|
||||||
|
for src, dst in repl_mapping:
|
||||||
|
new_line = _case_sensitive_replace(new_line, src, dst)
|
||||||
|
if new_line != old_line:
|
||||||
|
renamed[iline] = new_line
|
||||||
|
|
||||||
|
if not renamed:
|
||||||
|
# no changes
|
||||||
|
print(" ... no changes to %s." % path)
|
||||||
|
return
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
for iline, renamed_line in sorted(renamed.items(), key=lambda tup: tup[0]):
|
||||||
|
print("%3i orig: %s" % (iline + 1, org_lines[iline]))
|
||||||
|
print(" new : %s" % (_yellow(renamed_line)))
|
||||||
|
print(_green("%s (%i lines changed)" % (path, len(renamed))))
|
||||||
|
|
||||||
|
ret = raw_input(_green("Choose: "
|
||||||
|
"[q]uit, "
|
||||||
|
"[h]elp, "
|
||||||
|
"[s]kip file, "
|
||||||
|
"[i]gnore lines, "
|
||||||
|
"[c]lear ignores, "
|
||||||
|
"[a]ccept/save file: ".lower()))
|
||||||
|
|
||||||
|
if ret == "s":
|
||||||
|
# skip file entirely
|
||||||
|
print(" ... Skipping file %s." % path)
|
||||||
|
return
|
||||||
|
elif ret == "c":
|
||||||
|
# clear ignores - rerun rename
|
||||||
|
break
|
||||||
|
elif ret == "a":
|
||||||
|
# save result
|
||||||
|
for iline, renamed_line in renamed.items():
|
||||||
|
org_lines[iline] = renamed_line
|
||||||
|
|
||||||
|
if FAKE_MODE:
|
||||||
|
print(" ... Saved file %s (faked)" % path)
|
||||||
|
return
|
||||||
|
with open(path, 'w') as fil:
|
||||||
|
fil.writelines("\n".join(org_lines))
|
||||||
|
print(" ... Saved file %s" % path)
|
||||||
|
return
|
||||||
|
elif ret == "q":
|
||||||
|
print("Quit renaming program.")
|
||||||
|
sys.exit()
|
||||||
|
elif ret == "h":
|
||||||
|
raw_input(_HELP_TEXT.format(sources=in_list, targets=out_list))
|
||||||
|
elif ret.startswith("i"):
|
||||||
|
# ignore one or more lines
|
||||||
|
ignores = [int(ind) - 1 for ind in ret[1:].split(',') if ind.strip().isdigit()]
|
||||||
|
if not ignores:
|
||||||
|
raw_input("Ignore example: i 2,7,34,133\n (return to continue)")
|
||||||
|
continue
|
||||||
|
for ign in ignores:
|
||||||
|
renamed.pop(ign, None)
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Rename text in a source tree, or a single file")
|
||||||
|
|
||||||
|
parser.add_argument('-i', '--input', action='append',
|
||||||
|
help="Source word to rename (quote around multiple words)")
|
||||||
|
parser.add_argument('-o', '--output', action='append',
|
||||||
|
help="Word to rename a matching src-word to")
|
||||||
|
parser.add_argument('-x', '--exc', action='append',
|
||||||
|
help="File path patterns to exclude")
|
||||||
|
parser.add_argument('-a', '--auto', action='store_true',
|
||||||
|
help="Automatic mode, don't ask to rename")
|
||||||
|
parser.add_argument('-r', '--recursive', action='store_true',
|
||||||
|
help="Recurse subdirs")
|
||||||
|
parser.add_argument('-f', '--fileending', action='append',
|
||||||
|
help="Change which file endings to allow (default .py and .html)")
|
||||||
|
parser.add_argument('--nocolor', action='store_true',
|
||||||
|
help="Turn off in-program color")
|
||||||
|
parser.add_argument('--fake', action='store_true',
|
||||||
|
help="Simulate run but don't actually save")
|
||||||
|
parser.add_argument('path',
|
||||||
|
help="File or directory in which to rename text")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
in_list, out_list, exc_list, fileend_list = args.input, args.output, args.exc, args.fileending
|
||||||
|
|
||||||
|
if not (in_list and out_list):
|
||||||
|
print('At least one source- and destination word must be given.')
|
||||||
|
sys.exit()
|
||||||
|
if len(in_list) != len(out_list):
|
||||||
|
print('Number of sources must be identical to the number of destination arguments.')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
exc_list = exc_list or []
|
||||||
|
fileend_list = fileend_list or [".py", ".html"]
|
||||||
|
is_interactive = not args.auto
|
||||||
|
is_recursive = args.recursive
|
||||||
|
|
||||||
|
USE_COLOR = not args.nocolor
|
||||||
|
FAKE_MODE = args.fake
|
||||||
|
|
||||||
|
if is_recursive:
|
||||||
|
rename_in_tree(args.path, in_list, out_list, exc_list, fileend_list, is_interactive)
|
||||||
|
else:
|
||||||
|
rename_in_file(args.path, in_list, out_list, is_interactive)
|
||||||
13
bin/unix/evennia-docker-start.sh
Normal file
13
bin/unix/evennia-docker-start.sh
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
# called by the Dockerfile to start the server in docker mode
|
||||||
|
|
||||||
|
# remove leftover .pid files (such as from when dropping the container)
|
||||||
|
rm /usr/src/game/server/*.pid >& /dev/null || true
|
||||||
|
|
||||||
|
# start evennia server; log to server.log but also output to stdout so it can
|
||||||
|
# be viewed with docker-compose logs
|
||||||
|
exec 3>&1; evennia start 2>&1 1>&3 | tee /usr/src/game/server/logs/server.log; exec 3>&-
|
||||||
|
|
||||||
|
# start a shell to keep the container running
|
||||||
|
bash
|
||||||
|
|
@ -5,12 +5,13 @@ the python bin directory and makes the 'evennia' program available on
|
||||||
the command %path%.
|
the command %path%.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os, sys
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
# for pip install -e
|
# for pip install -e
|
||||||
sys.path.insert(0, os.path.abspath(os.getcwd()))
|
sys.path.insert(0, os.path.abspath(os.getcwd()))
|
||||||
# main library path
|
# main library path
|
||||||
sys.path.insert(0, os.path.join(sys.prefix, "Lib", "site-packages"))
|
sys.path.insert(0, os.path.join(sys.prefix, "Lib", "site-packages"))
|
||||||
|
|
||||||
from evennia.server.evennia_launcher import main
|
from evennia.server.evennia_launcher import main
|
||||||
main()
|
main()
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
0.6.0
|
0.8.0-dev
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ from builtins import object
|
||||||
|
|
||||||
# Typeclasses
|
# Typeclasses
|
||||||
|
|
||||||
DefaultPlayer = None
|
DefaultAccount = None
|
||||||
DefaultGuest = None
|
DefaultGuest = None
|
||||||
DefaultObject = None
|
DefaultObject = None
|
||||||
DefaultCharacter = None
|
DefaultCharacter = None
|
||||||
|
|
@ -36,7 +36,7 @@ DefaultScript = None
|
||||||
|
|
||||||
# Database models
|
# Database models
|
||||||
ObjectDB = None
|
ObjectDB = None
|
||||||
PlayerDB = None
|
AccountDB = None
|
||||||
ScriptDB = None
|
ScriptDB = None
|
||||||
ChannelDB = None
|
ChannelDB = None
|
||||||
Msg = None
|
Msg = None
|
||||||
|
|
@ -51,7 +51,7 @@ InterruptCommand = None
|
||||||
# search functions
|
# search functions
|
||||||
search_object = None
|
search_object = None
|
||||||
search_script = None
|
search_script = None
|
||||||
search_player = None
|
search_account = None
|
||||||
search_channel = None
|
search_channel = None
|
||||||
search_message = None
|
search_message = None
|
||||||
search_help = None
|
search_help = None
|
||||||
|
|
@ -60,7 +60,7 @@ search_tag = None
|
||||||
# create functions
|
# create functions
|
||||||
create_object = None
|
create_object = None
|
||||||
create_script = None
|
create_script = None
|
||||||
create_player = None
|
create_account = None
|
||||||
create_channel = None
|
create_channel = None
|
||||||
create_message = None
|
create_message = None
|
||||||
create_help_entry = None
|
create_help_entry = None
|
||||||
|
|
@ -79,9 +79,11 @@ EvMenu = None
|
||||||
EvTable = None
|
EvTable = None
|
||||||
EvForm = None
|
EvForm = None
|
||||||
EvEditor = None
|
EvEditor = None
|
||||||
|
EvMore = None
|
||||||
|
|
||||||
# Handlers
|
# Handlers
|
||||||
SESSION_HANDLER = None
|
SESSION_HANDLER = None
|
||||||
|
TASK_HANDLER = None
|
||||||
TICKER_HANDLER = None
|
TICKER_HANDLER = None
|
||||||
MONITOR_HANDLER = None
|
MONITOR_HANDLER = None
|
||||||
CHANNEL_HANDLER = None
|
CHANNEL_HANDLER = None
|
||||||
|
|
@ -108,26 +110,29 @@ def _create_version():
|
||||||
pass
|
pass
|
||||||
return version
|
return version
|
||||||
|
|
||||||
|
|
||||||
__version__ = _create_version()
|
__version__ = _create_version()
|
||||||
del _create_version
|
del _create_version
|
||||||
|
|
||||||
|
|
||||||
def _init():
|
def _init():
|
||||||
"""
|
"""
|
||||||
This function is called automatically by the launcher only after
|
This function is called automatically by the launcher only after
|
||||||
Evennia has fully initialized all its models. It sets up the API
|
Evennia has fully initialized all its models. It sets up the API
|
||||||
in a safe environment where all models are available already.
|
in a safe environment where all models are available already.
|
||||||
"""
|
"""
|
||||||
global DefaultPlayer, DefaultObject, DefaultGuest, DefaultCharacter
|
global DefaultAccount, DefaultObject, DefaultGuest, DefaultCharacter
|
||||||
global DefaultRoom, DefaultExit, DefaultChannel, DefaultScript
|
global DefaultRoom, DefaultExit, DefaultChannel, DefaultScript
|
||||||
global ObjectDB, PlayerDB, ScriptDB, ChannelDB, Msg
|
global ObjectDB, AccountDB, ScriptDB, ChannelDB, Msg
|
||||||
global Command, CmdSet, default_cmds, syscmdkeys, InterruptCommand
|
global Command, CmdSet, default_cmds, syscmdkeys, InterruptCommand
|
||||||
global search_object, search_script, search_player, search_channel, search_help, search_tag
|
global search_object, search_script, search_account, search_channel, search_help, search_tag, search_message
|
||||||
global create_object, create_script, create_player, create_channel, create_message, create_help_entry
|
global create_object, create_script, create_account, create_channel, create_message, create_help_entry
|
||||||
global settings,lockfuncs, logger, utils, gametime, ansi, spawn, managers
|
global settings, lockfuncs, logger, utils, gametime, ansi, spawn, managers
|
||||||
global contrib, TICKER_HANDLER, MONITOR_HANDLER, SESSION_HANDLER, CHANNEL_HANDLER
|
global contrib, TICKER_HANDLER, MONITOR_HANDLER, SESSION_HANDLER, CHANNEL_HANDLER, TASK_HANDLER
|
||||||
|
global EvMenu, EvTable, EvForm, EvMore, EvEditor
|
||||||
|
|
||||||
from .players.players import DefaultPlayer
|
from .accounts.accounts import DefaultAccount
|
||||||
from .players.players import DefaultGuest
|
from .accounts.accounts import DefaultGuest
|
||||||
from .objects.objects import DefaultObject
|
from .objects.objects import DefaultObject
|
||||||
from .objects.objects import DefaultCharacter
|
from .objects.objects import DefaultCharacter
|
||||||
from .objects.objects import DefaultRoom
|
from .objects.objects import DefaultRoom
|
||||||
|
|
@ -137,7 +142,7 @@ def _init():
|
||||||
|
|
||||||
# Database models
|
# Database models
|
||||||
from .objects.models import ObjectDB
|
from .objects.models import ObjectDB
|
||||||
from .players.models import PlayerDB
|
from .accounts.models import AccountDB
|
||||||
from .scripts.models import ScriptDB
|
from .scripts.models import ScriptDB
|
||||||
from .comms.models import ChannelDB
|
from .comms.models import ChannelDB
|
||||||
from .comms.models import Msg
|
from .comms.models import Msg
|
||||||
|
|
@ -149,7 +154,7 @@ def _init():
|
||||||
# search functions
|
# search functions
|
||||||
from .utils.search import search_object
|
from .utils.search import search_object
|
||||||
from .utils.search import search_script
|
from .utils.search import search_script
|
||||||
from .utils.search import search_player
|
from .utils.search import search_account
|
||||||
from .utils.search import search_message
|
from .utils.search import search_message
|
||||||
from .utils.search import search_channel
|
from .utils.search import search_channel
|
||||||
from .utils.search import search_help
|
from .utils.search import search_help
|
||||||
|
|
@ -158,7 +163,7 @@ def _init():
|
||||||
# create functions
|
# create functions
|
||||||
from .utils.create import create_object
|
from .utils.create import create_object
|
||||||
from .utils.create import create_script
|
from .utils.create import create_script
|
||||||
from .utils.create import create_player
|
from .utils.create import create_account
|
||||||
from .utils.create import create_channel
|
from .utils.create import create_channel
|
||||||
from .utils.create import create_message
|
from .utils.create import create_message
|
||||||
from .utils.create import create_help_entry
|
from .utils.create import create_help_entry
|
||||||
|
|
@ -178,6 +183,7 @@ def _init():
|
||||||
|
|
||||||
# handlers
|
# handlers
|
||||||
from .scripts.tickerhandler import TICKER_HANDLER
|
from .scripts.tickerhandler import TICKER_HANDLER
|
||||||
|
from .scripts.taskhandler import TASK_HANDLER
|
||||||
from .server.sessionhandler import SESSION_HANDLER
|
from .server.sessionhandler import SESSION_HANDLER
|
||||||
from .comms.channelhandler import CHANNEL_HANDLER
|
from .comms.channelhandler import CHANNEL_HANDLER
|
||||||
from .scripts.monitorhandler import MONITOR_HANDLER
|
from .scripts.monitorhandler import MONITOR_HANDLER
|
||||||
|
|
@ -189,6 +195,7 @@ def _init():
|
||||||
Parent for other containers
|
Parent for other containers
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _help(self):
|
def _help(self):
|
||||||
"Returns list of contents"
|
"Returns list of contents"
|
||||||
names = [name for name in self.__class__.__dict__ if not name.startswith('_')]
|
names = [name for name in self.__class__.__dict__ if not name.startswith('_')]
|
||||||
|
|
@ -196,13 +203,12 @@ def _init():
|
||||||
print(self.__doc__ + "-" * 60 + "\n" + ", ".join(names))
|
print(self.__doc__ + "-" * 60 + "\n" + ", ".join(names))
|
||||||
help = property(_help)
|
help = property(_help)
|
||||||
|
|
||||||
|
|
||||||
class DBmanagers(_EvContainer):
|
class DBmanagers(_EvContainer):
|
||||||
"""
|
"""
|
||||||
Links to instantiated database managers.
|
Links to instantiated database managers.
|
||||||
|
|
||||||
helpentry - HelpEntry.objects
|
helpentry - HelpEntry.objects
|
||||||
players - PlayerDB.objects
|
accounts - AccountDB.objects
|
||||||
scripts - ScriptDB.objects
|
scripts - ScriptDB.objects
|
||||||
msgs - Msg.objects
|
msgs - Msg.objects
|
||||||
channels - Channel.objects
|
channels - Channel.objects
|
||||||
|
|
@ -213,7 +219,7 @@ def _init():
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from .help.models import HelpEntry
|
from .help.models import HelpEntry
|
||||||
from .players.models import PlayerDB
|
from .accounts.models import AccountDB
|
||||||
from .scripts.models import ScriptDB
|
from .scripts.models import ScriptDB
|
||||||
from .comms.models import Msg, ChannelDB
|
from .comms.models import Msg, ChannelDB
|
||||||
from .objects.models import ObjectDB
|
from .objects.models import ObjectDB
|
||||||
|
|
@ -223,7 +229,7 @@ def _init():
|
||||||
|
|
||||||
# create container's properties
|
# create container's properties
|
||||||
helpentries = HelpEntry.objects
|
helpentries = HelpEntry.objects
|
||||||
players = PlayerDB.objects
|
accounts = AccountDB.objects
|
||||||
scripts = ScriptDB.objects
|
scripts = ScriptDB.objects
|
||||||
msgs = Msg.objects
|
msgs = Msg.objects
|
||||||
channels = ChannelDB.objects
|
channels = ChannelDB.objects
|
||||||
|
|
@ -232,14 +238,13 @@ def _init():
|
||||||
attributes = Attribute.objects
|
attributes = Attribute.objects
|
||||||
tags = Tag.objects
|
tags = Tag.objects
|
||||||
# remove these so they are not visible as properties
|
# remove these so they are not visible as properties
|
||||||
del HelpEntry, PlayerDB, ScriptDB, Msg, ChannelDB
|
del HelpEntry, AccountDB, ScriptDB, Msg, ChannelDB
|
||||||
#del ExternalChannelConnection
|
#del ExternalChannelConnection
|
||||||
del ObjectDB, ServerConfig, Tag, Attribute
|
del ObjectDB, ServerConfig, Tag, Attribute
|
||||||
|
|
||||||
managers = DBmanagers()
|
managers = DBmanagers()
|
||||||
del DBmanagers
|
del DBmanagers
|
||||||
|
|
||||||
|
|
||||||
class DefaultCmds(_EvContainer):
|
class DefaultCmds(_EvContainer):
|
||||||
"""
|
"""
|
||||||
This container holds direct shortcuts to all default commands in Evennia.
|
This container holds direct shortcuts to all default commands in Evennia.
|
||||||
|
|
@ -250,10 +255,10 @@ def _init():
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .commands.default.cmdset_character import CharacterCmdSet
|
from .commands.default.cmdset_character import CharacterCmdSet
|
||||||
from .commands.default.cmdset_player import PlayerCmdSet
|
from .commands.default.cmdset_account import AccountCmdSet
|
||||||
from .commands.default.cmdset_unloggedin import UnloggedinCmdSet
|
from .commands.default.cmdset_unloggedin import UnloggedinCmdSet
|
||||||
from .commands.default.cmdset_session import SessionCmdSet
|
from .commands.default.cmdset_session import SessionCmdSet
|
||||||
from .commands.default.muxcommand import MuxCommand, MuxPlayerCommand
|
from .commands.default.muxcommand import MuxCommand, MuxAccountCommand
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"populate the object with commands"
|
"populate the object with commands"
|
||||||
|
|
@ -264,15 +269,15 @@ def _init():
|
||||||
self.__dict__.update(dict([(c.__name__, c) for c in cmdlist]))
|
self.__dict__.update(dict([(c.__name__, c) for c in cmdlist]))
|
||||||
|
|
||||||
from .commands.default import (admin, batchprocess,
|
from .commands.default import (admin, batchprocess,
|
||||||
building, comms, general,
|
building, comms, general,
|
||||||
player, help, system, unloggedin)
|
account, help, system, unloggedin)
|
||||||
add_cmds(admin)
|
add_cmds(admin)
|
||||||
add_cmds(building)
|
add_cmds(building)
|
||||||
add_cmds(batchprocess)
|
add_cmds(batchprocess)
|
||||||
add_cmds(building)
|
add_cmds(building)
|
||||||
add_cmds(comms)
|
add_cmds(comms)
|
||||||
add_cmds(general)
|
add_cmds(general)
|
||||||
add_cmds(player)
|
add_cmds(account)
|
||||||
add_cmds(help)
|
add_cmds(help)
|
||||||
add_cmds(system)
|
add_cmds(system)
|
||||||
add_cmds(unloggedin)
|
add_cmds(unloggedin)
|
||||||
|
|
@ -280,7 +285,6 @@ def _init():
|
||||||
default_cmds = DefaultCmds()
|
default_cmds = DefaultCmds()
|
||||||
del DefaultCmds
|
del DefaultCmds
|
||||||
|
|
||||||
|
|
||||||
class SystemCmds(_EvContainer):
|
class SystemCmds(_EvContainer):
|
||||||
"""
|
"""
|
||||||
Creating commands with keys set to these constants will make
|
Creating commands with keys set to these constants will make
|
||||||
|
|
@ -293,7 +297,7 @@ def _init():
|
||||||
CMD_MULTIMATCH - multiple command matches were found
|
CMD_MULTIMATCH - multiple command matches were found
|
||||||
CMD_CHANNEL - the command name is a channel name
|
CMD_CHANNEL - the command name is a channel name
|
||||||
CMD_LOGINSTART - this command will be called as the very
|
CMD_LOGINSTART - this command will be called as the very
|
||||||
first command when a player connects to
|
first command when an account connects to
|
||||||
the server.
|
the server.
|
||||||
|
|
||||||
To access in code, do 'from evennia import syscmdkeys' then
|
To access in code, do 'from evennia import syscmdkeys' then
|
||||||
|
|
@ -311,6 +315,7 @@ def _init():
|
||||||
del SystemCmds
|
del SystemCmds
|
||||||
del _EvContainer
|
del _EvContainer
|
||||||
|
|
||||||
|
|
||||||
del object
|
del object
|
||||||
del absolute_import
|
del absolute_import
|
||||||
del print_function
|
del print_function
|
||||||
|
|
|
||||||
6
evennia/accounts/__init__.py
Normal file
6
evennia/accounts/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
"""
|
||||||
|
This sub-package defines the out-of-character entities known as
|
||||||
|
Accounts. These are equivalent to 'accounts' and can puppet one or
|
||||||
|
more Objects depending on settings. An Account has no in-game existence.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Typeclass for Player objects
|
Typeclass for Account objects
|
||||||
|
|
||||||
Note that this object is primarily intended to
|
Note that this object is primarily intended to
|
||||||
store OOC information, not game info! This
|
store OOC information, not game info! This
|
||||||
|
|
@ -15,8 +15,8 @@ import time
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from evennia.typeclasses.models import TypeclassBase
|
from evennia.typeclasses.models import TypeclassBase
|
||||||
from evennia.players.manager import PlayerManager
|
from evennia.accounts.manager import AccountManager
|
||||||
from evennia.players.models import PlayerDB
|
from evennia.accounts.models import AccountDB
|
||||||
from evennia.objects.models import ObjectDB
|
from evennia.objects.models import ObjectDB
|
||||||
from evennia.comms.models import ChannelDB
|
from evennia.comms.models import ChannelDB
|
||||||
from evennia.commands import cmdhandler
|
from evennia.commands import cmdhandler
|
||||||
|
|
@ -31,32 +31,32 @@ from evennia.commands.cmdsethandler import CmdSetHandler
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from future.utils import with_metaclass
|
from future.utils import with_metaclass
|
||||||
|
|
||||||
__all__ = ("DefaultPlayer",)
|
__all__ = ("DefaultAccount",)
|
||||||
|
|
||||||
_SESSIONS = None
|
_SESSIONS = None
|
||||||
|
|
||||||
_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
|
_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
|
||||||
_MULTISESSION_MODE = settings.MULTISESSION_MODE
|
_MULTISESSION_MODE = settings.MULTISESSION_MODE
|
||||||
_MAX_NR_CHARACTERS = settings.MAX_NR_CHARACTERS
|
_MAX_NR_CHARACTERS = settings.MAX_NR_CHARACTERS
|
||||||
_CMDSET_PLAYER = settings.CMDSET_PLAYER
|
_CMDSET_ACCOUNT = settings.CMDSET_ACCOUNT
|
||||||
_CONNECT_CHANNEL = None
|
_CONNECT_CHANNEL = None
|
||||||
|
|
||||||
|
|
||||||
class PlayerSessionHandler(object):
|
class AccountSessionHandler(object):
|
||||||
"""
|
"""
|
||||||
Manages the session(s) attached to a player.
|
Manages the session(s) attached to an account.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, player):
|
def __init__(self, account):
|
||||||
"""
|
"""
|
||||||
Initializes the handler.
|
Initializes the handler.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
player (Player): The Player on which this handler is defined.
|
account (Account): The Account on which this handler is defined.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.player = player
|
self.account = account
|
||||||
|
|
||||||
def get(self, sessid=None):
|
def get(self, sessid=None):
|
||||||
"""
|
"""
|
||||||
|
|
@ -75,9 +75,9 @@ class PlayerSessionHandler(object):
|
||||||
if not _SESSIONS:
|
if not _SESSIONS:
|
||||||
from evennia.server.sessionhandler import SESSIONS as _SESSIONS
|
from evennia.server.sessionhandler import SESSIONS as _SESSIONS
|
||||||
if sessid:
|
if sessid:
|
||||||
return make_iter(_SESSIONS.session_from_player(self.player, sessid))
|
return make_iter(_SESSIONS.session_from_account(self.account, sessid))
|
||||||
else:
|
else:
|
||||||
return _SESSIONS.sessions_from_player(self.player)
|
return _SESSIONS.sessions_from_account(self.account)
|
||||||
|
|
||||||
def all(self):
|
def all(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -100,19 +100,19 @@ class PlayerSessionHandler(object):
|
||||||
return len(self.get())
|
return len(self.get())
|
||||||
|
|
||||||
|
|
||||||
class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)):
|
||||||
"""
|
"""
|
||||||
This is the base Typeclass for all Players. Players represent
|
This is the base Typeclass for all Accounts. Accounts represent
|
||||||
the person playing the game and tracks account info, password
|
the person playing the game and tracks account info, password
|
||||||
etc. They are OOC entities without presence in-game. A Player
|
etc. They are OOC entities without presence in-game. An Account
|
||||||
can connect to a Character Object in order to "enter" the
|
can connect to a Character Object in order to "enter" the
|
||||||
game.
|
game.
|
||||||
|
|
||||||
Player Typeclass API:
|
Account Typeclass API:
|
||||||
|
|
||||||
* Available properties (only available on initiated typeclass objects)
|
* Available properties (only available on initiated typeclass objects)
|
||||||
|
|
||||||
- key (string) - name of player
|
- key (string) - name of account
|
||||||
- name (string)- wrapper for user.username
|
- name (string)- wrapper for user.username
|
||||||
- aliases (list of strings) - aliases to the object. Will be saved to
|
- aliases (list of strings) - aliases to the object. Will be saved to
|
||||||
database as AliasDB entries but returned as strings.
|
database as AliasDB entries but returned as strings.
|
||||||
|
|
@ -120,9 +120,9 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
- date_created (string) - time stamp of object creation
|
- date_created (string) - time stamp of object creation
|
||||||
- permissions (list of strings) - list of permission strings
|
- permissions (list of strings) - list of permission strings
|
||||||
- user (User, read-only) - django User authorization object
|
- user (User, read-only) - django User authorization object
|
||||||
- obj (Object) - game object controlled by player. 'character' can also
|
- obj (Object) - game object controlled by account. 'character' can also
|
||||||
be used.
|
be used.
|
||||||
- sessions (list of Sessions) - sessions connected to this player
|
- sessions (list of Sessions) - sessions connected to this account
|
||||||
- is_superuser (bool, read-only) - if the connected user is a superuser
|
- is_superuser (bool, read-only) - if the connected user is a superuser
|
||||||
|
|
||||||
* Handlers
|
* Handlers
|
||||||
|
|
@ -142,7 +142,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
- execute_cmd(raw_string)
|
- execute_cmd(raw_string)
|
||||||
- search(ostring, global_search=False, attribute_name=None,
|
- search(ostring, global_search=False, attribute_name=None,
|
||||||
use_nicks=False, location=None,
|
use_nicks=False, location=None,
|
||||||
ignore_errors=False, player=False)
|
ignore_errors=False, account=False)
|
||||||
- is_typeclass(typeclass, exact=False)
|
- is_typeclass(typeclass, exact=False)
|
||||||
- swap_typeclass(new_typeclass, clean_attributes=False, no_default=True)
|
- swap_typeclass(new_typeclass, clean_attributes=False, no_default=True)
|
||||||
- access(accessing_obj, access_type='read', default=False, no_superuser_bypass=False)
|
- access(accessing_obj, access_type='read', default=False, no_superuser_bypass=False)
|
||||||
|
|
@ -151,7 +151,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
* Hook methods
|
* Hook methods
|
||||||
|
|
||||||
basetype_setup()
|
basetype_setup()
|
||||||
at_player_creation()
|
at_account_creation()
|
||||||
|
|
||||||
> note that the following hooks are also found on Objects and are
|
> note that the following hooks are also found on Objects and are
|
||||||
usually handled on the character level:
|
usually handled on the character level:
|
||||||
|
|
@ -169,7 +169,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
objects = PlayerManager()
|
objects = AccountManager()
|
||||||
|
|
||||||
# properties
|
# properties
|
||||||
@lazy_property
|
@lazy_property
|
||||||
|
|
@ -186,24 +186,25 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
|
|
||||||
@lazy_property
|
@lazy_property
|
||||||
def sessions(self):
|
def sessions(self):
|
||||||
return PlayerSessionHandler(self)
|
return AccountSessionHandler(self)
|
||||||
|
|
||||||
# session-related methods
|
# session-related methods
|
||||||
|
|
||||||
def disconnect_session_from_player(self, session):
|
def disconnect_session_from_account(self, session, reason=None):
|
||||||
"""
|
"""
|
||||||
Access method for disconnecting a given session from the
|
Access method for disconnecting a given session from the
|
||||||
player (connection happens automatically in the
|
account (connection happens automatically in the
|
||||||
sessionhandler)
|
sessionhandler)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
session (Session): Session to disconnect.
|
session (Session): Session to disconnect.
|
||||||
|
reason (str, optional): Eventual reason for the disconnect.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
global _SESSIONS
|
global _SESSIONS
|
||||||
if not _SESSIONS:
|
if not _SESSIONS:
|
||||||
from evennia.server.sessionhandler import SESSIONS as _SESSIONS
|
from evennia.server.sessionhandler import SESSIONS as _SESSIONS
|
||||||
_SESSIONS.disconnect(session)
|
_SESSIONS.disconnect(session, reason)
|
||||||
|
|
||||||
# puppeting operations
|
# puppeting operations
|
||||||
|
|
||||||
|
|
@ -235,9 +236,9 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
# no access
|
# no access
|
||||||
self.msg("You don't have permission to puppet '%s'." % obj.key)
|
self.msg("You don't have permission to puppet '%s'." % obj.key)
|
||||||
return
|
return
|
||||||
if obj.player:
|
if obj.account:
|
||||||
# object already puppeted
|
# object already puppeted
|
||||||
if obj.player == self:
|
if obj.account == self:
|
||||||
if obj.sessions.count():
|
if obj.sessions.count():
|
||||||
# we may take over another of our sessions
|
# we may take over another of our sessions
|
||||||
# output messages to the affected sessions
|
# output messages to the affected sessions
|
||||||
|
|
@ -252,9 +253,9 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
self.msg(txt1 % obj.name, session=session)
|
self.msg(txt1 % obj.name, session=session)
|
||||||
self.msg(txt2 % obj.name, session=obj.sessions.all())
|
self.msg(txt2 % obj.name, session=obj.sessions.all())
|
||||||
self.unpuppet_object(obj.sessions.get())
|
self.unpuppet_object(obj.sessions.get())
|
||||||
elif obj.player.is_connected:
|
elif obj.account.is_connected:
|
||||||
# controlled by another player
|
# controlled by another account
|
||||||
self.msg("|c%s|R is already puppeted by another Player." % obj.key)
|
self.msg("|c%s|R is already puppeted by another Account." % obj.key)
|
||||||
return
|
return
|
||||||
|
|
||||||
# do the puppeting
|
# do the puppeting
|
||||||
|
|
@ -262,14 +263,14 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
# cleanly unpuppet eventual previous object puppeted by this session
|
# cleanly unpuppet eventual previous object puppeted by this session
|
||||||
self.unpuppet_object(session)
|
self.unpuppet_object(session)
|
||||||
# if we get to this point the character is ready to puppet or it
|
# if we get to this point the character is ready to puppet or it
|
||||||
# was left with a lingering player/session reference from an unclean
|
# was left with a lingering account/session reference from an unclean
|
||||||
# server kill or similar
|
# server kill or similar
|
||||||
|
|
||||||
obj.at_pre_puppet(self, session=session)
|
obj.at_pre_puppet(self, session=session)
|
||||||
|
|
||||||
# do the connection
|
# do the connection
|
||||||
obj.sessions.add(session)
|
obj.sessions.add(session)
|
||||||
obj.player = self
|
obj.account = self
|
||||||
session.puid = obj.id
|
session.puid = obj.id
|
||||||
session.puppet = obj
|
session.puppet = obj
|
||||||
# validate/start persistent scripts on object
|
# validate/start persistent scripts on object
|
||||||
|
|
@ -299,7 +300,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
obj.at_pre_unpuppet()
|
obj.at_pre_unpuppet()
|
||||||
obj.sessions.remove(session)
|
obj.sessions.remove(session)
|
||||||
if not obj.sessions.count():
|
if not obj.sessions.count():
|
||||||
del obj.player
|
del obj.account
|
||||||
obj.at_post_unpuppet(self, session=session)
|
obj.at_post_unpuppet(self, session=session)
|
||||||
# Just to be sure we're always clear.
|
# Just to be sure we're always clear.
|
||||||
session.puppet = None
|
session.puppet = None
|
||||||
|
|
@ -314,9 +315,9 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
|
|
||||||
def get_puppet(self, session):
|
def get_puppet(self, session):
|
||||||
"""
|
"""
|
||||||
Get an object puppeted by this session through this player. This is
|
Get an object puppeted by this session through this account. This is
|
||||||
the main method for retrieving the puppeted object from the
|
the main method for retrieving the puppeted object from the
|
||||||
player's end.
|
account's end.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
session (Session): Find puppeted object based on this session
|
session (Session): Find puppeted object based on this session
|
||||||
|
|
@ -333,7 +334,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
puppets (list): All puppeted objects currently controlled
|
puppets (list): All puppeted objects currently controlled
|
||||||
by this Player.
|
by this Account.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return list(set(session.puppet for session in self.sessions.all() if session.puppet))
|
return list(set(session.puppet for session in self.sessions.all() if session.puppet))
|
||||||
|
|
@ -359,7 +360,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Deletes the player permanently.
|
Deletes the account permanently.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
`*args` and `**kwargs` are passed on to the base delete
|
`*args` and `**kwargs` are passed on to the base delete
|
||||||
|
|
@ -375,12 +376,12 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
# no puppet to disconnect from
|
# no puppet to disconnect from
|
||||||
pass
|
pass
|
||||||
session.sessionhandler.disconnect(session, reason=_("Player being deleted."))
|
session.sessionhandler.disconnect(session, reason=_("Account being deleted."))
|
||||||
self.scripts.stop()
|
self.scripts.stop()
|
||||||
self.attributes.clear()
|
self.attributes.clear()
|
||||||
self.nicks.clear()
|
self.nicks.clear()
|
||||||
self.aliases.clear()
|
self.aliases.clear()
|
||||||
super(DefaultPlayer, self).delete(*args, **kwargs)
|
super(DefaultAccount, self).delete(*args, **kwargs)
|
||||||
# methods inherited from database model
|
# methods inherited from database model
|
||||||
|
|
||||||
def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs):
|
def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs):
|
||||||
|
|
@ -391,8 +392,8 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
text (str, optional): text data to send
|
text (str, optional): text data to send
|
||||||
from_obj (Object or Player, optional): Object sending. If given,
|
from_obj (Object or Account or list, optional): Object sending. If given, its
|
||||||
its at_msg_send() hook will be called.
|
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
|
||||||
Sessions to receive this send. If given, overrules the
|
Sessions to receive this send. If given, overrules the
|
||||||
default send behavior for the current
|
default send behavior for the current
|
||||||
|
|
@ -404,14 +405,15 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
"""
|
"""
|
||||||
if from_obj:
|
if from_obj:
|
||||||
# call hook
|
# call hook
|
||||||
try:
|
for obj in make_iter(from_obj):
|
||||||
from_obj.at_msg_send(text=text, to_obj=self, **kwargs)
|
try:
|
||||||
except Exception:
|
obj.at_msg_send(text=text, to_obj=self, **kwargs)
|
||||||
# this may not be assigned.
|
except Exception:
|
||||||
pass
|
# this may not be assigned.
|
||||||
|
logger.log_trace()
|
||||||
try:
|
try:
|
||||||
if not self.at_msg_receive(text=text, **kwargs):
|
if not self.at_msg_receive(text=text, **kwargs):
|
||||||
# abort message to this player
|
# abort message to this account
|
||||||
return
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
# this may not be assigned.
|
# this may not be assigned.
|
||||||
|
|
@ -426,9 +428,9 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
|
|
||||||
def execute_cmd(self, raw_string, session=None, **kwargs):
|
def execute_cmd(self, raw_string, session=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Do something as this player. This method is never called normally,
|
Do something as this account. This method is never called normally,
|
||||||
but only when the player object itself is supposed to execute the
|
but only when the account object itself is supposed to execute the
|
||||||
command. It takes player nicks into account, but not nicks of
|
command. It takes account nicks into account, but not nicks of
|
||||||
eventual puppets.
|
eventual puppets.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -445,33 +447,33 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
raw_string = to_unicode(raw_string)
|
raw_string = to_unicode(raw_string)
|
||||||
raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_player=False)
|
raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_account=False)
|
||||||
if not session and _MULTISESSION_MODE in (0, 1):
|
if not session and _MULTISESSION_MODE in (0, 1):
|
||||||
# for these modes we use the first/only session
|
# for these modes we use the first/only session
|
||||||
sessions = self.sessions.get()
|
sessions = self.sessions.get()
|
||||||
session = sessions[0] if sessions else None
|
session = sessions[0] if sessions else None
|
||||||
|
|
||||||
return cmdhandler.cmdhandler(self, raw_string,
|
return cmdhandler.cmdhandler(self, raw_string,
|
||||||
callertype="player", session=session, **kwargs)
|
callertype="account", session=session, **kwargs)
|
||||||
|
|
||||||
def search(self, searchdata, return_puppet=False, search_object=False,
|
def search(self, searchdata, return_puppet=False, search_object=False,
|
||||||
typeclass=None, nofound_string=None, multimatch_string=None, **kwargs):
|
typeclass=None, nofound_string=None, multimatch_string=None, use_nicks=True, **kwargs):
|
||||||
"""
|
"""
|
||||||
This is similar to `DefaultObject.search` but defaults to searching
|
This is similar to `DefaultObject.search` but defaults to searching
|
||||||
for Players only.
|
for Accounts only.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
searchdata (str or int): Search criterion, the Player's
|
searchdata (str or int): Search criterion, the Account's
|
||||||
key or dbref to search for.
|
key or dbref to search for.
|
||||||
return_puppet (bool, optional): Instructs the method to
|
return_puppet (bool, optional): Instructs the method to
|
||||||
return matches as the object the Player controls rather
|
return matches as the object the Account controls rather
|
||||||
than the Player itself (or None) if nothing is puppeted).
|
than the Account itself (or None) if nothing is puppeted).
|
||||||
search_object (bool, optional): Search for Objects instead of
|
search_object (bool, optional): Search for Objects instead of
|
||||||
Players. This is used by e.g. the @examine command when
|
Accounts. This is used by e.g. the @examine command when
|
||||||
wanting to examine Objects while OOC.
|
wanting to examine Objects while OOC.
|
||||||
typeclass (Player typeclass, optional): Limit the search
|
typeclass (Account typeclass, optional): Limit the search
|
||||||
only to this particular typeclass. This can be used to
|
only to this particular typeclass. This can be used to
|
||||||
limit to specific player typeclasses or to limit the search
|
limit to specific account typeclasses or to limit the search
|
||||||
to a particular Object typeclass if `search_object` is True.
|
to a particular Object typeclass if `search_object` is True.
|
||||||
nofound_string (str, optional): A one-time error message
|
nofound_string (str, optional): A one-time error message
|
||||||
to echo if `searchdata` leads to no matches. If not given,
|
to echo if `searchdata` leads to no matches. If not given,
|
||||||
|
|
@ -479,9 +481,10 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
multimatch_string (str, optional): A one-time error
|
multimatch_string (str, optional): A one-time error
|
||||||
message to echo if `searchdata` leads to multiple matches.
|
message to echo if `searchdata` leads to multiple matches.
|
||||||
If not given, will fall back to the default handler.
|
If not given, will fall back to the default handler.
|
||||||
|
use_nicks (bool, optional): Use account-level nick replacement.
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
match (Player, Object or None): A single Player or Object match.
|
match (Account, Object or None): A single Account or Object match.
|
||||||
Notes:
|
Notes:
|
||||||
Extra keywords are ignored, but are allowed in call in
|
Extra keywords are ignored, but are allowed in call in
|
||||||
order to make API more consistent with
|
order to make API more consistent with
|
||||||
|
|
@ -494,9 +497,11 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
if searchdata.lower() in ("me", "*me", "self", "*self",):
|
if searchdata.lower() in ("me", "*me", "self", "*self",):
|
||||||
return self
|
return self
|
||||||
if search_object:
|
if search_object:
|
||||||
matches = ObjectDB.objects.object_search(searchdata, typeclass=typeclass)
|
matches = ObjectDB.objects.object_search(searchdata, typeclass=typeclass, use_nicks=use_nicks)
|
||||||
else:
|
else:
|
||||||
matches = PlayerDB.objects.player_search(searchdata, typeclass=typeclass)
|
searchdata = self.nicks.nickreplace(searchdata, categories=("account", ), include_account=False)
|
||||||
|
|
||||||
|
matches = AccountDB.objects.account_search(searchdata, typeclass=typeclass)
|
||||||
matches = _AT_SEARCH_RESULT(matches, self, query=searchdata,
|
matches = _AT_SEARCH_RESULT(matches, self, query=searchdata,
|
||||||
nofound_string=nofound_string,
|
nofound_string=nofound_string,
|
||||||
multimatch_string=multimatch_string)
|
multimatch_string=multimatch_string)
|
||||||
|
|
@ -527,8 +532,8 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
result (bool): Result of access check.
|
result (bool): Result of access check.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
result = super(DefaultPlayer, self).access(accessing_obj, access_type=access_type,
|
result = super(DefaultAccount, self).access(accessing_obj, access_type=access_type,
|
||||||
default=default, no_superuser_bypass=no_superuser_bypass)
|
default=default, no_superuser_bypass=no_superuser_bypass)
|
||||||
self.at_access(result, accessing_obj, access_type, **kwargs)
|
self.at_access(result, accessing_obj, access_type, **kwargs)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
@ -554,33 +559,34 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
return time.time() - float(min(conn))
|
return time.time() - float(min(conn))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# player hooks
|
# account hooks
|
||||||
|
|
||||||
def basetype_setup(self):
|
def basetype_setup(self):
|
||||||
"""
|
"""
|
||||||
This sets up the basic properties for a player. Overload this
|
This sets up the basic properties for an account. Overload this
|
||||||
with at_player_creation rather than changing this method.
|
with at_account_creation rather than changing this method.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# A basic security setup
|
# A basic security setup
|
||||||
lockstring = "examine:perm(Wizards);edit:perm(Wizards);" \
|
lockstring = "examine:perm(Admin);edit:perm(Admin);" \
|
||||||
"delete:perm(Wizards);boot:perm(Wizards);msg:all()"
|
"delete:perm(Admin);boot:perm(Admin);msg:all();" \
|
||||||
|
"noidletimeout:perm(Builder) or perm(noidletimeout)"
|
||||||
self.locks.add(lockstring)
|
self.locks.add(lockstring)
|
||||||
|
|
||||||
# The ooc player cmdset
|
# The ooc account cmdset
|
||||||
self.cmdset.add_default(_CMDSET_PLAYER, permanent=True)
|
self.cmdset.add_default(_CMDSET_ACCOUNT, permanent=True)
|
||||||
|
|
||||||
def at_player_creation(self):
|
def at_account_creation(self):
|
||||||
"""
|
"""
|
||||||
This is called once, the very first time the player is created
|
This is called once, the very first time the account is created
|
||||||
(i.e. first time they register with the game). It's a good
|
(i.e. first time they register with the game). It's a good
|
||||||
place to store attributes all players should have, like
|
place to store attributes all accounts should have, like
|
||||||
configuration values etc.
|
configuration values etc.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# set an (empty) attribute holding the characters this player has
|
# set an (empty) attribute holding the characters this account has
|
||||||
lockstring = "attrread:perm(Admins);attredit:perm(Admins);" \
|
lockstring = "attrread:perm(Admins);attredit:perm(Admins);" \
|
||||||
"attrcreate:perm(Admins)"
|
"attrcreate:perm(Admins);"
|
||||||
self.attributes.add("_playable_characters", [], lockstring=lockstring)
|
self.attributes.add("_playable_characters", [], lockstring=lockstring)
|
||||||
self.attributes.add("_saved_protocol_flags", {}, lockstring=lockstring)
|
self.attributes.add("_saved_protocol_flags", {}, lockstring=lockstring)
|
||||||
|
|
||||||
|
|
@ -590,8 +596,8 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
that is, whenever it its typeclass is cached from memory. This
|
that is, whenever it its typeclass is cached from memory. This
|
||||||
happens on-demand first time the object is used or activated
|
happens on-demand first time the object is used or activated
|
||||||
in some way after being created but also after each server
|
in some way after being created but also after each server
|
||||||
restart or reload. In the case of player objects, this usually
|
restart or reload. In the case of account objects, this usually
|
||||||
happens the moment the player logs in or reconnects after a
|
happens the moment the account logs in or reconnects after a
|
||||||
reload.
|
reload.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -601,7 +607,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
# typeclass. You can often ignore these and rely on the character
|
# typeclass. You can often ignore these and rely on the character
|
||||||
# ones instead, unless you are implementing a multi-character game
|
# ones instead, unless you are implementing a multi-character game
|
||||||
# and have some things that should be done regardless of which
|
# and have some things that should be done regardless of which
|
||||||
# character is currently connected to this player.
|
# character is currently connected to this account.
|
||||||
|
|
||||||
def at_first_save(self):
|
def at_first_save(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -611,11 +617,11 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.basetype_setup()
|
self.basetype_setup()
|
||||||
self.at_player_creation()
|
self.at_account_creation()
|
||||||
|
|
||||||
permissions = settings.PERMISSION_PLAYER_DEFAULT
|
permissions = settings.PERMISSION_ACCOUNT_DEFAULT
|
||||||
if hasattr(self, "_createdict"):
|
if hasattr(self, "_createdict"):
|
||||||
# this will only be set if the utils.create_player
|
# this will only be set if the utils.create_account
|
||||||
# function was used to create the object.
|
# function was used to create the object.
|
||||||
cdict = self._createdict
|
cdict = self._createdict
|
||||||
if cdict.get("locks"):
|
if cdict.get("locks"):
|
||||||
|
|
@ -624,11 +630,11 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
permissions = cdict["permissions"]
|
permissions = cdict["permissions"]
|
||||||
del self._createdict
|
del self._createdict
|
||||||
|
|
||||||
self.permissions.add(permissions)
|
self.permissions.batch_add(*permissions)
|
||||||
|
|
||||||
def at_access(self, result, accessing_obj, access_type, **kwargs):
|
def at_access(self, result, accessing_obj, access_type, **kwargs):
|
||||||
"""
|
"""
|
||||||
This is triggered after an access-call on this Player has
|
This is triggered after an access-call on this Account has
|
||||||
completed.
|
completed.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -653,33 +659,41 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
|
|
||||||
def at_cmdset_get(self, **kwargs):
|
def at_cmdset_get(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Called just *before* cmdsets on this player are requested by
|
Called just *before* cmdsets on this account are requested by
|
||||||
the command handler. The cmdsets are available as
|
the command handler. The cmdsets are available as
|
||||||
`self.cmdset`. If changes need to be done on the fly to the
|
`self.cmdset`. If changes need to be done on the fly to the
|
||||||
cmdset before passing them on to the cmdhandler, this is the
|
cmdset before passing them on to the cmdhandler, this is the
|
||||||
place to do it. This is called also if the player currently
|
place to do it. This is called also if the account currently
|
||||||
have no cmdsets. kwargs are usually not used unless the
|
have no cmdsets. kwargs are usually not used unless the
|
||||||
cmdset is generated dynamically.
|
cmdset is generated dynamically.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def at_first_login(self):
|
def at_first_login(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Called the very first time this player logs into the game.
|
Called the very first time this account logs into the game.
|
||||||
Note that this is called *before* at_pre_login, so no session
|
Note that this is called *before* at_pre_login, so no session
|
||||||
is established and usually no character is yet assigned at
|
is established and usually no character is yet assigned at
|
||||||
this point. This hook is intended for player-specific setup
|
this point. This hook is intended for account-specific setup
|
||||||
like configurations.
|
like configurations.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def at_pre_login(self):
|
def at_pre_login(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Called every time the user logs in, just before the actual
|
Called every time the user logs in, just before the actual
|
||||||
login-state is set.
|
login-state is set.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -706,13 +720,15 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
else:
|
else:
|
||||||
logger.log_info("[%s]: %s" % (now, message))
|
logger.log_info("[%s]: %s" % (now, message))
|
||||||
|
|
||||||
def at_post_login(self, session=None):
|
def at_post_login(self, session=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Called at the end of the login process, just before letting
|
Called at the end of the login process, just before letting
|
||||||
the player loose.
|
the account loose.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
session (Session, optional): Session logging in, if any.
|
session (Session, optional): Session logging in, if any.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
This is called *before* an eventual Character's
|
This is called *before* an eventual Character's
|
||||||
|
|
@ -747,14 +763,14 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
return
|
return
|
||||||
elif _MULTISESSION_MODE in (2, 3):
|
elif _MULTISESSION_MODE in (2, 3):
|
||||||
# In this mode we by default end up at a character selection
|
# In this mode we by default end up at a character selection
|
||||||
# screen. We execute look on the player.
|
# screen. We execute look on the account.
|
||||||
# we make sure to clean up the _playable_characers list in case
|
# we make sure to clean up the _playable_characters list in case
|
||||||
# any was deleted in the interim.
|
# any was deleted in the interim.
|
||||||
self.db._playable_characters = [char for char in self.db._playable_characters if char]
|
self.db._playable_characters = [char for char in self.db._playable_characters if char]
|
||||||
self.msg(self.at_look(target=self.db._playable_characters,
|
self.msg(self.at_look(target=self.db._playable_characters,
|
||||||
session=session))
|
session=session))
|
||||||
|
|
||||||
def at_failed_login(self, session):
|
def at_failed_login(self, session, **kwargs):
|
||||||
"""
|
"""
|
||||||
Called by the login process if a user account is targeted correctly
|
Called by the login process if a user account is targeted correctly
|
||||||
but provided with an invalid password. By default it does nothing,
|
but provided with an invalid password. By default it does nothing,
|
||||||
|
|
@ -762,41 +778,87 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
session (session): Session logging in.
|
session (session): Session logging in.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def at_disconnect(self, reason=None):
|
def at_disconnect(self, reason=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Called just before user is disconnected.
|
Called just before user is disconnected.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
reason (str, optional): The reason given for the disconnect,
|
reason (str, optional): The reason given for the disconnect,
|
||||||
(echoed to the connection channel by default).
|
(echoed to the connection channel by default).
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
reason = reason and "(%s)" % reason or ""
|
reason = " (%s)" % reason if reason else ""
|
||||||
self._send_to_connect_channel("|R%s disconnected %s|n" % (self.key, reason))
|
self._send_to_connect_channel("|R%s disconnected%s|n" % (self.key, reason))
|
||||||
|
|
||||||
def at_post_disconnect(self):
|
def at_post_disconnect(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
This is called *after* disconnection is complete. No messages
|
This is called *after* disconnection is complete. No messages
|
||||||
can be relayed to the player from here. After this call, the
|
can be relayed to the account from here. After this call, the
|
||||||
player should not be accessed any more, making this a good
|
account should not be accessed any more, making this a good
|
||||||
spot for deleting it (in the case of a guest player account,
|
spot for deleting it (in the case of a guest account account,
|
||||||
for example).
|
for example).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def at_message_receive(self, message, from_obj=None):
|
def at_msg_receive(self, text=None, from_obj=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
This is currently unused.
|
This hook is called whenever someone sends a message to this
|
||||||
|
object using the `msg` method.
|
||||||
|
|
||||||
|
Note that from_obj may be None if the sender did not include
|
||||||
|
itself as an argument to the obj.msg() call - so you have to
|
||||||
|
check for this. .
|
||||||
|
|
||||||
|
Consider this a pre-processing method before msg is passed on
|
||||||
|
to the user session. If this method returns False, the msg
|
||||||
|
will not be passed on.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text (str, optional): The message received.
|
||||||
|
from_obj (any, optional): The object sending the message.
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
This includes any keywords sent to the `msg` method.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
receive (bool): If this message should be received.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
If this method returns False, the `msg` operation
|
||||||
|
will abort without sending the message.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def at_message_send(self, message, to_object):
|
def at_msg_send(self, text=None, to_obj=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
This is currently unused.
|
This is a hook that is called when *this* object sends a
|
||||||
|
message to another object with `obj.msg(text, to_obj=obj)`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text (str, optional): Text to send.
|
||||||
|
to_obj (any, optional): The object to send to.
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
Keywords passed from msg()
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Since this method is executed by `from_obj`, if no `from_obj`
|
||||||
|
was passed to `DefaultCharacter.msg` this hook will never
|
||||||
|
get called.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
@ -817,7 +879,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def at_look(self, target=None, session=None):
|
def at_look(self, target=None, session=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Called when this object executes a look. It allows to customize
|
Called when this object executes a look. It allows to customize
|
||||||
just what this means.
|
just what this means.
|
||||||
|
|
@ -826,6 +888,8 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
target (Object or list, optional): An object or a list
|
target (Object or list, optional): An object or a list
|
||||||
objects to inspect.
|
objects to inspect.
|
||||||
session (Session, optional): The session doing this look.
|
session (Session, optional): The session doing this look.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
look_string (str): A prepared look string, ready to send
|
look_string (str): A prepared look string, ready to send
|
||||||
|
|
@ -849,10 +913,10 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
result.append(nsess == 1 and "\n\n|wConnected session:|n" or "\n\n|wConnected sessions (%i):|n" % nsess)
|
result.append(nsess == 1 and "\n\n|wConnected session:|n" or "\n\n|wConnected sessions (%i):|n" % nsess)
|
||||||
for isess, sess in enumerate(sessions):
|
for isess, sess in enumerate(sessions):
|
||||||
csessid = sess.sessid
|
csessid = sess.sessid
|
||||||
addr = "%s (%s)" % (sess.protocol_key, isinstance(sess.address, tuple)
|
addr = "%s (%s)" % (sess.protocol_key, isinstance(sess.address, tuple) and
|
||||||
and str(sess.address[0]) or str(sess.address))
|
str(sess.address[0]) or str(sess.address))
|
||||||
result.append("\n %s %s" % (session.sessid == csessid and "|w* %s|n" % (isess + 1)
|
result.append("\n %s %s" % (session.sessid == csessid and "|w* %s|n" % (isess + 1) or
|
||||||
or " %s" % (isess + 1), addr))
|
" %s" % (isess + 1), addr))
|
||||||
result.append("\n\n |whelp|n - more commands")
|
result.append("\n\n |whelp|n - more commands")
|
||||||
result.append("\n |wooc <Text>|n - talk on public channel")
|
result.append("\n |wooc <Text>|n - talk on public channel")
|
||||||
|
|
||||||
|
|
@ -893,18 +957,21 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||||
return look_string
|
return look_string
|
||||||
|
|
||||||
|
|
||||||
class DefaultGuest(DefaultPlayer):
|
class DefaultGuest(DefaultAccount):
|
||||||
"""
|
"""
|
||||||
This class is used for guest logins. Unlike Players, Guests and
|
This class is used for guest logins. Unlike Accounts, Guests and
|
||||||
their characters are deleted after disconnection.
|
their characters are deleted after disconnection.
|
||||||
"""
|
"""
|
||||||
def at_post_login(self, session=None):
|
|
||||||
|
def at_post_login(self, session=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
In theory, guests only have one character regardless of which
|
In theory, guests only have one character regardless of which
|
||||||
MULTISESSION_MODE we're in. They don't get a choice.
|
MULTISESSION_MODE we're in. They don't get a choice.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
session (Session, optional): Session connecting.
|
session (Session, optional): Session connecting.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self._send_to_connect_channel("|G%s connected|n" % self.key)
|
self._send_to_connect_channel("|G%s connected|n" % self.key)
|
||||||
|
|
@ -922,9 +989,14 @@ class DefaultGuest(DefaultPlayer):
|
||||||
print "deleting Character:", character
|
print "deleting Character:", character
|
||||||
character.delete()
|
character.delete()
|
||||||
|
|
||||||
def at_post_disconnect(self):
|
def at_post_disconnect(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Once having disconnected, destroy the guest's characters and
|
Once having disconnected, destroy the guest's characters and
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
super(DefaultGuest, self).at_post_disconnect()
|
super(DefaultGuest, self).at_post_disconnect()
|
||||||
characters = self.db._playable_characters
|
characters = self.db._playable_characters
|
||||||
|
|
@ -9,19 +9,19 @@ from django.conf import settings
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||||
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
|
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
|
||||||
from evennia.players.models import PlayerDB
|
from evennia.accounts.models import AccountDB
|
||||||
from evennia.typeclasses.admin import AttributeInline, TagInline
|
from evennia.typeclasses.admin import AttributeInline, TagInline
|
||||||
from evennia.utils import create
|
from evennia.utils import create
|
||||||
|
|
||||||
|
|
||||||
# handle the custom User editor
|
# handle the custom User editor
|
||||||
class PlayerDBChangeForm(UserChangeForm):
|
class AccountDBChangeForm(UserChangeForm):
|
||||||
"""
|
"""
|
||||||
Modify the playerdb class.
|
Modify the accountdb class.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
class Meta(object):
|
class Meta(object):
|
||||||
model = PlayerDB
|
model = AccountDB
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
username = forms.RegexField(
|
username = forms.RegexField(
|
||||||
|
|
@ -44,19 +44,19 @@ class PlayerDBChangeForm(UserChangeForm):
|
||||||
username = self.cleaned_data['username']
|
username = self.cleaned_data['username']
|
||||||
if username.upper() == self.instance.username.upper():
|
if username.upper() == self.instance.username.upper():
|
||||||
return username
|
return username
|
||||||
elif PlayerDB.objects.filter(username__iexact=username):
|
elif AccountDB.objects.filter(username__iexact=username):
|
||||||
raise forms.ValidationError('A player with that name '
|
raise forms.ValidationError('An account with that name '
|
||||||
'already exists.')
|
'already exists.')
|
||||||
return self.cleaned_data['username']
|
return self.cleaned_data['username']
|
||||||
|
|
||||||
|
|
||||||
class PlayerDBCreationForm(UserCreationForm):
|
class AccountDBCreationForm(UserCreationForm):
|
||||||
"""
|
"""
|
||||||
Create a new PlayerDB instance.
|
Create a new AccountDB instance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta(object):
|
class Meta(object):
|
||||||
model = PlayerDB
|
model = AccountDB
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
username = forms.RegexField(
|
username = forms.RegexField(
|
||||||
|
|
@ -76,24 +76,24 @@ class PlayerDBCreationForm(UserCreationForm):
|
||||||
Cleanup username.
|
Cleanup username.
|
||||||
"""
|
"""
|
||||||
username = self.cleaned_data['username']
|
username = self.cleaned_data['username']
|
||||||
if PlayerDB.objects.filter(username__iexact=username):
|
if AccountDB.objects.filter(username__iexact=username):
|
||||||
raise forms.ValidationError('A player with that name already '
|
raise forms.ValidationError('An account with that name already '
|
||||||
'exists.')
|
'exists.')
|
||||||
return username
|
return username
|
||||||
|
|
||||||
|
|
||||||
class PlayerForm(forms.ModelForm):
|
class AccountForm(forms.ModelForm):
|
||||||
"""
|
"""
|
||||||
Defines how to display Players
|
Defines how to display Accounts
|
||||||
|
|
||||||
"""
|
"""
|
||||||
class Meta(object):
|
class Meta(object):
|
||||||
model = PlayerDB
|
model = AccountDB
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
db_key = forms.RegexField(
|
db_key = forms.RegexField(
|
||||||
label="Username",
|
label="Username",
|
||||||
initial="PlayerDummy",
|
initial="AccountDummy",
|
||||||
max_length=30,
|
max_length=30,
|
||||||
regex=r'^[\w. @+-]+$',
|
regex=r'^[\w. @+-]+$',
|
||||||
required=False,
|
required=False,
|
||||||
|
|
@ -101,32 +101,32 @@ class PlayerForm(forms.ModelForm):
|
||||||
error_messages={
|
error_messages={
|
||||||
'invalid': "This value may contain only letters, spaces, numbers"
|
'invalid': "This value may contain only letters, spaces, numbers"
|
||||||
" and @/./+/-/_ characters."},
|
" and @/./+/-/_ characters."},
|
||||||
help_text="This should be the same as the connected Player's key "
|
help_text="This should be the same as the connected Account's key "
|
||||||
"name. 30 characters or fewer. Letters, spaces, digits and "
|
"name. 30 characters or fewer. Letters, spaces, digits and "
|
||||||
"@/./+/-/_ only.")
|
"@/./+/-/_ only.")
|
||||||
|
|
||||||
db_typeclass_path = forms.CharField(
|
db_typeclass_path = forms.CharField(
|
||||||
label="Typeclass",
|
label="Typeclass",
|
||||||
initial=settings.BASE_PLAYER_TYPECLASS,
|
initial=settings.BASE_ACCOUNT_TYPECLASS,
|
||||||
widget=forms.TextInput(
|
widget=forms.TextInput(
|
||||||
attrs={'size': '78'}),
|
attrs={'size': '78'}),
|
||||||
help_text="Required. Defines what 'type' of entity this is. This "
|
help_text="Required. Defines what 'type' of entity this is. This "
|
||||||
"variable holds a Python path to a module with a valid "
|
"variable holds a Python path to a module with a valid "
|
||||||
"Evennia Typeclass. Defaults to "
|
"Evennia Typeclass. Defaults to "
|
||||||
"settings.BASE_PLAYER_TYPECLASS.")
|
"settings.BASE_ACCOUNT_TYPECLASS.")
|
||||||
|
|
||||||
db_permissions = forms.CharField(
|
db_permissions = forms.CharField(
|
||||||
label="Permissions",
|
label="Permissions",
|
||||||
initial=settings.PERMISSION_PLAYER_DEFAULT,
|
initial=settings.PERMISSION_ACCOUNT_DEFAULT,
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.TextInput(
|
widget=forms.TextInput(
|
||||||
attrs={'size': '78'}),
|
attrs={'size': '78'}),
|
||||||
help_text="In-game permissions. A comma-separated list of text "
|
help_text="In-game permissions. A comma-separated list of text "
|
||||||
"strings checked by certain locks. They are often used for "
|
"strings checked by certain locks. They are often used for "
|
||||||
"hierarchies, such as letting a Player have permission "
|
"hierarchies, such as letting an Account have permission "
|
||||||
"'Wizards', 'Builders' etc. A Player permission can be "
|
"'Admin', 'Builder' etc. An Account permission can be "
|
||||||
"overloaded by the permissions of a controlled Character. "
|
"overloaded by the permissions of a controlled Character. "
|
||||||
"Normal players use 'Players' by default.")
|
"Normal accounts use 'Accounts' by default.")
|
||||||
|
|
||||||
db_lock_storage = forms.CharField(
|
db_lock_storage = forms.CharField(
|
||||||
label="Locks",
|
label="Locks",
|
||||||
|
|
@ -137,64 +137,64 @@ class PlayerForm(forms.ModelForm):
|
||||||
"<i>type:lockfunction(args);type2:lockfunction2(args);...")
|
"<i>type:lockfunction(args);type2:lockfunction2(args);...")
|
||||||
db_cmdset_storage = forms.CharField(
|
db_cmdset_storage = forms.CharField(
|
||||||
label="cmdset",
|
label="cmdset",
|
||||||
initial=settings.CMDSET_PLAYER,
|
initial=settings.CMDSET_ACCOUNT,
|
||||||
widget=forms.TextInput(attrs={'size': '78'}),
|
widget=forms.TextInput(attrs={'size': '78'}),
|
||||||
required=False,
|
required=False,
|
||||||
help_text="python path to player cmdset class (set in "
|
help_text="python path to account cmdset class (set in "
|
||||||
"settings.CMDSET_PLAYER by default)")
|
"settings.CMDSET_ACCOUNT by default)")
|
||||||
|
|
||||||
|
|
||||||
class PlayerInline(admin.StackedInline):
|
class AccountInline(admin.StackedInline):
|
||||||
"""
|
"""
|
||||||
Inline creation of Player
|
Inline creation of Account
|
||||||
|
|
||||||
"""
|
"""
|
||||||
model = PlayerDB
|
model = AccountDB
|
||||||
template = "admin/players/stacked.html"
|
template = "admin/accounts/stacked.html"
|
||||||
form = PlayerForm
|
form = AccountForm
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
("In-game Permissions and Locks",
|
("In-game Permissions and Locks",
|
||||||
{'fields': ('db_lock_storage',),
|
{'fields': ('db_lock_storage',),
|
||||||
#{'fields': ('db_permissions', 'db_lock_storage'),
|
#{'fields': ('db_permissions', 'db_lock_storage'),
|
||||||
'description': "<i>These are permissions/locks for in-game use. "
|
'description': "<i>These are permissions/locks for in-game use. "
|
||||||
"They are unrelated to website access rights.</i>"}),
|
"They are unrelated to website access rights.</i>"}),
|
||||||
("In-game Player data",
|
("In-game Account data",
|
||||||
{'fields': ('db_typeclass_path', 'db_cmdset_storage'),
|
{'fields': ('db_typeclass_path', 'db_cmdset_storage'),
|
||||||
'description': "<i>These fields define in-game-specific properties "
|
'description': "<i>These fields define in-game-specific properties "
|
||||||
"for the Player object in-game.</i>"}))
|
"for the Account object in-game.</i>"}))
|
||||||
|
|
||||||
extra = 1
|
extra = 1
|
||||||
max_num = 1
|
max_num = 1
|
||||||
|
|
||||||
|
|
||||||
class PlayerTagInline(TagInline):
|
class AccountTagInline(TagInline):
|
||||||
"""
|
"""
|
||||||
Inline Player Tags.
|
Inline Account Tags.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
model = PlayerDB.db_tags.through
|
model = AccountDB.db_tags.through
|
||||||
related_field = "playerdb"
|
related_field = "accountdb"
|
||||||
|
|
||||||
|
|
||||||
class PlayerAttributeInline(AttributeInline):
|
class AccountAttributeInline(AttributeInline):
|
||||||
"""
|
"""
|
||||||
Inline Player Attributes.
|
Inline Account Attributes.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
model = PlayerDB.db_attributes.through
|
model = AccountDB.db_attributes.through
|
||||||
related_field = "playerdb"
|
related_field = "accountdb"
|
||||||
|
|
||||||
|
|
||||||
class PlayerDBAdmin(BaseUserAdmin):
|
class AccountDBAdmin(BaseUserAdmin):
|
||||||
"""
|
"""
|
||||||
This is the main creation screen for Users/players
|
This is the main creation screen for Users/accounts
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
list_display = ('username', 'email', 'is_staff', 'is_superuser')
|
list_display = ('username', 'email', 'is_staff', 'is_superuser')
|
||||||
form = PlayerDBChangeForm
|
form = AccountDBChangeForm
|
||||||
add_form = PlayerDBCreationForm
|
add_form = AccountDBCreationForm
|
||||||
inlines = [PlayerTagInline, PlayerAttributeInline]
|
inlines = [AccountTagInline, AccountAttributeInline]
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {'fields': ('username', 'password', 'email')}),
|
(None, {'fields': ('username', 'password', 'email')}),
|
||||||
('Website profile', {
|
('Website profile', {
|
||||||
|
|
@ -215,11 +215,11 @@ class PlayerDBAdmin(BaseUserAdmin):
|
||||||
'db_lock_storage'),
|
'db_lock_storage'),
|
||||||
'description': '<i>These are attributes that are more relevant '
|
'description': '<i>These are attributes that are more relevant '
|
||||||
'to gameplay.</i>'}))
|
'to gameplay.</i>'}))
|
||||||
# ('Game Options', {'fields': (
|
# ('Game Options', {'fields': (
|
||||||
# 'db_typeclass_path', 'db_cmdset_storage',
|
# 'db_typeclass_path', 'db_cmdset_storage',
|
||||||
# 'db_permissions', 'db_lock_storage'),
|
# 'db_permissions', 'db_lock_storage'),
|
||||||
# 'description': '<i>These are attributes that are '
|
# 'description': '<i>These are attributes that are '
|
||||||
# 'more relevant to gameplay.</i>'}))
|
# 'more relevant to gameplay.</i>'}))
|
||||||
|
|
||||||
add_fieldsets = (
|
add_fieldsets = (
|
||||||
(None,
|
(None,
|
||||||
|
|
@ -240,16 +240,17 @@ class PlayerDBAdmin(BaseUserAdmin):
|
||||||
"""
|
"""
|
||||||
obj.save()
|
obj.save()
|
||||||
if not change:
|
if not change:
|
||||||
#calling hooks for new player
|
# calling hooks for new account
|
||||||
obj.set_class_from_typeclass(typeclass_path=settings.BASE_PLAYER_TYPECLASS)
|
obj.set_class_from_typeclass(typeclass_path=settings.BASE_ACCOUNT_TYPECLASS)
|
||||||
obj.basetype_setup()
|
obj.basetype_setup()
|
||||||
obj.at_player_creation()
|
obj.at_account_creation()
|
||||||
|
|
||||||
def response_add(self, request, obj, post_url_continue=None):
|
def response_add(self, request, obj, post_url_continue=None):
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
if '_continue' in request.POST:
|
if '_continue' in request.POST:
|
||||||
return HttpResponseRedirect(reverse("admin:players_playerdb_change", args=[obj.id]))
|
return HttpResponseRedirect(reverse("admin:accounts_accountdb_change", args=[obj.id]))
|
||||||
return HttpResponseRedirect(reverse("admin:players_playerdb_change", args=[obj.id]))
|
return HttpResponseRedirect(reverse("admin:accounts_accountdb_change", args=[obj.id]))
|
||||||
|
|
||||||
admin.site.register(PlayerDB, PlayerDBAdmin)
|
|
||||||
|
admin.site.register(AccountDB, AccountDBAdmin)
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
"""
|
"""
|
||||||
Bots are a special child typeclasses of
|
Bots are a special child typeclasses of
|
||||||
Player that are controlled by the server.
|
Account that are controlled by the server.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import time
|
import time
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia.players.players import DefaultPlayer
|
from evennia.accounts.accounts import DefaultAccount
|
||||||
from evennia.scripts.scripts import DefaultScript
|
from evennia.scripts.scripts import DefaultScript
|
||||||
from evennia.utils import search
|
from evennia.utils import search
|
||||||
from evennia.utils import utils
|
from evennia.utils import utils
|
||||||
|
|
@ -28,6 +28,7 @@ class BotStarter(DefaultScript):
|
||||||
into gear when it is initialized.
|
into gear when it is initialized.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def at_script_creation(self):
|
def at_script_creation(self):
|
||||||
"""
|
"""
|
||||||
Called once, when script is created.
|
Called once, when script is created.
|
||||||
|
|
@ -37,10 +38,6 @@ class BotStarter(DefaultScript):
|
||||||
self.desc = "bot start/keepalive"
|
self.desc = "bot start/keepalive"
|
||||||
self.persistent = True
|
self.persistent = True
|
||||||
self.db.started = False
|
self.db.started = False
|
||||||
if _IDLE_TIMEOUT > 0:
|
|
||||||
# call before idle_timeout triggers
|
|
||||||
self.interval = int(max(60, _IDLE_TIMEOUT * 0.90))
|
|
||||||
self.start_delay = True
|
|
||||||
|
|
||||||
def at_start(self):
|
def at_start(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -48,7 +45,7 @@ class BotStarter(DefaultScript):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not self.db.started:
|
if not self.db.started:
|
||||||
self.player.start()
|
self.account.start()
|
||||||
self.db.started = True
|
self.db.started = True
|
||||||
|
|
||||||
def at_repeat(self):
|
def at_repeat(self):
|
||||||
|
|
@ -63,7 +60,7 @@ class BotStarter(DefaultScript):
|
||||||
global _SESSIONS
|
global _SESSIONS
|
||||||
if not _SESSIONS:
|
if not _SESSIONS:
|
||||||
from evennia.server.sessionhandler import SESSIONS as _SESSIONS
|
from evennia.server.sessionhandler import SESSIONS as _SESSIONS
|
||||||
for session in _SESSIONS.sessions_from_player(self.player):
|
for session in _SESSIONS.sessions_from_account(self.account):
|
||||||
session.update_session_counters(idle=True)
|
session.update_session_counters(idle=True)
|
||||||
|
|
||||||
def at_server_reload(self):
|
def at_server_reload(self):
|
||||||
|
|
@ -85,7 +82,7 @@ class BotStarter(DefaultScript):
|
||||||
# Bot base class
|
# Bot base class
|
||||||
|
|
||||||
|
|
||||||
class Bot(DefaultPlayer):
|
class Bot(DefaultAccount):
|
||||||
"""
|
"""
|
||||||
A Bot will start itself when the server starts (it will generally
|
A Bot will start itself when the server starts (it will generally
|
||||||
not do so on a reload - that will be handled by the normal Portal
|
not do so on a reload - that will be handled by the normal Portal
|
||||||
|
|
@ -100,8 +97,9 @@ class Bot(DefaultPlayer):
|
||||||
"""
|
"""
|
||||||
# the text encoding to use.
|
# the text encoding to use.
|
||||||
self.db.encoding = "utf-8"
|
self.db.encoding = "utf-8"
|
||||||
# A basic security setup
|
# A basic security setup (also avoid idle disconnects)
|
||||||
lockstring = "examine:perm(Wizards);edit:perm(Wizards);delete:perm(Wizards);boot:perm(Wizards);msg:false()"
|
lockstring = "examine:perm(Admin);edit:perm(Admin);delete:perm(Admin);" \
|
||||||
|
"boot:perm(Admin);msg:false();noidletimeout:true()"
|
||||||
self.locks.add(lockstring)
|
self.locks.add(lockstring)
|
||||||
# set the basics of being a bot
|
# set the basics of being a bot
|
||||||
script_key = "%s" % self.key
|
script_key = "%s" % self.key
|
||||||
|
|
@ -148,6 +146,7 @@ class IRCBot(Bot):
|
||||||
Bot for handling IRC connections.
|
Bot for handling IRC connections.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def start(self, ev_channel=None, irc_botname=None, irc_channel=None, irc_network=None, irc_port=None, irc_ssl=None):
|
def start(self, ev_channel=None, irc_botname=None, irc_channel=None, irc_network=None, irc_port=None, irc_ssl=None):
|
||||||
"""
|
"""
|
||||||
Start by telling the portal to start a new session.
|
Start by telling the portal to start a new session.
|
||||||
|
|
@ -206,12 +205,16 @@ class IRCBot(Bot):
|
||||||
"ssl": self.db.irc_ssl}
|
"ssl": self.db.irc_ssl}
|
||||||
_SESSIONS.start_bot_session("evennia.server.portal.irc.IRCBotFactory", configdict)
|
_SESSIONS.start_bot_session("evennia.server.portal.irc.IRCBotFactory", configdict)
|
||||||
|
|
||||||
|
def at_msg_send(self, **kwargs):
|
||||||
|
"Shortcut here or we can end up in infinite loop"
|
||||||
|
pass
|
||||||
|
|
||||||
def get_nicklist(self, caller):
|
def get_nicklist(self, caller):
|
||||||
"""
|
"""
|
||||||
Retrive the nick list from the connected channel.
|
Retrive the nick list from the connected channel.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
caller (Object or Player): The requester of the list. This will
|
caller (Object or Account): The requester of the list. This will
|
||||||
be stored and echoed to when the irc network replies with the
|
be stored and echoed to when the irc network replies with the
|
||||||
requested info.
|
requested info.
|
||||||
|
|
||||||
|
|
@ -231,7 +234,7 @@ class IRCBot(Bot):
|
||||||
Fire a ping to the IRC server.
|
Fire a ping to the IRC server.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
caller (Object or Player): The requester of the ping.
|
caller (Object or Account): The requester of the ping.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not hasattr(self, "_ping_callers"):
|
if not hasattr(self, "_ping_callers"):
|
||||||
|
|
@ -242,7 +245,7 @@ class IRCBot(Bot):
|
||||||
def reconnect(self):
|
def reconnect(self):
|
||||||
"""
|
"""
|
||||||
Force a protocol-side reconnect of the client without
|
Force a protocol-side reconnect of the client without
|
||||||
having to destroy/recreate the bot "player".
|
having to destroy/recreate the bot "account".
|
||||||
|
|
||||||
"""
|
"""
|
||||||
super(IRCBot, self).msg(reconnect="")
|
super(IRCBot, self).msg(reconnect="")
|
||||||
|
|
@ -257,7 +260,7 @@ class IRCBot(Bot):
|
||||||
Kwargs:
|
Kwargs:
|
||||||
options (dict): Options dict with the following allowed keys:
|
options (dict): Options dict with the following allowed keys:
|
||||||
- from_channel (str): dbid of a channel this text originated from.
|
- from_channel (str): dbid of a channel this text originated from.
|
||||||
- from_obj (list): list of objects this text.
|
- from_obj (list): list of objects sending this text.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from_obj = kwargs.get("from_obj", None)
|
from_obj = kwargs.get("from_obj", None)
|
||||||
|
|
@ -266,7 +269,7 @@ class IRCBot(Bot):
|
||||||
# cache channel lookup
|
# cache channel lookup
|
||||||
self.ndb.ev_channel = self.db.ev_channel
|
self.ndb.ev_channel = self.db.ev_channel
|
||||||
if "from_channel" in options and text and self.ndb.ev_channel.dbid == options["from_channel"]:
|
if "from_channel" in options and text and self.ndb.ev_channel.dbid == options["from_channel"]:
|
||||||
if not from_obj or from_obj != [self.id]:
|
if not from_obj or from_obj != [self]:
|
||||||
super(IRCBot, self).msg(channel=text)
|
super(IRCBot, self).msg(channel=text)
|
||||||
|
|
||||||
def execute_cmd(self, session=None, txt=None, **kwargs):
|
def execute_cmd(self, session=None, txt=None, **kwargs):
|
||||||
|
|
@ -323,8 +326,8 @@ class IRCBot(Bot):
|
||||||
for sess in _SESSIONS.get_sessions():
|
for sess in _SESSIONS.get_sessions():
|
||||||
delta_cmd = t0 - sess.cmd_last_visible
|
delta_cmd = t0 - sess.cmd_last_visible
|
||||||
delta_conn = t0 - session.conn_time
|
delta_conn = t0 - session.conn_time
|
||||||
player = sess.get_player()
|
account = sess.get_account()
|
||||||
whos.append("%s (%s/%s)" % (utils.crop("|w%s|n" % player.name, width=25),
|
whos.append("%s (%s/%s)" % (utils.crop("|w%s|n" % account.name, width=25),
|
||||||
utils.time_format(delta_conn, 0),
|
utils.time_format(delta_conn, 0),
|
||||||
utils.time_format(delta_cmd, 1)))
|
utils.time_format(delta_cmd, 1)))
|
||||||
text = "Who list (online/idle): %s" % ", ".join(sorted(whos, key=lambda w: w.lower()))
|
text = "Who list (online/idle): %s" % ", ".join(sorted(whos, key=lambda w: w.lower()))
|
||||||
|
|
@ -347,7 +350,7 @@ class IRCBot(Bot):
|
||||||
# cache channel lookup
|
# cache channel lookup
|
||||||
self.ndb.ev_channel = self.db.ev_channel
|
self.ndb.ev_channel = self.db.ev_channel
|
||||||
if self.ndb.ev_channel:
|
if self.ndb.ev_channel:
|
||||||
self.ndb.ev_channel.msg(text, senders=self.id)
|
self.ndb.ev_channel.msg(text, senders=self)
|
||||||
|
|
||||||
#
|
#
|
||||||
# RSS
|
# RSS
|
||||||
|
|
@ -359,6 +362,7 @@ class RSSBot(Bot):
|
||||||
its feed at regular intervals.
|
its feed at regular intervals.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def start(self, ev_channel=None, rss_url=None, rss_rate=None):
|
def start(self, ev_channel=None, rss_url=None, rss_rate=None):
|
||||||
"""
|
"""
|
||||||
Start by telling the portal to start a new RSS session
|
Start by telling the portal to start a new RSS session
|
||||||
|
|
@ -1,25 +1,22 @@
|
||||||
"""
|
"""
|
||||||
The managers for the custom Player object and permissions.
|
The managers for the custom Account object and permissions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.contrib.auth.models import UserManager
|
from django.contrib.auth.models import UserManager
|
||||||
#from functools import update_wrapper
|
from evennia.typeclasses.managers import (TypedObjectManager, TypeclassManager)
|
||||||
from evennia.typeclasses.managers import (returns_typeclass_list, returns_typeclass,
|
__all__ = ("AccountManager",)
|
||||||
TypedObjectManager, TypeclassManager)
|
|
||||||
from evennia.utils.utils import make_iter
|
|
||||||
__all__ = ("PlayerManager",)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Player Manager
|
# Account Manager
|
||||||
#
|
#
|
||||||
|
|
||||||
class PlayerDBManager(TypedObjectManager, UserManager):
|
class AccountDBManager(TypedObjectManager, UserManager):
|
||||||
"""
|
"""
|
||||||
This PlayerManager implements methods for searching
|
This AccountManager implements methods for searching
|
||||||
and manipulating Players directly from the database.
|
and manipulating Accounts directly from the database.
|
||||||
|
|
||||||
Evennia-specific search methods (will return Characters if
|
Evennia-specific search methods (will return Characters if
|
||||||
possible or a Typeclass/list of Typeclassed objects, whereas
|
possible or a Typeclass/list of Typeclassed objects, whereas
|
||||||
|
|
@ -30,49 +27,48 @@ class PlayerDBManager(TypedObjectManager, UserManager):
|
||||||
get_dbref_range
|
get_dbref_range
|
||||||
object_totals
|
object_totals
|
||||||
typeclass_search
|
typeclass_search
|
||||||
num_total_players
|
num_total_accounts
|
||||||
get_connected_players
|
get_connected_accounts
|
||||||
get_recently_created_players
|
get_recently_created_accounts
|
||||||
get_recently_connected_players
|
get_recently_connected_accounts
|
||||||
get_player_from_email
|
get_account_from_email
|
||||||
get_player_from_uid
|
get_account_from_uid
|
||||||
get_player_from_name
|
get_account_from_name
|
||||||
player_search (equivalent to evennia.search_player)
|
account_search (equivalent to evennia.search_account)
|
||||||
#swap_character
|
#swap_character
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def num_total_players(self):
|
|
||||||
|
def num_total_accounts(self):
|
||||||
"""
|
"""
|
||||||
Get total number of players.
|
Get total number of accounts.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
count (int): The total number of registered players.
|
count (int): The total number of registered accounts.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.count()
|
return self.count()
|
||||||
|
|
||||||
@returns_typeclass_list
|
def get_connected_accounts(self):
|
||||||
def get_connected_players(self):
|
|
||||||
"""
|
"""
|
||||||
Get all currently connected players.
|
Get all currently connected accounts.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
count (list): Player objects with currently
|
count (list): Account objects with currently
|
||||||
connected sessions.
|
connected sessions.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.filter(db_is_connected=True)
|
return self.filter(db_is_connected=True)
|
||||||
|
|
||||||
@returns_typeclass_list
|
def get_recently_created_accounts(self, days=7):
|
||||||
def get_recently_created_players(self, days=7):
|
|
||||||
"""
|
"""
|
||||||
Get players recently created.
|
Get accounts recently created.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
days (int, optional): How many days in the past "recently" means.
|
days (int, optional): How many days in the past "recently" means.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
players (list): The Players created the last `days` interval.
|
accounts (list): The Accounts created the last `days` interval.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
end_date = timezone.now()
|
end_date = timezone.now()
|
||||||
|
|
@ -80,16 +76,15 @@ class PlayerDBManager(TypedObjectManager, UserManager):
|
||||||
start_date = end_date - tdelta
|
start_date = end_date - tdelta
|
||||||
return self.filter(date_joined__range=(start_date, end_date))
|
return self.filter(date_joined__range=(start_date, end_date))
|
||||||
|
|
||||||
@returns_typeclass_list
|
def get_recently_connected_accounts(self, days=7):
|
||||||
def get_recently_connected_players(self, days=7):
|
|
||||||
"""
|
"""
|
||||||
Get players recently connected to the game.
|
Get accounts recently connected to the game.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
days (int, optional): Number of days backwards to check
|
days (int, optional): Number of days backwards to check
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
players (list): The Players connected to the game in the
|
accounts (list): The Accounts connected to the game in the
|
||||||
last `days` interval.
|
last `days` interval.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -97,33 +92,31 @@ class PlayerDBManager(TypedObjectManager, UserManager):
|
||||||
tdelta = datetime.timedelta(days)
|
tdelta = datetime.timedelta(days)
|
||||||
start_date = end_date - tdelta
|
start_date = end_date - tdelta
|
||||||
return self.filter(last_login__range=(
|
return self.filter(last_login__range=(
|
||||||
start_date, end_date)).order_by('-last_login')
|
start_date, end_date)).order_by('-last_login')
|
||||||
|
|
||||||
@returns_typeclass
|
def get_account_from_email(self, uemail):
|
||||||
def get_player_from_email(self, uemail):
|
|
||||||
"""
|
"""
|
||||||
Search player by
|
Search account by
|
||||||
Returns a player object based on email address.
|
Returns an account object based on email address.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
uemail (str): An email address to search for.
|
uemail (str): An email address to search for.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
player (Player): A found player, if found.
|
account (Account): A found account, if found.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.filter(email__iexact=uemail)
|
return self.filter(email__iexact=uemail)
|
||||||
|
|
||||||
@returns_typeclass
|
def get_account_from_uid(self, uid):
|
||||||
def get_player_from_uid(self, uid):
|
|
||||||
"""
|
"""
|
||||||
Get a player by id.
|
Get an account by id.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
uid (int): Player database id.
|
uid (int): Account database id.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
player (Player): The result.
|
account (Account): The result.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
|
@ -131,16 +124,15 @@ class PlayerDBManager(TypedObjectManager, UserManager):
|
||||||
except self.model.DoesNotExist:
|
except self.model.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@returns_typeclass
|
def get_account_from_name(self, uname):
|
||||||
def get_player_from_name(self, uname):
|
|
||||||
"""
|
"""
|
||||||
Get player object based on name.
|
Get account object based on name.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
uname (str): The Player name to search for.
|
uname (str): The Account name to search for.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
player (Player): The found player.
|
account (Account): The found account.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
|
@ -148,10 +140,9 @@ class PlayerDBManager(TypedObjectManager, UserManager):
|
||||||
except self.model.DoesNotExist:
|
except self.model.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@returns_typeclass_list
|
def search_account(self, ostring, exact=True, typeclass=None):
|
||||||
def search_player(self, ostring, exact=True, typeclass=None):
|
|
||||||
"""
|
"""
|
||||||
Searches for a particular player by name or
|
Searches for a particular account by name or
|
||||||
database id.
|
database id.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -161,7 +152,7 @@ class PlayerDBManager(TypedObjectManager, UserManager):
|
||||||
otherwise also match also keys containing the `ostring`
|
otherwise also match also keys containing the `ostring`
|
||||||
(non-case-sensitive fuzzy match).
|
(non-case-sensitive fuzzy match).
|
||||||
typeclass (str or Typeclass, optional): Limit the search only to
|
typeclass (str or Typeclass, optional): Limit the search only to
|
||||||
players of this typeclass.
|
accounts of this typeclass.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
dbref = self.dbref(ostring)
|
dbref = self.dbref(ostring)
|
||||||
|
|
@ -183,7 +174,8 @@ class PlayerDBManager(TypedObjectManager, UserManager):
|
||||||
else:
|
else:
|
||||||
return self.filter(**query)
|
return self.filter(**query)
|
||||||
# back-compatibility alias
|
# back-compatibility alias
|
||||||
player_search = search_player
|
account_search = search_account
|
||||||
|
|
||||||
class PlayerManager(PlayerDBManager, TypeclassManager):
|
|
||||||
|
class AccountManager(AccountDBManager, TypeclassManager):
|
||||||
pass
|
pass
|
||||||
|
|
@ -15,7 +15,7 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='PlayerDB',
|
name='AccountDB',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
|
|
@ -32,7 +32,7 @@ class Migration(migrations.Migration):
|
||||||
('db_typeclass_path', models.CharField(help_text=b"this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.", max_length=255, null=True, verbose_name=b'typeclass')),
|
('db_typeclass_path', models.CharField(help_text=b"this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.", max_length=255, null=True, verbose_name=b'typeclass')),
|
||||||
('db_date_created', models.DateTimeField(auto_now_add=True, verbose_name=b'creation date')),
|
('db_date_created', models.DateTimeField(auto_now_add=True, verbose_name=b'creation date')),
|
||||||
('db_lock_storage', models.TextField(help_text=b"locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.", verbose_name=b'locks', blank=True)),
|
('db_lock_storage', models.TextField(help_text=b"locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.", verbose_name=b'locks', blank=True)),
|
||||||
('db_is_connected', models.BooleanField(default=False, help_text=b'If player is connected to game or not', verbose_name=b'is_connected')),
|
('db_is_connected', models.BooleanField(default=False, help_text=b'If account is connected to game or not', verbose_name=b'is_connected')),
|
||||||
('db_cmdset_storage', models.CharField(help_text=b'optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.', max_length=255, null=True, verbose_name=b'cmdset')),
|
('db_cmdset_storage', models.CharField(help_text=b'optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.', max_length=255, null=True, verbose_name=b'cmdset')),
|
||||||
('db_is_bot', models.BooleanField(default=False, help_text=b'Used to identify irc/rss bots', verbose_name=b'is_bot')),
|
('db_is_bot', models.BooleanField(default=False, help_text=b'Used to identify irc/rss bots', verbose_name=b'is_bot')),
|
||||||
('db_attributes', models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute', null=True)),
|
('db_attributes', models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute', null=True)),
|
||||||
|
|
@ -41,8 +41,8 @@ class Migration(migrations.Migration):
|
||||||
('user_permissions', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions')),
|
('user_permissions', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Player',
|
'verbose_name': 'Account',
|
||||||
'verbose_name_plural': 'Players',
|
'verbose_name_plural': 'Accounts',
|
||||||
},
|
},
|
||||||
bases=(models.Model,),
|
bases=(models.Model,),
|
||||||
),
|
),
|
||||||
22
evennia/accounts/migrations/0002_move_defaults.py
Normal file
22
evennia/accounts/migrations/0002_move_defaults.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
def convert_defaults(apps, schema_editor):
|
||||||
|
AccountDB = apps.get_model("accounts", "AccountDB")
|
||||||
|
for account in AccountDB.objects.filter(db_typeclass_path="src.accounts.account.Account"):
|
||||||
|
account.db_typeclass_path = "typeclasses.accounts.Account"
|
||||||
|
account.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(convert_defaults),
|
||||||
|
]
|
||||||
|
|
@ -7,18 +7,18 @@ from django.db import models, migrations
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('players', '0002_move_defaults'),
|
('accounts', '0002_move_defaults'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='DefaultPlayer',
|
name='DefaultAccount',
|
||||||
fields=[
|
fields=[
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'proxy': True,
|
'proxy': True,
|
||||||
},
|
},
|
||||||
bases=('players.playerdb',),
|
bases=('accounts.accountdb',),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='DefaultGuest',
|
name='DefaultGuest',
|
||||||
|
|
@ -27,10 +27,10 @@ class Migration(migrations.Migration):
|
||||||
options={
|
options={
|
||||||
'proxy': True,
|
'proxy': True,
|
||||||
},
|
},
|
||||||
bases=('players.defaultplayer',),
|
bases=('accounts.defaultaccount',),
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='playerdb',
|
name='accountdb',
|
||||||
options={'verbose_name': 'Player'},
|
options={'verbose_name': 'Account'},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
@ -2,14 +2,14 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import models, migrations
|
||||||
import evennia.players.manager
|
import evennia.accounts.manager
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('players', '0003_auto_20150209_2234'),
|
('accounts', '0003_auto_20150209_2234'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
|
@ -17,31 +17,31 @@ class Migration(migrations.Migration):
|
||||||
name='DefaultGuest',
|
name='DefaultGuest',
|
||||||
),
|
),
|
||||||
migrations.DeleteModel(
|
migrations.DeleteModel(
|
||||||
name='DefaultPlayer',
|
name='DefaultAccount',
|
||||||
),
|
),
|
||||||
migrations.AlterModelManagers(
|
migrations.AlterModelManagers(
|
||||||
name='playerdb',
|
name='accountdb',
|
||||||
managers=[
|
managers=[
|
||||||
(b'objects', evennia.players.manager.PlayerDBManager()),
|
(b'objects', evennia.accounts.manager.AccountDBManager()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='playerdb',
|
model_name='accountdb',
|
||||||
name='email',
|
name='email',
|
||||||
field=models.EmailField(max_length=254, verbose_name='email address', blank=True),
|
field=models.EmailField(max_length=254, verbose_name='email address', blank=True),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='playerdb',
|
model_name='accountdb',
|
||||||
name='groups',
|
name='groups',
|
||||||
field=models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', verbose_name='groups'),
|
field=models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', verbose_name='groups'),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='playerdb',
|
model_name='accountdb',
|
||||||
name='last_login',
|
name='last_login',
|
||||||
field=models.DateTimeField(null=True, verbose_name='last login', blank=True),
|
field=models.DateTimeField(null=True, verbose_name='last login', blank=True),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='playerdb',
|
model_name='accountdb',
|
||||||
name='username',
|
name='username',
|
||||||
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, max_length=30, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.', 'invalid')], help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, verbose_name='username'),
|
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, max_length=30, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.', 'invalid')], help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, verbose_name='username'),
|
||||||
),
|
),
|
||||||
|
|
@ -9,12 +9,12 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('players', '0004_auto_20150403_2339'),
|
('accounts', '0004_auto_20150403_2339'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='playerdb',
|
model_name='accountdb',
|
||||||
name='username',
|
name='username',
|
||||||
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], verbose_name='username'),
|
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], verbose_name='username'),
|
||||||
),
|
),
|
||||||
31
evennia/accounts/migrations/0006_auto_20170606_1731.py
Normal file
31
evennia/accounts/migrations/0006_auto_20170606_1731.py
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.2 on 2017-06-06 17:31
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.contrib.auth.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0005_auto_20160905_0902'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='accountdb',
|
||||||
|
name='db_attributes',
|
||||||
|
field=models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='accountdb',
|
||||||
|
name='db_tags',
|
||||||
|
field=models.ManyToManyField(help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='accountdb',
|
||||||
|
name='username',
|
||||||
|
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.ASCIIUsernameValidator()], verbose_name='username'),
|
||||||
|
),
|
||||||
|
]
|
||||||
58
evennia/accounts/migrations/0007_copy_player_to_account.py
Normal file
58
evennia/accounts/migrations/0007_copy_player_to_account.py
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.2 on 2017-07-03 19:17
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.apps import apps as global_apps
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def forwards(apps, schema_editor):
|
||||||
|
try:
|
||||||
|
PlayerDB = apps.get_model('players', 'PlayerDB')
|
||||||
|
except LookupError:
|
||||||
|
# playerdb not available. Skip.
|
||||||
|
return
|
||||||
|
|
||||||
|
AccountDB = apps.get_model('accounts', 'AccountDB')
|
||||||
|
for player in PlayerDB.objects.all():
|
||||||
|
account = AccountDB(id=player.id,
|
||||||
|
password=player.password,
|
||||||
|
is_superuser=player.is_superuser,
|
||||||
|
last_login=player.last_login,
|
||||||
|
username=player.username,
|
||||||
|
first_name=player.first_name,
|
||||||
|
last_name=player.last_name,
|
||||||
|
email=player.email,
|
||||||
|
is_staff=player.is_staff,
|
||||||
|
is_active=player.is_active,
|
||||||
|
date_joined=player.date_joined,
|
||||||
|
db_key=player.db_key,
|
||||||
|
db_typeclass_path=player.db_typeclass_path,
|
||||||
|
db_date_created=player.db_date_created,
|
||||||
|
db_lock_storage=player.db_lock_storage,
|
||||||
|
db_is_connected=player.db_is_connected,
|
||||||
|
db_cmdset_storage=player.db_cmdset_storage,
|
||||||
|
db_is_bot=player.db_is_bot)
|
||||||
|
account.save()
|
||||||
|
for group in player.groups.all():
|
||||||
|
account.groups.add(group)
|
||||||
|
for user_permission in player.user_permissions.all():
|
||||||
|
account.user_permissions.add(user_permission)
|
||||||
|
for attr in player.db_attributes.all():
|
||||||
|
account.db_attributes.add(attr)
|
||||||
|
for tag in player.db_tags.all():
|
||||||
|
account.db_tags.add(tag)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0006_auto_20170606_1731'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(forwards, migrations.RunPython.noop)
|
||||||
|
]
|
||||||
|
|
||||||
|
if global_apps.is_installed('players'):
|
||||||
|
dependencies.append(('players', '0006_auto_20170606_1731'))
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
"""
|
"""
|
||||||
Player
|
Account
|
||||||
|
|
||||||
The player class is an extension of the default Django user class,
|
The account class is an extension of the default Django user class,
|
||||||
and is customized for the needs of Evennia.
|
and is customized for the needs of Evennia.
|
||||||
|
|
||||||
We use the Player to store a more mud-friendly style of permission
|
We use the Account to store a more mud-friendly style of permission
|
||||||
system as well as to allow the admin more flexibility by storing
|
system as well as to allow the admin more flexibility by storing
|
||||||
attributes on the Player. Within the game we should normally use the
|
attributes on the Account. Within the game we should normally use the
|
||||||
Player manager's methods to create users so that permissions are set
|
Account manager's methods to create users so that permissions are set
|
||||||
correctly.
|
correctly.
|
||||||
|
|
||||||
To make the Player model more flexible for your own game, it can also
|
To make the Account model more flexible for your own game, it can also
|
||||||
persistently store attributes of its own. This is ideal for extra
|
persistently store attributes of its own. This is ideal for extra
|
||||||
account info and OOC account configuration variables etc.
|
account info and OOC account configuration variables etc.
|
||||||
|
|
||||||
|
|
@ -22,11 +22,11 @@ from django.db import models
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.utils.encoding import smart_str
|
from django.utils.encoding import smart_str
|
||||||
|
|
||||||
from evennia.players.manager import PlayerDBManager
|
from evennia.accounts.manager import AccountDBManager
|
||||||
from evennia.typeclasses.models import TypedObject
|
from evennia.typeclasses.models import TypedObject
|
||||||
from evennia.utils.utils import make_iter
|
from evennia.utils.utils import make_iter
|
||||||
|
|
||||||
__all__ = ("PlayerDB",)
|
__all__ = ("AccountDB",)
|
||||||
|
|
||||||
#_ME = _("me")
|
#_ME = _("me")
|
||||||
#_SELF = _("self")
|
#_SELF = _("self")
|
||||||
|
|
@ -42,11 +42,11 @@ _TYPECLASS = None
|
||||||
|
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# PlayerDB
|
# AccountDB
|
||||||
#
|
#
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
|
|
||||||
class PlayerDB(TypedObject, AbstractUser):
|
class AccountDB(TypedObject, AbstractUser):
|
||||||
"""
|
"""
|
||||||
This is a special model using Django's 'profile' functionality
|
This is a special model using Django's 'profile' functionality
|
||||||
and extends the default Django User model. It is defined as such
|
and extends the default Django User model. It is defined as such
|
||||||
|
|
@ -66,18 +66,18 @@ class PlayerDB(TypedObject, AbstractUser):
|
||||||
- db - persistent attribute storage
|
- db - persistent attribute storage
|
||||||
- ndb - non-persistent attribute storage
|
- ndb - non-persistent attribute storage
|
||||||
|
|
||||||
The PlayerDB adds the following properties:
|
The AccountDB adds the following properties:
|
||||||
|
|
||||||
- is_connected - If any Session is currently connected to this Player
|
- is_connected - If any Session is currently connected to this Account
|
||||||
- name - alias for user.username
|
- name - alias for user.username
|
||||||
- sessions - sessions connected to this player
|
- sessions - sessions connected to this account
|
||||||
- is_superuser - bool if this player is a superuser
|
- is_superuser - bool if this account is a superuser
|
||||||
- is_bot - bool if this player is a bot and not a real player
|
- is_bot - bool if this account is a bot and not a real account
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#
|
#
|
||||||
# PlayerDB Database model setup
|
# AccountDB Database model setup
|
||||||
#
|
#
|
||||||
# inherited fields (from TypedObject):
|
# inherited fields (from TypedObject):
|
||||||
# db_key, db_typeclass_path, db_date_created, db_permissions
|
# db_key, db_typeclass_path, db_date_created, db_permissions
|
||||||
|
|
@ -89,20 +89,20 @@ class PlayerDB(TypedObject, AbstractUser):
|
||||||
help_text="If player is connected to game or not")
|
help_text="If player is connected to game or not")
|
||||||
# database storage of persistant cmdsets.
|
# database storage of persistant cmdsets.
|
||||||
db_cmdset_storage = models.CharField('cmdset', max_length=255, null=True,
|
db_cmdset_storage = models.CharField('cmdset', max_length=255, null=True,
|
||||||
help_text="optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.")
|
help_text="optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.")
|
||||||
# marks if this is a "virtual" bot player object
|
# marks if this is a "virtual" bot account object
|
||||||
db_is_bot = models.BooleanField(default=False, verbose_name="is_bot", help_text="Used to identify irc/rss bots")
|
db_is_bot = models.BooleanField(default=False, verbose_name="is_bot", help_text="Used to identify irc/rss bots")
|
||||||
|
|
||||||
# Database manager
|
# Database manager
|
||||||
objects = PlayerDBManager()
|
objects = AccountDBManager()
|
||||||
|
|
||||||
# defaults
|
# defaults
|
||||||
__settingsclasspath__ = settings.BASE_SCRIPT_TYPECLASS
|
__settingsclasspath__ = settings.BASE_SCRIPT_TYPECLASS
|
||||||
__defaultclasspath__ = "evennia.players.players.DefaultPlayer"
|
__defaultclasspath__ = "evennia.accounts.accounts.DefaultAccount"
|
||||||
__applabel__ = "players"
|
__applabel__ = "accounts"
|
||||||
|
|
||||||
class Meta(object):
|
class Meta(object):
|
||||||
verbose_name = 'Player'
|
verbose_name = 'Account'
|
||||||
|
|
||||||
# cmdset_storage property
|
# cmdset_storage property
|
||||||
# This seems very sensitive to caching, so leaving it be for now /Griatch
|
# This seems very sensitive to caching, so leaving it be for now /Griatch
|
||||||
|
|
@ -136,10 +136,10 @@ class PlayerDB(TypedObject, AbstractUser):
|
||||||
#
|
#
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return smart_str("%s(player %s)" % (self.name, self.dbid))
|
return smart_str("%s(account %s)" % (self.name, self.dbid))
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return u"%s(player#%s)" % (self.name, self.dbid)
|
return u"%s(account#%s)" % (self.name, self.dbid)
|
||||||
|
|
||||||
#@property
|
#@property
|
||||||
def __username_get(self):
|
def __username_get(self):
|
||||||
160
evennia/accounts/tests.py
Normal file
160
evennia/accounts/tests.py
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
from mock import Mock
|
||||||
|
from random import randint
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from evennia.accounts.accounts import AccountSessionHandler
|
||||||
|
from evennia.accounts.accounts import DefaultAccount
|
||||||
|
from evennia.server.session import Session
|
||||||
|
from evennia.utils import create
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class TestAccountSessionHandler(TestCase):
|
||||||
|
"Check AccountSessionHandler class"
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount)
|
||||||
|
self.handler = AccountSessionHandler(self.account)
|
||||||
|
|
||||||
|
def test_get(self):
|
||||||
|
"Check get method"
|
||||||
|
self.assertEqual(self.handler.get(), [])
|
||||||
|
self.assertEqual(self.handler.get(100), [])
|
||||||
|
|
||||||
|
import evennia.server.sessionhandler
|
||||||
|
|
||||||
|
s1 = Session()
|
||||||
|
s1.logged_in = True
|
||||||
|
s1.uid = self.account.uid
|
||||||
|
evennia.server.sessionhandler.SESSIONS[s1.uid] = s1
|
||||||
|
|
||||||
|
s2 = Session()
|
||||||
|
s2.logged_in = True
|
||||||
|
s2.uid = self.account.uid + 1
|
||||||
|
evennia.server.sessionhandler.SESSIONS[s2.uid] = s2
|
||||||
|
|
||||||
|
s3 = Session()
|
||||||
|
s3.logged_in = False
|
||||||
|
s3.uid = self.account.uid + 2
|
||||||
|
evennia.server.sessionhandler.SESSIONS[s3.uid] = s3
|
||||||
|
|
||||||
|
self.assertEqual(self.handler.get(), [s1])
|
||||||
|
self.assertEqual(self.handler.get(self.account.uid), [s1])
|
||||||
|
self.assertEqual(self.handler.get(self.account.uid + 1), [])
|
||||||
|
|
||||||
|
def test_all(self):
|
||||||
|
"Check all method"
|
||||||
|
self.assertEqual(self.handler.get(), self.handler.all())
|
||||||
|
|
||||||
|
def test_count(self):
|
||||||
|
"Check count method"
|
||||||
|
self.assertEqual(self.handler.count(), len(self.handler.get()))
|
||||||
|
|
||||||
|
class TestDefaultAccount(TestCase):
|
||||||
|
"Check DefaultAccount class"
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.s1 = Session()
|
||||||
|
self.s1.sessid = 0
|
||||||
|
|
||||||
|
def test_puppet_object_no_object(self):
|
||||||
|
"Check puppet_object method called with no object param"
|
||||||
|
|
||||||
|
try:
|
||||||
|
DefaultAccount().puppet_object(self.s1, None)
|
||||||
|
self.fail("Expected error: 'Object not found'")
|
||||||
|
except RuntimeError as re:
|
||||||
|
self.assertEqual("Object not found", re.message)
|
||||||
|
|
||||||
|
def test_puppet_object_no_session(self):
|
||||||
|
"Check puppet_object method called with no session param"
|
||||||
|
|
||||||
|
try:
|
||||||
|
DefaultAccount().puppet_object(None, Mock())
|
||||||
|
self.fail("Expected error: 'Session not found'")
|
||||||
|
except RuntimeError as re:
|
||||||
|
self.assertEqual("Session not found", re.message)
|
||||||
|
|
||||||
|
def test_puppet_object_already_puppeting(self):
|
||||||
|
"Check puppet_object method called, already puppeting this"
|
||||||
|
|
||||||
|
import evennia.server.sessionhandler
|
||||||
|
|
||||||
|
account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount)
|
||||||
|
self.s1.uid = account.uid
|
||||||
|
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
|
||||||
|
|
||||||
|
self.s1.logged_in = True
|
||||||
|
self.s1.data_out = Mock(return_value=None)
|
||||||
|
|
||||||
|
obj = Mock()
|
||||||
|
self.s1.puppet = obj
|
||||||
|
account.puppet_object(self.s1, obj)
|
||||||
|
self.s1.data_out.assert_called_with(options=None, text="You are already puppeting this object.")
|
||||||
|
self.assertIsNone(obj.at_post_puppet.call_args)
|
||||||
|
|
||||||
|
def test_puppet_object_no_permission(self):
|
||||||
|
"Check puppet_object method called, no permission"
|
||||||
|
|
||||||
|
import evennia.server.sessionhandler
|
||||||
|
|
||||||
|
account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount)
|
||||||
|
self.s1.uid = account.uid
|
||||||
|
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
|
||||||
|
|
||||||
|
self.s1.puppet = None
|
||||||
|
self.s1.logged_in = True
|
||||||
|
self.s1.data_out = Mock(return_value=None)
|
||||||
|
|
||||||
|
obj = Mock()
|
||||||
|
obj.access = Mock(return_value=False)
|
||||||
|
|
||||||
|
account.puppet_object(self.s1, obj)
|
||||||
|
|
||||||
|
self.assertTrue(self.s1.data_out.call_args[1]['text'].startswith("You don't have permission to puppet"))
|
||||||
|
self.assertIsNone(obj.at_post_puppet.call_args)
|
||||||
|
|
||||||
|
def test_puppet_object_joining_other_session(self):
|
||||||
|
"Check puppet_object method called, joining other session"
|
||||||
|
|
||||||
|
import evennia.server.sessionhandler
|
||||||
|
|
||||||
|
account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount)
|
||||||
|
self.s1.uid = account.uid
|
||||||
|
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
|
||||||
|
|
||||||
|
self.s1.puppet = None
|
||||||
|
self.s1.logged_in = True
|
||||||
|
self.s1.data_out = Mock(return_value=None)
|
||||||
|
|
||||||
|
obj = Mock()
|
||||||
|
obj.access = Mock(return_value=True)
|
||||||
|
obj.account = account
|
||||||
|
|
||||||
|
account.puppet_object(self.s1, obj)
|
||||||
|
# works because django.conf.settings.MULTISESSION_MODE is not in (1, 3)
|
||||||
|
self.assertTrue(self.s1.data_out.call_args[1]['text'].endswith("from another of your sessions."))
|
||||||
|
self.assertTrue(obj.at_post_puppet.call_args[1] == {})
|
||||||
|
|
||||||
|
def test_puppet_object_already_puppeted(self):
|
||||||
|
"Check puppet_object method called, already puppeted"
|
||||||
|
|
||||||
|
import evennia.server.sessionhandler
|
||||||
|
|
||||||
|
account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount)
|
||||||
|
self.s1.uid = account.uid
|
||||||
|
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
|
||||||
|
|
||||||
|
self.s1.puppet = None
|
||||||
|
self.s1.logged_in = True
|
||||||
|
self.s1.data_out = Mock(return_value=None)
|
||||||
|
|
||||||
|
obj = Mock()
|
||||||
|
obj.access = Mock(return_value=True)
|
||||||
|
obj.account = Mock()
|
||||||
|
obj.at_post_puppet = Mock()
|
||||||
|
|
||||||
|
account.puppet_object(self.s1, obj)
|
||||||
|
self.assertTrue(self.s1.data_out.call_args[1]['text'].endswith("is already puppeted by another Account."))
|
||||||
|
self.assertIsNone(obj.at_post_puppet.call_args)
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
This sub-package contains Evennia's command system. It handles
|
This sub-package contains Evennia's command system. It handles
|
||||||
everything related to parsing input from the player, building cmdsets
|
everything related to parsing input from the account, building cmdsets
|
||||||
and executing the code associated with a found command class.
|
and executing the code associated with a found command class.
|
||||||
|
|
||||||
commands.default contains all the default "mux-like" commands of
|
commands.default contains all the default "mux-like" commands of
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ command line. The processing of a command works as follows:
|
||||||
- object cmdsets: all objects at caller's location are scanned for non-empty
|
- object cmdsets: all objects at caller's location are scanned for non-empty
|
||||||
cmdsets. This includes cmdsets on exits.
|
cmdsets. This includes cmdsets on exits.
|
||||||
- caller: the caller is searched for its own currently active cmdset.
|
- caller: the caller is searched for its own currently active cmdset.
|
||||||
- player: lastly the cmdsets defined on caller.player are added.
|
- account: lastly the cmdsets defined on caller.account are added.
|
||||||
3. The collected cmdsets are merged together to a combined, current cmdset.
|
3. The collected cmdsets are merged together to a combined, current cmdset.
|
||||||
4. If the input string is empty -> check for CMD_NOINPUT command in
|
4. If the input string is empty -> check for CMD_NOINPUT command in
|
||||||
current cmdset or fallback to error message. Exit.
|
current cmdset or fallback to error message. Exit.
|
||||||
|
|
@ -85,50 +85,50 @@ CMD_LOGINSTART = "__unloggedin_look_command"
|
||||||
_SEARCH_AT_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
|
_SEARCH_AT_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
|
||||||
|
|
||||||
# Output strings. The first is the IN_GAME_ERRORS return, the second
|
# Output strings. The first is the IN_GAME_ERRORS return, the second
|
||||||
# is the normal "production message to echo to the player.
|
# is the normal "production message to echo to the account.
|
||||||
|
|
||||||
_ERROR_UNTRAPPED = (
|
_ERROR_UNTRAPPED = (
|
||||||
"""
|
"""
|
||||||
An untrapped error occurred.
|
An untrapped error occurred.
|
||||||
""",
|
""",
|
||||||
"""
|
"""
|
||||||
An untrapped error occurred. Please file a bug report detailing the steps to reproduce.
|
An untrapped error occurred. Please file a bug report detailing the steps to reproduce.
|
||||||
""")
|
""")
|
||||||
|
|
||||||
_ERROR_CMDSETS = (
|
_ERROR_CMDSETS = (
|
||||||
"""
|
"""
|
||||||
A cmdset merger-error occurred. This is often due to a syntax
|
A cmdset merger-error occurred. This is often due to a syntax
|
||||||
error in one of the cmdsets to merge.
|
error in one of the cmdsets to merge.
|
||||||
""",
|
""",
|
||||||
"""
|
"""
|
||||||
A cmdset merger-error occurred. Please file a bug report detailing the
|
A cmdset merger-error occurred. Please file a bug report detailing the
|
||||||
steps to reproduce.
|
steps to reproduce.
|
||||||
""")
|
""")
|
||||||
|
|
||||||
_ERROR_NOCMDSETS = (
|
_ERROR_NOCMDSETS = (
|
||||||
"""
|
"""
|
||||||
No command sets found! This is a critical bug that can have
|
No command sets found! This is a critical bug that can have
|
||||||
multiple causes.
|
multiple causes.
|
||||||
""",
|
""",
|
||||||
"""
|
"""
|
||||||
No command sets found! This is a sign of a critical bug. If
|
No command sets found! This is a sign of a critical bug. If
|
||||||
disconnecting/reconnecting doesn't" solve the problem, try to contact
|
disconnecting/reconnecting doesn't" solve the problem, try to contact
|
||||||
the server admin through" some other means for assistance.
|
the server admin through" some other means for assistance.
|
||||||
""")
|
""")
|
||||||
|
|
||||||
_ERROR_CMDHANDLER = (
|
_ERROR_CMDHANDLER = (
|
||||||
"""
|
"""
|
||||||
A command handler bug occurred. If this is not due to a local change,
|
A command handler bug occurred. If this is not due to a local change,
|
||||||
please file a bug report with the Evennia project, including the
|
please file a bug report with the Evennia project, including the
|
||||||
traceback and steps to reproduce.
|
traceback and steps to reproduce.
|
||||||
""",
|
""",
|
||||||
"""
|
"""
|
||||||
A command handler bug occurred. Please notify staff - they should
|
A command handler bug occurred. Please notify staff - they should
|
||||||
likely file a bug report with the Evennia project.
|
likely file a bug report with the Evennia project.
|
||||||
""")
|
""")
|
||||||
|
|
||||||
_ERROR_RECURSION_LIMIT = "Command recursion limit ({recursion_limit}) " \
|
_ERROR_RECURSION_LIMIT = "Command recursion limit ({recursion_limit}) " \
|
||||||
"reached for '{raw_string}' ({cmdclass})."
|
"reached for '{raw_cmdname}' ({cmdclass})."
|
||||||
|
|
||||||
|
|
||||||
# delayed imports
|
# delayed imports
|
||||||
|
|
@ -210,7 +210,7 @@ def _process_input(caller, prompt, result, cmd, generator):
|
||||||
part of yielding from a Command's `func`.
|
part of yielding from a Command's `func`.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
caller (Character, Player or Session): the caller.
|
caller (Character, Account or Session): the caller.
|
||||||
prompt (basestring): The sent prompt.
|
prompt (basestring): The sent prompt.
|
||||||
result (basestring): The unprocessed answer.
|
result (basestring): The unprocessed answer.
|
||||||
cmd (Command): The command itself.
|
cmd (Command): The command itself.
|
||||||
|
|
@ -234,34 +234,38 @@ class NoCmdSets(Exception):
|
||||||
|
|
||||||
class ExecSystemCommand(Exception):
|
class ExecSystemCommand(Exception):
|
||||||
"Run a system command"
|
"Run a system command"
|
||||||
|
|
||||||
def __init__(self, syscmd, sysarg):
|
def __init__(self, syscmd, sysarg):
|
||||||
self.args = (syscmd, sysarg) # needed by exception error handling
|
self.args = (syscmd, sysarg) # needed by exception error handling
|
||||||
self.syscmd = syscmd
|
self.syscmd = syscmd
|
||||||
self.sysarg = sysarg
|
self.sysarg = sysarg
|
||||||
|
|
||||||
|
|
||||||
class ErrorReported(Exception):
|
class ErrorReported(Exception):
|
||||||
"Re-raised when a subsructure already reported the error"
|
"Re-raised when a subsructure already reported the error"
|
||||||
|
|
||||||
def __init__(self, raw_string):
|
def __init__(self, raw_string):
|
||||||
self.args = (raw_string,)
|
self.args = (raw_string,)
|
||||||
self.raw_string = raw_string
|
self.raw_string = raw_string
|
||||||
|
|
||||||
# Helper function
|
# Helper function
|
||||||
|
|
||||||
|
|
||||||
@inlineCallbacks
|
@inlineCallbacks
|
||||||
def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string):
|
def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string):
|
||||||
"""
|
"""
|
||||||
Gather all relevant cmdsets and merge them.
|
Gather all relevant cmdsets and merge them.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
caller (Session, Player or Object): The entity executing the command. Which
|
caller (Session, Account or Object): The entity executing the command. Which
|
||||||
type of object this is depends on the current game state; for example
|
type of object this is depends on the current game state; for example
|
||||||
when the user is not logged in, this will be a Session, when being OOC
|
when the user is not logged in, this will be a Session, when being OOC
|
||||||
it will be a Player and when puppeting an object this will (often) be
|
it will be an Account and when puppeting an object this will (often) be
|
||||||
a Character Object. In the end it depends on where the cmdset is stored.
|
a Character Object. In the end it depends on where the cmdset is stored.
|
||||||
session (Session or None): The Session associated with caller, if any.
|
session (Session or None): The Session associated with caller, if any.
|
||||||
player (Player or None): The calling Player associated with caller, if any.
|
account (Account or None): The calling Account associated with caller, if any.
|
||||||
obj (Object or None): The Object associated with caller, if any.
|
obj (Object or None): The Object associated with caller, if any.
|
||||||
callertype (str): This identifies caller as either "player", "object" or "session"
|
callertype (str): This identifies caller as either "account", "object" or "session"
|
||||||
to avoid having to do this check internally.
|
to avoid having to do this check internally.
|
||||||
raw_string (str): The input string. This is only used for error reporting.
|
raw_string (str): The input string. This is only used for error reporting.
|
||||||
|
|
||||||
|
|
@ -272,18 +276,18 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string):
|
||||||
Notes:
|
Notes:
|
||||||
The cdmsets are merged in order or generality, so that the
|
The cdmsets are merged in order or generality, so that the
|
||||||
Object's cmdset is merged last (and will thus take precedence
|
Object's cmdset is merged last (and will thus take precedence
|
||||||
over same-named and same-prio commands on Player and Session).
|
over same-named and same-prio commands on Account and Session).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
@inlineCallbacks
|
@inlineCallbacks
|
||||||
def _get_channel_cmdset(player_or_obj):
|
def _get_channel_cmdset(account_or_obj):
|
||||||
"""
|
"""
|
||||||
Helper-method; Get channel-cmdsets
|
Helper-method; Get channel-cmdsets
|
||||||
"""
|
"""
|
||||||
# Create cmdset for all player's available channels
|
# Create cmdset for all account's available channels
|
||||||
try:
|
try:
|
||||||
channel_cmdset = yield CHANNELHANDLER.get_cmdset(player_or_obj)
|
channel_cmdset = yield CHANNELHANDLER.get_cmdset(account_or_obj)
|
||||||
returnValue([channel_cmdset])
|
returnValue([channel_cmdset])
|
||||||
except Exception:
|
except Exception:
|
||||||
_msg_err(caller, _ERROR_CMDSETS)
|
_msg_err(caller, _ERROR_CMDSETS)
|
||||||
|
|
@ -313,16 +317,16 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string):
|
||||||
_GA(lobj, "at_cmdset_get")(caller=caller)
|
_GA(lobj, "at_cmdset_get")(caller=caller)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.log_trace()
|
logger.log_trace()
|
||||||
# the call-type lock is checked here, it makes sure a player
|
# the call-type lock is checked here, it makes sure an account
|
||||||
# is not seeing e.g. the commands on a fellow player (which is why
|
# is not seeing e.g. the commands on a fellow account (which is why
|
||||||
# the no_superuser_bypass must be True)
|
# the no_superuser_bypass must be True)
|
||||||
local_obj_cmdsets = \
|
local_obj_cmdsets = \
|
||||||
yield list(chain.from_iterable(
|
yield list(chain.from_iterable(
|
||||||
lobj.cmdset.cmdset_stack for lobj in local_objlist
|
lobj.cmdset.cmdset_stack for lobj in local_objlist
|
||||||
if (lobj.cmdset.current and
|
if (lobj.cmdset.current and
|
||||||
lobj.access(caller, access_type='call', no_superuser_bypass=True))))
|
lobj.access(caller, access_type='call', no_superuser_bypass=True))))
|
||||||
for cset in local_obj_cmdsets:
|
for cset in local_obj_cmdsets:
|
||||||
#This is necessary for object sets, or we won't be able to
|
# This is necessary for object sets, or we won't be able to
|
||||||
# separate the command sets from each other in a busy room. We
|
# separate the command sets from each other in a busy room. We
|
||||||
# only keep the setting if duplicates were set to False/True
|
# only keep the setting if duplicates were set to False/True
|
||||||
# explicitly.
|
# explicitly.
|
||||||
|
|
@ -333,7 +337,6 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string):
|
||||||
_msg_err(caller, _ERROR_CMDSETS)
|
_msg_err(caller, _ERROR_CMDSETS)
|
||||||
raise ErrorReported(raw_string)
|
raise ErrorReported(raw_string)
|
||||||
|
|
||||||
|
|
||||||
@inlineCallbacks
|
@inlineCallbacks
|
||||||
def _get_cmdsets(obj):
|
def _get_cmdsets(obj):
|
||||||
"""
|
"""
|
||||||
|
|
@ -346,7 +349,7 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string):
|
||||||
_msg_err(caller, _ERROR_CMDSETS)
|
_msg_err(caller, _ERROR_CMDSETS)
|
||||||
raise ErrorReported(raw_string)
|
raise ErrorReported(raw_string)
|
||||||
try:
|
try:
|
||||||
returnValue((obj.cmdset.current, list(obj.cmdset.cmdset_stack)))
|
returnValue((obj.cmdset.current, list(obj.cmdset.cmdset_stack)))
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
returnValue(((None, None, None), []))
|
returnValue(((None, None, None), []))
|
||||||
|
|
||||||
|
|
@ -355,9 +358,9 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string):
|
||||||
# we are calling the command from the session level
|
# we are calling the command from the session level
|
||||||
report_to = session
|
report_to = session
|
||||||
current, cmdsets = yield _get_cmdsets(session)
|
current, cmdsets = yield _get_cmdsets(session)
|
||||||
if player: # this automatically implies logged-in
|
if account: # this automatically implies logged-in
|
||||||
pcurrent, player_cmdsets = yield _get_cmdsets(player)
|
pcurrent, account_cmdsets = yield _get_cmdsets(account)
|
||||||
cmdsets += player_cmdsets
|
cmdsets += account_cmdsets
|
||||||
current = current + pcurrent
|
current = current + pcurrent
|
||||||
if obj:
|
if obj:
|
||||||
ocurrent, obj_cmdsets = yield _get_cmdsets(obj)
|
ocurrent, obj_cmdsets = yield _get_cmdsets(obj)
|
||||||
|
|
@ -374,13 +377,13 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string):
|
||||||
channel_cmdsets = yield _get_channel_cmdset(obj)
|
channel_cmdsets = yield _get_channel_cmdset(obj)
|
||||||
cmdsets += channel_cmdsets
|
cmdsets += channel_cmdsets
|
||||||
if not current.no_channels:
|
if not current.no_channels:
|
||||||
channel_cmdsets = yield _get_channel_cmdset(player)
|
channel_cmdsets = yield _get_channel_cmdset(account)
|
||||||
cmdsets += channel_cmdsets
|
cmdsets += channel_cmdsets
|
||||||
|
|
||||||
elif callertype == "player":
|
elif callertype == "account":
|
||||||
# we are calling the command from the player level
|
# we are calling the command from the account level
|
||||||
report_to = player
|
report_to = account
|
||||||
current, cmdsets = yield _get_cmdsets(player)
|
current, cmdsets = yield _get_cmdsets(account)
|
||||||
if obj:
|
if obj:
|
||||||
ocurrent, obj_cmdsets = yield _get_cmdsets(obj)
|
ocurrent, obj_cmdsets = yield _get_cmdsets(obj)
|
||||||
current = current + ocurrent
|
current = current + ocurrent
|
||||||
|
|
@ -395,7 +398,7 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string):
|
||||||
# also objs may have channels
|
# also objs may have channels
|
||||||
cmdsets += yield _get_channel_cmdset(obj)
|
cmdsets += yield _get_channel_cmdset(obj)
|
||||||
if not current.no_channels:
|
if not current.no_channels:
|
||||||
cmdsets += yield _get_channel_cmdset(player)
|
cmdsets += yield _get_channel_cmdset(account)
|
||||||
|
|
||||||
elif callertype == "object":
|
elif callertype == "object":
|
||||||
# we are calling the command from the object level
|
# we are calling the command from the object level
|
||||||
|
|
@ -472,22 +475,22 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
|
||||||
This is the main mechanism that handles any string sent to the engine.
|
This is the main mechanism that handles any string sent to the engine.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
called_by (Session, Player or Object): Object from which this
|
called_by (Session, Account or Object): Object from which this
|
||||||
command was called. which this was called from. What this is
|
command was called. which this was called from. What this is
|
||||||
depends on the game state.
|
depends on the game state.
|
||||||
raw_string (str): The command string as given on the command line.
|
raw_string (str): The command string as given on the command line.
|
||||||
_testing (bool, optional): Used for debug purposes and decides if we
|
_testing (bool, optional): Used for debug purposes and decides if we
|
||||||
should actually execute the command or not. If True, the
|
should actually execute the command or not. If True, the
|
||||||
command instance will be returned.
|
command instance will be returned.
|
||||||
callertype (str, optional): One of "session", "player" or
|
callertype (str, optional): One of "session", "account" or
|
||||||
"object". These are treated in decending order, so when the
|
"object". These are treated in decending order, so when the
|
||||||
Session is the caller, it will merge its own cmdset into
|
Session is the caller, it will merge its own cmdset into
|
||||||
cmdsets from both Player and eventual puppeted Object (and
|
cmdsets from both Account and eventual puppeted Object (and
|
||||||
cmdsets in its room etc). A Player will only include its own
|
cmdsets in its room etc). An Account will only include its own
|
||||||
cmdset and the Objects and so on. Merge order is the same
|
cmdset and the Objects and so on. Merge order is the same
|
||||||
order, so that Object cmdsets are merged in last, giving them
|
order, so that Object cmdsets are merged in last, giving them
|
||||||
precendence for same-name and same-prio commands.
|
precendence for same-name and same-prio commands.
|
||||||
session (Session, optional): Relevant if callertype is "player" - the session will help
|
session (Session, optional): Relevant if callertype is "account" - the session will help
|
||||||
retrieve the correct cmdsets from puppeted objects.
|
retrieve the correct cmdsets from puppeted objects.
|
||||||
cmdobj (Command, optional): If given a command instance, this will be executed using
|
cmdobj (Command, optional): If given a command instance, this will be executed using
|
||||||
`called_by` as the caller, `raw_string` representing its arguments and (optionally)
|
`called_by` as the caller, `raw_string` representing its arguments and (optionally)
|
||||||
|
|
@ -513,20 +516,22 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@inlineCallbacks
|
@inlineCallbacks
|
||||||
def _run_command(cmd, cmdname, args, raw_string, cmdset, session, player):
|
def _run_command(cmd, cmdname, args, raw_cmdname, cmdset, session, account):
|
||||||
"""
|
"""
|
||||||
Helper function: This initializes and runs the Command
|
Helper function: This initializes and runs the Command
|
||||||
instance once the parser has identified it as either a normal
|
instance once the parser has identified it as either a normal
|
||||||
command or one of the system commands.
|
command or one of the system commands.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
cmd (Command): Command object.
|
cmd (Command): Command object
|
||||||
cmdname (str): Name of command.
|
cmdname (str): Name of command
|
||||||
args (str): Extra text entered after the identified command.
|
args (str): extra text entered after the identified command
|
||||||
raw_string (str): Full input string.
|
raw_cmdname (str): Name of Command, unaffected by eventual
|
||||||
|
prefix-stripping (if no prefix-stripping, this is the same
|
||||||
|
as cmdname).
|
||||||
cmdset (CmdSet): Command sert the command belongs to (if any)..
|
cmdset (CmdSet): Command sert the command belongs to (if any)..
|
||||||
session (Session): Session of caller (if any).
|
session (Session): Session of caller (if any).
|
||||||
player (Player): Player of caller (if any).
|
account (Account): Account of caller (if any).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
deferred (Deferred): this will fire with the return of the
|
deferred (Deferred): this will fire with the return of the
|
||||||
|
|
@ -540,15 +545,17 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
|
||||||
try:
|
try:
|
||||||
# Assign useful variables to the instance
|
# Assign useful variables to the instance
|
||||||
cmd.caller = caller
|
cmd.caller = caller
|
||||||
cmd.cmdstring = cmdname
|
cmd.cmdname = cmdname
|
||||||
|
cmd.raw_cmdname = raw_cmdname
|
||||||
|
cmd.cmdstring = cmdname # deprecated
|
||||||
cmd.args = args
|
cmd.args = args
|
||||||
cmd.cmdset = cmdset
|
cmd.cmdset = cmdset
|
||||||
cmd.session = session
|
cmd.session = session
|
||||||
cmd.player = player
|
cmd.account = account
|
||||||
cmd.raw_string = raw_string
|
cmd.raw_string = unformatted_raw_string
|
||||||
#cmd.obj # set via on-object cmdset handler for each command,
|
# cmd.obj # set via on-object cmdset handler for each command,
|
||||||
# since this may be different for every command when
|
# since this may be different for every command when
|
||||||
# merging multuple cmdsets
|
# merging multuple cmdsets
|
||||||
|
|
||||||
if hasattr(cmd, 'obj') and hasattr(cmd.obj, 'scripts'):
|
if hasattr(cmd, 'obj') and hasattr(cmd.obj, 'scripts'):
|
||||||
# cmd.obj is automatically made available by the cmdhandler.
|
# cmd.obj is automatically made available by the cmdhandler.
|
||||||
|
|
@ -566,7 +573,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
|
||||||
_COMMAND_NESTING[called_by] += 1
|
_COMMAND_NESTING[called_by] += 1
|
||||||
if _COMMAND_NESTING[called_by] > _COMMAND_RECURSION_LIMIT:
|
if _COMMAND_NESTING[called_by] > _COMMAND_RECURSION_LIMIT:
|
||||||
err = _ERROR_RECURSION_LIMIT.format(recursion_limit=_COMMAND_RECURSION_LIMIT,
|
err = _ERROR_RECURSION_LIMIT.format(recursion_limit=_COMMAND_RECURSION_LIMIT,
|
||||||
raw_string=raw_string,
|
raw_cmdname=raw_cmdname,
|
||||||
cmdclass=cmd.__class__)
|
cmdclass=cmd.__class__)
|
||||||
raise RuntimeError(err)
|
raise RuntimeError(err)
|
||||||
|
|
||||||
|
|
@ -611,16 +618,15 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
|
||||||
finally:
|
finally:
|
||||||
_COMMAND_NESTING[called_by] -= 1
|
_COMMAND_NESTING[called_by] -= 1
|
||||||
|
|
||||||
|
|
||||||
raw_string = to_unicode(raw_string, force_string=True)
|
raw_string = to_unicode(raw_string, force_string=True)
|
||||||
|
|
||||||
session, player, obj = session, None, None
|
session, account, obj = session, None, None
|
||||||
if callertype == "session":
|
if callertype == "session":
|
||||||
session = called_by
|
session = called_by
|
||||||
player = session.player
|
account = session.account
|
||||||
obj = session.puppet
|
obj = session.puppet
|
||||||
elif callertype == "player":
|
elif callertype == "account":
|
||||||
player = called_by
|
account = called_by
|
||||||
if session:
|
if session:
|
||||||
obj = yield session.puppet
|
obj = yield session.puppet
|
||||||
elif callertype == "object":
|
elif callertype == "object":
|
||||||
|
|
@ -629,32 +635,32 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
|
||||||
raise RuntimeError("cmdhandler: callertype %s is not valid." % callertype)
|
raise RuntimeError("cmdhandler: callertype %s is not valid." % callertype)
|
||||||
# the caller will be the one to receive messages and excert its permissions.
|
# the caller will be the one to receive messages and excert its permissions.
|
||||||
# we assign the caller with preference 'bottom up'
|
# we assign the caller with preference 'bottom up'
|
||||||
caller = obj or player or session
|
caller = obj or account or session
|
||||||
# The error_to is the default recipient for errors. Tries to make sure a player
|
# The error_to is the default recipient for errors. Tries to make sure an account
|
||||||
# does not get spammed for errors while preserving character mirroring.
|
# does not get spammed for errors while preserving character mirroring.
|
||||||
error_to = obj or session or player
|
error_to = obj or session or account
|
||||||
|
|
||||||
try: # catch bugs in cmdhandler itself
|
try: # catch bugs in cmdhandler itself
|
||||||
try: # catch special-type commands
|
try: # catch special-type commands
|
||||||
if cmdobj:
|
if cmdobj:
|
||||||
# the command object is already given
|
# the command object is already given
|
||||||
|
|
||||||
cmd = cmdobj() if callable(cmdobj) else cmdobj
|
cmd = cmdobj() if callable(cmdobj) else cmdobj
|
||||||
cmdname = cmdobj_key if cmdobj_key else cmd.key
|
cmdname = cmdobj_key if cmdobj_key else cmd.key
|
||||||
args = raw_string
|
args = raw_string
|
||||||
unformatted_raw_string = "%s%s" % (cmdname, args)
|
unformatted_raw_string = "%s%s" % (cmdname, args)
|
||||||
cmdset = None
|
cmdset = None
|
||||||
session = session
|
# session = session
|
||||||
player = player
|
# account = account
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# no explicit cmdobject given, figure it out
|
# no explicit cmdobject given, figure it out
|
||||||
|
cmdset = yield get_and_merge_cmdsets(caller, session, account, obj,
|
||||||
cmdset = yield get_and_merge_cmdsets(caller, session, player, obj,
|
callertype, raw_string)
|
||||||
callertype, raw_string)
|
|
||||||
if not cmdset:
|
if not cmdset:
|
||||||
# this is bad and shouldn't happen.
|
# this is bad and shouldn't happen.
|
||||||
raise NoCmdSets
|
raise NoCmdSets
|
||||||
|
# store the completely unmodified raw string - including
|
||||||
|
# whitespace and eventual prefixes-to-be-stripped.
|
||||||
unformatted_raw_string = raw_string
|
unformatted_raw_string = raw_string
|
||||||
raw_string = raw_string.strip()
|
raw_string = raw_string.strip()
|
||||||
if not raw_string:
|
if not raw_string:
|
||||||
|
|
@ -681,11 +687,11 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
|
||||||
sysarg = yield _SEARCH_AT_RESULT([match[2] for match in matches], caller, query=matches[0][0])
|
sysarg = yield _SEARCH_AT_RESULT([match[2] for match in matches], caller, query=matches[0][0])
|
||||||
raise ExecSystemCommand(syscmd, sysarg)
|
raise ExecSystemCommand(syscmd, sysarg)
|
||||||
|
|
||||||
cmdname, args, cmd = "", "", None
|
cmdname, args, cmd, raw_cmdname = "", "", None, ""
|
||||||
if len(matches) == 1:
|
if len(matches) == 1:
|
||||||
# We have a unique command match. But it may still be invalid.
|
# We have a unique command match. But it may still be invalid.
|
||||||
match = matches[0]
|
match = matches[0]
|
||||||
cmdname, args, cmd = match[0], match[1], match[2]
|
cmdname, args, cmd, raw_cmdname = match[0], match[1], match[2], match[5]
|
||||||
|
|
||||||
if not matches:
|
if not matches:
|
||||||
# No commands match our entered command
|
# No commands match our entered command
|
||||||
|
|
@ -697,8 +703,8 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
|
||||||
# fallback to default error text
|
# fallback to default error text
|
||||||
sysarg = _("Command '%s' is not available.") % raw_string
|
sysarg = _("Command '%s' is not available.") % raw_string
|
||||||
suggestions = string_suggestions(raw_string,
|
suggestions = string_suggestions(raw_string,
|
||||||
cmdset.get_all_cmd_keys_and_aliases(caller),
|
cmdset.get_all_cmd_keys_and_aliases(caller),
|
||||||
cutoff=0.7, maxnum=3)
|
cutoff=0.7, maxnum=3)
|
||||||
if suggestions:
|
if suggestions:
|
||||||
sysarg += _(" Maybe you meant %s?") % utils.list_to_string(suggestions, _('or'), addquote=True)
|
sysarg += _(" Maybe you meant %s?") % utils.list_to_string(suggestions, _('or'), addquote=True)
|
||||||
else:
|
else:
|
||||||
|
|
@ -718,7 +724,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
|
||||||
raise ExecSystemCommand(cmd, sysarg)
|
raise ExecSystemCommand(cmd, sysarg)
|
||||||
|
|
||||||
# A normal command.
|
# A normal command.
|
||||||
ret = yield _run_command(cmd, cmdname, args, unformatted_raw_string, cmdset, session, player)
|
ret = yield _run_command(cmd, cmdname, args, raw_cmdname, cmdset, session, account)
|
||||||
returnValue(ret)
|
returnValue(ret)
|
||||||
|
|
||||||
except ErrorReported as exc:
|
except ErrorReported as exc:
|
||||||
|
|
@ -734,7 +740,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
|
||||||
|
|
||||||
if syscmd:
|
if syscmd:
|
||||||
ret = yield _run_command(syscmd, syscmd.key, sysarg,
|
ret = yield _run_command(syscmd, syscmd.key, sysarg,
|
||||||
unformatted_raw_string, cmdset, session, player)
|
unformatted_raw_string, cmdset, session, account)
|
||||||
returnValue(ret)
|
returnValue(ret)
|
||||||
elif sysarg:
|
elif sysarg:
|
||||||
# return system arg
|
# return system arg
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ from django.conf import settings
|
||||||
from evennia.utils.logger import log_trace
|
from evennia.utils.logger import log_trace
|
||||||
|
|
||||||
_MULTIMATCH_REGEX = re.compile(settings.SEARCH_MULTIMATCH_REGEX, re.I + re.U)
|
_MULTIMATCH_REGEX = re.compile(settings.SEARCH_MULTIMATCH_REGEX, re.I + re.U)
|
||||||
|
_CMD_IGNORE_PREFIXES = settings.CMD_IGNORE_PREFIXES
|
||||||
|
|
||||||
|
|
||||||
def cmdparser(raw_string, cmdset, caller, match_index=None):
|
def cmdparser(raw_string, cmdset, caller, match_index=None):
|
||||||
"""
|
"""
|
||||||
|
|
@ -21,7 +23,7 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
|
||||||
Args:
|
Args:
|
||||||
raw_string (str): The unparsed text entered by the caller.
|
raw_string (str): The unparsed text entered by the caller.
|
||||||
cmdset (CmdSet): The merged, currently valid cmdset
|
cmdset (CmdSet): The merged, currently valid cmdset
|
||||||
caller (Session, Player or Object): The caller triggering this parsing.
|
caller (Session, Account or Object): The caller triggering this parsing.
|
||||||
match_index (int, optional): Index to pick a given match in a
|
match_index (int, optional): Index to pick a given match in a
|
||||||
list of same-named command matches. If this is given, it suggests
|
list of same-named command matches. If this is given, it suggests
|
||||||
this is not the first time this function was called: normally
|
this is not the first time this function was called: normally
|
||||||
|
|
@ -46,7 +48,7 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def create_match(cmdname, string, cmdobj):
|
def create_match(cmdname, string, cmdobj, raw_cmdname):
|
||||||
"""
|
"""
|
||||||
Builds a command match by splitting the incoming string and
|
Builds a command match by splitting the incoming string and
|
||||||
evaluating the quality of the match.
|
evaluating the quality of the match.
|
||||||
|
|
@ -55,56 +57,84 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
|
||||||
cmdname (str): Name of command to check for.
|
cmdname (str): Name of command to check for.
|
||||||
string (str): The string to match against.
|
string (str): The string to match against.
|
||||||
cmdobj (str): The full Command instance.
|
cmdobj (str): The full Command instance.
|
||||||
|
raw_cmdname (str, optional): If CMD_IGNORE_PREFIX is set and the cmdname starts with
|
||||||
|
one of the prefixes to ignore, this contains the raw, unstripped cmdname,
|
||||||
|
otherwise it is None.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
match (tuple): This is on the form (cmdname, args, cmdobj, cmdlen, mratio), where
|
match (tuple): This is on the form (cmdname, args, cmdobj, cmdlen, mratio, raw_cmdname), where
|
||||||
`cmdname` is the command's name and `args` the rest of the incoming string,
|
`cmdname` is the command's name and `args` the rest of the incoming string,
|
||||||
without said command name. `cmdobj` is the Command instance, the cmdlen is
|
without said command name. `cmdobj` is the Command instance, the cmdlen is
|
||||||
the same as len(cmdname) and mratio is a measure of how big a part of the
|
the same as len(cmdname) and mratio is a measure of how big a part of the
|
||||||
full input string the cmdname takes up - an exact match would be 1.0.
|
full input string the cmdname takes up - an exact match would be 1.0. Finally,
|
||||||
|
the `raw_cmdname` is the cmdname unmodified by eventual prefix-stripping.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
cmdlen, strlen = len(unicode(cmdname)), len(unicode(string))
|
cmdlen, strlen = len(unicode(cmdname)), len(unicode(string))
|
||||||
mratio = 1 - (strlen - cmdlen) / (1.0 * strlen)
|
mratio = 1 - (strlen - cmdlen) / (1.0 * strlen)
|
||||||
args = string[cmdlen:]
|
args = string[cmdlen:]
|
||||||
return (cmdname, args, cmdobj, cmdlen, mratio)
|
return (cmdname, args, cmdobj, cmdlen, mratio, raw_cmdname)
|
||||||
|
|
||||||
|
def build_matches(raw_string, include_prefixes=False):
|
||||||
|
l_raw_string = raw_string.lower()
|
||||||
|
matches = []
|
||||||
|
try:
|
||||||
|
if include_prefixes:
|
||||||
|
# use the cmdname as-is
|
||||||
|
for cmd in cmdset:
|
||||||
|
matches.extend([create_match(cmdname, raw_string, cmd, cmdname)
|
||||||
|
for cmdname in [cmd.key] + cmd.aliases
|
||||||
|
if cmdname and l_raw_string.startswith(cmdname.lower()) and
|
||||||
|
(not cmd.arg_regex or
|
||||||
|
cmd.arg_regex.match(l_raw_string[len(cmdname):]))])
|
||||||
|
else:
|
||||||
|
# strip prefixes set in settings
|
||||||
|
for cmd in cmdset:
|
||||||
|
for raw_cmdname in [cmd.key] + cmd.aliases:
|
||||||
|
cmdname = raw_cmdname.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_cmdname) > 1 else raw_cmdname
|
||||||
|
if cmdname and l_raw_string.startswith(cmdname.lower()) and \
|
||||||
|
(not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname):])):
|
||||||
|
matches.append(create_match(cmdname, raw_string, cmd, raw_cmdname))
|
||||||
|
except Exception:
|
||||||
|
log_trace("cmdhandler error. raw_input:%s" % raw_string)
|
||||||
|
return matches
|
||||||
|
|
||||||
|
def try_num_prefixes(raw_string):
|
||||||
|
if not matches:
|
||||||
|
# no matches found
|
||||||
|
num_ref_match = _MULTIMATCH_REGEX.match(raw_string)
|
||||||
|
if num_ref_match:
|
||||||
|
# the user might be trying to identify the command
|
||||||
|
# with a #num-command style syntax. We expect the regex to
|
||||||
|
# contain the groups "number" and "name".
|
||||||
|
mindex, new_raw_string = num_ref_match.group("number"), num_ref_match.group("name")
|
||||||
|
return mindex, new_raw_string
|
||||||
|
return None, None
|
||||||
|
|
||||||
if not raw_string:
|
if not raw_string:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
matches = []
|
# find mathces, first using the full name
|
||||||
|
matches = build_matches(raw_string, include_prefixes=True)
|
||||||
# match everything that begins with a matching cmdname.
|
|
||||||
l_raw_string = raw_string.lower()
|
|
||||||
for cmd in cmdset:
|
|
||||||
try:
|
|
||||||
matches.extend([create_match(cmdname, raw_string, cmd)
|
|
||||||
for cmdname in [cmd.key] + cmd.aliases
|
|
||||||
if cmdname and l_raw_string.startswith(cmdname.lower())
|
|
||||||
and (not cmd.arg_regex or
|
|
||||||
cmd.arg_regex.match(l_raw_string[len(cmdname):]))])
|
|
||||||
except Exception:
|
|
||||||
log_trace("cmdhandler error. raw_input:%s" % raw_string)
|
|
||||||
|
|
||||||
if not matches:
|
if not matches:
|
||||||
# no matches found
|
# try to match a number 1-cmdname, 2-cmdname etc
|
||||||
num_ref_match = _MULTIMATCH_REGEX.match(raw_string)
|
mindex, new_raw_string = try_num_prefixes(raw_string)
|
||||||
if num_ref_match:
|
if mindex is not None:
|
||||||
# the user might be trying to identify the command
|
return cmdparser(new_raw_string, cmdset, caller, match_index=int(mindex))
|
||||||
# with a #num-command style syntax. We expect the regex to
|
if _CMD_IGNORE_PREFIXES:
|
||||||
# contain the groups "number" and "name".
|
# still no match. Try to strip prefixes
|
||||||
mindex, new_raw_string = num_ref_match.group("number"), num_ref_match.group("name")
|
raw_string = raw_string.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_string) > 1 else raw_string
|
||||||
return cmdparser(new_raw_string, cmdset,
|
matches = build_matches(raw_string, include_prefixes=False)
|
||||||
caller, match_index=int(mindex))
|
|
||||||
|
|
||||||
# only select command matches we are actually allowed to call.
|
# only select command matches we are actually allowed to call.
|
||||||
matches = [match for match in matches if match[2].access(caller, 'cmd')]
|
matches = [match for match in matches if match[2].access(caller, 'cmd')]
|
||||||
|
|
||||||
|
# try to bring the number of matches down to 1
|
||||||
if len(matches) > 1:
|
if len(matches) > 1:
|
||||||
# See if it helps to analyze the match with preserved case but only if
|
# See if it helps to analyze the match with preserved case but only if
|
||||||
# it leaves at least one match.
|
# it leaves at least one match.
|
||||||
trimmed = [match for match in matches
|
trimmed = [match for match in matches
|
||||||
if raw_string.startswith(match[0])]
|
if raw_string.startswith(match[0])]
|
||||||
if trimmed:
|
if trimmed:
|
||||||
matches = trimmed
|
matches = trimmed
|
||||||
|
|
||||||
|
|
@ -122,11 +152,10 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
|
||||||
quality = [mat[4] for mat in matches]
|
quality = [mat[4] for mat in matches]
|
||||||
matches = matches[-quality.count(quality[-1]):]
|
matches = matches[-quality.count(quality[-1]):]
|
||||||
|
|
||||||
if len(matches) > 1 and match_index != None and 0 < match_index <= len(matches):
|
if len(matches) > 1 and match_index is not None and 0 < match_index <= len(matches):
|
||||||
# We couldn't separate match by quality, but we have an
|
# We couldn't separate match by quality, but we have an
|
||||||
# index argument to tell us which match to use.
|
# index argument to tell us which match to use.
|
||||||
matches = [matches[match_index-1]]
|
matches = [matches[match_index - 1]]
|
||||||
|
|
||||||
# no matter what we have at this point, we have to return it.
|
# no matter what we have at this point, we have to return it.
|
||||||
return matches
|
return matches
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ A Command Set (CmdSet) holds a set of commands. The Cmdsets can be
|
||||||
merged and combined to create new sets of commands in a
|
merged and combined to create new sets of commands in a
|
||||||
non-destructive way. This makes them very powerful for implementing
|
non-destructive way. This makes them very powerful for implementing
|
||||||
custom game states where different commands (or different variations
|
custom game states where different commands (or different variations
|
||||||
of commands) are available to the players depending on circumstance.
|
of commands) are available to the accounts depending on circumstance.
|
||||||
|
|
||||||
The available merge operations are partly borrowed from mathematical
|
The available merge operations are partly borrowed from mathematical
|
||||||
Set theory.
|
Set theory.
|
||||||
|
|
@ -51,7 +51,7 @@ class _CmdSetMeta(type):
|
||||||
cls.key = cls.__name__
|
cls.key = cls.__name__
|
||||||
cls.path = "%s.%s" % (cls.__module__, cls.__name__)
|
cls.path = "%s.%s" % (cls.__module__, cls.__name__)
|
||||||
|
|
||||||
if not type(cls.key_mergetypes) == dict:
|
if not isinstance(cls.key_mergetypes, dict):
|
||||||
cls.key_mergetypes = {}
|
cls.key_mergetypes = {}
|
||||||
|
|
||||||
super(_CmdSetMeta, cls).__init__(*args, **kwargs)
|
super(_CmdSetMeta, cls).__init__(*args, **kwargs)
|
||||||
|
|
@ -110,9 +110,9 @@ class CmdSet(with_metaclass(_CmdSetMeta, object)):
|
||||||
merger (i.e. A above) automatically taking
|
merger (i.e. A above) automatically taking
|
||||||
precedence. But if allow_duplicates is true, the
|
precedence. But if allow_duplicates is true, the
|
||||||
result will be a merger with more than one of each
|
result will be a merger with more than one of each
|
||||||
name match. This will usually lead to the player
|
name match. This will usually lead to the account
|
||||||
receiving a multiple-match error higher up the road,
|
receiving a multiple-match error higher up the road,
|
||||||
but can be good for things like cmdsets on non-player
|
but can be good for things like cmdsets on non-account
|
||||||
objects in a room, to allow the system to warn that
|
objects in a room, to allow the system to warn that
|
||||||
more than one 'ball' in the room has the same 'kick'
|
more than one 'ball' in the room has the same 'kick'
|
||||||
command defined on it, so it may offer a chance to
|
command defined on it, so it may offer a chance to
|
||||||
|
|
@ -134,7 +134,7 @@ class CmdSet(with_metaclass(_CmdSetMeta, object)):
|
||||||
commands
|
commands
|
||||||
no_channels - ignore the name of channels when matching against
|
no_channels - ignore the name of channels when matching against
|
||||||
commands (WARNING- this is dangerous since the
|
commands (WARNING- this is dangerous since the
|
||||||
player can then not even ask staff for help if
|
account can then not even ask staff for help if
|
||||||
something goes wrong)
|
something goes wrong)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -167,9 +167,9 @@ class CmdSet(with_metaclass(_CmdSetMeta, object)):
|
||||||
Creates a new CmdSet instance.
|
Creates a new CmdSet instance.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
cmdsetobj (Session, Player, Object, optional): This is the database object
|
cmdsetobj (Session, Account, Object, optional): This is the database object
|
||||||
to which this particular instance of cmdset is related. It
|
to which this particular instance of cmdset is related. It
|
||||||
is often a character but may also be a regular object, Player
|
is often a character but may also be a regular object, Account
|
||||||
or Session.
|
or Session.
|
||||||
key (str, optional): The idenfier for this cmdset. This
|
key (str, optional): The idenfier for this cmdset. This
|
||||||
helps if wanting to selectively remov cmdsets.
|
helps if wanting to selectively remov cmdsets.
|
||||||
|
|
@ -188,7 +188,7 @@ class CmdSet(with_metaclass(_CmdSetMeta, object)):
|
||||||
|
|
||||||
# initialize system
|
# initialize system
|
||||||
self.at_cmdset_creation()
|
self.at_cmdset_creation()
|
||||||
self._contains_cache = WeakKeyDictionary()#{}
|
self._contains_cache = WeakKeyDictionary() # {}
|
||||||
|
|
||||||
# Priority-sensitive merge operations for cmdsets
|
# Priority-sensitive merge operations for cmdsets
|
||||||
|
|
||||||
|
|
@ -214,7 +214,7 @@ class CmdSet(with_metaclass(_CmdSetMeta, object)):
|
||||||
cmdset_c.commands.extend(cmdset_b.commands)
|
cmdset_c.commands.extend(cmdset_b.commands)
|
||||||
else:
|
else:
|
||||||
cmdset_c.commands.extend([cmd for cmd in cmdset_b
|
cmdset_c.commands.extend([cmd for cmd in cmdset_b
|
||||||
if not cmd in cmdset_a])
|
if cmd not in cmdset_a])
|
||||||
return cmdset_c
|
return cmdset_c
|
||||||
|
|
||||||
def _intersect(self, cmdset_a, cmdset_b):
|
def _intersect(self, cmdset_a, cmdset_b):
|
||||||
|
|
@ -280,7 +280,7 @@ class CmdSet(with_metaclass(_CmdSetMeta, object)):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cmdset_c = cmdset_a._duplicate()
|
cmdset_c = cmdset_a._duplicate()
|
||||||
cmdset_c.commands = [cmd for cmd in cmdset_b if not cmd in cmdset_a]
|
cmdset_c.commands = [cmd for cmd in cmdset_b if cmd not in cmdset_a]
|
||||||
return cmdset_c
|
return cmdset_c
|
||||||
|
|
||||||
def _instantiate(self, cmd):
|
def _instantiate(self, cmd):
|
||||||
|
|
@ -411,7 +411,7 @@ class CmdSet(with_metaclass(_CmdSetMeta, object)):
|
||||||
cmdset_c = self._replace(self, cmdset_a)
|
cmdset_c = self._replace(self, cmdset_a)
|
||||||
elif mergetype == "Remove":
|
elif mergetype == "Remove":
|
||||||
cmdset_c = self._remove(self, cmdset_a)
|
cmdset_c = self._remove(self, cmdset_a)
|
||||||
else: # Union
|
else: # Union
|
||||||
cmdset_c = self._union(self, cmdset_a)
|
cmdset_c = self._union(self, cmdset_a)
|
||||||
|
|
||||||
# pass through options whenever they are set, unless the higher-prio
|
# pass through options whenever they are set, unless the higher-prio
|
||||||
|
|
@ -426,7 +426,7 @@ class CmdSet(with_metaclass(_CmdSetMeta, object)):
|
||||||
# This is used for diagnosis.
|
# This is used for diagnosis.
|
||||||
cmdset_c.actual_mergetype = mergetype
|
cmdset_c.actual_mergetype = mergetype
|
||||||
|
|
||||||
#print "__add__ for %s (prio %i) called with %s (prio %i)." % (self.key, self.priority, cmdset_a.key, cmdset_a.priority)
|
# print "__add__ for %s (prio %i) called with %s (prio %i)." % (self.key, self.priority, cmdset_a.key, cmdset_a.priority)
|
||||||
|
|
||||||
# return the system commands to the cmdset
|
# return the system commands to the cmdset
|
||||||
cmdset_c.add(sys_commands)
|
cmdset_c.add(sys_commands)
|
||||||
|
|
@ -604,7 +604,7 @@ class CmdSet(with_metaclass(_CmdSetMeta, object)):
|
||||||
names = []
|
names = []
|
||||||
if caller:
|
if caller:
|
||||||
[names.extend(cmd._keyaliases) for cmd in self.commands
|
[names.extend(cmd._keyaliases) for cmd in self.commands
|
||||||
if cmd.access(caller)]
|
if cmd.access(caller)]
|
||||||
else:
|
else:
|
||||||
[names.extend(cmd._keyaliases) for cmd in self.commands]
|
[names.extend(cmd._keyaliases) for cmd in self.commands]
|
||||||
return names
|
return names
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ intelligent container that, when added to other CmdSet make sure that
|
||||||
same-name commands are treated correctly (usually so there are no
|
same-name commands are treated correctly (usually so there are no
|
||||||
doublets). This temporary but up-to-date merger of CmdSet is jointly
|
doublets). This temporary but up-to-date merger of CmdSet is jointly
|
||||||
called the Current Cmset. It is this Current CmdSet that the
|
called the Current Cmset. It is this Current CmdSet that the
|
||||||
commandhandler looks through whenever a player enters a command (it
|
commandhandler looks through whenever an account enters a command (it
|
||||||
also adds CmdSets from objects in the room in real-time). All player
|
also adds CmdSets from objects in the room in real-time). All account
|
||||||
objects have a 'default cmdset' containing all the normal in-game mud
|
objects have a 'default cmdset' containing all the normal in-game mud
|
||||||
commands (look etc).
|
commands (look etc).
|
||||||
|
|
||||||
|
|
@ -19,12 +19,12 @@ So what is all this cmdset complexity good for?
|
||||||
In its simplest form, a CmdSet has no commands, only a key name. In
|
In its simplest form, a CmdSet has no commands, only a key name. In
|
||||||
this case the cmdset's use is up to each individual game - it can be
|
this case the cmdset's use is up to each individual game - it can be
|
||||||
used by an AI module for example (mobs in cmdset 'roam' move from room
|
used by an AI module for example (mobs in cmdset 'roam' move from room
|
||||||
to room, in cmdset 'attack' they enter combat with players).
|
to room, in cmdset 'attack' they enter combat with accounts).
|
||||||
|
|
||||||
Defining commands in cmdsets offer some further powerful game-design
|
Defining commands in cmdsets offer some further powerful game-design
|
||||||
consequences however. Here are some examples:
|
consequences however. Here are some examples:
|
||||||
|
|
||||||
As mentioned above, all players always have at least the Default
|
As mentioned above, all accounts always have at least the Default
|
||||||
CmdSet. This contains the set of all normal-use commands in-game,
|
CmdSet. This contains the set of all normal-use commands in-game,
|
||||||
stuff like look and @desc etc. Now assume our players end up in a dark
|
stuff like look and @desc etc. Now assume our players end up in a dark
|
||||||
room. You don't want the player to be able to do much in that dark
|
room. You don't want the player to be able to do much in that dark
|
||||||
|
|
@ -37,7 +37,7 @@ and have this completely replace the default cmdset.
|
||||||
|
|
||||||
Another example: Say you want your players to be able to go
|
Another example: Say you want your players to be able to go
|
||||||
fishing. You could implement this as a 'fish' command that fails
|
fishing. You could implement this as a 'fish' command that fails
|
||||||
whenever the player has no fishing rod. Easy enough. But what if you
|
whenever the account has no fishing rod. Easy enough. But what if you
|
||||||
want to make fishing more complex - maybe you want four-five different
|
want to make fishing more complex - maybe you want four-five different
|
||||||
commands for throwing your line, reeling in, etc? Most players won't
|
commands for throwing your line, reeling in, etc? Most players won't
|
||||||
(we assume) have fishing gear, and having all those detailed commands
|
(we assume) have fishing gear, and having all those detailed commands
|
||||||
|
|
@ -48,7 +48,7 @@ for a minor thing like fishing?
|
||||||
So instead you put all those detailed fishing commands into their own
|
So instead you put all those detailed fishing commands into their own
|
||||||
CommandSet called 'Fishing'. Whenever the player gives the command
|
CommandSet called 'Fishing'. Whenever the player gives the command
|
||||||
'fish' (presumably the code checks there is also water nearby), only
|
'fish' (presumably the code checks there is also water nearby), only
|
||||||
THEN this CommandSet is added to the Cmdhandler of the player. The
|
THEN this CommandSet is added to the Cmdhandler of the account. The
|
||||||
'throw' command (which normally throws rocks) is replaced by the
|
'throw' command (which normally throws rocks) is replaced by the
|
||||||
custom 'fishing variant' of throw. What has happened is that the
|
custom 'fishing variant' of throw. What has happened is that the
|
||||||
Fishing CommandSet was merged on top of the Default ones, and due to
|
Fishing CommandSet was merged on top of the Default ones, and due to
|
||||||
|
|
@ -80,28 +80,40 @@ __all__ = ("import_cmdset", "CmdSetHandler")
|
||||||
_CACHED_CMDSETS = {}
|
_CACHED_CMDSETS = {}
|
||||||
_CMDSET_PATHS = utils.make_iter(settings.CMDSET_PATHS)
|
_CMDSET_PATHS = utils.make_iter(settings.CMDSET_PATHS)
|
||||||
_IN_GAME_ERRORS = settings.IN_GAME_ERRORS
|
_IN_GAME_ERRORS = settings.IN_GAME_ERRORS
|
||||||
|
_CMDSET_FALLBACKS = settings.CMDSET_FALLBACKS
|
||||||
|
|
||||||
|
|
||||||
# Output strings
|
# Output strings
|
||||||
|
|
||||||
_ERROR_CMDSET_IMPORT = _(
|
_ERROR_CMDSET_IMPORT = _(
|
||||||
"""{traceback}
|
"""{traceback}
|
||||||
Error loading cmdset '{path}'
|
Error loading cmdset '{path}'
|
||||||
(Traceback was logged {timestamp})""")
|
(Traceback was logged {timestamp})""")
|
||||||
|
|
||||||
_ERROR_CMDSET_KEYERROR = _(
|
_ERROR_CMDSET_KEYERROR = _(
|
||||||
"""Error loading cmdset: No cmdset class '{classname}' in '{path}'.
|
"""Error loading cmdset: No cmdset class '{classname}' in '{path}'.
|
||||||
(Traceback was logged {timestamp})""")
|
(Traceback was logged {timestamp})""")
|
||||||
|
|
||||||
_ERROR_CMDSET_SYNTAXERROR = _(
|
_ERROR_CMDSET_SYNTAXERROR = _(
|
||||||
"""{traceback}
|
"""{traceback}
|
||||||
SyntaxError encountered when loading cmdset '{path}'.
|
SyntaxError encountered when loading cmdset '{path}'.
|
||||||
(Traceback was logged {timestamp})""")
|
(Traceback was logged {timestamp})""")
|
||||||
|
|
||||||
_ERROR_CMDSET_EXCEPTION = _(
|
_ERROR_CMDSET_EXCEPTION = _(
|
||||||
"""{traceback}
|
"""{traceback}
|
||||||
Compile/Run error when loading cmdset '{path}'.",
|
Compile/Run error when loading cmdset '{path}'.",
|
||||||
(Traceback was logged {timestamp})""")
|
(Traceback was logged {timestamp})""")
|
||||||
|
|
||||||
|
_ERROR_CMDSET_FALLBACK = _(
|
||||||
|
"""
|
||||||
|
Error encountered for cmdset at path '{path}'.
|
||||||
|
Replacing with fallback '{fallback_path}'.
|
||||||
|
""")
|
||||||
|
|
||||||
|
_ERROR_CMDSET_NO_FALLBACK = _(
|
||||||
|
"""Fallback path '{fallback_path}' failed to generate a cmdset."""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class _ErrorCmdSet(CmdSet):
|
class _ErrorCmdSet(CmdSet):
|
||||||
"""
|
"""
|
||||||
|
|
@ -110,6 +122,7 @@ class _ErrorCmdSet(CmdSet):
|
||||||
key = "_CMDSET_ERROR"
|
key = "_CMDSET_ERROR"
|
||||||
errmessage = "Error when loading cmdset."
|
errmessage = "Error when loading cmdset."
|
||||||
|
|
||||||
|
|
||||||
class _EmptyCmdSet(CmdSet):
|
class _EmptyCmdSet(CmdSet):
|
||||||
"""
|
"""
|
||||||
This cmdset represents an empty cmdset
|
This cmdset represents an empty cmdset
|
||||||
|
|
@ -118,6 +131,7 @@ class _EmptyCmdSet(CmdSet):
|
||||||
priority = -101
|
priority = -101
|
||||||
mergetype = "Union"
|
mergetype = "Union"
|
||||||
|
|
||||||
|
|
||||||
def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
|
def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
|
||||||
"""
|
"""
|
||||||
This helper function is used by the cmdsethandler to load a cmdset
|
This helper function is used by the cmdsethandler to load a cmdset
|
||||||
|
|
@ -128,7 +142,7 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
|
||||||
Args:
|
Args:
|
||||||
path (str): The path to the command set to load.
|
path (str): The path to the command set to load.
|
||||||
cmdsetobj (CmdSet): The database object/typeclass on which this cmdset is to be
|
cmdsetobj (CmdSet): The database object/typeclass on which this cmdset is to be
|
||||||
assigned (this can be also channels and exits, as well as players
|
assigned (this can be also channels and exits, as well as accounts
|
||||||
but there will always be such an object)
|
but there will always be such an object)
|
||||||
emit_to_obj (Object, optional): If given, error is emitted to
|
emit_to_obj (Object, optional): If given, error is emitted to
|
||||||
this object (in addition to logging)
|
this object (in addition to logging)
|
||||||
|
|
@ -142,11 +156,11 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
python_paths = [path] + ["%s.%s" % (prefix, path)
|
python_paths = [path] + ["%s.%s" % (prefix, path)
|
||||||
for prefix in _CMDSET_PATHS if not path.startswith(prefix)]
|
for prefix in _CMDSET_PATHS if not path.startswith(prefix)]
|
||||||
errstring = ""
|
errstring = ""
|
||||||
for python_path in python_paths:
|
for python_path in python_paths:
|
||||||
|
|
||||||
if "." in path:
|
if "." in path:
|
||||||
modpath, classname = python_path.rsplit(".", 1)
|
modpath, classname = python_path.rsplit(".", 1)
|
||||||
else:
|
else:
|
||||||
raise ImportError("The path '%s' is not on the form modulepath.ClassName" % path)
|
raise ImportError("The path '%s' is not on the form modulepath.ClassName" % path)
|
||||||
|
|
@ -179,7 +193,7 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
|
||||||
continue
|
continue
|
||||||
_CACHED_CMDSETS[python_path] = cmdsetclass
|
_CACHED_CMDSETS[python_path] = cmdsetclass
|
||||||
|
|
||||||
#instantiate the cmdset (and catch its errors)
|
# instantiate the cmdset (and catch its errors)
|
||||||
if callable(cmdsetclass):
|
if callable(cmdsetclass):
|
||||||
cmdsetclass = cmdsetclass(cmdsetobj)
|
cmdsetclass = cmdsetclass(cmdsetobj)
|
||||||
return cmdsetclass
|
return cmdsetclass
|
||||||
|
|
@ -223,7 +237,7 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
|
||||||
err_cmdset = _ErrorCmdSet()
|
err_cmdset = _ErrorCmdSet()
|
||||||
err_cmdset.errmessage = errstring
|
err_cmdset.errmessage = errstring
|
||||||
return err_cmdset
|
return err_cmdset
|
||||||
return None # undefined error
|
return None # undefined error
|
||||||
|
|
||||||
# classes
|
# classes
|
||||||
|
|
||||||
|
|
@ -266,7 +280,7 @@ class CmdSetHandler(object):
|
||||||
self.permanent_paths = [""]
|
self.permanent_paths = [""]
|
||||||
|
|
||||||
if init_true:
|
if init_true:
|
||||||
self.update(init_mode=True) #is then called from the object __init__.
|
self.update(init_mode=True) # is then called from the object __init__.
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -299,8 +313,8 @@ class CmdSetHandler(object):
|
||||||
if mergelist:
|
if mergelist:
|
||||||
tmpstring = _(" <Merged {mergelist} {mergetype}, prio {prio}>: {current}")
|
tmpstring = _(" <Merged {mergelist} {mergetype}, prio {prio}>: {current}")
|
||||||
string += tmpstring.format(mergelist="+".join(mergelist),
|
string += tmpstring.format(mergelist="+".join(mergelist),
|
||||||
mergetype=mergetype, prio=self.current.priority,
|
mergetype=mergetype, prio=self.current.priority,
|
||||||
current=self.current)
|
current=self.current)
|
||||||
else:
|
else:
|
||||||
permstring = "non-perm"
|
permstring = "non-perm"
|
||||||
if self.current.permanent:
|
if self.current.permanent:
|
||||||
|
|
@ -310,7 +324,7 @@ class CmdSetHandler(object):
|
||||||
prio=self.current.priority,
|
prio=self.current.priority,
|
||||||
permstring=permstring,
|
permstring=permstring,
|
||||||
keylist=", ".join(cmd.key for
|
keylist=", ".join(cmd.key for
|
||||||
cmd in sorted(self.current, key=lambda o: o.key)))
|
cmd in sorted(self.current, key=lambda o: o.key)))
|
||||||
return string.strip()
|
return string.strip()
|
||||||
|
|
||||||
def _import_cmdset(self, cmdset_path, emit_to_obj=None):
|
def _import_cmdset(self, cmdset_path, emit_to_obj=None):
|
||||||
|
|
@ -351,6 +365,22 @@ class CmdSetHandler(object):
|
||||||
elif path:
|
elif path:
|
||||||
cmdset = self._import_cmdset(path)
|
cmdset = self._import_cmdset(path)
|
||||||
if cmdset:
|
if cmdset:
|
||||||
|
if cmdset.key == '_CMDSET_ERROR':
|
||||||
|
# If a cmdset fails to load, check if we have a fallback path to use
|
||||||
|
fallback_path = _CMDSET_FALLBACKS.get(path, None)
|
||||||
|
if fallback_path:
|
||||||
|
err = _ERROR_CMDSET_FALLBACK.format(path=path, fallback_path=fallback_path)
|
||||||
|
logger.log_err(err)
|
||||||
|
if _IN_GAME_ERRORS:
|
||||||
|
self.obj.msg(err)
|
||||||
|
cmdset = self._import_cmdset(fallback_path)
|
||||||
|
# If no cmdset is returned from the fallback, we can't go further
|
||||||
|
if not cmdset:
|
||||||
|
err = _ERROR_CMDSET_NO_FALLBACK.format(fallback_path=fallback_path)
|
||||||
|
logger.log_err(err)
|
||||||
|
if _IN_GAME_ERRORS:
|
||||||
|
self.obj.msg(err)
|
||||||
|
continue
|
||||||
cmdset.permanent = cmdset.key != '_CMDSET_ERROR'
|
cmdset.permanent = cmdset.key != '_CMDSET_ERROR'
|
||||||
self.cmdset_stack.append(cmdset)
|
self.cmdset_stack.append(cmdset)
|
||||||
|
|
||||||
|
|
@ -514,10 +544,9 @@ class CmdSetHandler(object):
|
||||||
# legacy alias
|
# legacy alias
|
||||||
delete_default = remove_default
|
delete_default = remove_default
|
||||||
|
|
||||||
|
def get(self):
|
||||||
def all(self):
|
|
||||||
"""
|
"""
|
||||||
Show all cmdsets.
|
Get all cmdsets.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
cmdsets (list): All the command sets currently in the handler.
|
cmdsets (list): All the command sets currently in the handler.
|
||||||
|
|
@ -525,6 +554,9 @@ class CmdSetHandler(object):
|
||||||
"""
|
"""
|
||||||
return self.cmdset_stack
|
return self.cmdset_stack
|
||||||
|
|
||||||
|
# backwards-compatible alias
|
||||||
|
all = get
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""
|
"""
|
||||||
Removes all Command Sets from the handler except the default one
|
Removes all Command Sets from the handler except the default one
|
||||||
|
|
@ -560,16 +592,16 @@ class CmdSetHandler(object):
|
||||||
else:
|
else:
|
||||||
print [cset.path for cset in self.cmdset_stack], cmdset.path
|
print [cset.path for cset in self.cmdset_stack], cmdset.path
|
||||||
return any([cset for cset in self.cmdset_stack
|
return any([cset for cset in self.cmdset_stack
|
||||||
if cset.path == cmdset.path])
|
if cset.path == cmdset.path])
|
||||||
else:
|
else:
|
||||||
# try it as a path or key
|
# try it as a path or key
|
||||||
if must_be_default:
|
if must_be_default:
|
||||||
return self.cmdset_stack and (
|
return self.cmdset_stack and (
|
||||||
self.cmdset_stack[0].key == cmdset or
|
self.cmdset_stack[0].key == cmdset or
|
||||||
self.cmdset_stack[0].path == cmdset)
|
self.cmdset_stack[0].path == cmdset)
|
||||||
else:
|
else:
|
||||||
return any([cset for cset in self.cmdset_stack
|
return any([cset for cset in self.cmdset_stack
|
||||||
if cset.path == cmdset or cset.key == cmdset])
|
if cset.path == cmdset or cset.key == cmdset])
|
||||||
|
|
||||||
# backwards-compatability alias
|
# backwards-compatability alias
|
||||||
has_cmdset = has
|
has_cmdset = has
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ def _init_command(cls, **kwargs):
|
||||||
if "cmd:" not in cls.locks:
|
if "cmd:" not in cls.locks:
|
||||||
cls.locks = "cmd:all();" + cls.locks
|
cls.locks = "cmd:all();" + cls.locks
|
||||||
for lockstring in cls.locks.split(';'):
|
for lockstring in cls.locks.split(';'):
|
||||||
if lockstring and not ':' in lockstring:
|
if lockstring and ':' not in lockstring:
|
||||||
lockstring = "cmd:%s" % lockstring
|
lockstring = "cmd:%s" % lockstring
|
||||||
temp.append(lockstring)
|
temp.append(lockstring)
|
||||||
cls.lock_storage = ";".join(temp)
|
cls.lock_storage = ";".join(temp)
|
||||||
|
|
@ -152,14 +152,14 @@ class Command(with_metaclass(CommandMeta, object)):
|
||||||
is_exit = False
|
is_exit = False
|
||||||
# define the command not only by key but by the regex form of its arguments
|
# define the command not only by key but by the regex form of its arguments
|
||||||
arg_regex = settings.COMMAND_DEFAULT_ARG_REGEX
|
arg_regex = settings.COMMAND_DEFAULT_ARG_REGEX
|
||||||
# whether self.msg sends to all sessions of a related player/object (default
|
# whether self.msg sends to all sessions of a related account/object (default
|
||||||
# is to only send to the session sending the command).
|
# is to only send to the session sending the command).
|
||||||
msg_all_sessions = settings.COMMAND_DEFAULT_MSG_ALL_SESSIONS
|
msg_all_sessions = settings.COMMAND_DEFAULT_MSG_ALL_SESSIONS
|
||||||
|
|
||||||
# auto-set (by Evennia on command instantiation) are:
|
# auto-set (by Evennia on command instantiation) are:
|
||||||
# obj - which object this command is defined on
|
# obj - which object this command is defined on
|
||||||
# session - which session is responsible for triggering this command. Only set
|
# session - which session is responsible for triggering this command. Only set
|
||||||
# if triggered by a player.
|
# if triggered by an account.
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -307,7 +307,7 @@ class Command(with_metaclass(CommandMeta, object)):
|
||||||
session=None, **kwargs):
|
session=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
This is a shortcut instead of calling msg() directly on an
|
This is a shortcut instead of calling msg() directly on an
|
||||||
object - it will detect if caller is an Object or a Player and
|
object - it will detect if caller is an Object or an Account and
|
||||||
also appends self.session automatically if self.msg_all_sessions is False.
|
also appends self.session automatically if self.msg_all_sessions is False.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -340,7 +340,7 @@ class Command(with_metaclass(CommandMeta, object)):
|
||||||
Args:
|
Args:
|
||||||
raw_string (str): Execute this string as a command input.
|
raw_string (str): Execute this string as a command input.
|
||||||
session (Session, optional): If not given, the current command's Session will be used.
|
session (Session, optional): If not given, the current command's Session will be used.
|
||||||
obj (Object or Player, optional): Object or Player on which to call the execute_cmd.
|
obj (Object or Account, optional): Object or Account on which to call the execute_cmd.
|
||||||
If not given, self.caller will be used.
|
If not given, self.caller will be used.
|
||||||
|
|
||||||
Kwargs:
|
Kwargs:
|
||||||
|
|
@ -443,7 +443,7 @@ class Command(with_metaclass(CommandMeta, object)):
|
||||||
commands the caller can use.
|
commands the caller can use.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
caller (Object or Player): the caller asking for help on the command.
|
caller (Object or Account): the caller asking for help on the command.
|
||||||
cmdset (CmdSet): the command set (if you need additional commands).
|
cmdset (CmdSet): the command set (if you need additional commands).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
"""
|
"""
|
||||||
Player (OOC) commands. These are stored on the Player object
|
Account (OOC) commands. These are stored on the Account object
|
||||||
and self.caller is thus always a Player, not an Object/Character.
|
and self.caller is thus always an Account, not an Object/Character.
|
||||||
|
|
||||||
These commands go in the PlayerCmdset and are accessible also
|
These commands go in the AccountCmdset and are accessible also
|
||||||
when puppeting a Character (although with lower priority)
|
when puppeting a Character (although with lower priority)
|
||||||
|
|
||||||
These commands use the player_caller property which tells the command
|
These commands use the account_caller property which tells the command
|
||||||
parent (MuxCommand, usually) to setup caller correctly. They use
|
parent (MuxCommand, usually) to setup caller correctly. They use
|
||||||
self.player to make sure to always use the player object rather than
|
self.account to make sure to always use the account object rather than
|
||||||
self.caller (which change depending on the level you are calling from)
|
self.caller (which change depending on the level you are calling from)
|
||||||
The property self.character can be used to access the character when
|
The property self.character can be used to access the character when
|
||||||
these commands are triggered with a connected character (such as the
|
these commands are triggered with a connected character (such as the
|
||||||
case of the @ooc command), it is None if we are OOC.
|
case of the @ooc command), it is None if we are OOC.
|
||||||
|
|
||||||
Note that under MULTISESSION_MODE > 2, Player commands should use
|
Note that under MULTISESSION_MODE > 2, Account commands should use
|
||||||
self.msg() and similar methods to reroute returns to the correct
|
self.msg() and similar methods to reroute returns to the correct
|
||||||
method. Otherwise all text will be returned to all connected sessions.
|
method. Otherwise all text will be returned to all connected sessions.
|
||||||
|
|
||||||
|
|
@ -36,7 +36,7 @@ __all__ = ("CmdOOCLook", "CmdIC", "CmdOOC", "CmdPassword", "CmdQuit",
|
||||||
"CmdColorTest", "CmdQuell")
|
"CmdColorTest", "CmdQuell")
|
||||||
|
|
||||||
|
|
||||||
class MuxPlayerLookCommand(COMMAND_DEFAULT_CLASS):
|
class MuxAccountLookCommand(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
Custom parent (only) parsing for OOC looking, sets a "playable"
|
Custom parent (only) parsing for OOC looking, sets a "playable"
|
||||||
property on the command based on the parsing.
|
property on the command based on the parsing.
|
||||||
|
|
@ -46,19 +46,19 @@ class MuxPlayerLookCommand(COMMAND_DEFAULT_CLASS):
|
||||||
def parse(self):
|
def parse(self):
|
||||||
"""Custom parsing"""
|
"""Custom parsing"""
|
||||||
|
|
||||||
super(MuxPlayerLookCommand, self).parse()
|
super(MuxAccountLookCommand, self).parse()
|
||||||
|
|
||||||
if _MULTISESSION_MODE < 2:
|
if _MULTISESSION_MODE < 2:
|
||||||
# only one character allowed - not used in this mode
|
# only one character allowed - not used in this mode
|
||||||
self.playable = None
|
self.playable = None
|
||||||
return
|
return
|
||||||
|
|
||||||
playable = self.player.db._playable_characters
|
playable = self.account.db._playable_characters
|
||||||
if playable is not None:
|
if playable is not None:
|
||||||
# clean up list if character object was deleted in between
|
# clean up list if character object was deleted in between
|
||||||
if None in playable:
|
if None in playable:
|
||||||
playable = [character for character in playable if character]
|
playable = [character for character in playable if character]
|
||||||
self.player.db._playable_characters = playable
|
self.account.db._playable_characters = playable
|
||||||
# store playable property
|
# store playable property
|
||||||
if self.args:
|
if self.args:
|
||||||
self.playable = dict((utils.to_str(char.key.lower()), char)
|
self.playable = dict((utils.to_str(char.key.lower()), char)
|
||||||
|
|
@ -67,13 +67,13 @@ class MuxPlayerLookCommand(COMMAND_DEFAULT_CLASS):
|
||||||
self.playable = playable
|
self.playable = playable
|
||||||
|
|
||||||
|
|
||||||
# Obs - these are all intended to be stored on the Player, and as such,
|
# Obs - these are all intended to be stored on the Account, and as such,
|
||||||
# use self.player instead of self.caller, just to be sure. Also self.msg()
|
# use self.account instead of self.caller, just to be sure. Also self.msg()
|
||||||
# is used to make sure returns go to the right session
|
# is used to make sure returns go to the right session
|
||||||
|
|
||||||
# note that this is inheriting from MuxPlayerLookCommand,
|
# note that this is inheriting from MuxAccountLookCommand,
|
||||||
# and has the .playable property.
|
# and has the .playable property.
|
||||||
class CmdOOCLook(MuxPlayerLookCommand):
|
class CmdOOCLook(MuxAccountLookCommand):
|
||||||
"""
|
"""
|
||||||
look while out-of-character
|
look while out-of-character
|
||||||
|
|
||||||
|
|
@ -84,7 +84,7 @@ class CmdOOCLook(MuxPlayerLookCommand):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# This is an OOC version of the look command. Since a
|
# This is an OOC version of the look command. Since a
|
||||||
# Player doesn't have an in-game existence, there is no
|
# Account doesn't have an in-game existence, there is no
|
||||||
# concept of location or "self". If we are controlling
|
# concept of location or "self". If we are controlling
|
||||||
# a character, pass control over to normal look.
|
# a character, pass control over to normal look.
|
||||||
|
|
||||||
|
|
@ -94,7 +94,7 @@ class CmdOOCLook(MuxPlayerLookCommand):
|
||||||
help_category = "General"
|
help_category = "General"
|
||||||
|
|
||||||
# this is used by the parent
|
# this is used by the parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""implement the ooc look command"""
|
"""implement the ooc look command"""
|
||||||
|
|
@ -104,8 +104,8 @@ class CmdOOCLook(MuxPlayerLookCommand):
|
||||||
self.msg("You are out-of-character (OOC).\nUse |w@ic|n to get back into the game.")
|
self.msg("You are out-of-character (OOC).\nUse |w@ic|n to get back into the game.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# call on-player look helper method
|
# call on-account look helper method
|
||||||
self.msg(self.player.at_look(target=self.playable, session=self.session))
|
self.msg(self.account.at_look(target=self.playable, session=self.session))
|
||||||
|
|
||||||
|
|
||||||
class CmdCharCreate(COMMAND_DEFAULT_CLASS):
|
class CmdCharCreate(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -121,15 +121,15 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
|
||||||
if you want.
|
if you want.
|
||||||
"""
|
"""
|
||||||
key = "@charcreate"
|
key = "@charcreate"
|
||||||
locks = "cmd:pperm(Players)"
|
locks = "cmd:pperm(Player)"
|
||||||
help_category = "General"
|
help_category = "General"
|
||||||
|
|
||||||
# this is used by the parent
|
# this is used by the parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""create the new character"""
|
"""create the new character"""
|
||||||
player = self.player
|
account = self.account
|
||||||
if not self.args:
|
if not self.args:
|
||||||
self.msg("Usage: @charcreate <charname> [= description]")
|
self.msg("Usage: @charcreate <charname> [= description]")
|
||||||
return
|
return
|
||||||
|
|
@ -138,9 +138,9 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
charmax = _MAX_NR_CHARACTERS if _MULTISESSION_MODE > 1 else 1
|
charmax = _MAX_NR_CHARACTERS if _MULTISESSION_MODE > 1 else 1
|
||||||
|
|
||||||
if not player.is_superuser and \
|
if not account.is_superuser and \
|
||||||
(player.db._playable_characters and
|
(account.db._playable_characters and
|
||||||
len(player.db._playable_characters) >= charmax):
|
len(account.db._playable_characters) >= charmax):
|
||||||
self.msg("You may only create a maximum of %i characters." % charmax)
|
self.msg("You may only create a maximum of %i characters." % charmax)
|
||||||
return
|
return
|
||||||
from evennia.objects.models import ObjectDB
|
from evennia.objects.models import ObjectDB
|
||||||
|
|
@ -156,19 +156,19 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
|
||||||
# create the character
|
# create the character
|
||||||
start_location = ObjectDB.objects.get_id(settings.START_LOCATION)
|
start_location = ObjectDB.objects.get_id(settings.START_LOCATION)
|
||||||
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
|
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
|
||||||
permissions = settings.PERMISSION_PLAYER_DEFAULT
|
permissions = settings.PERMISSION_ACCOUNT_DEFAULT
|
||||||
new_character = create.create_object(typeclass, key=key,
|
new_character = create.create_object(typeclass, key=key,
|
||||||
location=start_location,
|
location=start_location,
|
||||||
home=default_home,
|
home=default_home,
|
||||||
permissions=permissions)
|
permissions=permissions)
|
||||||
# only allow creator (and immortals) to puppet this char
|
# only allow creator (and developers) to puppet this char
|
||||||
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" %
|
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" %
|
||||||
(new_character.id, player.id))
|
(new_character.id, account.id))
|
||||||
player.db._playable_characters.append(new_character)
|
account.db._playable_characters.append(new_character)
|
||||||
if desc:
|
if desc:
|
||||||
new_character.db.desc = desc
|
new_character.db.desc = desc
|
||||||
elif not new_character.db.desc:
|
elif not new_character.db.desc:
|
||||||
new_character.db.desc = "This is a Player."
|
new_character.db.desc = "This is a character."
|
||||||
self.msg("Created new character %s. Use |w@ic %s|n to enter the game as this character."
|
self.msg("Created new character %s. Use |w@ic %s|n to enter the game as this character."
|
||||||
% (new_character.key, new_character.key))
|
% (new_character.key, new_character.key))
|
||||||
|
|
||||||
|
|
@ -183,19 +183,19 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS):
|
||||||
Permanently deletes one of your characters.
|
Permanently deletes one of your characters.
|
||||||
"""
|
"""
|
||||||
key = "@chardelete"
|
key = "@chardelete"
|
||||||
locks = "cmd:pperm(Players)"
|
locks = "cmd:pperm(Player)"
|
||||||
help_category = "General"
|
help_category = "General"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""delete the character"""
|
"""delete the character"""
|
||||||
player = self.player
|
account = self.account
|
||||||
|
|
||||||
if not self.args:
|
if not self.args:
|
||||||
self.msg("Usage: @chardelete <charactername>")
|
self.msg("Usage: @chardelete <charactername>")
|
||||||
return
|
return
|
||||||
|
|
||||||
# use the playable_characters list to search
|
# use the playable_characters list to search
|
||||||
match = [char for char in utils.make_iter(player.db._playable_characters)
|
match = [char for char in utils.make_iter(account.db._playable_characters)
|
||||||
if char.key.lower() == self.args.lower()]
|
if char.key.lower() == self.args.lower()]
|
||||||
if not match:
|
if not match:
|
||||||
self.msg("You have no such character to delete.")
|
self.msg("You have no such character to delete.")
|
||||||
|
|
@ -219,9 +219,9 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS):
|
||||||
del caller.ndb._char_to_delete
|
del caller.ndb._char_to_delete
|
||||||
|
|
||||||
match = match[0]
|
match = match[0]
|
||||||
player.ndb._char_to_delete = match
|
account.ndb._char_to_delete = match
|
||||||
prompt = "|rThis will permanently destroy '%s'. This cannot be undone.|n Continue yes/[no]?"
|
prompt = "|rThis will permanently destroy '%s'. This cannot be undone.|n Continue yes/[no]?"
|
||||||
get_input(player, prompt % match.key, _callback)
|
get_input(account, prompt % match.key, _callback)
|
||||||
|
|
||||||
|
|
||||||
class CmdIC(COMMAND_DEFAULT_CLASS):
|
class CmdIC(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -234,12 +234,12 @@ class CmdIC(COMMAND_DEFAULT_CLASS):
|
||||||
Go in-character (IC) as a given Character.
|
Go in-character (IC) as a given Character.
|
||||||
|
|
||||||
This will attempt to "become" a different object assuming you have
|
This will attempt to "become" a different object assuming you have
|
||||||
the right to do so. Note that it's the PLAYER character that puppets
|
the right to do so. Note that it's the ACCOUNT character that puppets
|
||||||
characters/objects and which needs to have the correct permission!
|
characters/objects and which needs to have the correct permission!
|
||||||
|
|
||||||
You cannot become an object that is already controlled by another
|
You cannot become an object that is already controlled by another
|
||||||
player. In principle <character> can be any in-game object as long
|
account. In principle <character> can be any in-game object as long
|
||||||
as you the player have access right to puppet it.
|
as you the account have access right to puppet it.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "@ic"
|
key = "@ic"
|
||||||
|
|
@ -249,24 +249,24 @@ class CmdIC(COMMAND_DEFAULT_CLASS):
|
||||||
help_category = "General"
|
help_category = "General"
|
||||||
|
|
||||||
# this is used by the parent
|
# this is used by the parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""
|
"""
|
||||||
Main puppet method
|
Main puppet method
|
||||||
"""
|
"""
|
||||||
player = self.player
|
account = self.account
|
||||||
session = self.session
|
session = self.session
|
||||||
|
|
||||||
new_character = None
|
new_character = None
|
||||||
if not self.args:
|
if not self.args:
|
||||||
new_character = player.db._last_puppet
|
new_character = account.db._last_puppet
|
||||||
if not new_character:
|
if not new_character:
|
||||||
self.msg("Usage: @ic <character>")
|
self.msg("Usage: @ic <character>")
|
||||||
return
|
return
|
||||||
if not new_character:
|
if not new_character:
|
||||||
# search for a matching character
|
# search for a matching character
|
||||||
new_character = [char for char in search.object_search(self.args) if char.access(player, "puppet")]
|
new_character = [char for char in search.object_search(self.args) if char.access(account, "puppet")]
|
||||||
if not new_character:
|
if not new_character:
|
||||||
self.msg("That is not a valid character choice.")
|
self.msg("That is not a valid character choice.")
|
||||||
return
|
return
|
||||||
|
|
@ -277,15 +277,15 @@ class CmdIC(COMMAND_DEFAULT_CLASS):
|
||||||
else:
|
else:
|
||||||
new_character = new_character[0]
|
new_character = new_character[0]
|
||||||
try:
|
try:
|
||||||
player.puppet_object(session, new_character)
|
account.puppet_object(session, new_character)
|
||||||
player.db._last_puppet = new_character
|
account.db._last_puppet = new_character
|
||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
self.msg("|rYou cannot become |C%s|n: %s" % (new_character.name, exc))
|
self.msg("|rYou cannot become |C%s|n: %s" % (new_character.name, exc))
|
||||||
|
|
||||||
|
|
||||||
# note that this is inheriting from MuxPlayerLookCommand,
|
# note that this is inheriting from MuxAccountLookCommand,
|
||||||
# and as such has the .playable property.
|
# and as such has the .playable property.
|
||||||
class CmdOOC(MuxPlayerLookCommand):
|
class CmdOOC(MuxAccountLookCommand):
|
||||||
"""
|
"""
|
||||||
stop puppeting and go ooc
|
stop puppeting and go ooc
|
||||||
|
|
||||||
|
|
@ -298,30 +298,30 @@ class CmdOOC(MuxPlayerLookCommand):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "@ooc"
|
key = "@ooc"
|
||||||
locks = "cmd:pperm(Players)"
|
locks = "cmd:pperm(Player)"
|
||||||
aliases = "@unpuppet"
|
aliases = "@unpuppet"
|
||||||
help_category = "General"
|
help_category = "General"
|
||||||
|
|
||||||
# this is used by the parent
|
# this is used by the parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""Implement function"""
|
"""Implement function"""
|
||||||
|
|
||||||
player = self.player
|
account = self.account
|
||||||
session = self.session
|
session = self.session
|
||||||
|
|
||||||
old_char = player.get_puppet(session)
|
old_char = account.get_puppet(session)
|
||||||
if not old_char:
|
if not old_char:
|
||||||
string = "You are already OOC."
|
string = "You are already OOC."
|
||||||
self.msg(string)
|
self.msg(string)
|
||||||
return
|
return
|
||||||
|
|
||||||
player.db._last_puppet = old_char
|
account.db._last_puppet = old_char
|
||||||
|
|
||||||
# disconnect
|
# disconnect
|
||||||
try:
|
try:
|
||||||
player.unpuppet_object(session)
|
account.unpuppet_object(session)
|
||||||
self.msg("\n|GYou go OOC.|n\n")
|
self.msg("\n|GYou go OOC.|n\n")
|
||||||
|
|
||||||
if _MULTISESSION_MODE < 2:
|
if _MULTISESSION_MODE < 2:
|
||||||
|
|
@ -329,7 +329,7 @@ class CmdOOC(MuxPlayerLookCommand):
|
||||||
self.msg("You are out-of-character (OOC).\nUse |w@ic|n to get back into the game.")
|
self.msg("You are out-of-character (OOC).\nUse |w@ic|n to get back into the game.")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.msg(player.at_look(target=self.playable, session=session))
|
self.msg(account.at_look(target=self.playable, session=session))
|
||||||
|
|
||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
self.msg("|rCould not unpuppet from |c%s|n: %s" % (old_char, exc))
|
self.msg("|rCould not unpuppet from |c%s|n: %s" % (old_char, exc))
|
||||||
|
|
@ -350,21 +350,21 @@ class CmdSessions(COMMAND_DEFAULT_CLASS):
|
||||||
help_category = "General"
|
help_category = "General"
|
||||||
|
|
||||||
# this is used by the parent
|
# this is used by the parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""Implement function"""
|
"""Implement function"""
|
||||||
player = self.player
|
account = self.account
|
||||||
sessions = player.sessions.all()
|
sessions = account.sessions.all()
|
||||||
table = evtable.EvTable("|wsessid",
|
table = evtable.EvTable("|wsessid",
|
||||||
"|wprotocol",
|
"|wprotocol",
|
||||||
"|whost",
|
"|whost",
|
||||||
"|wpuppet/character",
|
"|wpuppet/character",
|
||||||
"|wlocation")
|
"|wlocation")
|
||||||
for sess in sorted(sessions, key=lambda x: x.sessid):
|
for sess in sorted(sessions, key=lambda x: x.sessid):
|
||||||
char = player.get_puppet(sess)
|
char = account.get_puppet(sess)
|
||||||
table.add_row(str(sess.sessid), str(sess.protocol_key),
|
table.add_row(str(sess.sessid), str(sess.protocol_key),
|
||||||
type(sess.address) == tuple and sess.address[0] or sess.address,
|
isinstance(sess.address, tuple) and sess.address[0] or sess.address,
|
||||||
char and str(char) or "None",
|
char and str(char) or "None",
|
||||||
char and str(char.location) or "N/A")
|
char and str(char.location) or "N/A")
|
||||||
self.msg("|wYour current session(s):|n\n%s" % table)
|
self.msg("|wYour current session(s):|n\n%s" % table)
|
||||||
|
|
@ -387,27 +387,27 @@ class CmdWho(COMMAND_DEFAULT_CLASS):
|
||||||
locks = "cmd:all()"
|
locks = "cmd:all()"
|
||||||
|
|
||||||
# this is used by the parent
|
# this is used by the parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""
|
"""
|
||||||
Get all connected players by polling session.
|
Get all connected accounts by polling session.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
player = self.player
|
account = self.account
|
||||||
session_list = SESSIONS.get_sessions()
|
session_list = SESSIONS.get_sessions()
|
||||||
|
|
||||||
session_list = sorted(session_list, key=lambda o: o.player.key)
|
session_list = sorted(session_list, key=lambda o: o.account.key)
|
||||||
|
|
||||||
if self.cmdstring == "doing":
|
if self.cmdstring == "doing":
|
||||||
show_session_data = False
|
show_session_data = False
|
||||||
else:
|
else:
|
||||||
show_session_data = player.check_permstring("Immortals") or player.check_permstring("Wizards")
|
show_session_data = account.check_permstring("Developer") or account.check_permstring("Admins")
|
||||||
|
|
||||||
nplayers = (SESSIONS.player_count())
|
naccounts = (SESSIONS.account_count())
|
||||||
if show_session_data:
|
if show_session_data:
|
||||||
# privileged info
|
# privileged info
|
||||||
table = evtable.EvTable("|wPlayer Name",
|
table = evtable.EvTable("|wAccount Name",
|
||||||
"|wOn for",
|
"|wOn for",
|
||||||
"|wIdle",
|
"|wIdle",
|
||||||
"|wPuppeting",
|
"|wPuppeting",
|
||||||
|
|
@ -420,10 +420,10 @@ class CmdWho(COMMAND_DEFAULT_CLASS):
|
||||||
continue
|
continue
|
||||||
delta_cmd = time.time() - session.cmd_last_visible
|
delta_cmd = time.time() - session.cmd_last_visible
|
||||||
delta_conn = time.time() - session.conn_time
|
delta_conn = time.time() - session.conn_time
|
||||||
player = session.get_player()
|
account = session.get_account()
|
||||||
puppet = session.get_puppet()
|
puppet = session.get_puppet()
|
||||||
location = puppet.location.key if puppet and puppet.location else "None"
|
location = puppet.location.key if puppet and puppet.location else "None"
|
||||||
table.add_row(utils.crop(player.name, width=25),
|
table.add_row(utils.crop(account.name, width=25),
|
||||||
utils.time_format(delta_conn, 0),
|
utils.time_format(delta_conn, 0),
|
||||||
utils.time_format(delta_cmd, 1),
|
utils.time_format(delta_cmd, 1),
|
||||||
utils.crop(puppet.key if puppet else "None", width=25),
|
utils.crop(puppet.key if puppet else "None", width=25),
|
||||||
|
|
@ -433,19 +433,19 @@ class CmdWho(COMMAND_DEFAULT_CLASS):
|
||||||
isinstance(session.address, tuple) and session.address[0] or session.address)
|
isinstance(session.address, tuple) and session.address[0] or session.address)
|
||||||
else:
|
else:
|
||||||
# unprivileged
|
# unprivileged
|
||||||
table = evtable.EvTable("|wPlayer name", "|wOn for", "|wIdle")
|
table = evtable.EvTable("|wAccount name", "|wOn for", "|wIdle")
|
||||||
for session in session_list:
|
for session in session_list:
|
||||||
if not session.logged_in:
|
if not session.logged_in:
|
||||||
continue
|
continue
|
||||||
delta_cmd = time.time() - session.cmd_last_visible
|
delta_cmd = time.time() - session.cmd_last_visible
|
||||||
delta_conn = time.time() - session.conn_time
|
delta_conn = time.time() - session.conn_time
|
||||||
player = session.get_player()
|
account = session.get_account()
|
||||||
table.add_row(utils.crop(player.key, width=25),
|
table.add_row(utils.crop(account.key, width=25),
|
||||||
utils.time_format(delta_conn, 0),
|
utils.time_format(delta_conn, 0),
|
||||||
utils.time_format(delta_cmd, 1))
|
utils.time_format(delta_cmd, 1))
|
||||||
is_one = nplayers == 1
|
is_one = naccounts == 1
|
||||||
self.msg("|wPlayers:|n\n%s\n%s unique account%s logged in."
|
self.msg("|wAccounts:|n\n%s\n%s unique account%s logged in."
|
||||||
% (table, "One" if is_one else nplayers, "" if is_one else "s"))
|
% (table, "One" if is_one else naccounts, "" if is_one else "s"))
|
||||||
|
|
||||||
|
|
||||||
class CmdOption(COMMAND_DEFAULT_CLASS):
|
class CmdOption(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -470,7 +470,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
|
||||||
locks = "cmd:all()"
|
locks = "cmd:all()"
|
||||||
|
|
||||||
# this is used by the parent
|
# this is used by the parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -508,7 +508,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
|
||||||
options["SCREENHEIGHT"] = options["SCREENHEIGHT"][0]
|
options["SCREENHEIGHT"] = options["SCREENHEIGHT"][0]
|
||||||
else:
|
else:
|
||||||
options["SCREENHEIGHT"] = " \n".join("%s : %s" % (screenid, size)
|
options["SCREENHEIGHT"] = " \n".join("%s : %s" % (screenid, size)
|
||||||
for screenid, size in options["SCREENHEIGHT"].iteritems())
|
for screenid, size in options["SCREENHEIGHT"].iteritems())
|
||||||
options.pop("TTYPE", None)
|
options.pop("TTYPE", None)
|
||||||
|
|
||||||
header = ("Name", "Value", "Saved") if saved_options else ("Name", "Value")
|
header = ("Name", "Value", "Saved") if saved_options else ("Name", "Value")
|
||||||
|
|
@ -549,10 +549,13 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
|
||||||
try:
|
try:
|
||||||
old_val = flags.get(new_name, False)
|
old_val = flags.get(new_name, False)
|
||||||
new_val = validator(new_val)
|
new_val = validator(new_val)
|
||||||
flags[new_name] = new_val
|
if old_val == new_val:
|
||||||
self.msg("Option |w%s|n was changed from '|w%s|n' to '|w%s|n'." % (new_name, old_val, new_val))
|
self.msg("Option |w%s|n was kept as '|w%s|n'." % (new_name, old_val))
|
||||||
|
else:
|
||||||
|
flags[new_name] = new_val
|
||||||
|
self.msg("Option |w%s|n was changed from '|w%s|n' to '|w%s|n'." % (new_name, old_val, new_val))
|
||||||
return {new_name: new_val}
|
return {new_name: new_val}
|
||||||
except Exception, err:
|
except Exception as err:
|
||||||
self.msg("|rCould not set option |w%s|r:|n %s" % (new_name, err))
|
self.msg("|rCould not set option |w%s|r:|n %s" % (new_name, err))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -572,28 +575,29 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
|
||||||
"TERM": utils.to_str,
|
"TERM": utils.to_str,
|
||||||
"UTF-8": validate_bool,
|
"UTF-8": validate_bool,
|
||||||
"XTERM256": validate_bool,
|
"XTERM256": validate_bool,
|
||||||
"INPUTDEBUG": validate_bool}
|
"INPUTDEBUG": validate_bool,
|
||||||
|
"FORCEDENDLINE": validate_bool}
|
||||||
|
|
||||||
name = self.lhs.upper()
|
name = self.lhs.upper()
|
||||||
val = self.rhs.strip()
|
val = self.rhs.strip()
|
||||||
optiondict = False
|
optiondict = False
|
||||||
if val and name in validators:
|
if val and name in validators:
|
||||||
optiondict = update(name, val, validators[name])
|
optiondict = update(name, val, validators[name])
|
||||||
else:
|
else:
|
||||||
self.msg("|rNo option named '|w%s|r'." % name)
|
self.msg("|rNo option named '|w%s|r'." % name)
|
||||||
if optiondict:
|
if optiondict:
|
||||||
# a valid setting
|
# a valid setting
|
||||||
if "save" in self.switches:
|
if "save" in self.switches:
|
||||||
# save this option only
|
# save this option only
|
||||||
saved_options = self.player.attributes.get("_saved_protocol_flags", default={})
|
saved_options = self.account.attributes.get("_saved_protocol_flags", default={})
|
||||||
saved_options.update(optiondict)
|
saved_options.update(optiondict)
|
||||||
self.player.attributes.add("_saved_protocol_flags", saved_options)
|
self.account.attributes.add("_saved_protocol_flags", saved_options)
|
||||||
for key in optiondict:
|
for key in optiondict:
|
||||||
self.msg("|gSaved option %s.|n" % key)
|
self.msg("|gSaved option %s.|n" % key)
|
||||||
if "clear" in self.switches:
|
if "clear" in self.switches:
|
||||||
# clear this save
|
# clear this save
|
||||||
for key in optiondict:
|
for key in optiondict:
|
||||||
self.player.attributes.get("_saved_protocol_flags", {}).pop(key, None)
|
self.account.attributes.get("_saved_protocol_flags", {}).pop(key, None)
|
||||||
self.msg("|gCleared saved %s." % key)
|
self.msg("|gCleared saved %s." % key)
|
||||||
self.session.update_flags(**optiondict)
|
self.session.update_flags(**optiondict)
|
||||||
|
|
||||||
|
|
@ -608,27 +612,27 @@ class CmdPassword(COMMAND_DEFAULT_CLASS):
|
||||||
Changes your password. Make sure to pick a safe one.
|
Changes your password. Make sure to pick a safe one.
|
||||||
"""
|
"""
|
||||||
key = "@password"
|
key = "@password"
|
||||||
locks = "cmd:pperm(Players)"
|
locks = "cmd:pperm(Player)"
|
||||||
|
|
||||||
# this is used by the parent
|
# this is used by the parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""hook function."""
|
"""hook function."""
|
||||||
|
|
||||||
player = self.player
|
account = self.account
|
||||||
if not self.rhs:
|
if not self.rhs:
|
||||||
self.msg("Usage: @password <oldpass> = <newpass>")
|
self.msg("Usage: @password <oldpass> = <newpass>")
|
||||||
return
|
return
|
||||||
oldpass = self.lhslist[0] # Both of these are
|
oldpass = self.lhslist[0] # Both of these are
|
||||||
newpass = self.rhslist[0] # already stripped by parse()
|
newpass = self.rhslist[0] # already stripped by parse()
|
||||||
if not player.check_password(oldpass):
|
if not account.check_password(oldpass):
|
||||||
self.msg("The specified old password isn't correct.")
|
self.msg("The specified old password isn't correct.")
|
||||||
elif len(newpass) < 3:
|
elif len(newpass) < 3:
|
||||||
self.msg("Passwords must be at least three characters long.")
|
self.msg("Passwords must be at least three characters long.")
|
||||||
else:
|
else:
|
||||||
player.set_password(newpass)
|
account.set_password(newpass)
|
||||||
player.save()
|
account.save()
|
||||||
self.msg("Password changed.")
|
self.msg("Password changed.")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -646,30 +650,31 @@ class CmdQuit(COMMAND_DEFAULT_CLASS):
|
||||||
game. Use the /all switch to disconnect from all sessions.
|
game. Use the /all switch to disconnect from all sessions.
|
||||||
"""
|
"""
|
||||||
key = "@quit"
|
key = "@quit"
|
||||||
aliases = "quit"
|
|
||||||
locks = "cmd:all()"
|
locks = "cmd:all()"
|
||||||
|
|
||||||
# this is used by the parent
|
# this is used by the parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""hook function"""
|
"""hook function"""
|
||||||
player = self.player
|
account = self.account
|
||||||
|
|
||||||
if 'all' in self.switches:
|
if 'all' in self.switches:
|
||||||
player.msg("|RQuitting|n all sessions. Hope to see you soon again.", session=self.session)
|
account.msg("|RQuitting|n all sessions. Hope to see you soon again.", session=self.session)
|
||||||
for session in player.sessions.all():
|
reason = "quit/all"
|
||||||
player.disconnect_session_from_player(session)
|
for session in account.sessions.all():
|
||||||
|
account.disconnect_session_from_account(session, reason)
|
||||||
else:
|
else:
|
||||||
nsess = len(player.sessions.all())
|
nsess = len(account.sessions.all())
|
||||||
|
reason = "quit"
|
||||||
if nsess == 2:
|
if nsess == 2:
|
||||||
player.msg("|RQuitting|n. One session is still connected.", session=self.session)
|
account.msg("|RQuitting|n. One session is still connected.", session=self.session)
|
||||||
elif nsess > 2:
|
elif nsess > 2:
|
||||||
player.msg("|RQuitting|n. %i sessions are still connected." % (nsess-1), session=self.session)
|
account.msg("|RQuitting|n. %i sessions are still connected." % (nsess - 1), session=self.session)
|
||||||
else:
|
else:
|
||||||
# we are quitting the last available session
|
# we are quitting the last available session
|
||||||
player.msg("|RQuitting|n. Hope to see you again, soon.", session=self.session)
|
account.msg("|RQuitting|n. Hope to see you again, soon.", session=self.session)
|
||||||
player.disconnect_session_from_player(self.session)
|
account.disconnect_session_from_account(self.session, reason)
|
||||||
|
|
||||||
|
|
||||||
class CmdColorTest(COMMAND_DEFAULT_CLASS):
|
class CmdColorTest(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -686,12 +691,19 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
|
||||||
color - if not you will see rubbish appear.
|
color - if not you will see rubbish appear.
|
||||||
"""
|
"""
|
||||||
key = "@color"
|
key = "@color"
|
||||||
aliases = "color"
|
|
||||||
locks = "cmd:all()"
|
locks = "cmd:all()"
|
||||||
help_category = "General"
|
help_category = "General"
|
||||||
|
|
||||||
# this is used by the parent
|
# this is used by the parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
|
# the slices of the ANSI_PARSER lists to use for retrieving the
|
||||||
|
# relevant color tags to display. Replace if using another schema.
|
||||||
|
# This command can only show one set of markup.
|
||||||
|
slice_bright_fg = slice(7, 15) # from ANSI_PARSER.ansi_map
|
||||||
|
slice_dark_fg = slice(15, 23) # from ANSI_PARSER.ansi_map
|
||||||
|
slice_dark_bg = slice(-8, None) # from ANSI_PARSER.ansi_map
|
||||||
|
slice_bright_bg = slice(None, None) # from ANSI_PARSER.ansi_xterm256_bright_bg_map
|
||||||
|
|
||||||
def table_format(self, table):
|
def table_format(self, table):
|
||||||
"""
|
"""
|
||||||
|
|
@ -718,14 +730,16 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
|
||||||
ap = ansi.ANSI_PARSER
|
ap = ansi.ANSI_PARSER
|
||||||
# ansi colors
|
# ansi colors
|
||||||
# show all ansi color-related codes
|
# show all ansi color-related codes
|
||||||
col1 = ["%s%s|n" % (code, code.replace("|", "||")) for code, _ in ap.ext_ansi_map[48:56]]
|
bright_fg = ["%s%s|n" % (code, code.replace("|", "||"))
|
||||||
col2 = ["%s%s|n" % (code, code.replace("|", "||")) for code, _ in ap.ext_ansi_map[56:64]]
|
for code, _ in ap.ansi_map[self.slice_bright_fg]]
|
||||||
col3 = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", ""))
|
dark_fg = ["%s%s|n" % (code, code.replace("|", "||"))
|
||||||
for code, _ in ap.ext_ansi_map[-8:]]
|
for code, _ in ap.ansi_map[self.slice_dark_fg]]
|
||||||
col4 = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", ""))
|
dark_bg = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", ""))
|
||||||
for code, _ in ap.ansi_bright_bgs[-8:]]
|
for code, _ in ap.ansi_map[self.slice_dark_bg]]
|
||||||
col2.extend(["" for _ in range(len(col1)-len(col2))])
|
bright_bg = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", ""))
|
||||||
table = utils.format_table([col1, col2, col4, col3])
|
for code, _ in ap.ansi_xterm256_bright_bg_map[self.slice_bright_bg]]
|
||||||
|
dark_fg.extend(["" for _ in range(len(bright_fg) - len(dark_fg))])
|
||||||
|
table = utils.format_table([bright_fg, dark_fg, bright_bg, dark_bg])
|
||||||
string = "ANSI colors:"
|
string = "ANSI colors:"
|
||||||
for row in table:
|
for row in table:
|
||||||
string += "\n " + " ".join(row)
|
string += "\n " + " ".join(row)
|
||||||
|
|
@ -743,16 +757,16 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
|
||||||
# foreground table
|
# foreground table
|
||||||
table[ir].append("|%i%i%i%s|n" % (ir, ig, ib, "||%i%i%i" % (ir, ig, ib)))
|
table[ir].append("|%i%i%i%s|n" % (ir, ig, ib, "||%i%i%i" % (ir, ig, ib)))
|
||||||
# background table
|
# background table
|
||||||
table[6+ir].append("|%i%i%i|[%i%i%i%s|n"
|
table[6 + ir].append("|%i%i%i|[%i%i%i%s|n"
|
||||||
% (5 - ir, 5 - ig, 5 - ib, ir, ig, ib, "||[%i%i%i" % (ir, ig, ib)))
|
% (5 - ir, 5 - ig, 5 - ib, ir, ig, ib, "||[%i%i%i" % (ir, ig, ib)))
|
||||||
table = self.table_format(table)
|
table = self.table_format(table)
|
||||||
string = "Xterm256 colors (if not all hues show, your client might not report that it can handle xterm256):"
|
string = "Xterm256 colors (if not all hues show, your client might not report that it can handle xterm256):"
|
||||||
string += "\n" + "\n".join("".join(row) for row in table)
|
string += "\n" + "\n".join("".join(row) for row in table)
|
||||||
table = [[], [], [], [], [], [], [], [], [], [], [], []]
|
table = [[], [], [], [], [], [], [], [], [], [], [], []]
|
||||||
for ibatch in range(4):
|
for ibatch in range(4):
|
||||||
for igray in range(6):
|
for igray in range(6):
|
||||||
letter = chr(97 + (ibatch*6 + igray))
|
letter = chr(97 + (ibatch * 6 + igray))
|
||||||
inverse = chr(122 - (ibatch*6 + igray))
|
inverse = chr(122 - (ibatch * 6 + igray))
|
||||||
table[0 + igray].append("|=%s%s |n" % (letter, "||=%s" % letter))
|
table[0 + igray].append("|=%s%s |n" % (letter, "||=%s" % letter))
|
||||||
table[6 + igray].append("|=%s|[=%s%s |n" % (inverse, letter, "||[=%s" % letter))
|
table[6 + igray].append("|=%s|[=%s%s |n" % (inverse, letter, "||[=%s" % letter))
|
||||||
for igray in range(6):
|
for igray in range(6):
|
||||||
|
|
@ -776,30 +790,30 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
class CmdQuell(COMMAND_DEFAULT_CLASS):
|
class CmdQuell(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
use character's permissions instead of player's
|
use character's permissions instead of account's
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
quell
|
quell
|
||||||
unquell
|
unquell
|
||||||
|
|
||||||
Normally the permission level of the Player is used when puppeting a
|
Normally the permission level of the Account is used when puppeting a
|
||||||
Character/Object to determine access. This command will switch the lock
|
Character/Object to determine access. This command will switch the lock
|
||||||
system to make use of the puppeted Object's permissions instead. This is
|
system to make use of the puppeted Object's permissions instead. This is
|
||||||
useful mainly for testing.
|
useful mainly for testing.
|
||||||
Hierarchical permission quelling only work downwards, thus a Player cannot
|
Hierarchical permission quelling only work downwards, thus an Account cannot
|
||||||
use a higher-permission Character to escalate their permission level.
|
use a higher-permission Character to escalate their permission level.
|
||||||
Use the unquell command to revert back to normal operation.
|
Use the unquell command to revert back to normal operation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "@quell"
|
key = "@quell"
|
||||||
aliases = ["@unquell"]
|
aliases = ["@unquell"]
|
||||||
locks = "cmd:pperm(Players)"
|
locks = "cmd:pperm(Player)"
|
||||||
help_category = "General"
|
help_category = "General"
|
||||||
|
|
||||||
# this is used by the parent
|
# this is used by the parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def _recache_locks(self, player):
|
def _recache_locks(self, account):
|
||||||
"""Helper method to reset the lockhandler on an already puppeted object"""
|
"""Helper method to reset the lockhandler on an already puppeted object"""
|
||||||
if self.session:
|
if self.session:
|
||||||
char = self.session.puppet
|
char = self.session.puppet
|
||||||
|
|
@ -808,31 +822,31 @@ class CmdQuell(COMMAND_DEFAULT_CLASS):
|
||||||
# the lock caches (otherwise the superuser status change
|
# the lock caches (otherwise the superuser status change
|
||||||
# won't be visible until repuppet)
|
# won't be visible until repuppet)
|
||||||
char.locks.reset()
|
char.locks.reset()
|
||||||
player.locks.reset()
|
account.locks.reset()
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""Perform the command"""
|
"""Perform the command"""
|
||||||
player = self.player
|
account = self.account
|
||||||
permstr = player.is_superuser and " (superuser)" or "(%s)" % (", ".join(player.permissions.all()))
|
permstr = account.is_superuser and " (superuser)" or "(%s)" % (", ".join(account.permissions.all()))
|
||||||
if self.cmdstring == '@unquell':
|
if self.cmdstring in ('unquell', '@unquell'):
|
||||||
if not player.attributes.get('_quell'):
|
if not account.attributes.get('_quell'):
|
||||||
self.msg("Already using normal Player permissions %s." % permstr)
|
self.msg("Already using normal Account permissions %s." % permstr)
|
||||||
else:
|
else:
|
||||||
player.attributes.remove('_quell')
|
account.attributes.remove('_quell')
|
||||||
self.msg("Player permissions %s restored." % permstr)
|
self.msg("Account permissions %s restored." % permstr)
|
||||||
else:
|
else:
|
||||||
if player.attributes.get('_quell'):
|
if account.attributes.get('_quell'):
|
||||||
self.msg("Already quelling Player %s permissions." % permstr)
|
self.msg("Already quelling Account %s permissions." % permstr)
|
||||||
return
|
return
|
||||||
player.attributes.add('_quell', True)
|
account.attributes.add('_quell', True)
|
||||||
puppet = self.session.puppet
|
puppet = self.session.puppet
|
||||||
if puppet:
|
if puppet:
|
||||||
cpermstr = "(%s)" % ", ".join(puppet.permissions.all())
|
cpermstr = "(%s)" % ", ".join(puppet.permissions.all())
|
||||||
cpermstr = "Quelling to current puppet's permissions %s." % cpermstr
|
cpermstr = "Quelling to current puppet's permissions %s." % cpermstr
|
||||||
cpermstr += "\n(Note: If this is higher than Player permissions %s," \
|
cpermstr += "\n(Note: If this is higher than Account permissions %s," \
|
||||||
" the lowest of the two will be used.)" % permstr
|
" the lowest of the two will be used.)" % permstr
|
||||||
cpermstr += "\nUse @unquell to return to normal permission usage."
|
cpermstr += "\nUse @unquell to return to normal permission usage."
|
||||||
self.msg(cpermstr)
|
self.msg(cpermstr)
|
||||||
else:
|
else:
|
||||||
self.msg("Quelling Player permissions%s. Use @unquell to get them back." % permstr)
|
self.msg("Quelling Account permissions%s. Use @unquell to get them back." % permstr)
|
||||||
self._recache_locks(player)
|
self._recache_locks(account)
|
||||||
|
|
@ -16,27 +16,27 @@ COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||||
PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
|
PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
|
||||||
|
|
||||||
# limit members for API inclusion
|
# limit members for API inclusion
|
||||||
__all__ = ("CmdBoot", "CmdBan", "CmdUnban", "CmdDelPlayer",
|
__all__ = ("CmdBoot", "CmdBan", "CmdUnban", "CmdDelAccount",
|
||||||
"CmdEmit", "CmdNewPassword", "CmdPerm", "CmdWall")
|
"CmdEmit", "CmdNewPassword", "CmdPerm", "CmdWall")
|
||||||
|
|
||||||
|
|
||||||
class CmdBoot(COMMAND_DEFAULT_CLASS):
|
class CmdBoot(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
kick a player from the server.
|
kick an account from the server.
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
@boot[/switches] <player obj> [: reason]
|
@boot[/switches] <account obj> [: reason]
|
||||||
|
|
||||||
Switches:
|
Switches:
|
||||||
quiet - Silently boot without informing player
|
quiet - Silently boot without informing account
|
||||||
sid - boot by session id instead of name or dbref
|
sid - boot by session id instead of name or dbref
|
||||||
|
|
||||||
Boot a player object from the server. If a reason is
|
Boot an account object from the server. If a reason is
|
||||||
supplied it will be echoed to the user unless /quiet is set.
|
supplied it will be echoed to the user unless /quiet is set.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "@boot"
|
key = "@boot"
|
||||||
locks = "cmd:perm(boot) or perm(Wizards)"
|
locks = "cmd:perm(boot) or perm(Admin)"
|
||||||
help_category = "Admin"
|
help_category = "Admin"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -45,7 +45,7 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
|
||||||
args = self.args
|
args = self.args
|
||||||
|
|
||||||
if not args:
|
if not args:
|
||||||
caller.msg("Usage: @boot[/switches] <player> [:reason]")
|
caller.msg("Usage: @boot[/switches] <account> [:reason]")
|
||||||
return
|
return
|
||||||
|
|
||||||
if ':' in args:
|
if ':' in args:
|
||||||
|
|
@ -64,10 +64,10 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
|
||||||
boot_list.append(sess)
|
boot_list.append(sess)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
# Boot by player object
|
# Boot by account object
|
||||||
pobj = search.player_search(args)
|
pobj = search.account_search(args)
|
||||||
if not pobj:
|
if not pobj:
|
||||||
caller.msg("Player %s was not found." % args)
|
caller.msg("Account %s was not found." % args)
|
||||||
return
|
return
|
||||||
pobj = pobj[0]
|
pobj = pobj[0]
|
||||||
if not pobj.access(caller, 'boot'):
|
if not pobj.access(caller, 'boot'):
|
||||||
|
|
@ -75,12 +75,12 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
return
|
return
|
||||||
# we have a bootable object with a connected user
|
# we have a bootable object with a connected user
|
||||||
matches = SESSIONS.sessions_from_player(pobj)
|
matches = SESSIONS.sessions_from_account(pobj)
|
||||||
for match in matches:
|
for match in matches:
|
||||||
boot_list.append(match)
|
boot_list.append(match)
|
||||||
|
|
||||||
if not boot_list:
|
if not boot_list:
|
||||||
caller.msg("No matching sessions found. The Player does not seem to be online.")
|
caller.msg("No matching sessions found. The Account does not seem to be online.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Carry out the booting of the sessions in the boot list.
|
# Carry out the booting of the sessions in the boot list.
|
||||||
|
|
@ -93,7 +93,7 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
for session in boot_list:
|
for session in boot_list:
|
||||||
session.msg(feedback)
|
session.msg(feedback)
|
||||||
session.player.disconnect_session_from_player(session)
|
session.account.disconnect_session_from_account(session)
|
||||||
|
|
||||||
|
|
||||||
# regex matching IP addresses with wildcards, eg. 233.122.4.*
|
# regex matching IP addresses with wildcards, eg. 233.122.4.*
|
||||||
|
|
@ -118,7 +118,7 @@ def list_bans(banlist):
|
||||||
|
|
||||||
class CmdBan(COMMAND_DEFAULT_CLASS):
|
class CmdBan(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
ban a player from the server
|
ban an account from the server
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@ban [<name or ip> [: reason]]
|
@ban [<name or ip> [: reason]]
|
||||||
|
|
@ -128,8 +128,8 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
|
||||||
This command bans a user from accessing the game. Supply an optional
|
This command bans a user from accessing the game. Supply an optional
|
||||||
reason to be able to later remember why the ban was put in place.
|
reason to be able to later remember why the ban was put in place.
|
||||||
|
|
||||||
It is often preferable to ban a player from the server than to
|
It is often preferable to ban an account from the server than to
|
||||||
delete a player with @delplayer. If banned by name, that player
|
delete an account with @delaccount. If banned by name, that account
|
||||||
account can no longer be logged into.
|
account can no longer be logged into.
|
||||||
|
|
||||||
IP (Internet Protocol) address banning allows blocking all access
|
IP (Internet Protocol) address banning allows blocking all access
|
||||||
|
|
@ -151,7 +151,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
key = "@ban"
|
key = "@ban"
|
||||||
aliases = ["@bans"]
|
aliases = ["@bans"]
|
||||||
locks = "cmd:perm(ban) or perm(Immortals)"
|
locks = "cmd:perm(ban) or perm(Developer)"
|
||||||
help_category = "Admin"
|
help_category = "Admin"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -171,9 +171,9 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
|
||||||
if not banlist:
|
if not banlist:
|
||||||
banlist = []
|
banlist = []
|
||||||
|
|
||||||
if not self.args or (self.switches
|
if not self.args or (self.switches and
|
||||||
and not any(switch in ('ip', 'name')
|
not any(switch in ('ip', 'name')
|
||||||
for switch in self.switches)):
|
for switch in self.switches)):
|
||||||
self.caller.msg(list_bans(banlist))
|
self.caller.msg(list_bans(banlist))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -206,19 +206,19 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
class CmdUnban(COMMAND_DEFAULT_CLASS):
|
class CmdUnban(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
remove a ban from a player
|
remove a ban from an account
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@unban <banid>
|
@unban <banid>
|
||||||
|
|
||||||
This will clear a player name/ip ban previously set with the @ban
|
This will clear an account name/ip ban previously set with the @ban
|
||||||
command. Use this command without an argument to view a numbered
|
command. Use this command without an argument to view a numbered
|
||||||
list of bans. Use the numbers in this list to select which one to
|
list of bans. Use the numbers in this list to select which one to
|
||||||
unban.
|
unban.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
key = "@unban"
|
key = "@unban"
|
||||||
locks = "cmd:perm(unban) or perm(Immortals)"
|
locks = "cmd:perm(unban) or perm(Developer)"
|
||||||
help_category = "Admin"
|
help_category = "Admin"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -249,23 +249,23 @@ class CmdUnban(COMMAND_DEFAULT_CLASS):
|
||||||
(num, " ".join([s for s in ban[:2]])))
|
(num, " ".join([s for s in ban[:2]])))
|
||||||
|
|
||||||
|
|
||||||
class CmdDelPlayer(COMMAND_DEFAULT_CLASS):
|
class CmdDelAccount(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
delete a player from the server
|
delete an account from the server
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@delplayer[/switch] <name> [: reason]
|
@delaccount[/switch] <name> [: reason]
|
||||||
|
|
||||||
Switch:
|
Switch:
|
||||||
delobj - also delete the player's currently
|
delobj - also delete the account's currently
|
||||||
assigned in-game object.
|
assigned in-game object.
|
||||||
|
|
||||||
Completely deletes a user from the server database,
|
Completely deletes a user from the server database,
|
||||||
making their nick and e-mail again available.
|
making their nick and e-mail again available.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "@delplayer"
|
key = "@delaccount"
|
||||||
locks = "cmd:perm(delplayer) or perm(Immortals)"
|
locks = "cmd:perm(delaccount) or perm(Developer)"
|
||||||
help_category = "Admin"
|
help_category = "Admin"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -274,49 +274,49 @@ class CmdDelPlayer(COMMAND_DEFAULT_CLASS):
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
args = self.args
|
args = self.args
|
||||||
|
|
||||||
if hasattr(caller, 'player'):
|
if hasattr(caller, 'account'):
|
||||||
caller = caller.player
|
caller = caller.account
|
||||||
|
|
||||||
if not args:
|
if not args:
|
||||||
self.msg("Usage: @delplayer <player/user name or #id> [: reason]")
|
self.msg("Usage: @delaccount <account/user name or #id> [: reason]")
|
||||||
return
|
return
|
||||||
|
|
||||||
reason = ""
|
reason = ""
|
||||||
if ':' in args:
|
if ':' in args:
|
||||||
args, reason = [arg.strip() for arg in args.split(':', 1)]
|
args, reason = [arg.strip() for arg in args.split(':', 1)]
|
||||||
|
|
||||||
# We use player_search since we want to be sure to find also players
|
# We use account_search since we want to be sure to find also accounts
|
||||||
# that lack characters.
|
# that lack characters.
|
||||||
players = search.player_search(args)
|
accounts = search.account_search(args)
|
||||||
|
|
||||||
if not players:
|
if not accounts:
|
||||||
self.msg('Could not find a player by that name.')
|
self.msg('Could not find an account by that name.')
|
||||||
return
|
return
|
||||||
|
|
||||||
if len(players) > 1:
|
if len(accounts) > 1:
|
||||||
string = "There were multiple matches:\n"
|
string = "There were multiple matches:\n"
|
||||||
string += "\n".join(" %s %s" % (player.id, player.key) for player in players)
|
string += "\n".join(" %s %s" % (account.id, account.key) for account in accounts)
|
||||||
self.msg(string)
|
self.msg(string)
|
||||||
return
|
return
|
||||||
|
|
||||||
# one single match
|
# one single match
|
||||||
|
|
||||||
player = players.pop()
|
account = accounts.first()
|
||||||
|
|
||||||
if not player.access(caller, 'delete'):
|
if not account.access(caller, 'delete'):
|
||||||
string = "You don't have the permissions to delete that player."
|
string = "You don't have the permissions to delete that account."
|
||||||
self.msg(string)
|
self.msg(string)
|
||||||
return
|
return
|
||||||
|
|
||||||
uname = player.username
|
uname = account.username
|
||||||
# boot the player then delete
|
# boot the account then delete
|
||||||
self.msg("Informing and disconnecting player ...")
|
self.msg("Informing and disconnecting account ...")
|
||||||
string = "\nYour account '%s' is being *permanently* deleted.\n" % uname
|
string = "\nYour account '%s' is being *permanently* deleted.\n" % uname
|
||||||
if reason:
|
if reason:
|
||||||
string += " Reason given:\n '%s'" % reason
|
string += " Reason given:\n '%s'" % reason
|
||||||
player.msg(string)
|
account.msg(string)
|
||||||
player.delete()
|
account.delete()
|
||||||
self.msg("Player %s was successfully deleted." % uname)
|
self.msg("Account %s was successfully deleted." % uname)
|
||||||
|
|
||||||
|
|
||||||
class CmdEmit(COMMAND_DEFAULT_CLASS):
|
class CmdEmit(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -330,18 +330,18 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
Switches:
|
Switches:
|
||||||
room : limit emits to rooms only (default)
|
room : limit emits to rooms only (default)
|
||||||
players : limit emits to players only
|
accounts : limit emits to accounts only
|
||||||
contents : send to the contents of matched objects too
|
contents : send to the contents of matched objects too
|
||||||
|
|
||||||
Emits a message to the selected objects or to
|
Emits a message to the selected objects or to
|
||||||
your immediate surroundings. If the object is a room,
|
your immediate surroundings. If the object is a room,
|
||||||
send to its contents. @remit and @pemit are just
|
send to its contents. @remit and @pemit are just
|
||||||
limited forms of @emit, for sending to rooms and
|
limited forms of @emit, for sending to rooms and
|
||||||
to players respectively.
|
to accounts respectively.
|
||||||
"""
|
"""
|
||||||
key = "@emit"
|
key = "@emit"
|
||||||
aliases = ["@pemit", "@remit"]
|
aliases = ["@pemit", "@remit"]
|
||||||
locks = "cmd:perm(emit) or perm(Builders)"
|
locks = "cmd:perm(emit) or perm(Builder)"
|
||||||
help_category = "Admin"
|
help_category = "Admin"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -359,7 +359,7 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
|
||||||
return
|
return
|
||||||
|
|
||||||
rooms_only = 'rooms' in self.switches
|
rooms_only = 'rooms' in self.switches
|
||||||
players_only = 'players' in self.switches
|
accounts_only = 'accounts' in self.switches
|
||||||
send_to_contents = 'contents' in self.switches
|
send_to_contents = 'contents' in self.switches
|
||||||
|
|
||||||
# we check which command was used to force the switches
|
# we check which command was used to force the switches
|
||||||
|
|
@ -367,7 +367,7 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
|
||||||
rooms_only = True
|
rooms_only = True
|
||||||
send_to_contents = True
|
send_to_contents = True
|
||||||
elif self.cmdstring == '@pemit':
|
elif self.cmdstring == '@pemit':
|
||||||
players_only = True
|
accounts_only = True
|
||||||
|
|
||||||
if not self.rhs:
|
if not self.rhs:
|
||||||
message = self.args
|
message = self.args
|
||||||
|
|
@ -384,8 +384,8 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
|
||||||
if rooms_only and obj.location is not None:
|
if rooms_only and obj.location is not None:
|
||||||
caller.msg("%s is not a room. Ignored." % objname)
|
caller.msg("%s is not a room. Ignored." % objname)
|
||||||
continue
|
continue
|
||||||
if players_only and not obj.has_player:
|
if accounts_only and not obj.has_account:
|
||||||
caller.msg("%s has no active player. Ignored." % objname)
|
caller.msg("%s has no active account. Ignored." % objname)
|
||||||
continue
|
continue
|
||||||
if obj.access(caller, 'tell'):
|
if obj.access(caller, 'tell'):
|
||||||
obj.msg(message)
|
obj.msg(message)
|
||||||
|
|
@ -400,16 +400,16 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
class CmdNewPassword(COMMAND_DEFAULT_CLASS):
|
class CmdNewPassword(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
change the password of a player
|
change the password of an account
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@userpassword <user obj> = <new password>
|
@userpassword <user obj> = <new password>
|
||||||
|
|
||||||
Set a player's password.
|
Set an account's password.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "@userpassword"
|
key = "@userpassword"
|
||||||
locks = "cmd:perm(newpassword) or perm(Wizards)"
|
locks = "cmd:perm(newpassword) or perm(Admin)"
|
||||||
help_category = "Admin"
|
help_category = "Admin"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -421,36 +421,36 @@ class CmdNewPassword(COMMAND_DEFAULT_CLASS):
|
||||||
self.msg("Usage: @userpassword <user obj> = <new password>")
|
self.msg("Usage: @userpassword <user obj> = <new password>")
|
||||||
return
|
return
|
||||||
|
|
||||||
# the player search also matches 'me' etc.
|
# the account search also matches 'me' etc.
|
||||||
player = caller.search_player(self.lhs)
|
account = caller.search_account(self.lhs)
|
||||||
if not player:
|
if not account:
|
||||||
return
|
return
|
||||||
player.set_password(self.rhs)
|
account.set_password(self.rhs)
|
||||||
player.save()
|
account.save()
|
||||||
self.msg("%s - new password set to '%s'." % (player.name, self.rhs))
|
self.msg("%s - new password set to '%s'." % (account.name, self.rhs))
|
||||||
if player.character != caller:
|
if account.character != caller:
|
||||||
player.msg("%s has changed your password to '%s'." % (caller.name,
|
account.msg("%s has changed your password to '%s'." % (caller.name,
|
||||||
self.rhs))
|
self.rhs))
|
||||||
|
|
||||||
|
|
||||||
class CmdPerm(COMMAND_DEFAULT_CLASS):
|
class CmdPerm(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
set the permissions of a player/object
|
set the permissions of an account/object
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@perm[/switch] <object> [= <permission>[,<permission>,...]]
|
@perm[/switch] <object> [= <permission>[,<permission>,...]]
|
||||||
@perm[/switch] *<player> [= <permission>[,<permission>,...]]
|
@perm[/switch] *<account> [= <permission>[,<permission>,...]]
|
||||||
|
|
||||||
Switches:
|
Switches:
|
||||||
del : delete the given permission from <object> or <player>.
|
del : delete the given permission from <object> or <account>.
|
||||||
player : set permission on a player (same as adding * to name)
|
account : set permission on an account (same as adding * to name)
|
||||||
|
|
||||||
This command sets/clears individual permission strings on an object
|
This command sets/clears individual permission strings on an object
|
||||||
or player. If no permission is given, list all permissions on <object>.
|
or account. If no permission is given, list all permissions on <object>.
|
||||||
"""
|
"""
|
||||||
key = "@perm"
|
key = "@perm"
|
||||||
aliases = "@setperm"
|
aliases = "@setperm"
|
||||||
locks = "cmd:perm(perm) or perm(Immortals)"
|
locks = "cmd:perm(perm) or perm(Developer)"
|
||||||
help_category = "Admin"
|
help_category = "Admin"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -465,11 +465,11 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
return
|
return
|
||||||
|
|
||||||
playermode = 'player' in self.switches or lhs.startswith('*')
|
accountmode = 'account' in self.switches or lhs.startswith('*')
|
||||||
lhs = lhs.lstrip("*")
|
lhs = lhs.lstrip("*")
|
||||||
|
|
||||||
if playermode:
|
if accountmode:
|
||||||
obj = caller.search_player(lhs)
|
obj = caller.search_account(lhs)
|
||||||
else:
|
else:
|
||||||
obj = caller.search(lhs, global_search=True)
|
obj = caller.search(lhs, global_search=True)
|
||||||
if not obj:
|
if not obj:
|
||||||
|
|
@ -485,19 +485,19 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
|
||||||
string += "<None>"
|
string += "<None>"
|
||||||
else:
|
else:
|
||||||
string += ", ".join(obj.permissions.all())
|
string += ", ".join(obj.permissions.all())
|
||||||
if (hasattr(obj, 'player') and
|
if (hasattr(obj, 'account') and
|
||||||
hasattr(obj.player, 'is_superuser') and
|
hasattr(obj.account, 'is_superuser') and
|
||||||
obj.player.is_superuser):
|
obj.account.is_superuser):
|
||||||
string += "\n(... but this object is currently controlled by a SUPERUSER! "
|
string += "\n(... but this object is currently controlled by a SUPERUSER! "
|
||||||
string += "All access checks are passed automatically.)"
|
string += "All access checks are passed automatically.)"
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
return
|
return
|
||||||
|
|
||||||
# we supplied an argument on the form obj = perm
|
# we supplied an argument on the form obj = perm
|
||||||
locktype = "edit" if playermode else "control"
|
locktype = "edit" if accountmode else "control"
|
||||||
if not obj.access(caller, locktype):
|
if not obj.access(caller, locktype):
|
||||||
caller.msg("You are not allowed to edit this %s's permissions."
|
caller.msg("You are not allowed to edit this %s's permissions."
|
||||||
% ("player" if playermode else "object"))
|
% ("account" if accountmode else "object"))
|
||||||
return
|
return
|
||||||
|
|
||||||
caller_result = []
|
caller_result = []
|
||||||
|
|
@ -525,13 +525,13 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
|
||||||
return
|
return
|
||||||
|
|
||||||
if perm in permissions:
|
if perm in permissions:
|
||||||
caller_result.append("\nPermission '%s' is already defined on %s." % (rhs, obj.name))
|
caller_result.append("\nPermission '%s' is already defined on %s." % (perm, obj.name))
|
||||||
else:
|
else:
|
||||||
obj.permissions.add(perm)
|
obj.permissions.add(perm)
|
||||||
plystring = "the Player" if playermode else "the Object/Character"
|
plystring = "the Account" if accountmode else "the Object/Character"
|
||||||
caller_result.append("\nPermission '%s' given to %s (%s)." % (rhs, obj.name, plystring))
|
caller_result.append("\nPermission '%s' given to %s (%s)." % (perm, obj.name, plystring))
|
||||||
target_result.append("\n%s gives you (%s, %s) the permission '%s'."
|
target_result.append("\n%s gives you (%s, %s) the permission '%s'."
|
||||||
% (caller.name, obj.name, plystring, rhs))
|
% (caller.name, obj.name, plystring, perm))
|
||||||
caller.msg("".join(caller_result).strip())
|
caller.msg("".join(caller_result).strip())
|
||||||
if target_result:
|
if target_result:
|
||||||
obj.msg("".join(target_result).strip())
|
obj.msg("".join(target_result).strip())
|
||||||
|
|
@ -544,10 +544,11 @@ class CmdWall(COMMAND_DEFAULT_CLASS):
|
||||||
Usage:
|
Usage:
|
||||||
@wall <message>
|
@wall <message>
|
||||||
|
|
||||||
Announces a message to all connected players.
|
Announces a message to all connected sessions
|
||||||
|
including all currently unlogged in.
|
||||||
"""
|
"""
|
||||||
key = "@wall"
|
key = "@wall"
|
||||||
locks = "cmd:perm(wall) or perm(Wizards)"
|
locks = "cmd:perm(wall) or perm(Admin)"
|
||||||
help_category = "Admin"
|
help_category = "Admin"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -556,5 +557,5 @@ class CmdWall(COMMAND_DEFAULT_CLASS):
|
||||||
self.caller.msg("Usage: @wall <message>")
|
self.caller.msg("Usage: @wall <message>")
|
||||||
return
|
return
|
||||||
message = "%s shouts \"%s\"" % (self.caller.name, self.args)
|
message = "%s shouts \"%s\"" % (self.caller.name, self.args)
|
||||||
self.msg("Announcing to all connected players ...")
|
self.msg("Announcing to all connected sessions ...")
|
||||||
SESSIONS.announce_all(message)
|
SESSIONS.announce_all(message)
|
||||||
|
|
|
||||||
|
|
@ -237,7 +237,7 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
key = "@batchcommands"
|
key = "@batchcommands"
|
||||||
aliases = ["@batchcommand", "@batchcmd"]
|
aliases = ["@batchcommand", "@batchcmd"]
|
||||||
locks = "cmd:perm(batchcommands) or superuser()"
|
locks = "cmd:perm(batchcommands) or perm(Developer)"
|
||||||
help_category = "Building"
|
help_category = "Building"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,8 +1,8 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
This is the cmdset for Player (OOC) commands. These are
|
This is the cmdset for Account (OOC) commands. These are
|
||||||
stored on the Player object and should thus be able to handle getting
|
stored on the Account object and should thus be able to handle getting
|
||||||
a Player object as caller rather than a Character.
|
an Account object as caller rather than a Character.
|
||||||
|
|
||||||
Note - in order for session-rerouting (in MULTISESSION_MODE=2) to
|
Note - in order for session-rerouting (in MULTISESSION_MODE=2) to
|
||||||
function, all commands in this cmdset should use the self.msg()
|
function, all commands in this cmdset should use the self.msg()
|
||||||
|
|
@ -11,33 +11,33 @@ command method rather than caller.msg().
|
||||||
|
|
||||||
from evennia.commands.cmdset import CmdSet
|
from evennia.commands.cmdset import CmdSet
|
||||||
from evennia.commands.default import help, comms, admin, system
|
from evennia.commands.default import help, comms, admin, system
|
||||||
from evennia.commands.default import building, player
|
from evennia.commands.default import building, account
|
||||||
|
|
||||||
|
|
||||||
class PlayerCmdSet(CmdSet):
|
class AccountCmdSet(CmdSet):
|
||||||
"""
|
"""
|
||||||
Implements the player command set.
|
Implements the account command set.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "DefaultPlayer"
|
key = "DefaultAccount"
|
||||||
priority = -10
|
priority = -10
|
||||||
|
|
||||||
def at_cmdset_creation(self):
|
def at_cmdset_creation(self):
|
||||||
"Populates the cmdset"
|
"Populates the cmdset"
|
||||||
|
|
||||||
# Player-specific commands
|
# Account-specific commands
|
||||||
self.add(player.CmdOOCLook())
|
self.add(account.CmdOOCLook())
|
||||||
self.add(player.CmdIC())
|
self.add(account.CmdIC())
|
||||||
self.add(player.CmdOOC())
|
self.add(account.CmdOOC())
|
||||||
self.add(player.CmdCharCreate())
|
self.add(account.CmdCharCreate())
|
||||||
self.add(player.CmdCharDelete())
|
self.add(account.CmdCharDelete())
|
||||||
#self.add(player.CmdSessions())
|
# self.add(account.CmdSessions())
|
||||||
self.add(player.CmdWho())
|
self.add(account.CmdWho())
|
||||||
self.add(player.CmdOption())
|
self.add(account.CmdOption())
|
||||||
self.add(player.CmdQuit())
|
self.add(account.CmdQuit())
|
||||||
self.add(player.CmdPassword())
|
self.add(account.CmdPassword())
|
||||||
self.add(player.CmdColorTest())
|
self.add(account.CmdColorTest())
|
||||||
self.add(player.CmdQuell())
|
self.add(account.CmdQuell())
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
self.add(building.CmdExamine())
|
self.add(building.CmdExamine())
|
||||||
|
|
@ -52,7 +52,7 @@ class PlayerCmdSet(CmdSet):
|
||||||
self.add(system.CmdPy())
|
self.add(system.CmdPy())
|
||||||
|
|
||||||
# Admin commands
|
# Admin commands
|
||||||
self.add(admin.CmdDelPlayer())
|
self.add(admin.CmdDelAccount())
|
||||||
self.add(admin.CmdNewPassword())
|
self.add(admin.CmdNewPassword())
|
||||||
|
|
||||||
# Comm commands
|
# Comm commands
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
"""
|
"""
|
||||||
This module ties together all the commands default Character objects have
|
This module ties together all the commands default Character objects have
|
||||||
available (i.e. IC commands). Note that some commands, such as
|
available (i.e. IC commands). Note that some commands, such as
|
||||||
communication-commands are instead put on the player level, in the
|
communication-commands are instead put on the account level, in the
|
||||||
Player cmdset. Player commands remain available also to Characters.
|
Account cmdset. Account commands remain available also to Characters.
|
||||||
"""
|
"""
|
||||||
from evennia.commands.cmdset import CmdSet
|
from evennia.commands.cmdset import CmdSet
|
||||||
from evennia.commands.default import general, help, admin, system
|
from evennia.commands.default import general, help, admin, system
|
||||||
from evennia.commands.default import building
|
from evennia.commands.default import building
|
||||||
from evennia.commands.default import batchprocess
|
from evennia.commands.default import batchprocess
|
||||||
|
|
||||||
|
|
||||||
class CharacterCmdSet(CmdSet):
|
class CharacterCmdSet(CmdSet):
|
||||||
"""
|
"""
|
||||||
Implements the default command set.
|
Implements the default command set.
|
||||||
|
|
@ -25,7 +26,7 @@ class CharacterCmdSet(CmdSet):
|
||||||
self.add(general.CmdInventory())
|
self.add(general.CmdInventory())
|
||||||
self.add(general.CmdPose())
|
self.add(general.CmdPose())
|
||||||
self.add(general.CmdNick())
|
self.add(general.CmdNick())
|
||||||
self.add(general.CmdDesc())
|
self.add(general.CmdSetDesc())
|
||||||
self.add(general.CmdGet())
|
self.add(general.CmdGet())
|
||||||
self.add(general.CmdDrop())
|
self.add(general.CmdDrop())
|
||||||
self.add(general.CmdGive())
|
self.add(general.CmdGive())
|
||||||
|
|
@ -41,12 +42,12 @@ class CharacterCmdSet(CmdSet):
|
||||||
self.add(system.CmdPy())
|
self.add(system.CmdPy())
|
||||||
self.add(system.CmdScripts())
|
self.add(system.CmdScripts())
|
||||||
self.add(system.CmdObjects())
|
self.add(system.CmdObjects())
|
||||||
self.add(system.CmdPlayers())
|
self.add(system.CmdAccounts())
|
||||||
self.add(system.CmdService())
|
self.add(system.CmdService())
|
||||||
self.add(system.CmdAbout())
|
self.add(system.CmdAbout())
|
||||||
self.add(system.CmdTime())
|
self.add(system.CmdTime())
|
||||||
self.add(system.CmdServerLoad())
|
self.add(system.CmdServerLoad())
|
||||||
#self.add(system.CmdPs())
|
# self.add(system.CmdPs())
|
||||||
self.add(system.CmdTickers())
|
self.add(system.CmdTickers())
|
||||||
|
|
||||||
# Admin commands
|
# Admin commands
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
This module stores session-level commands.
|
This module stores session-level commands.
|
||||||
"""
|
"""
|
||||||
from evennia.commands.cmdset import CmdSet
|
from evennia.commands.cmdset import CmdSet
|
||||||
from evennia.commands.default import player
|
from evennia.commands.default import account
|
||||||
|
|
||||||
|
|
||||||
class SessionCmdSet(CmdSet):
|
class SessionCmdSet(CmdSet):
|
||||||
"""
|
"""
|
||||||
|
|
@ -13,4 +14,4 @@ class SessionCmdSet(CmdSet):
|
||||||
|
|
||||||
def at_cmdset_creation(self):
|
def at_cmdset_creation(self):
|
||||||
"Populate the cmdset"
|
"Populate the cmdset"
|
||||||
self.add(player.CmdSessions())
|
self.add(account.CmdSessions())
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,16 @@
|
||||||
Comsystem command module.
|
Comsystem command module.
|
||||||
|
|
||||||
Comm commands are OOC commands and intended to be made available to
|
Comm commands are OOC commands and intended to be made available to
|
||||||
the Player at all times (they go into the PlayerCmdSet). So we
|
the Account at all times (they go into the AccountCmdSet). So we
|
||||||
make sure to homogenize self.caller to always be the player object
|
make sure to homogenize self.caller to always be the account object
|
||||||
for easy handling.
|
for easy handling.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from past.builtins import cmp
|
from past.builtins import cmp
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia.comms.models import ChannelDB, Msg
|
from evennia.comms.models import ChannelDB, Msg
|
||||||
from evennia.players.models import PlayerDB
|
from evennia.accounts.models import AccountDB
|
||||||
from evennia.players import bots
|
from evennia.accounts import bots
|
||||||
from evennia.comms.channelhandler import CHANNELHANDLER
|
from evennia.comms.channelhandler import CHANNELHANDLER
|
||||||
from evennia.locks.lockhandler import LockException
|
from evennia.locks.lockhandler import LockException
|
||||||
from evennia.utils import create, utils, evtable
|
from evennia.utils import create, utils, evtable
|
||||||
|
|
@ -69,14 +69,14 @@ class CmdAddCom(COMMAND_DEFAULT_CLASS):
|
||||||
locks = "cmd:not pperm(channel_banned)"
|
locks = "cmd:not pperm(channel_banned)"
|
||||||
|
|
||||||
# this is used by the COMMAND_DEFAULT_CLASS parent
|
# this is used by the COMMAND_DEFAULT_CLASS parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""Implement the command"""
|
"""Implement the command"""
|
||||||
|
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
args = self.args
|
args = self.args
|
||||||
player = caller
|
account = caller
|
||||||
|
|
||||||
if not args:
|
if not args:
|
||||||
self.msg("Usage: addcom [alias =] channelname.")
|
self.msg("Usage: addcom [alias =] channelname.")
|
||||||
|
|
@ -96,21 +96,21 @@ class CmdAddCom(COMMAND_DEFAULT_CLASS):
|
||||||
return
|
return
|
||||||
|
|
||||||
# check permissions
|
# check permissions
|
||||||
if not channel.access(player, 'listen'):
|
if not channel.access(account, 'listen'):
|
||||||
self.msg("%s: You are not allowed to listen to this channel." % channel.key)
|
self.msg("%s: You are not allowed to listen to this channel." % channel.key)
|
||||||
return
|
return
|
||||||
|
|
||||||
string = ""
|
string = ""
|
||||||
if not channel.has_connection(player):
|
if not channel.has_connection(account):
|
||||||
# we want to connect as well.
|
# we want to connect as well.
|
||||||
if not channel.connect(player):
|
if not channel.connect(account):
|
||||||
# if this would have returned True, the player is connected
|
# if this would have returned True, the account is connected
|
||||||
self.msg("%s: You are not allowed to join this channel." % channel.key)
|
self.msg("%s: You are not allowed to join this channel." % channel.key)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
string += "You now listen to the channel %s. " % channel.key
|
string += "You now listen to the channel %s. " % channel.key
|
||||||
else:
|
else:
|
||||||
if channel.unmute(player):
|
if channel.unmute(account):
|
||||||
string += "You unmute channel %s." % channel.key
|
string += "You unmute channel %s." % channel.key
|
||||||
else:
|
else:
|
||||||
string += "You are already connected to channel %s." % channel.key
|
string += "You are already connected to channel %s." % channel.key
|
||||||
|
|
@ -145,13 +145,13 @@ class CmdDelCom(COMMAND_DEFAULT_CLASS):
|
||||||
locks = "cmd:not perm(channel_banned)"
|
locks = "cmd:not perm(channel_banned)"
|
||||||
|
|
||||||
# this is used by the COMMAND_DEFAULT_CLASS parent
|
# this is used by the COMMAND_DEFAULT_CLASS parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""Implementing the command. """
|
"""Implementing the command. """
|
||||||
|
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
player = caller
|
account = caller
|
||||||
|
|
||||||
if not self.args:
|
if not self.args:
|
||||||
self.msg("Usage: delcom <alias or channel>")
|
self.msg("Usage: delcom <alias or channel>")
|
||||||
|
|
@ -161,7 +161,7 @@ class CmdDelCom(COMMAND_DEFAULT_CLASS):
|
||||||
channel = find_channel(caller, ostring, silent=True, noaliases=True)
|
channel = find_channel(caller, ostring, silent=True, noaliases=True)
|
||||||
if channel:
|
if channel:
|
||||||
# we have given a channel name - unsubscribe
|
# we have given a channel name - unsubscribe
|
||||||
if not channel.has_connection(player):
|
if not channel.has_connection(account):
|
||||||
self.msg("You are not listening to that channel.")
|
self.msg("You are not listening to that channel.")
|
||||||
return
|
return
|
||||||
chkey = channel.key.lower()
|
chkey = channel.key.lower()
|
||||||
|
|
@ -171,7 +171,7 @@ class CmdDelCom(COMMAND_DEFAULT_CLASS):
|
||||||
for nick in [nick for nick in make_iter(caller.nicks.get(category="channel", return_obj=True))
|
for nick in [nick for nick in make_iter(caller.nicks.get(category="channel", return_obj=True))
|
||||||
if nick and nick.pk and nick.value[3].lower() == chkey]:
|
if nick and nick.pk and nick.value[3].lower() == chkey]:
|
||||||
nick.delete()
|
nick.delete()
|
||||||
disconnect = channel.disconnect(player)
|
disconnect = channel.disconnect(account)
|
||||||
if disconnect:
|
if disconnect:
|
||||||
wipednicks = " Eventual aliases were removed." if delnicks else ""
|
wipednicks = " Eventual aliases were removed." if delnicks else ""
|
||||||
self.msg("You stop listening to channel '%s'.%s" % (channel.key, wipednicks))
|
self.msg("You stop listening to channel '%s'.%s" % (channel.key, wipednicks))
|
||||||
|
|
@ -209,7 +209,7 @@ class CmdAllCom(COMMAND_DEFAULT_CLASS):
|
||||||
help_category = "Comms"
|
help_category = "Comms"
|
||||||
|
|
||||||
# this is used by the COMMAND_DEFAULT_CLASS parent
|
# this is used by the COMMAND_DEFAULT_CLASS parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""Runs the function"""
|
"""Runs the function"""
|
||||||
|
|
@ -268,12 +268,12 @@ class CmdChannels(COMMAND_DEFAULT_CLASS):
|
||||||
Use addcom/delcom to join and leave channels
|
Use addcom/delcom to join and leave channels
|
||||||
"""
|
"""
|
||||||
key = "@channels"
|
key = "@channels"
|
||||||
aliases = ["@clist", "channels", "comlist", "chanlist", "channellist", "all channels"]
|
aliases = ["@clist", "comlist", "chanlist", "channellist", "all channels"]
|
||||||
help_category = "Comms"
|
help_category = "Comms"
|
||||||
locks = "cmd: not pperm(channel_banned)"
|
locks = "cmd: not pperm(channel_banned)"
|
||||||
|
|
||||||
# this is used by the COMMAND_DEFAULT_CLASS parent
|
# this is used by the COMMAND_DEFAULT_CLASS parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""Implement function"""
|
"""Implement function"""
|
||||||
|
|
@ -297,7 +297,7 @@ class CmdChannels(COMMAND_DEFAULT_CLASS):
|
||||||
clower = chan.key.lower()
|
clower = chan.key.lower()
|
||||||
nicks = caller.nicks.get(category="channel", return_obj=True)
|
nicks = caller.nicks.get(category="channel", return_obj=True)
|
||||||
comtable.add_row(*["%s%s" % (chan.key, chan.aliases.all() and
|
comtable.add_row(*["%s%s" % (chan.key, chan.aliases.all() and
|
||||||
"(%s)" % ",".join(chan.aliases.all()) or ""),
|
"(%s)" % ",".join(chan.aliases.all()) or ""),
|
||||||
"%s" % ",".join(nick.db_key for nick in make_iter(nicks)
|
"%s" % ",".join(nick.db_key for nick in make_iter(nicks)
|
||||||
if nick and nick.value[3].lower() == clower),
|
if nick and nick.value[3].lower() == clower),
|
||||||
chan.db.desc])
|
chan.db.desc])
|
||||||
|
|
@ -345,7 +345,7 @@ class CmdCdestroy(COMMAND_DEFAULT_CLASS):
|
||||||
locks = "cmd: not pperm(channel_banned)"
|
locks = "cmd: not pperm(channel_banned)"
|
||||||
|
|
||||||
# this is used by the COMMAND_DEFAULT_CLASS parent
|
# this is used by the COMMAND_DEFAULT_CLASS parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""Destroy objects cleanly."""
|
"""Destroy objects cleanly."""
|
||||||
|
|
@ -372,15 +372,15 @@ class CmdCdestroy(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
class CmdCBoot(COMMAND_DEFAULT_CLASS):
|
class CmdCBoot(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
kick a player from a channel you control
|
kick an account from a channel you control
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@cboot[/quiet] <channel> = <player> [:reason]
|
@cboot[/quiet] <channel> = <account> [:reason]
|
||||||
|
|
||||||
Switches:
|
Switches:
|
||||||
quiet - don't notify the channel
|
quiet - don't notify the channel
|
||||||
|
|
||||||
Kicks a player or object from a channel you control.
|
Kicks an account or object from a channel you control.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -389,13 +389,13 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS):
|
||||||
help_category = "Comms"
|
help_category = "Comms"
|
||||||
|
|
||||||
# this is used by the COMMAND_DEFAULT_CLASS parent
|
# this is used by the COMMAND_DEFAULT_CLASS parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""implement the function"""
|
"""implement the function"""
|
||||||
|
|
||||||
if not self.args or not self.rhs:
|
if not self.args or not self.rhs:
|
||||||
string = "Usage: @cboot[/quiet] <channel> = <player> [:reason]"
|
string = "Usage: @cboot[/quiet] <channel> = <account> [:reason]"
|
||||||
self.msg(string)
|
self.msg(string)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -404,12 +404,12 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS):
|
||||||
return
|
return
|
||||||
reason = ""
|
reason = ""
|
||||||
if ":" in self.rhs:
|
if ":" in self.rhs:
|
||||||
playername, reason = self.rhs.rsplit(":", 1)
|
accountname, reason = self.rhs.rsplit(":", 1)
|
||||||
searchstring = playername.lstrip('*')
|
searchstring = accountname.lstrip('*')
|
||||||
else:
|
else:
|
||||||
searchstring = self.rhs.lstrip('*')
|
searchstring = self.rhs.lstrip('*')
|
||||||
player = self.caller.search(searchstring, player=True)
|
account = self.caller.search(searchstring, account=True)
|
||||||
if not player:
|
if not account:
|
||||||
return
|
return
|
||||||
if reason:
|
if reason:
|
||||||
reason = " (reason: %s)" % reason
|
reason = " (reason: %s)" % reason
|
||||||
|
|
@ -417,20 +417,20 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS):
|
||||||
string = "You don't control this channel."
|
string = "You don't control this channel."
|
||||||
self.msg(string)
|
self.msg(string)
|
||||||
return
|
return
|
||||||
if player not in channel.db_subscriptions.all():
|
if not channel.subscriptions.has(account):
|
||||||
string = "Player %s is not connected to channel %s." % (player.key, channel.key)
|
string = "Account %s is not connected to channel %s." % (account.key, channel.key)
|
||||||
self.msg(string)
|
self.msg(string)
|
||||||
return
|
return
|
||||||
if "quiet" not in self.switches:
|
if "quiet" not in self.switches:
|
||||||
string = "%s boots %s from channel.%s" % (self.caller, player.key, reason)
|
string = "%s boots %s from channel.%s" % (self.caller, account.key, reason)
|
||||||
channel.msg(string)
|
channel.msg(string)
|
||||||
# find all player's nicks linked to this channel and delete them
|
# find all account's nicks linked to this channel and delete them
|
||||||
for nick in [nick for nick in
|
for nick in [nick for nick in
|
||||||
player.character.nicks.get(category="channel") or []
|
account.character.nicks.get(category="channel") or []
|
||||||
if nick.value[3].lower() == channel.key]:
|
if nick.value[3].lower() == channel.key]:
|
||||||
nick.delete()
|
nick.delete()
|
||||||
# disconnect player
|
# disconnect account
|
||||||
channel.disconnect(player)
|
channel.disconnect(account)
|
||||||
CHANNELHANDLER.update()
|
CHANNELHANDLER.update()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -453,11 +453,11 @@ class CmdCemit(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
key = "@cemit"
|
key = "@cemit"
|
||||||
aliases = ["@cmsg"]
|
aliases = ["@cmsg"]
|
||||||
locks = "cmd: not pperm(channel_banned) and pperm(Players)"
|
locks = "cmd: not pperm(channel_banned) and pperm(Player)"
|
||||||
help_category = "Comms"
|
help_category = "Comms"
|
||||||
|
|
||||||
# this is used by the COMMAND_DEFAULT_CLASS parent
|
# this is used by the COMMAND_DEFAULT_CLASS parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""Implement function"""
|
"""Implement function"""
|
||||||
|
|
@ -496,7 +496,7 @@ class CmdCWho(COMMAND_DEFAULT_CLASS):
|
||||||
help_category = "Comms"
|
help_category = "Comms"
|
||||||
|
|
||||||
# this is used by the COMMAND_DEFAULT_CLASS parent
|
# this is used by the COMMAND_DEFAULT_CLASS parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""implement function"""
|
"""implement function"""
|
||||||
|
|
@ -530,11 +530,11 @@ class CmdChannelCreate(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
key = "@ccreate"
|
key = "@ccreate"
|
||||||
aliases = "channelcreate"
|
aliases = "channelcreate"
|
||||||
locks = "cmd:not pperm(channel_banned) and pperm(Players)"
|
locks = "cmd:not pperm(channel_banned) and pperm(Player)"
|
||||||
help_category = "Comms"
|
help_category = "Comms"
|
||||||
|
|
||||||
# this is used by the COMMAND_DEFAULT_CLASS parent
|
# this is used by the COMMAND_DEFAULT_CLASS parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""Implement the command"""
|
"""Implement the command"""
|
||||||
|
|
@ -587,7 +587,7 @@ class CmdClock(COMMAND_DEFAULT_CLASS):
|
||||||
help_category = "Comms"
|
help_category = "Comms"
|
||||||
|
|
||||||
# this is used by the COMMAND_DEFAULT_CLASS parent
|
# this is used by the COMMAND_DEFAULT_CLASS parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""run the function"""
|
"""run the function"""
|
||||||
|
|
@ -614,7 +614,7 @@ class CmdClock(COMMAND_DEFAULT_CLASS):
|
||||||
# Try to add the lock
|
# Try to add the lock
|
||||||
try:
|
try:
|
||||||
channel.locks.add(self.rhs)
|
channel.locks.add(self.rhs)
|
||||||
except LockException, err:
|
except LockException as err:
|
||||||
self.msg(err)
|
self.msg(err)
|
||||||
return
|
return
|
||||||
string = "Lock(s) applied. "
|
string = "Lock(s) applied. "
|
||||||
|
|
@ -639,7 +639,7 @@ class CmdCdesc(COMMAND_DEFAULT_CLASS):
|
||||||
help_category = "Comms"
|
help_category = "Comms"
|
||||||
|
|
||||||
# this is used by the COMMAND_DEFAULT_CLASS parent
|
# this is used by the COMMAND_DEFAULT_CLASS parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""Implement command"""
|
"""Implement command"""
|
||||||
|
|
@ -666,10 +666,10 @@ class CmdCdesc(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
class CmdPage(COMMAND_DEFAULT_CLASS):
|
class CmdPage(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
send a private message to another player
|
send a private message to another account
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
page[/switches] [<player>,<player>,... = <message>]
|
page[/switches] [<account>,<account>,... = <message>]
|
||||||
tell ''
|
tell ''
|
||||||
page <number>
|
page <number>
|
||||||
|
|
||||||
|
|
@ -687,12 +687,12 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
|
||||||
help_category = "Comms"
|
help_category = "Comms"
|
||||||
|
|
||||||
# this is used by the COMMAND_DEFAULT_CLASS parent
|
# this is used by the COMMAND_DEFAULT_CLASS parent
|
||||||
player_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""Implement function using the Msg methods"""
|
"""Implement function using the Msg methods"""
|
||||||
|
|
||||||
# Since player_caller is set above, this will be a Player.
|
# Since account_caller is set above, this will be an Account.
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
|
|
||||||
# get the messages we've sent (not to channels)
|
# get the messages we've sent (not to channels)
|
||||||
|
|
@ -718,7 +718,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
|
||||||
try:
|
try:
|
||||||
number = int(self.args)
|
number = int(self.args)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.msg("Usage: tell [<player> = msg]")
|
self.msg("Usage: tell [<account> = msg]")
|
||||||
return
|
return
|
||||||
|
|
||||||
if len(pages) > number:
|
if len(pages) > number:
|
||||||
|
|
@ -767,7 +767,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
|
||||||
self.msg("Noone found to page.")
|
self.msg("Noone found to page.")
|
||||||
return
|
return
|
||||||
|
|
||||||
header = "|wPlayer|n |c%s|n |wpages:|n" % caller.key
|
header = "|wAccount|n |c%s|n |wpages:|n" % caller.key
|
||||||
message = self.rhs
|
message = self.rhs
|
||||||
|
|
||||||
# if message begins with a :, we assume it is a 'page-pose'
|
# if message begins with a :, we assume it is a 'page-pose'
|
||||||
|
|
@ -778,7 +778,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
|
||||||
create.create_message(caller, message,
|
create.create_message(caller, message,
|
||||||
receivers=recobjs)
|
receivers=recobjs)
|
||||||
|
|
||||||
# tell the players they got a message.
|
# tell the accounts they got a message.
|
||||||
received = []
|
received = []
|
||||||
rstrings = []
|
rstrings = []
|
||||||
for pobj in recobjs:
|
for pobj in recobjs:
|
||||||
|
|
@ -805,7 +805,7 @@ def _list_bots():
|
||||||
bots (str): A table of bots or an error message.
|
bots (str): A table of bots or an error message.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
ircbots = [bot for bot in PlayerDB.objects.filter(db_is_bot=True, username__startswith="ircbot-")]
|
ircbots = [bot for bot in AccountDB.objects.filter(db_is_bot=True, username__startswith="ircbot-")]
|
||||||
if ircbots:
|
if ircbots:
|
||||||
from evennia.utils.evtable import EvTable
|
from evennia.utils.evtable import EvTable
|
||||||
table = EvTable("|w#dbref|n", "|wbotname|n", "|wev-channel|n",
|
table = EvTable("|w#dbref|n", "|wbotname|n", "|wev-channel|n",
|
||||||
|
|
@ -836,7 +836,7 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
@irc2chan myircchan = irc.dalnet.net 6667 #mychannel evennia-bot
|
@irc2chan myircchan = irc.dalnet.net 6667 #mychannel evennia-bot
|
||||||
@irc2chan public = irc.freenode.net 6667 #evgaming #evbot:players.mybot.MyBot
|
@irc2chan public = irc.freenode.net 6667 #evgaming #evbot:accounts.mybot.MyBot
|
||||||
|
|
||||||
This creates an IRC bot that connects to a given IRC network and
|
This creates an IRC bot that connects to a given IRC network and
|
||||||
channel. If a custom typeclass path is given, this will be used
|
channel. If a custom typeclass path is given, this will be used
|
||||||
|
|
@ -850,7 +850,7 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "@irc2chan"
|
key = "@irc2chan"
|
||||||
locks = "cmd:serversetting(IRC_ENABLED) and pperm(Immortals)"
|
locks = "cmd:serversetting(IRC_ENABLED) and pperm(Developer)"
|
||||||
help_category = "Comms"
|
help_category = "Comms"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -868,11 +868,11 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches:
|
if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches:
|
||||||
botname = "ircbot-%s" % self.lhs
|
botname = "ircbot-%s" % self.lhs
|
||||||
matches = PlayerDB.objects.filter(db_is_bot=True, username=botname)
|
matches = AccountDB.objects.filter(db_is_bot=True, username=botname)
|
||||||
dbref = utils.dbref(self.lhs)
|
dbref = utils.dbref(self.lhs)
|
||||||
if not matches and dbref:
|
if not matches and dbref:
|
||||||
# try dbref match
|
# try dbref match
|
||||||
matches = PlayerDB.objects.filter(db_is_bot=True, id=dbref)
|
matches = AccountDB.objects.filter(db_is_bot=True, id=dbref)
|
||||||
if matches:
|
if matches:
|
||||||
matches[0].delete()
|
matches[0].delete()
|
||||||
self.msg("IRC connection destroyed.")
|
self.msg("IRC connection destroyed.")
|
||||||
|
|
@ -890,7 +890,7 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
self.rhs = self.rhs.replace('#', ' ') # to avoid Python comment issues
|
self.rhs = self.rhs.replace('#', ' ') # to avoid Python comment issues
|
||||||
try:
|
try:
|
||||||
irc_network, irc_port, irc_channel, irc_botname = \
|
irc_network, irc_port, irc_channel, irc_botname = \
|
||||||
[part.strip() for part in self.rhs.split(None, 4)]
|
[part.strip() for part in self.rhs.split(None, 4)]
|
||||||
irc_channel = "#%s" % irc_channel
|
irc_channel = "#%s" % irc_channel
|
||||||
except Exception:
|
except Exception:
|
||||||
string = "IRC bot definition '%s' is not valid." % self.rhs
|
string = "IRC bot definition '%s' is not valid." % self.rhs
|
||||||
|
|
@ -906,16 +906,16 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
irc_ssl = "ssl" in self.switches
|
irc_ssl = "ssl" in self.switches
|
||||||
|
|
||||||
# create a new bot
|
# create a new bot
|
||||||
bot = PlayerDB.objects.filter(username__iexact=botname)
|
bot = AccountDB.objects.filter(username__iexact=botname)
|
||||||
if bot:
|
if bot:
|
||||||
# re-use an existing bot
|
# re-use an existing bot
|
||||||
bot = bot[0]
|
bot = bot[0]
|
||||||
if not bot.is_bot:
|
if not bot.is_bot:
|
||||||
self.msg("Player '%s' already exists and is not a bot." % botname)
|
self.msg("Account '%s' already exists and is not a bot." % botname)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
bot = create.create_player(botname, None, None, typeclass=botclass)
|
bot = create.create_account(botname, None, None, typeclass=botclass)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
self.msg("|rError, could not create the bot:|n '%s'." % err)
|
self.msg("|rError, could not create the bot:|n '%s'." % err)
|
||||||
return
|
return
|
||||||
|
|
@ -943,7 +943,7 @@ class CmdIRCStatus(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
key = "@ircstatus"
|
key = "@ircstatus"
|
||||||
locks = "cmd:serversetting(IRC_ENABLED) and perm(ircstatus) or perm(Builders))"
|
locks = "cmd:serversetting(IRC_ENABLED) and perm(ircstatus) or perm(Builder))"
|
||||||
help_category = "Comms"
|
help_category = "Comms"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -963,7 +963,7 @@ class CmdIRCStatus(COMMAND_DEFAULT_CLASS):
|
||||||
return
|
return
|
||||||
matches = None
|
matches = None
|
||||||
if utils.dbref(botname):
|
if utils.dbref(botname):
|
||||||
matches = PlayerDB.objects.filter(db_is_bot=True, id=utils.dbref(botname))
|
matches = AccountDB.objects.filter(db_is_bot=True, id=utils.dbref(botname))
|
||||||
if not matches:
|
if not matches:
|
||||||
self.msg("No matching IRC-bot found. Use @ircstatus without arguments to list active bots.")
|
self.msg("No matching IRC-bot found. Use @ircstatus without arguments to list active bots.")
|
||||||
return
|
return
|
||||||
|
|
@ -981,7 +981,7 @@ class CmdIRCStatus(COMMAND_DEFAULT_CLASS):
|
||||||
# an asynchronous call.
|
# an asynchronous call.
|
||||||
self.caller.msg("Requesting nicklist from %s (%s:%s)." % (channel, network, port))
|
self.caller.msg("Requesting nicklist from %s (%s:%s)." % (channel, network, port))
|
||||||
ircbot.get_nicklist(self.caller)
|
ircbot.get_nicklist(self.caller)
|
||||||
elif self.caller.locks.check_lockstring(self.caller, "dummy:perm(ircstatus) or perm(Immortals)"):
|
elif self.caller.locks.check_lockstring(self.caller, "dummy:perm(ircstatus) or perm(Developer)"):
|
||||||
# reboot the client
|
# reboot the client
|
||||||
self.caller.msg("Forcing a disconnect + reconnect of %s." % chtext)
|
self.caller.msg("Forcing a disconnect + reconnect of %s." % chtext)
|
||||||
ircbot.reconnect()
|
ircbot.reconnect()
|
||||||
|
|
@ -1016,7 +1016,7 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "@rss2chan"
|
key = "@rss2chan"
|
||||||
locks = "cmd:serversetting(RSS_ENABLED) and pperm(Immortals)"
|
locks = "cmd:serversetting(RSS_ENABLED) and pperm(Developer)"
|
||||||
help_category = "Comms"
|
help_category = "Comms"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -1038,7 +1038,7 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
if 'list' in self.switches:
|
if 'list' in self.switches:
|
||||||
# show all connections
|
# show all connections
|
||||||
rssbots = [bot for bot in PlayerDB.objects.filter(db_is_bot=True, username__startswith="rssbot-")]
|
rssbots = [bot for bot in AccountDB.objects.filter(db_is_bot=True, username__startswith="rssbot-")]
|
||||||
if rssbots:
|
if rssbots:
|
||||||
from evennia.utils.evtable import EvTable
|
from evennia.utils.evtable import EvTable
|
||||||
table = EvTable("|wdbid|n", "|wupdate rate|n", "|wev-channel",
|
table = EvTable("|wdbid|n", "|wupdate rate|n", "|wev-channel",
|
||||||
|
|
@ -1052,10 +1052,10 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches:
|
if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches:
|
||||||
botname = "rssbot-%s" % self.lhs
|
botname = "rssbot-%s" % self.lhs
|
||||||
matches = PlayerDB.objects.filter(db_is_bot=True, db_key=botname)
|
matches = AccountDB.objects.filter(db_is_bot=True, db_key=botname)
|
||||||
if not matches:
|
if not matches:
|
||||||
# try dbref match
|
# try dbref match
|
||||||
matches = PlayerDB.objects.filter(db_is_bot=True, id=self.args.lstrip("#"))
|
matches = AccountDB.objects.filter(db_is_bot=True, id=self.args.lstrip("#"))
|
||||||
if matches:
|
if matches:
|
||||||
matches[0].delete()
|
matches[0].delete()
|
||||||
self.msg("RSS connection destroyed.")
|
self.msg("RSS connection destroyed.")
|
||||||
|
|
@ -1072,14 +1072,14 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
botname = "rssbot-%s" % url
|
botname = "rssbot-%s" % url
|
||||||
# create a new bot
|
# create a new bot
|
||||||
bot = PlayerDB.objects.filter(username__iexact=botname)
|
bot = AccountDB.objects.filter(username__iexact=botname)
|
||||||
if bot:
|
if bot:
|
||||||
# re-use existing bot
|
# re-use existing bot
|
||||||
bot = bot[0]
|
bot = bot[0]
|
||||||
if not bot.is_bot:
|
if not bot.is_bot:
|
||||||
self.msg("Player '%s' already exists and is not a bot." % botname)
|
self.msg("Account '%s' already exists and is not a bot." % botname)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
bot = create.create_player(botname, None, None, typeclass=bots.RSSBot)
|
bot = create.create_account(botname, None, None, typeclass=bots.RSSBot)
|
||||||
bot.start(ev_channel=channel, rss_url=url, rss_rate=10)
|
bot.start(ev_channel=channel, rss_url=url, rss_rate=10)
|
||||||
self.msg("RSS reporter created. Fetching RSS.")
|
self.msg("RSS reporter created. Fetching RSS.")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"""
|
"""
|
||||||
General Character commands usually available to all characters
|
General Character commands usually available to all characters
|
||||||
"""
|
"""
|
||||||
|
import re
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia.utils import utils, evtable
|
from evennia.utils import utils, evtable
|
||||||
from evennia.typeclasses.attributes import NickTemplateInvalid
|
from evennia.typeclasses.attributes import NickTemplateInvalid
|
||||||
|
|
@ -9,7 +10,7 @@ COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||||
|
|
||||||
# limit symbol import for API
|
# limit symbol import for API
|
||||||
__all__ = ("CmdHome", "CmdLook", "CmdNick",
|
__all__ = ("CmdHome", "CmdLook", "CmdNick",
|
||||||
"CmdInventory", "CmdGet", "CmdDrop", "CmdGive",
|
"CmdInventory", "CmdSetDesc", "CmdGet", "CmdDrop", "CmdGive",
|
||||||
"CmdSay", "CmdWhisper", "CmdPose", "CmdAccess")
|
"CmdSay", "CmdWhisper", "CmdPose", "CmdAccess")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -24,7 +25,7 @@ class CmdHome(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "home"
|
key = "home"
|
||||||
locks = "cmd:perm(home) or perm(Builders)"
|
locks = "cmd:perm(home) or perm(Builder)"
|
||||||
arg_regex = r"$"
|
arg_regex = r"$"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -47,7 +48,7 @@ class CmdLook(COMMAND_DEFAULT_CLASS):
|
||||||
Usage:
|
Usage:
|
||||||
look
|
look
|
||||||
look <obj>
|
look <obj>
|
||||||
look *<player>
|
look *<account>
|
||||||
|
|
||||||
Observes your location or objects in your vicinity.
|
Observes your location or objects in your vicinity.
|
||||||
"""
|
"""
|
||||||
|
|
@ -67,7 +68,7 @@ class CmdLook(COMMAND_DEFAULT_CLASS):
|
||||||
caller.msg("You have no location to look at!")
|
caller.msg("You have no location to look at!")
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
target = caller.search(self.args, use_dbref=caller.check_permstring("Builders"))
|
target = caller.search(self.args)
|
||||||
if not target:
|
if not target:
|
||||||
return
|
return
|
||||||
self.msg(caller.at_look(target))
|
self.msg(caller.at_look(target))
|
||||||
|
|
@ -75,37 +76,41 @@ class CmdLook(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
class CmdNick(COMMAND_DEFAULT_CLASS):
|
class CmdNick(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
define a personal alias/nick
|
define a personal alias/nick by defining a string to
|
||||||
|
match and replace it with another on the fly
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
nick[/switches] <string> [= [replacement_string]]
|
nick[/switches] <string> [= [replacement_string]]
|
||||||
nick[/switches] <template> = <replacement_template>
|
nick[/switches] <template> = <replacement_template>
|
||||||
nick/delete <string> or number
|
nick/delete <string> or number
|
||||||
nick/test <test string>
|
nicks
|
||||||
|
|
||||||
Switches:
|
Switches:
|
||||||
inputline - replace on the inputline (default)
|
inputline - replace on the inputline (default)
|
||||||
object - replace on object-lookup
|
object - replace on object-lookup
|
||||||
player - replace on player-lookup
|
account - replace on account-lookup
|
||||||
delete - remove nick by name or by index given by /list
|
|
||||||
clearall - clear all nicks
|
|
||||||
list - show all defined aliases (also "nicks" works)
|
list - show all defined aliases (also "nicks" works)
|
||||||
test - test input to see what it matches with
|
delete - remove nick by index in /list
|
||||||
|
clearall - clear all nicks
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
nick hi = say Hello, I'm Sarah!
|
nick hi = say Hello, I'm Sarah!
|
||||||
nick/object tom = the tall man
|
nick/object tom = the tall man
|
||||||
nick build $1 $2 = @create/drop $1;$2 - (template)
|
nick build $1 $2 = @create/drop $1;$2
|
||||||
nick tell $1 $2=@page $1=$2 - (template)
|
nick tell $1 $2=@page $1=$2
|
||||||
|
nick tm?$1=@page tallman=$1
|
||||||
|
nick tm\=$1=@page tallman=$1
|
||||||
|
|
||||||
A 'nick' is a personal string replacement. Use $1, $2, ... to catch arguments.
|
A 'nick' is a personal string replacement. Use $1, $2, ... to catch arguments.
|
||||||
Put the last $-marker without an ending space to catch all remaining text. You
|
Put the last $-marker without an ending space to catch all remaining text. You
|
||||||
can also use unix-glob matching:
|
can also use unix-glob matching for the left-hand side <string>:
|
||||||
|
|
||||||
* - matches everything
|
* - matches everything
|
||||||
? - matches a single character
|
? - matches 0 or 1 single characters
|
||||||
[seq] - matches all chars in sequence
|
[abcd] - matches these chars in any order
|
||||||
[!seq] - matches everything not in sequence
|
[!abcd] - matches everything not among these chars
|
||||||
|
\= - escape literal '=' you want in your <string>
|
||||||
|
|
||||||
Note that no objects are actually renamed or changed by this command - your nicks
|
Note that no objects are actually renamed or changed by this command - your nicks
|
||||||
are only available to you. If you want to permanently add keywords to an object
|
are only available to you. If you want to permanently add keywords to an object
|
||||||
|
|
@ -113,17 +118,39 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
key = "nick"
|
key = "nick"
|
||||||
aliases = ["nickname", "nicks", "@nick", "@nicks", "alias"]
|
aliases = ["nickname", "nicks"]
|
||||||
locks = "cmd:all()"
|
locks = "cmd:all()"
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
"""
|
||||||
|
Support escaping of = with \=
|
||||||
|
"""
|
||||||
|
super(CmdNick, self).parse()
|
||||||
|
args = (self.lhs or "") + (" = %s" % self.rhs if self.rhs else "")
|
||||||
|
parts = re.split(r"(?<!\\)=", args, 1)
|
||||||
|
self.rhs = None
|
||||||
|
if len(parts) < 2:
|
||||||
|
self.lhs = parts[0].strip()
|
||||||
|
else:
|
||||||
|
self.lhs, self.rhs = [part.strip() for part in parts]
|
||||||
|
self.lhs = self.lhs.replace("\=", "=")
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""Create the nickname"""
|
"""Create the nickname"""
|
||||||
|
|
||||||
caller = self.caller
|
def _cy(string):
|
||||||
switches = self.switches
|
"add color to the special markers"
|
||||||
nicktypes = [switch for switch in switches if switch in ("object", "player", "inputline")] or ["inputline"]
|
return re.sub(r"(\$[0-9]+|\*|\?|\[.+?\])", r"|Y\1|n", string)
|
||||||
|
|
||||||
nicklist = utils.make_iter(caller.nicks.get(return_obj=True) or [])
|
caller = self.caller
|
||||||
|
account = self.caller.account or caller
|
||||||
|
switches = self.switches
|
||||||
|
nicktypes = [switch for switch in switches if switch in (
|
||||||
|
"object", "account", "inputline")] or ["inputline"]
|
||||||
|
|
||||||
|
nicklist = (utils.make_iter(caller.nicks.get(category="inputline", return_obj=True) or []) +
|
||||||
|
utils.make_iter(caller.nicks.get(category="object", return_obj=True) or []) +
|
||||||
|
utils.make_iter(account.nicks.get(category="account", return_obj=True) or []))
|
||||||
|
|
||||||
if 'list' in switches or self.cmdstring in ("nicks", "@nicks"):
|
if 'list' in switches or self.cmdstring in ("nicks", "@nicks"):
|
||||||
|
|
||||||
|
|
@ -133,24 +160,51 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
|
||||||
table = evtable.EvTable("#", "Type", "Nick match", "Replacement")
|
table = evtable.EvTable("#", "Type", "Nick match", "Replacement")
|
||||||
for inum, nickobj in enumerate(nicklist):
|
for inum, nickobj in enumerate(nicklist):
|
||||||
_, _, nickvalue, replacement = nickobj.value
|
_, _, nickvalue, replacement = nickobj.value
|
||||||
table.add_row(str(inum + 1), nickobj.db_category, nickvalue, replacement)
|
table.add_row(str(inum + 1), nickobj.db_category, _cy(nickvalue), _cy(replacement))
|
||||||
string = "|wDefined Nicks:|n\n%s" % table
|
string = "|wDefined Nicks:|n\n%s" % table
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'clearall' in switches:
|
if 'clearall' in switches:
|
||||||
caller.nicks.clear()
|
caller.nicks.clear()
|
||||||
|
caller.account.nicks.clear()
|
||||||
caller.msg("Cleared all nicks.")
|
caller.msg("Cleared all nicks.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if 'delete' in switches or 'del' in switches:
|
||||||
|
if not self.args or not self.lhs:
|
||||||
|
caller.msg("usage nick/delete #num ('nicks' for list)")
|
||||||
|
return
|
||||||
|
# see if a number was given
|
||||||
|
arg = self.args.lstrip("#")
|
||||||
|
if arg.isdigit():
|
||||||
|
# we are given a index in nicklist
|
||||||
|
delindex = int(arg)
|
||||||
|
if 0 < delindex <= len(nicklist):
|
||||||
|
oldnick = nicklist[delindex - 1]
|
||||||
|
_, _, old_nickstring, old_replstring = oldnick.value
|
||||||
|
else:
|
||||||
|
caller.msg("Not a valid nick index. See 'nicks' for a list.")
|
||||||
|
return
|
||||||
|
nicktype = oldnick.category
|
||||||
|
nicktypestr = "%s-nick" % nicktype.capitalize()
|
||||||
|
|
||||||
|
if nicktype == "account":
|
||||||
|
account.nicks.remove(old_nickstring, category=nicktype)
|
||||||
|
else:
|
||||||
|
caller.nicks.remove(old_nickstring, category=nicktype)
|
||||||
|
caller.msg("%s removed: '|w%s|n' -> |w%s|n." % (
|
||||||
|
nicktypestr, old_nickstring, old_replstring))
|
||||||
|
return
|
||||||
|
|
||||||
if not self.args or not self.lhs:
|
if not self.args or not self.lhs:
|
||||||
caller.msg("Usage: nick[/switches] nickname = [realname]")
|
caller.msg("Usage: nick[/switches] nickname = [realname]")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# setting new nicks
|
||||||
|
|
||||||
nickstring = self.lhs
|
nickstring = self.lhs
|
||||||
replstring = self.rhs
|
replstring = self.rhs
|
||||||
old_nickstring = None
|
|
||||||
old_replstring = None
|
|
||||||
|
|
||||||
if replstring == nickstring:
|
if replstring == nickstring:
|
||||||
caller.msg("No point in setting nick same as the string to replace...")
|
caller.msg("No point in setting nick same as the string to replace...")
|
||||||
|
|
@ -160,47 +214,40 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
|
||||||
errstring = ""
|
errstring = ""
|
||||||
string = ""
|
string = ""
|
||||||
for nicktype in nicktypes:
|
for nicktype in nicktypes:
|
||||||
oldnick = caller.nicks.get(key=nickstring, category=nicktype, return_obj=True)
|
if nicktype == "account":
|
||||||
|
obj = account
|
||||||
|
else:
|
||||||
|
obj = caller
|
||||||
|
|
||||||
|
nicktypestr = "%s-nick" % nicktype.capitalize()
|
||||||
|
old_nickstring = None
|
||||||
|
old_replstring = None
|
||||||
|
|
||||||
|
oldnick = obj.nicks.get(key=nickstring, category=nicktype, return_obj=True)
|
||||||
if oldnick:
|
if oldnick:
|
||||||
_, _, old_nickstring, old_replstring = oldnick.value
|
_, _, old_nickstring, old_replstring = oldnick.value
|
||||||
else:
|
if replstring:
|
||||||
# no old nick, see if a number was given
|
|
||||||
arg = self.args.lstrip("#")
|
|
||||||
if arg.isdigit():
|
|
||||||
# we are given a index in nicklist
|
|
||||||
delindex = int(arg)
|
|
||||||
if 0 < delindex <= len(nicklist):
|
|
||||||
oldnick = nicklist[delindex-1]
|
|
||||||
_, _, old_nickstring, old_replstring = oldnick.value
|
|
||||||
else:
|
|
||||||
errstring += "Not a valid nick index."
|
|
||||||
else:
|
|
||||||
errstring += "Nick not found."
|
|
||||||
if "delete" in switches or "del" in switches:
|
|
||||||
# clear the nick
|
|
||||||
if old_nickstring and caller.nicks.has(old_nickstring, category=nicktype):
|
|
||||||
caller.nicks.remove(old_nickstring, category=nicktype)
|
|
||||||
string += "\nNick removed: '|w%s|n' -> |w%s|n." % (old_nickstring, old_replstring)
|
|
||||||
else:
|
|
||||||
errstring += "\nNick '|w%s|n' was not deleted." % old_nickstring
|
|
||||||
elif replstring:
|
|
||||||
# creating new nick
|
# creating new nick
|
||||||
errstring = ""
|
errstring = ""
|
||||||
if oldnick:
|
if oldnick:
|
||||||
string += "\nNick '|w%s|n' updated to map to '|w%s|n'." % (old_nickstring, replstring)
|
if replstring == old_replstring:
|
||||||
|
string += "\nIdentical %s already set." % nicktypestr.lower()
|
||||||
|
else:
|
||||||
|
string += "\n%s '|w%s|n' updated to map to '|w%s|n'." % (
|
||||||
|
nicktypestr, old_nickstring, replstring)
|
||||||
else:
|
else:
|
||||||
string += "\nNick '|w%s|n' mapped to '|w%s|n'." % (nickstring, replstring)
|
string += "\n%s '|w%s|n' mapped to '|w%s|n'." % (nicktypestr, nickstring, replstring)
|
||||||
try:
|
try:
|
||||||
caller.nicks.add(nickstring, replstring, category=nicktype)
|
obj.nicks.add(nickstring, replstring, category=nicktype)
|
||||||
except NickTemplateInvalid:
|
except NickTemplateInvalid:
|
||||||
caller.msg("You must use the same $-markers both in the nick and in the replacement.")
|
caller.msg("You must use the same $-markers both in the nick and in the replacement.")
|
||||||
return
|
return
|
||||||
elif old_nickstring and old_replstring:
|
elif old_nickstring and old_replstring:
|
||||||
# just looking at the nick
|
# just looking at the nick
|
||||||
string += "\nNick '|w%s|n' maps to '|w%s|n'." % (old_nickstring, old_replstring)
|
string += "\n%s '|w%s|n' maps to '|w%s|n'." % (nicktypestr, old_nickstring, old_replstring)
|
||||||
errstring = ""
|
errstring = ""
|
||||||
string = errstring if errstring else string
|
string = errstring if errstring else string
|
||||||
caller.msg(string)
|
caller.msg(_cy(string))
|
||||||
|
|
||||||
|
|
||||||
class CmdInventory(COMMAND_DEFAULT_CLASS):
|
class CmdInventory(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -267,13 +314,17 @@ class CmdGet(COMMAND_DEFAULT_CLASS):
|
||||||
caller.msg("You can't get that.")
|
caller.msg("You can't get that.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# calling at_before_get hook method
|
||||||
|
if not obj.at_before_get(caller):
|
||||||
|
return
|
||||||
|
|
||||||
obj.move_to(caller, quiet=True)
|
obj.move_to(caller, quiet=True)
|
||||||
caller.msg("You pick up %s." % obj.name)
|
caller.msg("You pick up %s." % obj.name)
|
||||||
caller.location.msg_contents("%s picks up %s." %
|
caller.location.msg_contents("%s picks up %s." %
|
||||||
(caller.name,
|
(caller.name,
|
||||||
obj.name),
|
obj.name),
|
||||||
exclude=caller)
|
exclude=caller)
|
||||||
# calling hook method
|
# calling at_get hook method
|
||||||
obj.at_get(caller)
|
obj.at_get(caller)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -308,6 +359,10 @@ class CmdDrop(COMMAND_DEFAULT_CLASS):
|
||||||
if not obj:
|
if not obj:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Call the object script's at_before_drop() method.
|
||||||
|
if not obj.at_before_drop(caller):
|
||||||
|
return
|
||||||
|
|
||||||
obj.move_to(caller.location, quiet=True)
|
obj.move_to(caller.location, quiet=True)
|
||||||
caller.msg("You drop %s." % (obj.name,))
|
caller.msg("You drop %s." % (obj.name,))
|
||||||
caller.location.msg_contents("%s drops %s." %
|
caller.location.msg_contents("%s drops %s." %
|
||||||
|
|
@ -350,6 +405,11 @@ class CmdGive(COMMAND_DEFAULT_CLASS):
|
||||||
if not to_give.location == caller:
|
if not to_give.location == caller:
|
||||||
caller.msg("You are not holding %s." % to_give.key)
|
caller.msg("You are not holding %s." % to_give.key)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# calling at_before_give hook method
|
||||||
|
if not to_give.at_before_give(caller, target):
|
||||||
|
return
|
||||||
|
|
||||||
# give object
|
# give object
|
||||||
caller.msg("You give %s to %s." % (to_give.key, target.key))
|
caller.msg("You give %s to %s." % (to_give.key, target.key))
|
||||||
to_give.move_to(target, quiet=True)
|
to_give.move_to(target, quiet=True)
|
||||||
|
|
@ -358,18 +418,18 @@ class CmdGive(COMMAND_DEFAULT_CLASS):
|
||||||
to_give.at_give(caller, target)
|
to_give.at_give(caller, target)
|
||||||
|
|
||||||
|
|
||||||
class CmdDesc(COMMAND_DEFAULT_CLASS):
|
class CmdSetDesc(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
describe yourself
|
describe yourself
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
desc <description>
|
setdesc <description>
|
||||||
|
|
||||||
Add a description to yourself. This
|
Add a description to yourself. This
|
||||||
will be visible to people when they
|
will be visible to people when they
|
||||||
look at you.
|
look at you.
|
||||||
"""
|
"""
|
||||||
key = "desc"
|
key = "setdesc"
|
||||||
locks = "cmd:all()"
|
locks = "cmd:all()"
|
||||||
arg_regex = r"\s|$"
|
arg_regex = r"\s|$"
|
||||||
|
|
||||||
|
|
@ -409,16 +469,15 @@ class CmdSay(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
speech = self.args
|
speech = self.args
|
||||||
|
|
||||||
# calling the speech hook on the location
|
# Calling the at_before_say hook on the character
|
||||||
speech = caller.location.at_say(caller, speech)
|
speech = caller.at_before_say(speech)
|
||||||
|
|
||||||
# Feedback for the object doing the talking.
|
# If speech is empty, stop here
|
||||||
caller.msg('You say, "%s|n"' % speech)
|
if not speech:
|
||||||
|
return
|
||||||
|
|
||||||
# Build the string to emit to neighbors.
|
# Call the at_after_say hook on the character
|
||||||
emit_string = '%s says, "%s|n"' % (caller.name, speech)
|
caller.at_say(speech, msg_self=True)
|
||||||
caller.location.msg_contents(text=(emit_string, {"type": "say"}),
|
|
||||||
exclude=caller, from_obj=caller)
|
|
||||||
|
|
||||||
|
|
||||||
class CmdWhisper(COMMAND_DEFAULT_CLASS):
|
class CmdWhisper(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -426,10 +485,11 @@ class CmdWhisper(COMMAND_DEFAULT_CLASS):
|
||||||
Speak privately as your character to another
|
Speak privately as your character to another
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
whisper <player> = <message>
|
whisper <character> = <message>
|
||||||
|
whisper <char1>, <char2> = <message?
|
||||||
|
|
||||||
Talk privately to those in your current location, without
|
Talk privately to one or more characters in your current location, without
|
||||||
others being informed.
|
others in the room being informed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "whisper"
|
key = "whisper"
|
||||||
|
|
@ -441,26 +501,25 @@ class CmdWhisper(COMMAND_DEFAULT_CLASS):
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
|
|
||||||
if not self.lhs or not self.rhs:
|
if not self.lhs or not self.rhs:
|
||||||
caller.msg("Usage: whisper <player> = <message>")
|
caller.msg("Usage: whisper <character> = <message>")
|
||||||
return
|
return
|
||||||
|
|
||||||
receiver = caller.search(self.lhs)
|
receivers = [recv.strip() for recv in self.lhs.split(",")]
|
||||||
|
|
||||||
if not receiver:
|
receivers = [caller.search(receiver) for receiver in receivers]
|
||||||
return
|
receivers = [recv for recv in receivers if recv]
|
||||||
|
|
||||||
if caller == receiver:
|
|
||||||
caller.msg("You can't whisper to yourself.")
|
|
||||||
return
|
|
||||||
|
|
||||||
speech = self.rhs
|
speech = self.rhs
|
||||||
|
# If the speech is empty, abort the command
|
||||||
|
if not speech or not receivers:
|
||||||
|
return
|
||||||
|
|
||||||
# Feedback for the object doing the talking.
|
# Call a hook to change the speech before whispering
|
||||||
caller.msg('You whisper to %s, "%s|n"' % (receiver.key, speech))
|
speech = caller.at_before_say(speech, whisper=True, receivers=receivers)
|
||||||
|
|
||||||
# Build the string to emit to receiver.
|
# no need for self-message if we are whispering to ourselves (for some reason)
|
||||||
emit_string = '%s whispers, "%s|n"' % (caller.name, speech)
|
msg_self = None if caller in receivers else True
|
||||||
receiver.msg(text=(emit_string, {"type": "whisper"}), from_obj=caller)
|
caller.at_say(speech, msg_self=msg_self, receivers=receivers, whisper=True)
|
||||||
|
|
||||||
|
|
||||||
class CmdPose(COMMAND_DEFAULT_CLASS):
|
class CmdPose(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -529,15 +588,15 @@ class CmdAccess(COMMAND_DEFAULT_CLASS):
|
||||||
hierarchy_full = settings.PERMISSION_HIERARCHY
|
hierarchy_full = settings.PERMISSION_HIERARCHY
|
||||||
string = "\n|wPermission Hierarchy|n (climbing):\n %s" % ", ".join(hierarchy_full)
|
string = "\n|wPermission Hierarchy|n (climbing):\n %s" % ", ".join(hierarchy_full)
|
||||||
|
|
||||||
if self.caller.player.is_superuser:
|
if self.caller.account.is_superuser:
|
||||||
cperms = "<Superuser>"
|
cperms = "<Superuser>"
|
||||||
pperms = "<Superuser>"
|
pperms = "<Superuser>"
|
||||||
else:
|
else:
|
||||||
cperms = ", ".join(caller.permissions.all())
|
cperms = ", ".join(caller.permissions.all())
|
||||||
pperms = ", ".join(caller.player.permissions.all())
|
pperms = ", ".join(caller.account.permissions.all())
|
||||||
|
|
||||||
string += "\n|wYour access|n:"
|
string += "\n|wYour access|n:"
|
||||||
string += "\nCharacter |c%s|n: %s" % (caller.key, cperms)
|
string += "\nCharacter |c%s|n: %s" % (caller.key, cperms)
|
||||||
if hasattr(caller, 'player'):
|
if hasattr(caller, 'account'):
|
||||||
string += "\nPlayer |c%s|n: %s" % (caller.player.key, pperms)
|
string += "\nAccount |c%s|n: %s" % (caller.account.key, pperms)
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ from evennia.utils.utils import string_suggestions, class_from_module
|
||||||
|
|
||||||
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||||
HELP_MORE = settings.HELP_MORE
|
HELP_MORE = settings.HELP_MORE
|
||||||
|
CMD_IGNORE_PREFIXES = settings.CMD_IGNORE_PREFIXES
|
||||||
|
|
||||||
# limit symbol import for API
|
# limit symbol import for API
|
||||||
__all__ = ("CmdHelp", "CmdSetHelp")
|
__all__ = ("CmdHelp", "CmdSetHelp")
|
||||||
|
|
@ -67,14 +68,14 @@ class CmdHelp(Command):
|
||||||
|
|
||||||
if self.session.protocol_key in ("websocket", "ajax/comet"):
|
if self.session.protocol_key in ("websocket", "ajax/comet"):
|
||||||
try:
|
try:
|
||||||
options = self.player.db._saved_webclient_options
|
options = self.account.db._saved_webclient_options
|
||||||
if options and options["helppopup"]:
|
if options and options["helppopup"]:
|
||||||
usemore = False
|
usemore = False
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if usemore:
|
if usemore:
|
||||||
evmore.msg(self.caller, text)
|
evmore.msg(self.caller, text, session=self.session)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.msg((text, {"type": "help"}))
|
self.msg((text, {"type": "help"}))
|
||||||
|
|
@ -135,12 +136,12 @@ class CmdHelp(Command):
|
||||||
Helper method. If this return True, the given cmd
|
Helper method. If this return True, the given cmd
|
||||||
auto-help will be viewable in the help listing.
|
auto-help will be viewable in the help listing.
|
||||||
Override this to easily select what is shown to
|
Override this to easily select what is shown to
|
||||||
the player. Note that only commands available
|
the account. Note that only commands available
|
||||||
in the caller's merged cmdset are available.
|
in the caller's merged cmdset are available.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
cmd (Command): Command class from the merged cmdset
|
cmd (Command): Command class from the merged cmdset
|
||||||
caller (Character, Player or Session): The current caller
|
caller (Character, Account or Session): The current caller
|
||||||
executing the help command.
|
executing the help command.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -231,6 +232,15 @@ class CmdHelp(Command):
|
||||||
|
|
||||||
# try an exact command auto-help match
|
# try an exact command auto-help match
|
||||||
match = [cmd for cmd in all_cmds if cmd == query]
|
match = [cmd for cmd in all_cmds if cmd == query]
|
||||||
|
|
||||||
|
if not match:
|
||||||
|
# try an inexact match with prefixes stripped from query and cmds
|
||||||
|
_query = query[1:] if query[0] in CMD_IGNORE_PREFIXES else query
|
||||||
|
|
||||||
|
match = [cmd for cmd in all_cmds
|
||||||
|
for m in cmd._matchset if m == _query or
|
||||||
|
m[0] in CMD_IGNORE_PREFIXES and m[1:] == _query]
|
||||||
|
|
||||||
if len(match) == 1:
|
if len(match) == 1:
|
||||||
formatted = self.format_help_entry(match[0].key,
|
formatted = self.format_help_entry(match[0].key,
|
||||||
match[0].get_help(caller, cmdset),
|
match[0].get_help(caller, cmdset),
|
||||||
|
|
@ -257,7 +267,7 @@ class CmdHelp(Command):
|
||||||
return
|
return
|
||||||
|
|
||||||
# no exact matches found. Just give suggestions.
|
# no exact matches found. Just give suggestions.
|
||||||
self.msg(self.format_help_entry("", "No help entry found for '%s'" % query, None, suggested=suggestions))
|
self.msg((self.format_help_entry("", "No help entry found for '%s'" % query, None, suggested=suggestions), {"type": "help"}))
|
||||||
|
|
||||||
|
|
||||||
def _loadhelp(caller):
|
def _loadhelp(caller):
|
||||||
|
|
@ -306,9 +316,8 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS):
|
||||||
is to let everyone read the help file.
|
is to let everyone read the help file.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
key = "@help"
|
key = "@sethelp"
|
||||||
aliases = "@sethelp"
|
locks = "cmd:perm(Helper)"
|
||||||
locks = "cmd:perm(PlayerHelpers)"
|
|
||||||
help_category = "Building"
|
help_category = "Building"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
"""
|
"""
|
||||||
The command template for the default MUX-style command set. There
|
The command template for the default MUX-style command set. There
|
||||||
is also an Player/OOC version that makes sure caller is a Player object.
|
is also an Account/OOC version that makes sure caller is an Account object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from evennia.utils import utils
|
from evennia.utils import utils
|
||||||
from evennia.commands.command import Command
|
from evennia.commands.command import Command
|
||||||
|
|
||||||
# limit symbol import for API
|
# limit symbol import for API
|
||||||
__all__ = ("MuxCommand", "MuxPlayerCommand")
|
__all__ = ("MuxCommand", "MuxAccountCommand")
|
||||||
|
|
||||||
|
|
||||||
class MuxCommand(Command):
|
class MuxCommand(Command):
|
||||||
|
|
@ -22,6 +22,7 @@ class MuxCommand(Command):
|
||||||
used by Evennia to create the automatic help entry for
|
used by Evennia to create the automatic help entry for
|
||||||
the command, so make sure to document consistently here.
|
the command, so make sure to document consistently here.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def has_perm(self, srcobj):
|
def has_perm(self, srcobj):
|
||||||
"""
|
"""
|
||||||
This is called by the cmdhandler to determine
|
This is called by the cmdhandler to determine
|
||||||
|
|
@ -128,17 +129,17 @@ class MuxCommand(Command):
|
||||||
self.rhs = rhs
|
self.rhs = rhs
|
||||||
self.rhslist = rhslist
|
self.rhslist = rhslist
|
||||||
|
|
||||||
# if the class has the player_caller property set on itself, we make
|
# if the class has the account_caller property set on itself, we make
|
||||||
# sure that self.caller is always the player if possible. We also create
|
# sure that self.caller is always the account if possible. We also create
|
||||||
# a special property "character" for the puppeted object, if any. This
|
# a special property "character" for the puppeted object, if any. This
|
||||||
# is convenient for commands defined on the Player only.
|
# is convenient for commands defined on the Account only.
|
||||||
if hasattr(self, "player_caller") and self.player_caller:
|
if hasattr(self, "account_caller") and self.account_caller:
|
||||||
if utils.inherits_from(self.caller, "evennia.objects.objects.DefaultObject"):
|
if utils.inherits_from(self.caller, "evennia.objects.objects.DefaultObject"):
|
||||||
# caller is an Object/Character
|
# caller is an Object/Character
|
||||||
self.character = self.caller
|
self.character = self.caller
|
||||||
self.caller = self.caller.player
|
self.caller = self.caller.account
|
||||||
elif utils.inherits_from(self.caller, "evennia.players.players.DefaultPlayer"):
|
elif utils.inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount"):
|
||||||
# caller was already a Player
|
# caller was already an Account
|
||||||
self.character = self.caller.get_puppet(self.session)
|
self.character = self.caller.get_puppet(self.session)
|
||||||
else:
|
else:
|
||||||
self.character = None
|
self.character = None
|
||||||
|
|
@ -177,32 +178,33 @@ class MuxCommand(Command):
|
||||||
self.caller.msg(string)
|
self.caller.msg(string)
|
||||||
|
|
||||||
|
|
||||||
class MuxPlayerCommand(MuxCommand):
|
class MuxAccountCommand(MuxCommand):
|
||||||
"""
|
"""
|
||||||
This is an on-Player version of the MuxCommand. Since these commands sit
|
This is an on-Account version of the MuxCommand. Since these commands sit
|
||||||
on Players rather than on Characters/Objects, we need to check
|
on Accounts rather than on Characters/Objects, we need to check
|
||||||
this in the parser.
|
this in the parser.
|
||||||
|
|
||||||
Player commands are available also when puppeting a Character, it's
|
Account commands are available also when puppeting a Character, it's
|
||||||
just that they are applied with a lower priority and are always
|
just that they are applied with a lower priority and are always
|
||||||
available, also when disconnected from a character (i.e. "ooc").
|
available, also when disconnected from a character (i.e. "ooc").
|
||||||
|
|
||||||
This class makes sure that caller is always a Player object, while
|
This class makes sure that caller is always an Account object, while
|
||||||
creating a new property "character" that is set only if a
|
creating a new property "character" that is set only if a
|
||||||
character is actually attached to this Player and Session.
|
character is actually attached to this Account and Session.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def parse(self):
|
def parse(self):
|
||||||
"""
|
"""
|
||||||
We run the parent parser as usual, then fix the result
|
We run the parent parser as usual, then fix the result
|
||||||
"""
|
"""
|
||||||
super(MuxPlayerCommand, self).parse()
|
super(MuxAccountCommand, self).parse()
|
||||||
|
|
||||||
if utils.inherits_from(self.caller, "evennia.objects.objects.DefaultObject"):
|
if utils.inherits_from(self.caller, "evennia.objects.objects.DefaultObject"):
|
||||||
# caller is an Object/Character
|
# caller is an Object/Character
|
||||||
self.character = self.caller
|
self.character = self.caller
|
||||||
self.caller = self.caller.player
|
self.caller = self.caller.account
|
||||||
elif utils.inherits_from(self.caller, "evennia.players.players.DefaultPlayer"):
|
elif utils.inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount"):
|
||||||
# caller was already a Player
|
# caller was already an Account
|
||||||
self.character = self.caller.get_puppet(self.session)
|
self.character = self.caller.get_puppet(self.session)
|
||||||
else:
|
else:
|
||||||
self.character = None
|
self.character = None
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ from django.conf import settings
|
||||||
from evennia.server.sessionhandler import SESSIONS
|
from evennia.server.sessionhandler import SESSIONS
|
||||||
from evennia.scripts.models import ScriptDB
|
from evennia.scripts.models import ScriptDB
|
||||||
from evennia.objects.models import ObjectDB
|
from evennia.objects.models import ObjectDB
|
||||||
from evennia.players.models import PlayerDB
|
from evennia.accounts.models import AccountDB
|
||||||
from evennia.utils import logger, utils, gametime, create
|
from evennia.utils import logger, utils, gametime, create
|
||||||
from evennia.utils.eveditor import EvEditor
|
from evennia.utils.eveditor import EvEditor
|
||||||
from evennia.utils.evtable import EvTable
|
from evennia.utils.evtable import EvTable
|
||||||
|
|
@ -47,7 +47,7 @@ class CmdReload(COMMAND_DEFAULT_CLASS):
|
||||||
@reset to purge) and at_reload() hooks will be called.
|
@reset to purge) and at_reload() hooks will be called.
|
||||||
"""
|
"""
|
||||||
key = "@reload"
|
key = "@reload"
|
||||||
locks = "cmd:perm(reload) or perm(Immortals)"
|
locks = "cmd:perm(reload) or perm(Developer)"
|
||||||
help_category = "System"
|
help_category = "System"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -58,7 +58,7 @@ class CmdReload(COMMAND_DEFAULT_CLASS):
|
||||||
if self.args:
|
if self.args:
|
||||||
reason = "(Reason: %s) " % self.args.rstrip(".")
|
reason = "(Reason: %s) " % self.args.rstrip(".")
|
||||||
SESSIONS.announce_all(" Server restart initiated %s..." % reason)
|
SESSIONS.announce_all(" Server restart initiated %s..." % reason)
|
||||||
SESSIONS.server.shutdown(mode='reload')
|
SESSIONS.portal_restart_server()
|
||||||
|
|
||||||
|
|
||||||
class CmdReset(COMMAND_DEFAULT_CLASS):
|
class CmdReset(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -83,7 +83,7 @@ class CmdReset(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
key = "@reset"
|
key = "@reset"
|
||||||
aliases = ['@reboot']
|
aliases = ['@reboot']
|
||||||
locks = "cmd:perm(reload) or perm(Immortals)"
|
locks = "cmd:perm(reload) or perm(Developer)"
|
||||||
help_category = "System"
|
help_category = "System"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -91,7 +91,7 @@ class CmdReset(COMMAND_DEFAULT_CLASS):
|
||||||
Reload the system.
|
Reload the system.
|
||||||
"""
|
"""
|
||||||
SESSIONS.announce_all(" Server resetting/restarting ...")
|
SESSIONS.announce_all(" Server resetting/restarting ...")
|
||||||
SESSIONS.server.shutdown(mode='reset')
|
SESSIONS.portal_reset_server()
|
||||||
|
|
||||||
|
|
||||||
class CmdShutdown(COMMAND_DEFAULT_CLASS):
|
class CmdShutdown(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -105,7 +105,7 @@ class CmdShutdown(COMMAND_DEFAULT_CLASS):
|
||||||
Gracefully shut down both Server and Portal.
|
Gracefully shut down both Server and Portal.
|
||||||
"""
|
"""
|
||||||
key = "@shutdown"
|
key = "@shutdown"
|
||||||
locks = "cmd:perm(shutdown) or perm(Immortals)"
|
locks = "cmd:perm(shutdown) or perm(Developer)"
|
||||||
help_category = "System"
|
help_category = "System"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -119,7 +119,6 @@ class CmdShutdown(COMMAND_DEFAULT_CLASS):
|
||||||
announcement += "%s\n" % self.args
|
announcement += "%s\n" % self.args
|
||||||
logger.log_info('Server shutdown by %s.' % self.caller.name)
|
logger.log_info('Server shutdown by %s.' % self.caller.name)
|
||||||
SESSIONS.announce_all(announcement)
|
SESSIONS.announce_all(announcement)
|
||||||
SESSIONS.server.shutdown(mode='shutdown')
|
|
||||||
SESSIONS.portal_shutdown()
|
SESSIONS.portal_shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -133,11 +132,11 @@ def _py_code(caller, buf):
|
||||||
"""
|
"""
|
||||||
measure_time = caller.db._py_measure_time
|
measure_time = caller.db._py_measure_time
|
||||||
string = "Executing code%s ..." % (
|
string = "Executing code%s ..." % (
|
||||||
" (measure timing)" if measure_time else "")
|
" (measure timing)" if measure_time else "")
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
_run_code_snippet(caller, buf, mode="exec",
|
_run_code_snippet(caller, buf, mode="exec",
|
||||||
measure_time=measure_time,
|
measure_time=measure_time,
|
||||||
show_input=False)
|
show_input=False)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -147,7 +146,7 @@ def _py_quit(caller):
|
||||||
|
|
||||||
|
|
||||||
def _run_code_snippet(caller, pycode, mode="eval", measure_time=False,
|
def _run_code_snippet(caller, pycode, mode="eval", measure_time=False,
|
||||||
show_input=True):
|
show_input=True):
|
||||||
"""
|
"""
|
||||||
Run code and try to display information to the caller.
|
Run code and try to display information to the caller.
|
||||||
|
|
||||||
|
|
@ -161,25 +160,26 @@ def _run_code_snippet(caller, pycode, mode="eval", measure_time=False,
|
||||||
# Try to retrieve the session
|
# Try to retrieve the session
|
||||||
session = caller
|
session = caller
|
||||||
if hasattr(caller, "sessions"):
|
if hasattr(caller, "sessions"):
|
||||||
session = caller.sessions.get()[0]
|
sessions = caller.sessions.all()
|
||||||
|
|
||||||
# import useful variables
|
# import useful variables
|
||||||
import evennia
|
import evennia
|
||||||
available_vars = {
|
available_vars = {
|
||||||
'self': caller,
|
'self': caller,
|
||||||
'me': caller,
|
'me': caller,
|
||||||
'here': getattr(caller, "location", None),
|
'here': getattr(caller, "location", None),
|
||||||
'evennia': evennia,
|
'evennia': evennia,
|
||||||
'ev': evennia,
|
'ev': evennia,
|
||||||
'inherits_from': utils.inherits_from,
|
'inherits_from': utils.inherits_from,
|
||||||
}
|
}
|
||||||
|
|
||||||
if show_input:
|
if show_input:
|
||||||
try:
|
for session in sessions:
|
||||||
caller.msg(">>> %s" % pycode, session=session,
|
try:
|
||||||
options={"raw": True})
|
caller.msg(">>> %s" % pycode, session=session,
|
||||||
except TypeError:
|
options={"raw": True})
|
||||||
caller.msg(">>> %s" % pycode, options={"raw": True})
|
except TypeError:
|
||||||
|
caller.msg(">>> %s" % pycode, options={"raw": True})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
|
|
@ -206,10 +206,11 @@ def _run_code_snippet(caller, pycode, mode="eval", measure_time=False,
|
||||||
errlist = errlist[4:]
|
errlist = errlist[4:]
|
||||||
ret = "\n".join("%s" % line for line in errlist if line)
|
ret = "\n".join("%s" % line for line in errlist if line)
|
||||||
|
|
||||||
try:
|
for session in sessions:
|
||||||
caller.msg(ret, session=session, options={"raw": True})
|
try:
|
||||||
except TypeError:
|
caller.msg(ret, session=session, options={"raw": True})
|
||||||
caller.msg(ret, options={"raw": True})
|
except TypeError:
|
||||||
|
caller.msg(ret, options={"raw": True})
|
||||||
|
|
||||||
|
|
||||||
class CmdPy(COMMAND_DEFAULT_CLASS):
|
class CmdPy(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -244,7 +245,7 @@ class CmdPy(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
key = "@py"
|
key = "@py"
|
||||||
aliases = ["!"]
|
aliases = ["!"]
|
||||||
locks = "cmd:perm(py) or perm(Immortals)"
|
locks = "cmd:perm(py) or perm(Developer)"
|
||||||
help_category = "System"
|
help_category = "System"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -256,8 +257,8 @@ class CmdPy(COMMAND_DEFAULT_CLASS):
|
||||||
if "edit" in self.switches:
|
if "edit" in self.switches:
|
||||||
caller.db._py_measure_time = "time" in self.switches
|
caller.db._py_measure_time = "time" in self.switches
|
||||||
EvEditor(self.caller, loadfunc=_py_load, savefunc=_py_code,
|
EvEditor(self.caller, loadfunc=_py_load, savefunc=_py_code,
|
||||||
quitfunc=_py_quit, key="Python exec: :w or :!", persistent=True,
|
quitfunc=_py_quit, key="Python exec: :w or :!", persistent=True,
|
||||||
codefunc=_py_code)
|
codefunc=_py_code)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not pycode:
|
if not pycode:
|
||||||
|
|
@ -327,7 +328,7 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
key = "@scripts"
|
key = "@scripts"
|
||||||
aliases = ["@globalscript", "@listscripts"]
|
aliases = ["@globalscript", "@listscripts"]
|
||||||
locks = "cmd:perm(listscripts) or perm(Wizards)"
|
locks = "cmd:perm(listscripts) or perm(Admin)"
|
||||||
help_category = "System"
|
help_category = "System"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -409,7 +410,7 @@ class CmdObjects(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
key = "@objects"
|
key = "@objects"
|
||||||
aliases = ["@listobjects", "@listobjs", '@stats', '@db']
|
aliases = ["@listobjects", "@listobjs", '@stats', '@db']
|
||||||
locks = "cmd:perm(listobjects) or perm(Builders)"
|
locks = "cmd:perm(listobjects) or perm(Builder)"
|
||||||
help_category = "System"
|
help_category = "System"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -455,24 +456,24 @@ class CmdObjects(COMMAND_DEFAULT_CLASS):
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
|
|
||||||
|
|
||||||
class CmdPlayers(COMMAND_DEFAULT_CLASS):
|
class CmdAccounts(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
list all registered players
|
list all registered accounts
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@players [nr]
|
@accounts [nr]
|
||||||
|
|
||||||
Lists statistics about the Players registered with the game.
|
Lists statistics about the Accounts registered with the game.
|
||||||
It will list the <nr> amount of latest registered players
|
It will list the <nr> amount of latest registered accounts
|
||||||
If not given, <nr> defaults to 10.
|
If not given, <nr> defaults to 10.
|
||||||
"""
|
"""
|
||||||
key = "@players"
|
key = "@accounts"
|
||||||
aliases = ["@listplayers"]
|
aliases = ["@listaccounts"]
|
||||||
locks = "cmd:perm(listplayers) or perm(Wizards)"
|
locks = "cmd:perm(listaccounts) or perm(Admin)"
|
||||||
help_category = "System"
|
help_category = "System"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""List the players"""
|
"""List the accounts"""
|
||||||
|
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
if self.args and self.args.isdigit():
|
if self.args and self.args.isdigit():
|
||||||
|
|
@ -480,21 +481,21 @@ class CmdPlayers(COMMAND_DEFAULT_CLASS):
|
||||||
else:
|
else:
|
||||||
nlim = 10
|
nlim = 10
|
||||||
|
|
||||||
nplayers = PlayerDB.objects.count()
|
naccounts = AccountDB.objects.count()
|
||||||
|
|
||||||
# typeclass table
|
# typeclass table
|
||||||
dbtotals = PlayerDB.objects.object_totals()
|
dbtotals = AccountDB.objects.object_totals()
|
||||||
typetable = EvTable("|wtypeclass|n", "|wcount|n", "|w%%|n", border="cells", align="l")
|
typetable = EvTable("|wtypeclass|n", "|wcount|n", "|w%%|n", border="cells", align="l")
|
||||||
for path, count in dbtotals.items():
|
for path, count in dbtotals.items():
|
||||||
typetable.add_row(path, count, "%.2f" % ((float(count) / nplayers) * 100))
|
typetable.add_row(path, count, "%.2f" % ((float(count) / naccounts) * 100))
|
||||||
# last N table
|
# last N table
|
||||||
plyrs = PlayerDB.objects.all().order_by("db_date_created")[max(0, nplayers - nlim):]
|
plyrs = AccountDB.objects.all().order_by("db_date_created")[max(0, naccounts - nlim):]
|
||||||
latesttable = EvTable("|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", border="cells", align="l")
|
latesttable = EvTable("|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", border="cells", align="l")
|
||||||
for ply in plyrs:
|
for ply in plyrs:
|
||||||
latesttable.add_row(utils.datetime_format(ply.date_created), ply.dbref, ply.key, ply.path)
|
latesttable.add_row(utils.datetime_format(ply.date_created), ply.dbref, ply.key, ply.path)
|
||||||
|
|
||||||
string = "\n|wPlayer typeclass distribution:|n\n%s" % typetable
|
string = "\n|wAccount typeclass distribution:|n\n%s" % typetable
|
||||||
string += "\n|wLast %s Players created:|n\n%s" % (min(nplayers, nlim), latesttable)
|
string += "\n|wLast %s Accounts created:|n\n%s" % (min(naccounts, nlim), latesttable)
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -520,7 +521,7 @@ class CmdService(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
key = "@service"
|
key = "@service"
|
||||||
aliases = ["@services"]
|
aliases = ["@services"]
|
||||||
locks = "cmd:perm(service) or perm(Immortals)"
|
locks = "cmd:perm(service) or perm(Developer)"
|
||||||
help_category = "System"
|
help_category = "System"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -610,25 +611,25 @@ class CmdAbout(COMMAND_DEFAULT_CLASS):
|
||||||
"""Display information about server or target"""
|
"""Display information about server or target"""
|
||||||
|
|
||||||
string = """
|
string = """
|
||||||
|cEvennia|n %s|n
|
|cEvennia|n {version}|n
|
||||||
MUD/MUX/MU* development system
|
MU* development system
|
||||||
|
|
||||||
|wLicence|n https://opensource.org/licenses/BSD-3-Clause
|
|wLicence|n https://opensource.org/licenses/BSD-3-Clause
|
||||||
|wWeb|n http://www.evennia.com
|
|wWeb|n http://www.evennia.com
|
||||||
|wIrc|n #evennia on FreeNode
|
|wIrc|n #evennia on irc.freenode.net:6667
|
||||||
|wForum|n http://www.evennia.com/discussions
|
|wForum|n http://www.evennia.com/discussions
|
||||||
|wMaintainer|n (2010-) Griatch (griatch AT gmail DOT com)
|
|wMaintainer|n (2010-) Griatch (griatch AT gmail DOT com)
|
||||||
|wMaintainer|n (2006-10) Greg Taylor
|
|wMaintainer|n (2006-10) Greg Taylor
|
||||||
|
|
||||||
|wOS|n %s
|
|wOS|n {os}
|
||||||
|wPython|n %s
|
|wPython|n {python}
|
||||||
|wTwisted|n %s
|
|wTwisted|n {twisted}
|
||||||
|wDjango|n %s
|
|wDjango|n {django}
|
||||||
""" % (utils.get_evennia_version(),
|
""".format(version=utils.get_evennia_version(),
|
||||||
os.name,
|
os=os.name,
|
||||||
sys.version.split()[0],
|
python=sys.version.split()[0],
|
||||||
twisted.version.short(),
|
twisted=twisted.version.short(),
|
||||||
django.get_version())
|
django=django.get_version())
|
||||||
self.caller.msg(string)
|
self.caller.msg(string)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -644,7 +645,7 @@ class CmdTime(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
key = "@time"
|
key = "@time"
|
||||||
aliases = "@uptime"
|
aliases = "@uptime"
|
||||||
locks = "cmd:perm(time) or perm(Players)"
|
locks = "cmd:perm(time) or perm(Player)"
|
||||||
help_category = "System"
|
help_category = "System"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -702,7 +703,7 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
key = "@server"
|
key = "@server"
|
||||||
aliases = ["@serverload", "@serverprocess"]
|
aliases = ["@serverload", "@serverprocess"]
|
||||||
locks = "cmd:perm(list) or perm(Immortals)"
|
locks = "cmd:perm(list) or perm(Developer)"
|
||||||
help_category = "System"
|
help_category = "System"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -719,7 +720,7 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS):
|
||||||
now, _ = _IDMAPPER.cache_size()
|
now, _ = _IDMAPPER.cache_size()
|
||||||
string = "The Idmapper cache freed |w{idmapper}|n database objects.\n" \
|
string = "The Idmapper cache freed |w{idmapper}|n database objects.\n" \
|
||||||
"The Python garbage collector freed |w{gc}|n Python instances total."
|
"The Python garbage collector freed |w{gc}|n Python instances total."
|
||||||
self.caller.msg(string.format(idmapper=(prev-now), gc=nflushed))
|
self.caller.msg(string.format(idmapper=(prev - now), gc=nflushed))
|
||||||
return
|
return
|
||||||
|
|
||||||
# display active processes
|
# display active processes
|
||||||
|
|
@ -823,7 +824,7 @@ class CmdTickers(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
key = "@tickers"
|
key = "@tickers"
|
||||||
help_category = "System"
|
help_category = "System"
|
||||||
locks = "cmd:perm(tickers) or perm(Builders)"
|
locks = "cmd:perm(tickers) or perm(Builder)"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
from evennia import TICKER_HANDLER
|
from evennia import TICKER_HANDLER
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,19 @@ main test suite started with
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import types
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from mock import Mock
|
from mock import Mock, mock
|
||||||
|
|
||||||
from evennia.commands.default.cmdset_character import CharacterCmdSet
|
from evennia.commands.default.cmdset_character import CharacterCmdSet
|
||||||
from evennia.utils.test_resources import EvenniaTest
|
from evennia.utils.test_resources import EvenniaTest
|
||||||
from evennia.commands.default import help, general, system, admin, player, building, batchprocess, comms
|
from evennia.commands.default import help, general, system, admin, account, building, batchprocess, comms
|
||||||
from evennia.commands.command import Command, InterruptCommand
|
from evennia.commands.command import Command, InterruptCommand
|
||||||
from evennia.utils import ansi, utils
|
from evennia.utils import ansi, utils
|
||||||
from evennia.server.sessionhandler import SESSIONS
|
from evennia.server.sessionhandler import SESSIONS
|
||||||
|
from evennia import search_object
|
||||||
|
from evennia import DefaultObject, DefaultCharacter
|
||||||
|
|
||||||
|
|
||||||
# set up signal here since we are not starting the server
|
# set up signal here since we are not starting the server
|
||||||
|
|
@ -34,12 +37,13 @@ _RE = re.compile(r"^\+|-+\+|\+-+|--*|\|(?:\s|$)", re.MULTILINE)
|
||||||
# Command testing
|
# Command testing
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class CommandTest(EvenniaTest):
|
class CommandTest(EvenniaTest):
|
||||||
"""
|
"""
|
||||||
Tests a command
|
Tests a command
|
||||||
"""
|
"""
|
||||||
|
def call(self, cmdobj, args, msg=None, cmdset=None, noansi=True, caller=None,
|
||||||
def call(self, cmdobj, args, msg=None, cmdset=None, noansi=True, caller=None, receiver=None, cmdstring=None, obj=None):
|
receiver=None, cmdstring=None, obj=None):
|
||||||
"""
|
"""
|
||||||
Test a command by assigning all the needed
|
Test a command by assigning all the needed
|
||||||
properties to cmdobj and running
|
properties to cmdobj and running
|
||||||
|
|
@ -57,11 +61,13 @@ class CommandTest(EvenniaTest):
|
||||||
caller = caller if caller else self.char1
|
caller = caller if caller else self.char1
|
||||||
receiver = receiver if receiver else caller
|
receiver = receiver if receiver else caller
|
||||||
cmdobj.caller = caller
|
cmdobj.caller = caller
|
||||||
cmdobj.cmdstring = cmdstring if cmdstring else cmdobj.key
|
cmdobj.cmdname = cmdstring if cmdstring else cmdobj.key
|
||||||
|
cmdobj.raw_cmdname = cmdobj.cmdname
|
||||||
|
cmdobj.cmdstring = cmdobj.cmdname # deprecated
|
||||||
cmdobj.args = args
|
cmdobj.args = args
|
||||||
cmdobj.cmdset = cmdset
|
cmdobj.cmdset = cmdset
|
||||||
cmdobj.session = SESSIONS.session_from_sessid(1)
|
cmdobj.session = SESSIONS.session_from_sessid(1)
|
||||||
cmdobj.player = self.player
|
cmdobj.account = self.account
|
||||||
cmdobj.raw_string = cmdobj.key + " " + args
|
cmdobj.raw_string = cmdobj.key + " " + args
|
||||||
cmdobj.obj = obj or (caller if caller else self.char1)
|
cmdobj.obj = obj or (caller if caller else self.char1)
|
||||||
# test
|
# test
|
||||||
|
|
@ -71,23 +77,27 @@ class CommandTest(EvenniaTest):
|
||||||
receiver.msg = Mock()
|
receiver.msg = Mock()
|
||||||
cmdobj.at_pre_cmd()
|
cmdobj.at_pre_cmd()
|
||||||
cmdobj.parse()
|
cmdobj.parse()
|
||||||
cmdobj.func()
|
ret = cmdobj.func()
|
||||||
|
if isinstance(ret, types.GeneratorType):
|
||||||
|
ret.next()
|
||||||
cmdobj.at_post_cmd()
|
cmdobj.at_post_cmd()
|
||||||
|
except StopIteration:
|
||||||
|
pass
|
||||||
except InterruptCommand:
|
except InterruptCommand:
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
# clean out prettytable sugar. We only operate on text-type
|
# clean out evtable sugar. We only operate on text-type
|
||||||
stored_msg = [args[0] if args and args[0] else kwargs.get("text",utils.to_str(kwargs, force_string=True))
|
stored_msg = [args[0] if args and args[0] else kwargs.get("text", utils.to_str(kwargs, force_string=True))
|
||||||
for name, args, kwargs in receiver.msg.mock_calls]
|
for name, args, kwargs in receiver.msg.mock_calls]
|
||||||
# Get the first element of a tuple if msg received a tuple instead of a string
|
# Get the first element of a tuple if msg received a tuple instead of a string
|
||||||
stored_msg = [smsg[0] if isinstance(smsg, tuple) else smsg for smsg in stored_msg]
|
stored_msg = [smsg[0] if isinstance(smsg, tuple) else smsg for smsg in stored_msg]
|
||||||
if msg is not None:
|
if msg is not None:
|
||||||
returned_msg = "||".join(_RE.sub("", mess) for mess in stored_msg)
|
returned_msg = "||".join(_RE.sub("", mess) for mess in stored_msg)
|
||||||
returned_msg = ansi.parse_ansi(returned_msg, strip_ansi=noansi).strip()
|
returned_msg = ansi.parse_ansi(returned_msg, strip_ansi=noansi).strip()
|
||||||
if msg == "" and returned_msg or not returned_msg.startswith(msg.strip()):
|
if msg == "" and returned_msg or not returned_msg.startswith(msg.strip()):
|
||||||
sep1 = "\n" + "="*30 + "Wanted message" + "="*34 + "\n"
|
sep1 = "\n" + "=" * 30 + "Wanted message" + "=" * 34 + "\n"
|
||||||
sep2 = "\n" + "="*30 + "Returned message" + "="*32 + "\n"
|
sep2 = "\n" + "=" * 30 + "Returned message" + "=" * 32 + "\n"
|
||||||
sep3 = "\n" + "="*78
|
sep3 = "\n" + "=" * 78
|
||||||
retval = sep1 + msg.strip() + sep2 + returned_msg + sep3
|
retval = sep1 + msg.strip() + sep2 + returned_msg + sep3
|
||||||
raise AssertionError(retval)
|
raise AssertionError(retval)
|
||||||
else:
|
else:
|
||||||
|
|
@ -116,11 +126,12 @@ class TestGeneral(CommandTest):
|
||||||
self.call(general.CmdPose(), "looks around", "Char looks around")
|
self.call(general.CmdPose(), "looks around", "Char looks around")
|
||||||
|
|
||||||
def test_nick(self):
|
def test_nick(self):
|
||||||
self.call(general.CmdNick(), "testalias = testaliasedstring1", "Nick 'testalias' mapped to 'testaliasedstring1'.")
|
self.call(general.CmdNick(), "testalias = testaliasedstring1", "Inputlinenick 'testalias' mapped to 'testaliasedstring1'.")
|
||||||
self.call(general.CmdNick(), "/player testalias = testaliasedstring2", "Nick 'testalias' mapped to 'testaliasedstring2'.")
|
self.call(general.CmdNick(), "/account testalias = testaliasedstring2", "Accountnick 'testalias' mapped to 'testaliasedstring2'.")
|
||||||
self.call(general.CmdNick(), "/object testalias = testaliasedstring3", "Nick 'testalias' mapped to 'testaliasedstring3'.")
|
self.call(general.CmdNick(), "/object testalias = testaliasedstring3", "Objectnick 'testalias' mapped to 'testaliasedstring3'.")
|
||||||
self.assertEqual(u"testaliasedstring1", self.char1.nicks.get("testalias"))
|
self.assertEqual(u"testaliasedstring1", self.char1.nicks.get("testalias"))
|
||||||
self.assertEqual(u"testaliasedstring2", self.char1.nicks.get("testalias", category="player"))
|
self.assertEqual(None, self.char1.nicks.get("testalias", category="account"))
|
||||||
|
self.assertEqual(u"testaliasedstring2", self.char1.account.nicks.get("testalias", category="account"))
|
||||||
self.assertEqual(u"testaliasedstring3", self.char1.nicks.get("testalias", category="object"))
|
self.assertEqual(u"testaliasedstring3", self.char1.nicks.get("testalias", category="object"))
|
||||||
|
|
||||||
def test_get_and_drop(self):
|
def test_get_and_drop(self):
|
||||||
|
|
@ -131,7 +142,7 @@ class TestGeneral(CommandTest):
|
||||||
self.call(general.CmdSay(), "Testing", "You say, \"Testing\"")
|
self.call(general.CmdSay(), "Testing", "You say, \"Testing\"")
|
||||||
|
|
||||||
def test_whisper(self):
|
def test_whisper(self):
|
||||||
self.call(general.CmdWhisper(), "Obj = Testing", "You whisper to Obj, \"Testing\"")
|
self.call(general.CmdWhisper(), "Obj = Testing", "You whisper to Obj, \"Testing\"", caller=self.char2)
|
||||||
|
|
||||||
def test_access(self):
|
def test_access(self):
|
||||||
self.call(general.CmdAccess(), "", "Permission Hierarchy (climbing):")
|
self.call(general.CmdAccess(), "", "Permission Hierarchy (climbing):")
|
||||||
|
|
@ -170,54 +181,54 @@ class TestAdmin(CommandTest):
|
||||||
self.call(admin.CmdEmit(), "Char2 = Test", "Emitted to Char2:\nTest")
|
self.call(admin.CmdEmit(), "Char2 = Test", "Emitted to Char2:\nTest")
|
||||||
|
|
||||||
def test_perm(self):
|
def test_perm(self):
|
||||||
self.call(admin.CmdPerm(), "Obj = Builders", "Permission 'Builders' given to Obj (the Object/Character).")
|
self.call(admin.CmdPerm(), "Obj = Builder", "Permission 'Builder' given to Obj (the Object/Character).")
|
||||||
self.call(admin.CmdPerm(), "Char2 = Builders", "Permission 'Builders' given to Char2 (the Object/Character).")
|
self.call(admin.CmdPerm(), "Char2 = Builder", "Permission 'Builder' given to Char2 (the Object/Character).")
|
||||||
|
|
||||||
def test_wall(self):
|
def test_wall(self):
|
||||||
self.call(admin.CmdWall(), "Test", "Announcing to all connected players ...")
|
self.call(admin.CmdWall(), "Test", "Announcing to all connected sessions ...")
|
||||||
|
|
||||||
def test_ban(self):
|
def test_ban(self):
|
||||||
self.call(admin.CmdBan(), "Char", "NameBan char was added.")
|
self.call(admin.CmdBan(), "Char", "NameBan char was added.")
|
||||||
|
|
||||||
|
|
||||||
class TestPlayer(CommandTest):
|
class TestAccount(CommandTest):
|
||||||
|
|
||||||
def test_ooc_look(self):
|
def test_ooc_look(self):
|
||||||
if settings.MULTISESSION_MODE < 2:
|
if settings.MULTISESSION_MODE < 2:
|
||||||
self.call(player.CmdOOCLook(), "", "You are outofcharacter (OOC).", caller=self.player)
|
self.call(account.CmdOOCLook(), "", "You are outofcharacter (OOC).", caller=self.account)
|
||||||
if settings.MULTISESSION_MODE == 2:
|
if settings.MULTISESSION_MODE == 2:
|
||||||
self.call(player.CmdOOCLook(), "", "Account TestPlayer (you are OutofCharacter)", caller=self.player)
|
self.call(account.CmdOOCLook(), "", "Account TestAccount (you are OutofCharacter)", caller=self.account)
|
||||||
|
|
||||||
def test_ooc(self):
|
def test_ooc(self):
|
||||||
self.call(player.CmdOOC(), "", "You go OOC.", caller=self.player)
|
self.call(account.CmdOOC(), "", "You go OOC.", caller=self.account)
|
||||||
|
|
||||||
def test_ic(self):
|
def test_ic(self):
|
||||||
self.player.unpuppet_object(self.session)
|
self.account.unpuppet_object(self.session)
|
||||||
self.call(player.CmdIC(), "Char", "You become Char.", caller=self.player, receiver=self.char1)
|
self.call(account.CmdIC(), "Char", "You become Char.", caller=self.account, receiver=self.char1)
|
||||||
|
|
||||||
def test_password(self):
|
def test_password(self):
|
||||||
self.call(player.CmdPassword(), "testpassword = testpassword", "Password changed.", caller=self.player)
|
self.call(account.CmdPassword(), "testpassword = testpassword", "Password changed.", caller=self.account)
|
||||||
|
|
||||||
def test_option(self):
|
def test_option(self):
|
||||||
self.call(player.CmdOption(), "", "Client settings", caller=self.player)
|
self.call(account.CmdOption(), "", "Client settings", caller=self.account)
|
||||||
|
|
||||||
def test_who(self):
|
def test_who(self):
|
||||||
self.call(player.CmdWho(), "", "Players:", caller=self.player)
|
self.call(account.CmdWho(), "", "Accounts:", caller=self.account)
|
||||||
|
|
||||||
def test_quit(self):
|
def test_quit(self):
|
||||||
self.call(player.CmdQuit(), "", "Quitting. Hope to see you again, soon.", caller=self.player)
|
self.call(account.CmdQuit(), "", "Quitting. Hope to see you again, soon.", caller=self.account)
|
||||||
|
|
||||||
def test_sessions(self):
|
def test_sessions(self):
|
||||||
self.call(player.CmdSessions(), "", "Your current session(s):", caller=self.player)
|
self.call(account.CmdSessions(), "", "Your current session(s):", caller=self.account)
|
||||||
|
|
||||||
def test_color_test(self):
|
def test_color_test(self):
|
||||||
self.call(player.CmdColorTest(), "ansi", "ANSI colors:", caller=self.player)
|
self.call(account.CmdColorTest(), "ansi", "ANSI colors:", caller=self.account)
|
||||||
|
|
||||||
def test_char_create(self):
|
def test_char_create(self):
|
||||||
self.call(player.CmdCharCreate(), "Test1=Test char", "Created new character Test1. Use @ic Test1 to enter the game", caller=self.player)
|
self.call(account.CmdCharCreate(), "Test1=Test char", "Created new character Test1. Use @ic Test1 to enter the game", caller=self.account)
|
||||||
|
|
||||||
def test_quell(self):
|
def test_quell(self):
|
||||||
self.call(player.CmdQuell(), "", "Quelling to current puppet's permissions (immortals).", caller=self.player)
|
self.call(account.CmdQuell(), "", "Quelling to current puppet's permissions (developer).", caller=self.account)
|
||||||
|
|
||||||
|
|
||||||
class TestBuilding(CommandTest):
|
class TestBuilding(CommandTest):
|
||||||
|
|
@ -229,7 +240,8 @@ class TestBuilding(CommandTest):
|
||||||
self.call(building.CmdExamine(), "Obj", "Name/key: Obj")
|
self.call(building.CmdExamine(), "Obj", "Name/key: Obj")
|
||||||
|
|
||||||
def test_set_obj_alias(self):
|
def test_set_obj_alias(self):
|
||||||
self.call(building.CmdSetObjAlias(), "Obj = TestObj1b", "Alias(es) for 'Obj(#4)' set to testobj1b.")
|
self.call(building.CmdSetObjAlias(), "Obj =", "Cleared aliases from Obj(#4)")
|
||||||
|
self.call(building.CmdSetObjAlias(), "Obj = TestObj1b", "Alias(es) for 'Obj(#4)' set to 'testobj1b'.")
|
||||||
|
|
||||||
def test_copy(self):
|
def test_copy(self):
|
||||||
self.call(building.CmdCopy(), "Obj = TestObj2;TestObj2b, TestObj3;TestObj3b", "Copied Obj to 'TestObj3' (aliases: ['TestObj3b']")
|
self.call(building.CmdCopy(), "Obj = TestObj2;TestObj2b, TestObj3;TestObj3b", "Copied Obj to 'TestObj3' (aliases: ['TestObj3b']")
|
||||||
|
|
@ -248,7 +260,10 @@ class TestBuilding(CommandTest):
|
||||||
self.call(building.CmdDesc(), "Obj2=TestDesc", "The description was set on Obj2(#5).")
|
self.call(building.CmdDesc(), "Obj2=TestDesc", "The description was set on Obj2(#5).")
|
||||||
|
|
||||||
def test_wipe(self):
|
def test_wipe(self):
|
||||||
|
confirm = building.CmdDestroy.confirm
|
||||||
|
building.CmdDestroy.confirm = False
|
||||||
self.call(building.CmdDestroy(), "Obj", "Obj was destroyed.")
|
self.call(building.CmdDestroy(), "Obj", "Obj was destroyed.")
|
||||||
|
building.CmdDestroy.confirm = confirm
|
||||||
|
|
||||||
def test_dig(self):
|
def test_dig(self):
|
||||||
self.call(building.CmdDig(), "TestRoom1=testroom;tr,back;b", "Created room TestRoom1")
|
self.call(building.CmdDig(), "TestRoom1=testroom;tr,back;b", "Created room TestRoom1")
|
||||||
|
|
@ -256,6 +271,9 @@ class TestBuilding(CommandTest):
|
||||||
def test_tunnel(self):
|
def test_tunnel(self):
|
||||||
self.call(building.CmdTunnel(), "n = TestRoom2;test2", "Created room TestRoom2")
|
self.call(building.CmdTunnel(), "n = TestRoom2;test2", "Created room TestRoom2")
|
||||||
|
|
||||||
|
def test_tunnel_exit_typeclass(self):
|
||||||
|
self.call(building.CmdTunnel(), "n:evennia.objects.objects.DefaultExit = TestRoom3", "Created room TestRoom3")
|
||||||
|
|
||||||
def test_exit_commands(self):
|
def test_exit_commands(self):
|
||||||
self.call(building.CmdOpen(), "TestExit1=Room2", "Created new Exit 'TestExit1' from Room to Room2")
|
self.call(building.CmdOpen(), "TestExit1=Room2", "Created new Exit 'TestExit1' from Room to Room2")
|
||||||
self.call(building.CmdLink(), "TestExit1=Room", "Link created TestExit1 > Room (one way).")
|
self.call(building.CmdLink(), "TestExit1=Room", "Link created TestExit1 > Room (one way).")
|
||||||
|
|
@ -269,10 +287,10 @@ class TestBuilding(CommandTest):
|
||||||
|
|
||||||
def test_typeclass(self):
|
def test_typeclass(self):
|
||||||
self.call(building.CmdTypeclass(), "Obj = evennia.objects.objects.DefaultExit",
|
self.call(building.CmdTypeclass(), "Obj = evennia.objects.objects.DefaultExit",
|
||||||
"Obj changed typeclass from evennia.objects.objects.DefaultObject to evennia.objects.objects.DefaultExit.")
|
"Obj changed typeclass from evennia.objects.objects.DefaultObject to evennia.objects.objects.DefaultExit.")
|
||||||
|
|
||||||
def test_lock(self):
|
def test_lock(self):
|
||||||
self.call(building.CmdLock(), "Obj = test:perm(Immortals)", "Added lock 'test:perm(Immortals)' to Obj.")
|
self.call(building.CmdLock(), "Obj = test:perm(Developer)", "Added lock 'test:perm(Developer)' to Obj.")
|
||||||
|
|
||||||
def test_find(self):
|
def test_find(self):
|
||||||
self.call(building.CmdFind(), "Room2", "One Match")
|
self.call(building.CmdFind(), "Room2", "One Match")
|
||||||
|
|
@ -283,44 +301,111 @@ class TestBuilding(CommandTest):
|
||||||
def test_teleport(self):
|
def test_teleport(self):
|
||||||
self.call(building.CmdTeleport(), "Room2", "Room2(#2)\n|Teleported to Room2.")
|
self.call(building.CmdTeleport(), "Room2", "Room2(#2)\n|Teleported to Room2.")
|
||||||
|
|
||||||
|
def test_spawn(self):
|
||||||
|
def getObject(commandTest, objKeyStr):
|
||||||
|
# A helper function to get a spawned object and
|
||||||
|
# check that it exists in the process.
|
||||||
|
query = search_object(objKeyStr)
|
||||||
|
commandTest.assertIsNotNone(query)
|
||||||
|
obj = query[0]
|
||||||
|
commandTest.assertIsNotNone(obj)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
# Tests "@spawn" without any arguments.
|
||||||
|
self.call(building.CmdSpawn(), " ", "Usage: @spawn")
|
||||||
|
|
||||||
|
# Tests "@spawn <prototype_dictionary>" without specifying location.
|
||||||
|
self.call(building.CmdSpawn(), \
|
||||||
|
"{'key':'goblin', 'typeclass':'evennia.DefaultCharacter'}", "Spawned goblin")
|
||||||
|
goblin = getObject(self, "goblin")
|
||||||
|
|
||||||
|
# Tests that the spawned object's type is a DefaultCharacter.
|
||||||
|
self.assertIsInstance(goblin, DefaultCharacter)
|
||||||
|
|
||||||
|
# Tests that the spawned object's location is the same as the caharacter's location, since
|
||||||
|
# we did not specify it.
|
||||||
|
self.assertEqual(goblin.location, self.char1.location)
|
||||||
|
goblin.delete()
|
||||||
|
|
||||||
|
# Test "@spawn <prototype_dictionary>" with a location other than the character's.
|
||||||
|
spawnLoc = self.room2
|
||||||
|
if spawnLoc == self.char1.location:
|
||||||
|
# Just to make sure we use a different location, in case someone changes
|
||||||
|
# char1's default location in the future...
|
||||||
|
spawnLoc = self.room1
|
||||||
|
|
||||||
|
self.call(building.CmdSpawn(), \
|
||||||
|
"{'prototype':'GOBLIN', 'key':'goblin', 'location':'%s'}" \
|
||||||
|
% spawnLoc.dbref, "Spawned goblin")
|
||||||
|
goblin = getObject(self, "goblin")
|
||||||
|
self.assertEqual(goblin.location, spawnLoc)
|
||||||
|
goblin.delete()
|
||||||
|
|
||||||
|
# Tests "@spawn <prototype_name>"
|
||||||
|
self.call(building.CmdSpawn(), "'BALL'", "Spawned Ball")
|
||||||
|
ball = getObject(self, "Ball")
|
||||||
|
self.assertEqual(ball.location, self.char1.location)
|
||||||
|
self.assertIsInstance(ball, DefaultObject)
|
||||||
|
ball.delete()
|
||||||
|
|
||||||
|
# Tests "@spawn/noloc ..." without specifying a location.
|
||||||
|
# Location should be "None".
|
||||||
|
self.call(building.CmdSpawn(), "/noloc 'BALL'", "Spawned Ball")
|
||||||
|
ball = getObject(self, "Ball")
|
||||||
|
self.assertIsNone(ball.location)
|
||||||
|
ball.delete()
|
||||||
|
|
||||||
|
# Tests "@spawn/noloc ...", but DO specify a location.
|
||||||
|
# Location should be the specified location.
|
||||||
|
self.call(building.CmdSpawn(), \
|
||||||
|
"/noloc {'prototype':'BALL', 'location':'%s'}" \
|
||||||
|
% spawnLoc.dbref, "Spawned Ball")
|
||||||
|
ball = getObject(self, "Ball")
|
||||||
|
self.assertEqual(ball.location, spawnLoc)
|
||||||
|
ball.delete()
|
||||||
|
|
||||||
|
# test calling spawn with an invalid prototype.
|
||||||
|
self.call(building.CmdSpawn(), \
|
||||||
|
"'NO_EXIST'", "No prototype named 'NO_EXIST'")
|
||||||
|
|
||||||
|
|
||||||
class TestComms(CommandTest):
|
class TestComms(CommandTest):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(CommandTest, self).setUp()
|
super(CommandTest, self).setUp()
|
||||||
self.call(comms.CmdChannelCreate(), "testchan;test=Test Channel", "Created channel testchan and connected to it.", receiver=self.player)
|
self.call(comms.CmdChannelCreate(), "testchan;test=Test Channel", "Created channel testchan and connected to it.", receiver=self.account)
|
||||||
|
|
||||||
def test_toggle_com(self):
|
def test_toggle_com(self):
|
||||||
self.call(comms.CmdAddCom(), "tc = testchan", "You are already connected to channel testchan. You can now", receiver=self.player)
|
self.call(comms.CmdAddCom(), "tc = testchan", "You are already connected to channel testchan. You can now", receiver=self.account)
|
||||||
self.call(comms.CmdDelCom(), "tc", "Your alias 'tc' for channel testchan was cleared.", receiver=self.player)
|
self.call(comms.CmdDelCom(), "tc", "Your alias 'tc' for channel testchan was cleared.", receiver=self.account)
|
||||||
|
|
||||||
def test_channels(self):
|
def test_channels(self):
|
||||||
self.call(comms.CmdChannels(), "" ,"Available channels (use comlist,addcom and delcom to manage", receiver=self.player)
|
self.call(comms.CmdChannels(), "", "Available channels (use comlist,addcom and delcom to manage", receiver=self.account)
|
||||||
|
|
||||||
def test_all_com(self):
|
def test_all_com(self):
|
||||||
self.call(comms.CmdAllCom(), "", "Available channels (use comlist,addcom and delcom to manage", receiver=self.player)
|
self.call(comms.CmdAllCom(), "", "Available channels (use comlist,addcom and delcom to manage", receiver=self.account)
|
||||||
|
|
||||||
def test_clock(self):
|
def test_clock(self):
|
||||||
self.call(comms.CmdClock(), "testchan=send:all()", "Lock(s) applied. Current locks on testchan:", receiver=self.player)
|
self.call(comms.CmdClock(), "testchan=send:all()", "Lock(s) applied. Current locks on testchan:", receiver=self.account)
|
||||||
|
|
||||||
def test_cdesc(self):
|
def test_cdesc(self):
|
||||||
self.call(comms.CmdCdesc(), "testchan = Test Channel", "Description of channel 'testchan' set to 'Test Channel'.", receiver=self.player)
|
self.call(comms.CmdCdesc(), "testchan = Test Channel", "Description of channel 'testchan' set to 'Test Channel'.", receiver=self.account)
|
||||||
|
|
||||||
def test_cemit(self):
|
def test_cemit(self):
|
||||||
self.call(comms.CmdCemit(), "testchan = Test Message", "[testchan] Test Message|Sent to channel testchan: Test Message", receiver=self.player)
|
self.call(comms.CmdCemit(), "testchan = Test Message", "[testchan] Test Message|Sent to channel testchan: Test Message", receiver=self.account)
|
||||||
|
|
||||||
def test_cwho(self):
|
def test_cwho(self):
|
||||||
self.call(comms.CmdCWho(), "testchan", "Channel subscriptions\ntestchan:\n TestPlayer", receiver=self.player)
|
self.call(comms.CmdCWho(), "testchan", "Channel subscriptions\ntestchan:\n TestAccount", receiver=self.account)
|
||||||
|
|
||||||
def test_page(self):
|
def test_page(self):
|
||||||
self.call(comms.CmdPage(), "TestPlayer2 = Test", "TestPlayer2 is offline. They will see your message if they list their pages later.|You paged TestPlayer2 with: 'Test'.", receiver=self.player)
|
self.call(comms.CmdPage(), "TestAccount2 = Test", "TestAccount2 is offline. They will see your message if they list their pages later.|You paged TestAccount2 with: 'Test'.", receiver=self.account)
|
||||||
|
|
||||||
def test_cboot(self):
|
def test_cboot(self):
|
||||||
# No one else connected to boot
|
# No one else connected to boot
|
||||||
self.call(comms.CmdCBoot(), "", "Usage: @cboot[/quiet] <channel> = <player> [:reason]", receiver=self.player)
|
self.call(comms.CmdCBoot(), "", "Usage: @cboot[/quiet] <channel> = <account> [:reason]", receiver=self.account)
|
||||||
|
|
||||||
def test_cdestroy(self):
|
def test_cdestroy(self):
|
||||||
self.call(comms.CmdCdestroy(), "testchan" ,"[testchan] TestPlayer: testchan is being destroyed. Make sure to change your aliases.|Channel 'testchan' was destroyed.", receiver=self.player)
|
self.call(comms.CmdCdestroy(), "testchan", "[testchan] TestAccount: testchan is being destroyed. Make sure to change your aliases.|Channel 'testchan' was destroyed.", receiver=self.account)
|
||||||
|
|
||||||
|
|
||||||
class TestBatchProcess(CommandTest):
|
class TestBatchProcess(CommandTest):
|
||||||
|
|
@ -328,7 +413,11 @@ class TestBatchProcess(CommandTest):
|
||||||
# cannot test batchcode here, it must run inside the server process
|
# cannot test batchcode here, it must run inside the server process
|
||||||
self.call(batchprocess.CmdBatchCommands(), "example_batch_cmds", "Running Batchcommand processor Automatic mode for example_batch_cmds")
|
self.call(batchprocess.CmdBatchCommands(), "example_batch_cmds", "Running Batchcommand processor Automatic mode for example_batch_cmds")
|
||||||
# we make sure to delete the button again here to stop the running reactor
|
# we make sure to delete the button again here to stop the running reactor
|
||||||
|
confirm = building.CmdDestroy.confirm
|
||||||
|
building.CmdDestroy.confirm = False
|
||||||
self.call(building.CmdDestroy(), "button", "button was destroyed.")
|
self.call(building.CmdDestroy(), "button", "button was destroyed.")
|
||||||
|
building.CmdDestroy.confirm = confirm
|
||||||
|
|
||||||
|
|
||||||
class CmdInterrupt(Command):
|
class CmdInterrupt(Command):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from collections import defaultdict
|
||||||
from random import getrandbits
|
from random import getrandbits
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate
|
||||||
from evennia.players.models import PlayerDB
|
from evennia.accounts.models import AccountDB
|
||||||
from evennia.objects.models import ObjectDB
|
from evennia.objects.models import ObjectDB
|
||||||
from evennia.server.models import ServerConfig
|
from evennia.server.models import ServerConfig
|
||||||
from evennia.comms.models import ChannelDB
|
from evennia.comms.models import ChannelDB
|
||||||
|
|
@ -25,7 +25,7 @@ MULTISESSION_MODE = settings.MULTISESSION_MODE
|
||||||
CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
|
CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
|
||||||
|
|
||||||
# Helper function to throttle failed connection attempts.
|
# Helper function to throttle failed connection attempts.
|
||||||
# This can easily be used to limit player creation too,
|
# This can easily be used to limit account creation too,
|
||||||
# (just supply a different storage dictionary), but this
|
# (just supply a different storage dictionary), but this
|
||||||
# would also block dummyrunner, so it's not added as default.
|
# would also block dummyrunner, so it's not added as default.
|
||||||
|
|
||||||
|
|
@ -77,17 +77,17 @@ def _throttle(session, maxlim=None, timeout=None, storage=_LATEST_FAILED_LOGINS)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def create_guest_player(session):
|
def create_guest_account(session):
|
||||||
"""
|
"""
|
||||||
Creates a guest player/character for this session, if one is available.
|
Creates a guest account/character for this session, if one is available.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
session (Session): the session which will use the guest player/character.
|
session (Session): the session which will use the guest account/character.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
GUEST_ENABLED (boolean), player (Player):
|
GUEST_ENABLED (boolean), account (Account):
|
||||||
the boolean is whether guest accounts are enabled at all.
|
the boolean is whether guest accounts are enabled at all.
|
||||||
the Player which was created from an available guest name.
|
the Account which was created from an available guest name.
|
||||||
"""
|
"""
|
||||||
# check if guests are enabled.
|
# check if guests are enabled.
|
||||||
if not settings.GUEST_ENABLED:
|
if not settings.GUEST_ENABLED:
|
||||||
|
|
@ -105,25 +105,25 @@ def create_guest_player(session):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Find an available guest name.
|
# Find an available guest name.
|
||||||
playername = None
|
accountname = None
|
||||||
for name in settings.GUEST_LIST:
|
for name in settings.GUEST_LIST:
|
||||||
if not PlayerDB.objects.filter(username__iexact=playername).count():
|
if not AccountDB.objects.filter(username__iexact=accountname).count():
|
||||||
playername = name
|
accountname = name
|
||||||
break
|
break
|
||||||
if not playername:
|
if not accountname:
|
||||||
session.msg("All guest accounts are in use. Please try again later.")
|
session.msg("All guest accounts are in use. Please try again later.")
|
||||||
return True, None
|
return True, None
|
||||||
else:
|
else:
|
||||||
# build a new player with the found guest playername
|
# build a new account with the found guest accountname
|
||||||
password = "%016x" % getrandbits(64)
|
password = "%016x" % getrandbits(64)
|
||||||
home = ObjectDB.objects.get_id(settings.GUEST_HOME)
|
home = ObjectDB.objects.get_id(settings.GUEST_HOME)
|
||||||
permissions = settings.PERMISSION_GUEST_DEFAULT
|
permissions = settings.PERMISSION_GUEST_DEFAULT
|
||||||
typeclass = settings.BASE_CHARACTER_TYPECLASS
|
typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||||
ptypeclass = settings.BASE_GUEST_TYPECLASS
|
ptypeclass = settings.BASE_GUEST_TYPECLASS
|
||||||
new_player = _create_player(session, playername, password, permissions, ptypeclass)
|
new_account = _create_account(session, accountname, password, permissions, ptypeclass)
|
||||||
if new_player:
|
if new_account:
|
||||||
_create_character(session, new_player, typeclass, home, permissions)
|
_create_character(session, new_account, typeclass, home, permissions)
|
||||||
return True, new_player
|
return True, new_account
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
# We are in the middle between logged in and -not, so we have
|
# We are in the middle between logged in and -not, so we have
|
||||||
|
|
@ -134,42 +134,42 @@ def create_guest_player(session):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def create_normal_player(session, name, password):
|
def create_normal_account(session, name, password):
|
||||||
"""
|
"""
|
||||||
Creates a player with the given name and password.
|
Creates an account with the given name and password.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
session (Session): the session which is requesting to create a player.
|
session (Session): the session which is requesting to create an account.
|
||||||
name (str): the name that the player wants to use for login.
|
name (str): the name that the account wants to use for login.
|
||||||
password (str): the password desired by this player, for login.
|
password (str): the password desired by this account, for login.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
player (Player): the player which was created from the name and password.
|
account (Account): the account which was created from the name and password.
|
||||||
"""
|
"""
|
||||||
# check for too many login errors too quick.
|
# check for too many login errors too quick.
|
||||||
if _throttle(session, maxlim=5, timeout=5*60):
|
if _throttle(session, maxlim=5, timeout=5 * 60):
|
||||||
# timeout is 5 minutes.
|
# timeout is 5 minutes.
|
||||||
session.msg("|RYou made too many connection attempts. Try again in a few minutes.|n")
|
session.msg("|RYou made too many connection attempts. Try again in a few minutes.|n")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Match account name and check password
|
# Match account name and check password
|
||||||
player = authenticate(username=name, password=password)
|
account = authenticate(username=name, password=password)
|
||||||
|
|
||||||
if not player:
|
if not account:
|
||||||
# No playername or password match
|
# No accountname or password match
|
||||||
session.msg("Incorrect login information given.")
|
session.msg("Incorrect login information given.")
|
||||||
# this just updates the throttle
|
# this just updates the throttle
|
||||||
_throttle(session)
|
_throttle(session)
|
||||||
# calls player hook for a failed login if possible.
|
# calls account hook for a failed login if possible.
|
||||||
player = PlayerDB.objects.get_player_from_name(name)
|
account = AccountDB.objects.get_account_from_name(name)
|
||||||
if player:
|
if account:
|
||||||
player.at_failed_login(session)
|
account.at_failed_login(session)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Check IP and/or name bans
|
# Check IP and/or name bans
|
||||||
bans = ServerConfig.objects.conf("server_bans")
|
bans = ServerConfig.objects.conf("server_bans")
|
||||||
if bans and (any(tup[0] == player.name.lower() for tup in bans)
|
if bans and (any(tup[0] == account.name.lower() for tup in bans) or
|
||||||
or
|
|
||||||
any(tup[2].match(session.address) for tup in bans if tup[2])):
|
any(tup[2].match(session.address) for tup in bans if tup[2])):
|
||||||
# this is a banned IP or name!
|
# this is a banned IP or name!
|
||||||
string = "|rYou have been banned and cannot continue from here." \
|
string = "|rYou have been banned and cannot continue from here." \
|
||||||
|
|
@ -178,7 +178,7 @@ def create_normal_player(session, name, password):
|
||||||
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
|
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return player
|
return account
|
||||||
|
|
||||||
|
|
||||||
class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
|
class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -186,8 +186,8 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
|
||||||
connect to the game
|
connect to the game
|
||||||
|
|
||||||
Usage (at login screen):
|
Usage (at login screen):
|
||||||
connect playername password
|
connect accountname password
|
||||||
connect "player name" "pass word"
|
connect "account name" "pass word"
|
||||||
|
|
||||||
Use the create command to first create an account before logging in.
|
Use the create command to first create an account before logging in.
|
||||||
|
|
||||||
|
|
@ -204,12 +204,12 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
|
||||||
have a unique position in that their func() receives
|
have a unique position in that their func() receives
|
||||||
a session object instead of a source_object like all
|
a session object instead of a source_object like all
|
||||||
other types of logged-in commands (this is because
|
other types of logged-in commands (this is because
|
||||||
there is no object yet before the player has logged in)
|
there is no object yet before the account has logged in)
|
||||||
"""
|
"""
|
||||||
session = self.caller
|
session = self.caller
|
||||||
|
|
||||||
# check for too many login errors too quick.
|
# check for too many login errors too quick.
|
||||||
if _throttle(session, maxlim=5, timeout=5*60, storage=_LATEST_FAILED_LOGINS):
|
if _throttle(session, maxlim=5, timeout=5 * 60, storage=_LATEST_FAILED_LOGINS):
|
||||||
# timeout is 5 minutes.
|
# timeout is 5 minutes.
|
||||||
session.msg("|RYou made too many connection attempts. Try again in a few minutes.|n")
|
session.msg("|RYou made too many connection attempts. Try again in a few minutes.|n")
|
||||||
return
|
return
|
||||||
|
|
@ -222,9 +222,9 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
|
||||||
parts = parts[0].split(None, 1)
|
parts = parts[0].split(None, 1)
|
||||||
# Guest login
|
# Guest login
|
||||||
if len(parts) == 1 and parts[0].lower() == "guest":
|
if len(parts) == 1 and parts[0].lower() == "guest":
|
||||||
enabled, new_player = create_guest_player(session)
|
enabled, new_account = create_guest_account(session)
|
||||||
if new_player:
|
if new_account:
|
||||||
session.sessionhandler.login(session, new_player)
|
session.sessionhandler.login(session, new_account)
|
||||||
if enabled:
|
if enabled:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -233,20 +233,20 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
|
||||||
return
|
return
|
||||||
|
|
||||||
name, password = parts
|
name, password = parts
|
||||||
player = create_normal_player(session, name, password)
|
account = create_normal_account(session, name, password)
|
||||||
if player:
|
if account:
|
||||||
session.sessionhandler.login(session, player)
|
session.sessionhandler.login(session, account)
|
||||||
|
|
||||||
|
|
||||||
class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
|
class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
create a new player account
|
create a new account account
|
||||||
|
|
||||||
Usage (at login screen):
|
Usage (at login screen):
|
||||||
create <playername> <password>
|
create <accountname> <password>
|
||||||
create "player name" "pass word"
|
create "account name" "pass word"
|
||||||
|
|
||||||
This creates a new player account.
|
This creates a new account account.
|
||||||
|
|
||||||
If you have spaces in your name, enclose it in double quotes.
|
If you have spaces in your name, enclose it in double quotes.
|
||||||
"""
|
"""
|
||||||
|
|
@ -271,29 +271,29 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
|
||||||
"\nIf <name> or <password> contains spaces, enclose it in double quotes."
|
"\nIf <name> or <password> contains spaces, enclose it in double quotes."
|
||||||
session.msg(string)
|
session.msg(string)
|
||||||
return
|
return
|
||||||
playername, password = parts
|
accountname, password = parts
|
||||||
|
|
||||||
# sanity checks
|
# sanity checks
|
||||||
if not re.findall(r"^[\w. @+\-']+$", playername) or not (0 < len(playername) <= 30):
|
if not re.findall(r"^[\w. @+\-']+$", accountname) or not (0 < len(accountname) <= 30):
|
||||||
# this echoes the restrictions made by django's auth
|
# this echoes the restrictions made by django's auth
|
||||||
# module (except not allowing spaces, for convenience of
|
# module (except not allowing spaces, for convenience of
|
||||||
# logging in).
|
# logging in).
|
||||||
string = "\n\r Playername can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_/' only."
|
string = "\n\r Accountname can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_/' only."
|
||||||
session.msg(string)
|
session.msg(string)
|
||||||
return
|
return
|
||||||
# strip excessive spaces in playername
|
# strip excessive spaces in accountname
|
||||||
playername = re.sub(r"\s+", " ", playername).strip()
|
accountname = re.sub(r"\s+", " ", accountname).strip()
|
||||||
if PlayerDB.objects.filter(username__iexact=playername):
|
if AccountDB.objects.filter(username__iexact=accountname):
|
||||||
# player already exists (we also ignore capitalization here)
|
# account already exists (we also ignore capitalization here)
|
||||||
session.msg("Sorry, there is already a player with the name '%s'." % playername)
|
session.msg("Sorry, there is already an account with the name '%s'." % accountname)
|
||||||
return
|
return
|
||||||
# Reserve playernames found in GUEST_LIST
|
# Reserve accountnames found in GUEST_LIST
|
||||||
if settings.GUEST_LIST and playername.lower() in (guest.lower() for guest in settings.GUEST_LIST):
|
if settings.GUEST_LIST and accountname.lower() in (guest.lower() for guest in settings.GUEST_LIST):
|
||||||
string = "\n\r That name is reserved. Please choose another Playername."
|
string = "\n\r That name is reserved. Please choose another Accountname."
|
||||||
session.msg(string)
|
session.msg(string)
|
||||||
return
|
return
|
||||||
if not re.findall(r"^[\w. @+\-']+$", password) or not (3 < len(password)):
|
if not re.findall(r"^[\w. @+\-']+$", password) or not (3 < len(password)):
|
||||||
string = "\n\r Password should be longer than 3 characers. Letters, spaces, digits and @/./+/-/_/' only." \
|
string = "\n\r Password should be longer than 3 characters. Letters, spaces, digits and @/./+/-/_/' only." \
|
||||||
"\nFor best security, make it longer than 8 characters. You can also use a phrase of" \
|
"\nFor best security, make it longer than 8 characters. You can also use a phrase of" \
|
||||||
"\nmany words if you enclose the password in double quotes."
|
"\nmany words if you enclose the password in double quotes."
|
||||||
session.msg(string)
|
session.msg(string)
|
||||||
|
|
@ -301,8 +301,8 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
# Check IP and/or name bans
|
# Check IP and/or name bans
|
||||||
bans = ServerConfig.objects.conf("server_bans")
|
bans = ServerConfig.objects.conf("server_bans")
|
||||||
if bans and (any(tup[0] == playername.lower() for tup in bans)
|
if bans and (any(tup[0] == accountname.lower() for tup in bans) or
|
||||||
or
|
|
||||||
any(tup[2].match(session.address) for tup in bans if tup[2])):
|
any(tup[2].match(session.address) for tup in bans if tup[2])):
|
||||||
# this is a banned IP or name!
|
# this is a banned IP or name!
|
||||||
string = "|rYou have been banned and cannot continue from here." \
|
string = "|rYou have been banned and cannot continue from here." \
|
||||||
|
|
@ -311,22 +311,22 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
|
||||||
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
|
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# everything's ok. Create the new player account.
|
# everything's ok. Create the new account account.
|
||||||
try:
|
try:
|
||||||
permissions = settings.PERMISSION_PLAYER_DEFAULT
|
permissions = settings.PERMISSION_ACCOUNT_DEFAULT
|
||||||
typeclass = settings.BASE_CHARACTER_TYPECLASS
|
typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||||
new_player = _create_player(session, playername, password, permissions)
|
new_account = _create_account(session, accountname, password, permissions)
|
||||||
if new_player:
|
if new_account:
|
||||||
if MULTISESSION_MODE < 2:
|
if MULTISESSION_MODE < 2:
|
||||||
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
|
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
|
||||||
_create_character(session, new_player, typeclass, default_home, permissions)
|
_create_character(session, new_account, typeclass, default_home, permissions)
|
||||||
# tell the caller everything went well.
|
# tell the caller everything went well.
|
||||||
string = "A new account '%s' was created. Welcome!"
|
string = "A new account '%s' was created. Welcome!"
|
||||||
if " " in playername:
|
if " " in accountname:
|
||||||
string += "\n\nYou can now log in with the command 'connect \"%s\" <your password>'."
|
string += "\n\nYou can now log in with the command 'connect \"%s\" <your password>'."
|
||||||
else:
|
else:
|
||||||
string += "\n\nYou can now log with the command 'connect %s <your password>'."
|
string += "\n\nYou can now log with the command 'connect %s <your password>'."
|
||||||
session.msg(string % (playername, playername))
|
session.msg(string % (accountname, accountname))
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
# We are in the middle between logged in and -not, so we have
|
# We are in the middle between logged in and -not, so we have
|
||||||
|
|
@ -344,7 +344,7 @@ class CmdUnconnectedQuit(COMMAND_DEFAULT_CLASS):
|
||||||
quit
|
quit
|
||||||
|
|
||||||
We maintain a different version of the quit command
|
We maintain a different version of the quit command
|
||||||
here for unconnected players for the sake of simplicity. The logged in
|
here for unconnected accounts for the sake of simplicity. The logged in
|
||||||
version is a bit more complicated.
|
version is a bit more complicated.
|
||||||
"""
|
"""
|
||||||
key = "quit"
|
key = "quit"
|
||||||
|
|
@ -516,50 +516,50 @@ class CmdUnconnectedScreenreader(COMMAND_DEFAULT_CLASS):
|
||||||
self.session.sessionhandler.session_portal_sync(self.session)
|
self.session.sessionhandler.session_portal_sync(self.session)
|
||||||
|
|
||||||
|
|
||||||
def _create_player(session, playername, password, permissions, typeclass=None, email=None):
|
def _create_account(session, accountname, password, permissions, typeclass=None, email=None):
|
||||||
"""
|
"""
|
||||||
Helper function, creates a player of the specified typeclass.
|
Helper function, creates an account of the specified typeclass.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
new_player = create.create_player(playername, email, password, permissions=permissions, typeclass=typeclass)
|
new_account = create.create_account(accountname, email, password, permissions=permissions, typeclass=typeclass)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
session.msg("There was an error creating the Player:\n%s\n If this problem persists, contact an admin." % e)
|
session.msg("There was an error creating the Account:\n%s\n If this problem persists, contact an admin." % e)
|
||||||
logger.log_trace()
|
logger.log_trace()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# This needs to be set so the engine knows this player is
|
# This needs to be set so the engine knows this account is
|
||||||
# logging in for the first time. (so it knows to call the right
|
# logging in for the first time. (so it knows to call the right
|
||||||
# hooks during login later)
|
# hooks during login later)
|
||||||
new_player.db.FIRST_LOGIN = True
|
new_account.db.FIRST_LOGIN = True
|
||||||
|
|
||||||
# join the new player to the public channel
|
# join the new account to the public channel
|
||||||
pchannel = ChannelDB.objects.get_channel(settings.DEFAULT_CHANNELS[0]["key"])
|
pchannel = ChannelDB.objects.get_channel(settings.DEFAULT_CHANNELS[0]["key"])
|
||||||
if not pchannel or not pchannel.connect(new_player):
|
if not pchannel or not pchannel.connect(new_account):
|
||||||
string = "New player '%s' could not connect to public channel!" % new_player.key
|
string = "New account '%s' could not connect to public channel!" % new_account.key
|
||||||
logger.log_err(string)
|
logger.log_err(string)
|
||||||
return new_player
|
return new_account
|
||||||
|
|
||||||
|
|
||||||
def _create_character(session, new_player, typeclass, home, permissions):
|
def _create_character(session, new_account, typeclass, home, permissions):
|
||||||
"""
|
"""
|
||||||
Helper function, creates a character based on a player's name.
|
Helper function, creates a character based on an account's name.
|
||||||
This is meant for Guest and MULTISESSION_MODE < 2 situations.
|
This is meant for Guest and MULTISESSION_MODE < 2 situations.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
new_character = create.create_object(typeclass, key=new_player.key, home=home, permissions=permissions)
|
new_character = create.create_object(typeclass, key=new_account.key, home=home, permissions=permissions)
|
||||||
# set playable character list
|
# set playable character list
|
||||||
new_player.db._playable_characters.append(new_character)
|
new_account.db._playable_characters.append(new_character)
|
||||||
|
|
||||||
# allow only the character itself and the player to puppet this character (and Immortals).
|
# allow only the character itself and the account to puppet this character (and Developers).
|
||||||
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" %
|
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" %
|
||||||
(new_character.id, new_player.id))
|
(new_character.id, new_account.id))
|
||||||
|
|
||||||
# If no description is set, set a default description
|
# If no description is set, set a default description
|
||||||
if not new_character.db.desc:
|
if not new_character.db.desc:
|
||||||
new_character.db.desc = "This is a Player."
|
new_character.db.desc = "This is a character."
|
||||||
# We need to set this to have @ic auto-connect to this character
|
# We need to set this to have @ic auto-connect to this character
|
||||||
new_player.db._last_puppet = new_character
|
new_account.db._last_puppet = new_character
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
session.msg("There was an error creating the Character:\n%s\n If this problem persists, contact an admin." % e)
|
session.msg("There was an error creating the Character:\n%s\n If this problem persists, contact an admin." % e)
|
||||||
logger.log_trace()
|
logger.log_trace()
|
||||||
|
|
|
||||||
|
|
@ -12,46 +12,67 @@ from evennia.commands.command import Command
|
||||||
|
|
||||||
class _CmdA(Command):
|
class _CmdA(Command):
|
||||||
key = "A"
|
key = "A"
|
||||||
|
|
||||||
def __init__(self, cmdset, *args, **kwargs):
|
def __init__(self, cmdset, *args, **kwargs):
|
||||||
super(_CmdA, self).__init__(*args, **kwargs)
|
super(_CmdA, self).__init__(*args, **kwargs)
|
||||||
self.from_cmdset = cmdset
|
self.from_cmdset = cmdset
|
||||||
|
|
||||||
|
|
||||||
class _CmdB(Command):
|
class _CmdB(Command):
|
||||||
key = "B"
|
key = "B"
|
||||||
|
|
||||||
def __init__(self, cmdset, *args, **kwargs):
|
def __init__(self, cmdset, *args, **kwargs):
|
||||||
super(_CmdB, self).__init__(*args, **kwargs)
|
super(_CmdB, self).__init__(*args, **kwargs)
|
||||||
self.from_cmdset = cmdset
|
self.from_cmdset = cmdset
|
||||||
|
|
||||||
|
|
||||||
class _CmdC(Command):
|
class _CmdC(Command):
|
||||||
key = "C"
|
key = "C"
|
||||||
|
|
||||||
def __init__(self, cmdset, *args, **kwargs):
|
def __init__(self, cmdset, *args, **kwargs):
|
||||||
super(_CmdC, self).__init__(*args, **kwargs)
|
super(_CmdC, self).__init__(*args, **kwargs)
|
||||||
self.from_cmdset = cmdset
|
self.from_cmdset = cmdset
|
||||||
|
|
||||||
|
|
||||||
class _CmdD(Command):
|
class _CmdD(Command):
|
||||||
key = "D"
|
key = "D"
|
||||||
|
|
||||||
def __init__(self, cmdset, *args, **kwargs):
|
def __init__(self, cmdset, *args, **kwargs):
|
||||||
super(_CmdD, self).__init__(*args, **kwargs)
|
super(_CmdD, self).__init__(*args, **kwargs)
|
||||||
self.from_cmdset = cmdset
|
self.from_cmdset = cmdset
|
||||||
|
|
||||||
|
|
||||||
class _CmdSetA(CmdSet):
|
class _CmdSetA(CmdSet):
|
||||||
key = "A"
|
key = "A"
|
||||||
def at_cmdset_creation(self):
|
|
||||||
|
def at_cmdset_creation(self):
|
||||||
self.add(_CmdA("A"))
|
self.add(_CmdA("A"))
|
||||||
self.add(_CmdB("A"))
|
self.add(_CmdB("A"))
|
||||||
self.add(_CmdC("A"))
|
self.add(_CmdC("A"))
|
||||||
self.add(_CmdD("A"))
|
self.add(_CmdD("A"))
|
||||||
|
|
||||||
|
|
||||||
class _CmdSetB(CmdSet):
|
class _CmdSetB(CmdSet):
|
||||||
key = "B"
|
key = "B"
|
||||||
def at_cmdset_creation(self):
|
|
||||||
|
def at_cmdset_creation(self):
|
||||||
self.add(_CmdA("B"))
|
self.add(_CmdA("B"))
|
||||||
self.add(_CmdB("B"))
|
self.add(_CmdB("B"))
|
||||||
self.add(_CmdC("B"))
|
self.add(_CmdC("B"))
|
||||||
|
|
||||||
|
|
||||||
class _CmdSetC(CmdSet):
|
class _CmdSetC(CmdSet):
|
||||||
key = "C"
|
key = "C"
|
||||||
def at_cmdset_creation(self):
|
|
||||||
|
def at_cmdset_creation(self):
|
||||||
self.add(_CmdA("C"))
|
self.add(_CmdA("C"))
|
||||||
self.add(_CmdB("C"))
|
self.add(_CmdB("C"))
|
||||||
|
|
||||||
|
|
||||||
class _CmdSetD(CmdSet):
|
class _CmdSetD(CmdSet):
|
||||||
key = "D"
|
key = "D"
|
||||||
def at_cmdset_creation(self):
|
|
||||||
|
def at_cmdset_creation(self):
|
||||||
self.add(_CmdA("D"))
|
self.add(_CmdA("D"))
|
||||||
self.add(_CmdB("D"))
|
self.add(_CmdB("D"))
|
||||||
self.add(_CmdC("D"))
|
self.add(_CmdC("D"))
|
||||||
|
|
@ -59,8 +80,10 @@ class _CmdSetD(CmdSet):
|
||||||
|
|
||||||
# testing Command Sets
|
# testing Command Sets
|
||||||
|
|
||||||
|
|
||||||
class TestCmdSetMergers(TestCase):
|
class TestCmdSetMergers(TestCase):
|
||||||
"Test merging of cmdsets"
|
"Test merging of cmdsets"
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestCmdSetMergers, self).setUp()
|
super(TestCmdSetMergers, self).setUp()
|
||||||
self.cmdset_a = _CmdSetA()
|
self.cmdset_a = _CmdSetA()
|
||||||
|
|
@ -70,16 +93,16 @@ class TestCmdSetMergers(TestCase):
|
||||||
|
|
||||||
def test_union(self):
|
def test_union(self):
|
||||||
a, c = self.cmdset_a, self.cmdset_c
|
a, c = self.cmdset_a, self.cmdset_c
|
||||||
cmdset_f = a + c # same-prio
|
cmdset_f = a + c # same-prio
|
||||||
self.assertEqual(len(cmdset_f.commands), 4)
|
self.assertEqual(len(cmdset_f.commands), 4)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 2)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 2)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 2)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 2)
|
||||||
cmdset_f = c + a # same-prio, inverse order
|
cmdset_f = c + a # same-prio, inverse order
|
||||||
self.assertEqual(len(cmdset_f.commands), 4)
|
self.assertEqual(len(cmdset_f.commands), 4)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 4)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 4)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 0)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 0)
|
||||||
a.priority = 1
|
a.priority = 1
|
||||||
cmdset_f = a + c # high prio A
|
cmdset_f = a + c # high prio A
|
||||||
self.assertEqual(len(cmdset_f.commands), 4)
|
self.assertEqual(len(cmdset_f.commands), 4)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 4)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 4)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 0)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 0)
|
||||||
|
|
@ -87,16 +110,16 @@ class TestCmdSetMergers(TestCase):
|
||||||
def test_intersect(self):
|
def test_intersect(self):
|
||||||
a, c = self.cmdset_a, self.cmdset_c
|
a, c = self.cmdset_a, self.cmdset_c
|
||||||
a.mergetype = "Intersect"
|
a.mergetype = "Intersect"
|
||||||
cmdset_f = a + c # same-prio - c's Union kicks in
|
cmdset_f = a + c # same-prio - c's Union kicks in
|
||||||
self.assertEqual(len(cmdset_f.commands), 4)
|
self.assertEqual(len(cmdset_f.commands), 4)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 2)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 2)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 2)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 2)
|
||||||
cmdset_f = c + a # same-prio - a's Intersect kicks in
|
cmdset_f = c + a # same-prio - a's Intersect kicks in
|
||||||
self.assertEqual(len(cmdset_f.commands), 2)
|
self.assertEqual(len(cmdset_f.commands), 2)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 2)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 2)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 0)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 0)
|
||||||
a.priority = 1
|
a.priority = 1
|
||||||
cmdset_f = a + c # high prio A, intersect kicks in
|
cmdset_f = a + c # high prio A, intersect kicks in
|
||||||
self.assertEqual(len(cmdset_f.commands), 2)
|
self.assertEqual(len(cmdset_f.commands), 2)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 2)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 2)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 0)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 0)
|
||||||
|
|
@ -104,16 +127,16 @@ class TestCmdSetMergers(TestCase):
|
||||||
def test_replace(self):
|
def test_replace(self):
|
||||||
a, c = self.cmdset_a, self.cmdset_c
|
a, c = self.cmdset_a, self.cmdset_c
|
||||||
c.mergetype = "Replace"
|
c.mergetype = "Replace"
|
||||||
cmdset_f = a + c # same-prio. C's Replace kicks in
|
cmdset_f = a + c # same-prio. C's Replace kicks in
|
||||||
self.assertEqual(len(cmdset_f.commands), 2)
|
self.assertEqual(len(cmdset_f.commands), 2)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 0)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 0)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 2)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 2)
|
||||||
cmdset_f = c + a # same-prio. A's Union kicks in
|
cmdset_f = c + a # same-prio. A's Union kicks in
|
||||||
self.assertEqual(len(cmdset_f.commands), 4)
|
self.assertEqual(len(cmdset_f.commands), 4)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 4)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 4)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 0)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 0)
|
||||||
c.priority = 1
|
c.priority = 1
|
||||||
cmdset_f = c + a # c higher prio. C's Replace kicks in
|
cmdset_f = c + a # c higher prio. C's Replace kicks in
|
||||||
self.assertEqual(len(cmdset_f.commands), 2)
|
self.assertEqual(len(cmdset_f.commands), 2)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 0)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 0)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 2)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 2)
|
||||||
|
|
@ -121,16 +144,16 @@ class TestCmdSetMergers(TestCase):
|
||||||
def test_remove(self):
|
def test_remove(self):
|
||||||
a, c = self.cmdset_a, self.cmdset_c
|
a, c = self.cmdset_a, self.cmdset_c
|
||||||
c.mergetype = "Remove"
|
c.mergetype = "Remove"
|
||||||
cmdset_f = a + c # same-prio. C's Remove kicks in
|
cmdset_f = a + c # same-prio. C's Remove kicks in
|
||||||
self.assertEqual(len(cmdset_f.commands), 2)
|
self.assertEqual(len(cmdset_f.commands), 2)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 2)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 2)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 0)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 0)
|
||||||
cmdset_f = c + a # same-prio. A's Union kicks in
|
cmdset_f = c + a # same-prio. A's Union kicks in
|
||||||
self.assertEqual(len(cmdset_f.commands), 4)
|
self.assertEqual(len(cmdset_f.commands), 4)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 4)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 4)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 0)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 0)
|
||||||
c.priority = 1
|
c.priority = 1
|
||||||
cmdset_f = c + a # c higher prio. C's Remove kicks in
|
cmdset_f = c + a # c higher prio. C's Remove kicks in
|
||||||
self.assertEqual(len(cmdset_f.commands), 2)
|
self.assertEqual(len(cmdset_f.commands), 2)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 2)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "A"), 2)
|
||||||
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 0)
|
self.assertEqual(sum(1 for cmd in cmdset_f.commands if cmd.from_cmdset == "C"), 0)
|
||||||
|
|
@ -138,15 +161,15 @@ class TestCmdSetMergers(TestCase):
|
||||||
def test_order(self):
|
def test_order(self):
|
||||||
"Merge in reverse- and forward orders, same priorities"
|
"Merge in reverse- and forward orders, same priorities"
|
||||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||||
cmdset_f = d + c + b + a # merge in reverse order of priority
|
cmdset_f = d + c + b + a # merge in reverse order of priority
|
||||||
self.assertEqual(cmdset_f.priority, 0)
|
self.assertEqual(cmdset_f.priority, 0)
|
||||||
self.assertEqual(cmdset_f.mergetype, "Union")
|
self.assertEqual(cmdset_f.mergetype, "Union")
|
||||||
self.assertEqual(len(cmdset_f.commands), 4)
|
self.assertEqual(len(cmdset_f.commands), 4)
|
||||||
self.assertTrue(all(True for cmd in cmdset_f.commands if cmd.from_cmdset == "A"))
|
self.assertTrue(all(True for cmd in cmdset_f.commands if cmd.from_cmdset == "A"))
|
||||||
cmdset_f = a + b + c + d # merge in order of priority
|
cmdset_f = a + b + c + d # merge in order of priority
|
||||||
self.assertEqual(cmdset_f.priority, 0)
|
self.assertEqual(cmdset_f.priority, 0)
|
||||||
self.assertEqual(cmdset_f.mergetype, "Union")
|
self.assertEqual(cmdset_f.mergetype, "Union")
|
||||||
self.assertEqual(len(cmdset_f.commands), 4) # duplicates setting from A transfers
|
self.assertEqual(len(cmdset_f.commands), 4) # duplicates setting from A transfers
|
||||||
self.assertTrue(all(True for cmd in cmdset_f.commands if cmd.from_cmdset == "D"))
|
self.assertTrue(all(True for cmd in cmdset_f.commands if cmd.from_cmdset == "D"))
|
||||||
|
|
||||||
def test_priority_order(self):
|
def test_priority_order(self):
|
||||||
|
|
@ -156,12 +179,12 @@ class TestCmdSetMergers(TestCase):
|
||||||
b.priority = 1
|
b.priority = 1
|
||||||
c.priority = 0
|
c.priority = 0
|
||||||
d.priority = -1
|
d.priority = -1
|
||||||
cmdset_f = d + c + b + a # merge in reverse order of priority
|
cmdset_f = d + c + b + a # merge in reverse order of priority
|
||||||
self.assertEqual(cmdset_f.priority, 2)
|
self.assertEqual(cmdset_f.priority, 2)
|
||||||
self.assertEqual(cmdset_f.mergetype, "Union")
|
self.assertEqual(cmdset_f.mergetype, "Union")
|
||||||
self.assertEqual(len(cmdset_f.commands), 4)
|
self.assertEqual(len(cmdset_f.commands), 4)
|
||||||
self.assertTrue(all(True for cmd in cmdset_f.commands if cmd.from_cmdset == "A"))
|
self.assertTrue(all(True for cmd in cmdset_f.commands if cmd.from_cmdset == "A"))
|
||||||
cmdset_f = a + b + c + d # merge in order of priority
|
cmdset_f = a + b + c + d # merge in order of priority
|
||||||
self.assertEqual(cmdset_f.priority, 2)
|
self.assertEqual(cmdset_f.priority, 2)
|
||||||
self.assertEqual(cmdset_f.mergetype, "Union")
|
self.assertEqual(cmdset_f.mergetype, "Union")
|
||||||
self.assertEqual(len(cmdset_f.commands), 4)
|
self.assertEqual(len(cmdset_f.commands), 4)
|
||||||
|
|
@ -176,13 +199,13 @@ class TestCmdSetMergers(TestCase):
|
||||||
a.no_objs = True
|
a.no_objs = True
|
||||||
a.no_channels = True
|
a.no_channels = True
|
||||||
a.duplicates = True
|
a.duplicates = True
|
||||||
cmdset_f = d + c + b + a # reverse, same-prio
|
cmdset_f = d + c + b + a # reverse, same-prio
|
||||||
self.assertTrue(cmdset_f.no_exits)
|
self.assertTrue(cmdset_f.no_exits)
|
||||||
self.assertTrue(cmdset_f.no_objs)
|
self.assertTrue(cmdset_f.no_objs)
|
||||||
self.assertTrue(cmdset_f.no_channels)
|
self.assertTrue(cmdset_f.no_channels)
|
||||||
self.assertTrue(cmdset_f.duplicates)
|
self.assertTrue(cmdset_f.duplicates)
|
||||||
self.assertEqual(len(cmdset_f.commands), 8)
|
self.assertEqual(len(cmdset_f.commands), 8)
|
||||||
cmdset_f = a + b + c + d # forward, same-prio
|
cmdset_f = a + b + c + d # forward, same-prio
|
||||||
self.assertTrue(cmdset_f.no_exits)
|
self.assertTrue(cmdset_f.no_exits)
|
||||||
self.assertTrue(cmdset_f.no_objs)
|
self.assertTrue(cmdset_f.no_objs)
|
||||||
self.assertTrue(cmdset_f.no_channels)
|
self.assertTrue(cmdset_f.no_channels)
|
||||||
|
|
@ -192,13 +215,13 @@ class TestCmdSetMergers(TestCase):
|
||||||
b.priority = 1
|
b.priority = 1
|
||||||
c.priority = 0
|
c.priority = 0
|
||||||
d.priority = -1
|
d.priority = -1
|
||||||
cmdset_f = d + c + b + a # reverse, A top priority
|
cmdset_f = d + c + b + a # reverse, A top priority
|
||||||
self.assertTrue(cmdset_f.no_exits)
|
self.assertTrue(cmdset_f.no_exits)
|
||||||
self.assertTrue(cmdset_f.no_objs)
|
self.assertTrue(cmdset_f.no_objs)
|
||||||
self.assertTrue(cmdset_f.no_channels)
|
self.assertTrue(cmdset_f.no_channels)
|
||||||
self.assertTrue(cmdset_f.duplicates)
|
self.assertTrue(cmdset_f.duplicates)
|
||||||
self.assertEqual(len(cmdset_f.commands), 4)
|
self.assertEqual(len(cmdset_f.commands), 4)
|
||||||
cmdset_f = a + b + c + d # forward, A top priority. This never happens in practice.
|
cmdset_f = a + b + c + d # forward, A top priority. This never happens in practice.
|
||||||
self.assertTrue(cmdset_f.no_exits)
|
self.assertTrue(cmdset_f.no_exits)
|
||||||
self.assertTrue(cmdset_f.no_objs)
|
self.assertTrue(cmdset_f.no_objs)
|
||||||
self.assertTrue(cmdset_f.no_channels)
|
self.assertTrue(cmdset_f.no_channels)
|
||||||
|
|
@ -208,13 +231,13 @@ class TestCmdSetMergers(TestCase):
|
||||||
b.priority = 0
|
b.priority = 0
|
||||||
c.priority = 1
|
c.priority = 1
|
||||||
d.priority = 2
|
d.priority = 2
|
||||||
cmdset_f = d + c + b + a # reverse, A low prio. This never happens in practice.
|
cmdset_f = d + c + b + a # reverse, A low prio. This never happens in practice.
|
||||||
self.assertTrue(cmdset_f.no_exits)
|
self.assertTrue(cmdset_f.no_exits)
|
||||||
self.assertTrue(cmdset_f.no_objs)
|
self.assertTrue(cmdset_f.no_objs)
|
||||||
self.assertTrue(cmdset_f.no_channels)
|
self.assertTrue(cmdset_f.no_channels)
|
||||||
self.assertFalse(cmdset_f.duplicates)
|
self.assertFalse(cmdset_f.duplicates)
|
||||||
self.assertEqual(len(cmdset_f.commands), 4)
|
self.assertEqual(len(cmdset_f.commands), 4)
|
||||||
cmdset_f = a + b + c + d # forward, A low prio
|
cmdset_f = a + b + c + d # forward, A low prio
|
||||||
self.assertTrue(cmdset_f.no_exits)
|
self.assertTrue(cmdset_f.no_exits)
|
||||||
self.assertTrue(cmdset_f.no_objs)
|
self.assertTrue(cmdset_f.no_objs)
|
||||||
self.assertTrue(cmdset_f.no_channels)
|
self.assertTrue(cmdset_f.no_channels)
|
||||||
|
|
@ -224,7 +247,7 @@ class TestCmdSetMergers(TestCase):
|
||||||
b.no_objs = False
|
b.no_objs = False
|
||||||
d.duplicates = False
|
d.duplicates = False
|
||||||
# higher-prio sets will change the option up the chain
|
# higher-prio sets will change the option up the chain
|
||||||
cmdset_f = a + b + c + d # forward, A low prio
|
cmdset_f = a + b + c + d # forward, A low prio
|
||||||
self.assertFalse(cmdset_f.no_exits)
|
self.assertFalse(cmdset_f.no_exits)
|
||||||
self.assertFalse(cmdset_f.no_objs)
|
self.assertFalse(cmdset_f.no_objs)
|
||||||
self.assertTrue(cmdset_f.no_channels)
|
self.assertTrue(cmdset_f.no_channels)
|
||||||
|
|
@ -235,16 +258,26 @@ class TestCmdSetMergers(TestCase):
|
||||||
c.priority = 0
|
c.priority = 0
|
||||||
d.priority = 0
|
d.priority = 0
|
||||||
c.duplicates = True
|
c.duplicates = True
|
||||||
cmdset_f = d + b + c + a # two last mergers duplicates=True
|
cmdset_f = d + b + c + a # two last mergers duplicates=True
|
||||||
self.assertEqual(len(cmdset_f.commands), 10)
|
self.assertEqual(len(cmdset_f.commands), 10)
|
||||||
|
|
||||||
# test cmdhandler functions
|
# test cmdhandler functions
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
from evennia.commands import cmdhandler
|
from evennia.commands import cmdhandler
|
||||||
from twisted.trial.unittest import TestCase as TwistedTestCase
|
from twisted.trial.unittest import TestCase as TwistedTestCase
|
||||||
|
|
||||||
|
|
||||||
|
def _mockdelay(time, func, *args, **kwargs):
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest):
|
class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest):
|
||||||
"Test the cmdhandler.get_and_merge_cmdsets function."
|
"Test the cmdhandler.get_and_merge_cmdsets function."
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
self.patch(sys.modules['evennia.server.sessionhandler'], 'delay', _mockdelay)
|
||||||
super(TestGetAndMergeCmdSets, self).setUp()
|
super(TestGetAndMergeCmdSets, self).setUp()
|
||||||
self.cmdset_a = _CmdSetA()
|
self.cmdset_a = _CmdSetA()
|
||||||
self.cmdset_b = _CmdSetB()
|
self.cmdset_b = _CmdSetB()
|
||||||
|
|
@ -261,20 +294,22 @@ class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest):
|
||||||
a.no_channels = True
|
a.no_channels = True
|
||||||
self.set_cmdsets(self.session, a)
|
self.set_cmdsets(self.session, a)
|
||||||
deferred = cmdhandler.get_and_merge_cmdsets(self.session, self.session, None, None, "session", "")
|
deferred = cmdhandler.get_and_merge_cmdsets(self.session, self.session, None, None, "session", "")
|
||||||
|
|
||||||
def _callback(cmdset):
|
def _callback(cmdset):
|
||||||
self.assertEqual(cmdset.key, "A")
|
self.assertEqual(cmdset.key, "A")
|
||||||
deferred.addCallback(_callback)
|
deferred.addCallback(_callback)
|
||||||
return deferred
|
return deferred
|
||||||
|
|
||||||
def test_from_player(self):
|
def test_from_account(self):
|
||||||
from evennia.commands.default.cmdset_player import PlayerCmdSet
|
from evennia.commands.default.cmdset_account import AccountCmdSet
|
||||||
a = self.cmdset_a
|
a = self.cmdset_a
|
||||||
a.no_channels = True
|
a.no_channels = True
|
||||||
self.set_cmdsets(self.player, a)
|
self.set_cmdsets(self.account, a)
|
||||||
deferred = cmdhandler.get_and_merge_cmdsets(self.player, None, self.player, None, "player", "")
|
deferred = cmdhandler.get_and_merge_cmdsets(self.account, None, self.account, None, "account", "")
|
||||||
# get_and_merge_cmdsets converts to lower-case internally.
|
# get_and_merge_cmdsets converts to lower-case internally.
|
||||||
|
|
||||||
def _callback(cmdset):
|
def _callback(cmdset):
|
||||||
pcmdset = PlayerCmdSet()
|
pcmdset = AccountCmdSet()
|
||||||
pcmdset.at_cmdset_creation()
|
pcmdset.at_cmdset_creation()
|
||||||
pcmds = [cmd.key for cmd in pcmdset.commands] + ["a", "b", "c", "d"]
|
pcmds = [cmd.key for cmd in pcmdset.commands] + ["a", "b", "c", "d"]
|
||||||
self.assertTrue(all(cmd.key in pcmds for cmd in cmdset.commands))
|
self.assertTrue(all(cmd.key in pcmds for cmd in cmdset.commands))
|
||||||
|
|
@ -286,7 +321,8 @@ class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest):
|
||||||
self.set_cmdsets(self.obj1, self.cmdset_a)
|
self.set_cmdsets(self.obj1, self.cmdset_a)
|
||||||
deferred = cmdhandler.get_and_merge_cmdsets(self.obj1, None, None, self.obj1, "object", "")
|
deferred = cmdhandler.get_and_merge_cmdsets(self.obj1, None, None, self.obj1, "object", "")
|
||||||
# get_and_merge_cmdsets converts to lower-case internally.
|
# get_and_merge_cmdsets converts to lower-case internally.
|
||||||
_callback = lambda cmdset: self.assertEqual(sum(1 for cmd in cmdset.commands if cmd.key in ("a", "b", "c", "d")), 4)
|
|
||||||
|
def _callback(cmdset): return self.assertEqual(sum(1 for cmd in cmdset.commands if cmd.key in ("a", "b", "c", "d")), 4)
|
||||||
deferred.addCallback(_callback)
|
deferred.addCallback(_callback)
|
||||||
return deferred
|
return deferred
|
||||||
|
|
||||||
|
|
@ -295,7 +331,9 @@ class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest):
|
||||||
a.no_exits = True
|
a.no_exits = True
|
||||||
a.no_channels = True
|
a.no_channels = True
|
||||||
self.set_cmdsets(self.obj1, a, b, c, d)
|
self.set_cmdsets(self.obj1, a, b, c, d)
|
||||||
|
|
||||||
deferred = cmdhandler.get_and_merge_cmdsets(self.obj1, None, None, self.obj1, "object", "")
|
deferred = cmdhandler.get_and_merge_cmdsets(self.obj1, None, None, self.obj1, "object", "")
|
||||||
|
|
||||||
def _callback(cmdset):
|
def _callback(cmdset):
|
||||||
self.assertTrue(cmdset.no_exits)
|
self.assertTrue(cmdset.no_exits)
|
||||||
self.assertTrue(cmdset.no_channels)
|
self.assertTrue(cmdset.no_channels)
|
||||||
|
|
@ -305,18 +343,19 @@ class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest):
|
||||||
|
|
||||||
def test_autocmdsets(self):
|
def test_autocmdsets(self):
|
||||||
import evennia
|
import evennia
|
||||||
from evennia.commands.default.cmdset_player import PlayerCmdSet
|
from evennia.commands.default.cmdset_account import AccountCmdSet
|
||||||
from evennia.comms.channelhandler import CHANNEL_HANDLER
|
from evennia.comms.channelhandler import CHANNEL_HANDLER
|
||||||
testchannel = evennia.create_channel("channeltest", locks="listen:all();send:all()")
|
testchannel = evennia.create_channel("channeltest", locks="listen:all();send:all()")
|
||||||
CHANNEL_HANDLER.add(testchannel)
|
CHANNEL_HANDLER.add(testchannel)
|
||||||
CHANNEL_HANDLER.update()
|
CHANNEL_HANDLER.update()
|
||||||
self.assertTrue(testchannel.connect(self.player))
|
self.assertTrue(testchannel.connect(self.account))
|
||||||
self.assertTrue(testchannel.has_connection(self.player))
|
self.assertTrue(testchannel.has_connection(self.account))
|
||||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||||
self.set_cmdsets(self.player, a, b, c, d)
|
self.set_cmdsets(self.account, a, b, c, d)
|
||||||
deferred = cmdhandler.get_and_merge_cmdsets(self.session, self.session, self.player, self.char1, "session", "")
|
deferred = cmdhandler.get_and_merge_cmdsets(self.session, self.session, self.account, self.char1, "session", "")
|
||||||
|
|
||||||
def _callback(cmdset):
|
def _callback(cmdset):
|
||||||
pcmdset = PlayerCmdSet()
|
pcmdset = AccountCmdSet()
|
||||||
pcmdset.at_cmdset_creation()
|
pcmdset.at_cmdset_creation()
|
||||||
pcmds = [cmd.key for cmd in pcmdset.commands] + ["a", "b", "c", "d"] + ["out"]
|
pcmds = [cmd.key for cmd in pcmdset.commands] + ["a", "b", "c", "d"] + ["out"]
|
||||||
self.assertTrue(all(cmd.key or hasattr(cmd, "is_channel") in pcmds for cmd in cmdset.commands))
|
self.assertTrue(all(cmd.key or hasattr(cmd, "is_channel") in pcmds for cmd in cmdset.commands))
|
||||||
|
|
@ -332,6 +371,7 @@ class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest):
|
||||||
d.duplicates = True
|
d.duplicates = True
|
||||||
self.set_cmdsets(self.obj1, a, b, c, d)
|
self.set_cmdsets(self.obj1, a, b, c, d)
|
||||||
deferred = cmdhandler.get_and_merge_cmdsets(self.obj1, None, None, self.obj1, "object", "")
|
deferred = cmdhandler.get_and_merge_cmdsets(self.obj1, None, None, self.obj1, "object", "")
|
||||||
|
|
||||||
def _callback(cmdset):
|
def _callback(cmdset):
|
||||||
self.assertEqual(len(cmdset.commands), 9)
|
self.assertEqual(len(cmdset.commands), 9)
|
||||||
deferred.addCallback(_callback)
|
deferred.addCallback(_callback)
|
||||||
|
|
|
||||||
|
|
@ -53,13 +53,14 @@ class ChannelAdmin(admin.ModelAdmin):
|
||||||
list_display = ('id', 'db_key', 'db_lock_storage', "subscriptions")
|
list_display = ('id', 'db_key', 'db_lock_storage', "subscriptions")
|
||||||
list_display_links = ("id", 'db_key')
|
list_display_links = ("id", 'db_key')
|
||||||
ordering = ["db_key"]
|
ordering = ["db_key"]
|
||||||
search_fields = ['id', 'db_key', 'db_aliases']
|
search_fields = ['id', 'db_key', 'db_tags__db_key']
|
||||||
save_as = True
|
save_as = True
|
||||||
save_on_top = True
|
save_on_top = True
|
||||||
list_select_related = True
|
list_select_related = True
|
||||||
|
raw_id_fields = ('db_object_subscriptions', 'db_account_subscriptions',)
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {'fields': (('db_key',), 'db_lock_storage', 'db_subscriptions')}),
|
(None, {'fields': (('db_key',), 'db_lock_storage', 'db_account_subscriptions', 'db_object_subscriptions')}),
|
||||||
)
|
)
|
||||||
|
|
||||||
def subscriptions(self, obj):
|
def subscriptions(self, obj):
|
||||||
"""
|
"""
|
||||||
|
|
@ -69,7 +70,7 @@ class ChannelAdmin(admin.ModelAdmin):
|
||||||
obj (Channel): The channel to get subs from.
|
obj (Channel): The channel to get subs from.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return ", ".join([str(sub) for sub in obj.db_subscriptions.all()])
|
return ", ".join([str(sub) for sub in obj.subscriptions.all()])
|
||||||
|
|
||||||
def save_model(self, request, obj, form, change):
|
def save_model(self, request, obj, form, change):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ from django.utils.translation import ugettext as _
|
||||||
_CHANNEL_COMMAND_CLASS = None
|
_CHANNEL_COMMAND_CLASS = None
|
||||||
_CHANNELDB = None
|
_CHANNELDB = None
|
||||||
|
|
||||||
|
|
||||||
class ChannelCommand(command.Command):
|
class ChannelCommand(command.Command):
|
||||||
"""
|
"""
|
||||||
{channelkey} channel
|
{channelkey} channel
|
||||||
|
|
@ -112,7 +113,7 @@ class ChannelCommand(command.Command):
|
||||||
self.msg(string % channelkey)
|
self.msg(string % channelkey)
|
||||||
return
|
return
|
||||||
if msg == "on":
|
if msg == "on":
|
||||||
caller = caller if not hasattr(caller, 'player') else caller.player
|
caller = caller if not hasattr(caller, 'account') else caller.account
|
||||||
unmuted = channel.unmute(caller)
|
unmuted = channel.unmute(caller)
|
||||||
if unmuted:
|
if unmuted:
|
||||||
self.msg("You start listening to %s." % channel)
|
self.msg("You start listening to %s." % channel)
|
||||||
|
|
@ -120,7 +121,7 @@ class ChannelCommand(command.Command):
|
||||||
self.msg("You were already listening to %s." % channel)
|
self.msg("You were already listening to %s." % channel)
|
||||||
return
|
return
|
||||||
if msg == "off":
|
if msg == "off":
|
||||||
caller = caller if not hasattr(caller, 'player') else caller.player
|
caller = caller if not hasattr(caller, 'account') else caller.account
|
||||||
muted = channel.mute(caller)
|
muted = channel.mute(caller)
|
||||||
if muted:
|
if muted:
|
||||||
self.msg("You stop listening to %s." % channel)
|
self.msg("You stop listening to %s." % channel)
|
||||||
|
|
@ -130,11 +131,12 @@ class ChannelCommand(command.Command):
|
||||||
if self.history_start is not None:
|
if self.history_start is not None:
|
||||||
# Try to view history
|
# Try to view history
|
||||||
log_file = channel.attributes.get("log_file", default="channel_%s.log" % channel.key)
|
log_file = channel.attributes.get("log_file", default="channel_%s.log" % channel.key)
|
||||||
send_msg = lambda lines: self.msg("".join(line.split("[-]", 1)[1]
|
|
||||||
if "[-]" in line else line for line in lines))
|
def send_msg(lines): return self.msg("".join(line.split("[-]", 1)[1]
|
||||||
|
if "[-]" in line else line for line in lines))
|
||||||
tail_log_file(log_file, self.history_start, 20, callback=send_msg)
|
tail_log_file(log_file, self.history_start, 20, callback=send_msg)
|
||||||
else:
|
else:
|
||||||
caller = caller if not hasattr(caller, 'player') else caller.player
|
caller = caller if not hasattr(caller, 'account') else caller.account
|
||||||
if caller in channel.mutelist:
|
if caller in channel.mutelist:
|
||||||
self.msg("You currently have %s muted." % channel)
|
self.msg("You currently have %s muted." % channel)
|
||||||
return
|
return
|
||||||
|
|
@ -145,7 +147,7 @@ class ChannelCommand(command.Command):
|
||||||
Let users know that this command is for communicating on a channel.
|
Let users know that this command is for communicating on a channel.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
caller (TypedObject): A Character or Player who has entered an ambiguous command.
|
caller (TypedObject): A Character or Account who has entered an ambiguous command.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A string with identifying information to disambiguate the object, conventionally with a preceding space.
|
A string with identifying information to disambiguate the object, conventionally with a preceding space.
|
||||||
|
|
@ -164,28 +166,31 @@ class ChannelHandler(object):
|
||||||
evennia.create_channel())
|
evennia.create_channel())
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""
|
"""
|
||||||
Initializes the channel handler's internal state.
|
Initializes the channel handler's internal state.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.cached_channel_cmds = {}
|
self._cached_channel_cmds = {}
|
||||||
self.cached_cmdsets = {}
|
self._cached_cmdsets = {}
|
||||||
|
self._cached_channels = {}
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""
|
"""
|
||||||
Returns the string representation of the handler
|
Returns the string representation of the handler
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return ", ".join(str(cmd) for cmd in self.cached_channel_cmds)
|
return ", ".join(str(cmd) for cmd in self._cached_channel_cmds)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""
|
"""
|
||||||
Reset the cache storage.
|
Reset the cache storage.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.cached_channel_cmds = {}
|
self._cached_channel_cmds = {}
|
||||||
self.cached_cmdsets = {}
|
self._cached_cmdsets = {}
|
||||||
|
self._cached_channels = {}
|
||||||
|
|
||||||
def add(self, channel):
|
def add(self, channel):
|
||||||
"""
|
"""
|
||||||
|
|
@ -208,20 +213,22 @@ class ChannelHandler(object):
|
||||||
|
|
||||||
# map the channel to a searchable command
|
# map the channel to a searchable command
|
||||||
cmd = _CHANNEL_COMMAND_CLASS(
|
cmd = _CHANNEL_COMMAND_CLASS(
|
||||||
key=channel.key.strip().lower(),
|
key=channel.key.strip().lower(),
|
||||||
aliases=channel.aliases.all(),
|
aliases=channel.aliases.all(),
|
||||||
locks="cmd:all();%s" % channel.locks,
|
locks="cmd:all();%s" % channel.locks,
|
||||||
help_category="Channel names",
|
help_category="Channel names",
|
||||||
obj=channel,
|
obj=channel,
|
||||||
is_channel=True)
|
is_channel=True)
|
||||||
# format the help entry
|
# format the help entry
|
||||||
key = channel.key
|
key = channel.key
|
||||||
cmd.__doc__ = cmd.__doc__.format(channelkey=key,
|
cmd.__doc__ = cmd.__doc__.format(channelkey=key,
|
||||||
lower_channelkey=key.strip().lower(),
|
lower_channelkey=key.strip().lower(),
|
||||||
channeldesc=channel.attributes.get("desc", default="").strip())
|
channeldesc=channel.attributes.get(
|
||||||
self.cached_channel_cmds[channel] = cmd
|
"desc", default="").strip())
|
||||||
self.cached_cmdsets = {}
|
self._cached_channel_cmds[channel] = cmd
|
||||||
add_channel = add # legacy alias
|
self._cached_channels[key] = channel
|
||||||
|
self._cached_cmdsets = {}
|
||||||
|
add_channel = add # legacy alias
|
||||||
|
|
||||||
def remove(self, channel):
|
def remove(self, channel):
|
||||||
"""
|
"""
|
||||||
|
|
@ -244,11 +251,28 @@ class ChannelHandler(object):
|
||||||
global _CHANNELDB
|
global _CHANNELDB
|
||||||
if not _CHANNELDB:
|
if not _CHANNELDB:
|
||||||
from evennia.comms.models import ChannelDB as _CHANNELDB
|
from evennia.comms.models import ChannelDB as _CHANNELDB
|
||||||
self.cached_channel_cmds = {}
|
self._cached_channel_cmds = {}
|
||||||
self.cached_cmdsets = {}
|
self._cached_cmdsets = {}
|
||||||
|
self._cached_channels = {}
|
||||||
for channel in _CHANNELDB.objects.get_all_channels():
|
for channel in _CHANNELDB.objects.get_all_channels():
|
||||||
self.add(channel)
|
self.add(channel)
|
||||||
|
|
||||||
|
def get(self, channelname=None):
|
||||||
|
"""
|
||||||
|
Get a channel from the handler, or all channels
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channelame (str, optional): Channel key, case insensitive.
|
||||||
|
Returns
|
||||||
|
channels (list): The matching channels in a list, or all
|
||||||
|
channels in the handler.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if channelname:
|
||||||
|
channel = self._cached_channels.get(channelname.lower(), None)
|
||||||
|
return [channel] if channel else []
|
||||||
|
return self._cached_channels.values()
|
||||||
|
|
||||||
def get_cmdset(self, source_object):
|
def get_cmdset(self, source_object):
|
||||||
"""
|
"""
|
||||||
Retrieve cmdset for channels this source_object has
|
Retrieve cmdset for channels this source_object has
|
||||||
|
|
@ -263,14 +287,14 @@ class ChannelHandler(object):
|
||||||
access to.
|
access to.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if source_object in self.cached_cmdsets:
|
if source_object in self._cached_cmdsets:
|
||||||
return self.cached_cmdsets[source_object]
|
return self._cached_cmdsets[source_object]
|
||||||
else:
|
else:
|
||||||
# create a new cmdset holding all viable channels
|
# create a new cmdset holding all viable channels
|
||||||
chan_cmdset = None
|
chan_cmdset = None
|
||||||
chan_cmds = [channelcmd for channel, channelcmd in self.cached_channel_cmds.iteritems()
|
chan_cmds = [channelcmd for channel, channelcmd in self._cached_channel_cmds.iteritems()
|
||||||
if channel.subscriptions.has(source_object) and
|
if channel.subscriptions.has(source_object) and
|
||||||
channelcmd.access(source_object, 'send')]
|
channelcmd.access(source_object, 'send')]
|
||||||
if chan_cmds:
|
if chan_cmds:
|
||||||
chan_cmdset = cmdset.CmdSet()
|
chan_cmdset = cmdset.CmdSet()
|
||||||
chan_cmdset.key = 'ChannelCmdSet'
|
chan_cmdset.key = 'ChannelCmdSet'
|
||||||
|
|
@ -278,8 +302,9 @@ class ChannelHandler(object):
|
||||||
chan_cmdset.duplicates = True
|
chan_cmdset.duplicates = True
|
||||||
for cmd in chan_cmds:
|
for cmd in chan_cmds:
|
||||||
chan_cmdset.add(cmd)
|
chan_cmdset.add(cmd)
|
||||||
self.cached_cmdsets[source_object] = chan_cmdset
|
self._cached_cmdsets[source_object] = chan_cmdset
|
||||||
return chan_cmdset
|
return chan_cmdset
|
||||||
|
|
||||||
|
|
||||||
CHANNEL_HANDLER = ChannelHandler()
|
CHANNEL_HANDLER = ChannelHandler()
|
||||||
CHANNELHANDLER = CHANNEL_HANDLER # legacy
|
CHANNELHANDLER = CHANNEL_HANDLER # legacy
|
||||||
|
|
|
||||||
|
|
@ -62,26 +62,26 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
|
|
||||||
def has_connection(self, subscriber):
|
def has_connection(self, subscriber):
|
||||||
"""
|
"""
|
||||||
Checks so this player is actually listening
|
Checks so this account is actually listening
|
||||||
to this channel.
|
to this channel.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
subscriber (Player or Object): Entity to check.
|
subscriber (Account or Object): Entity to check.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
has_sub (bool): Whether the subscriber is subscribing to
|
has_sub (bool): Whether the subscriber is subscribing to
|
||||||
this channel or not.
|
this channel or not.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
This will first try Player subscribers and only try Object
|
This will first try Account subscribers and only try Object
|
||||||
if the Player fails.
|
if the Account fails.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
has_sub = self.subscriptions.has(subscriber)
|
has_sub = self.subscriptions.has(subscriber)
|
||||||
if not has_sub and hasattr(subscriber, "player"):
|
if not has_sub and hasattr(subscriber, "account"):
|
||||||
# it's common to send an Object when we
|
# it's common to send an Object when we
|
||||||
# by default only allow Players to subscribe.
|
# by default only allow Accounts to subscribe.
|
||||||
has_sub = self.subscriptions.has(subscriber.player)
|
has_sub = self.subscriptions.has(subscriber.account)
|
||||||
return has_sub
|
return has_sub
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -94,16 +94,22 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
listening = [ob for ob in subs if ob.is_connected and ob not in self.mutelist]
|
listening = [ob for ob in subs if ob.is_connected and ob not in self.mutelist]
|
||||||
if subs:
|
if subs:
|
||||||
# display listening subscribers in bold
|
# display listening subscribers in bold
|
||||||
string = ", ".join([player.key if player not in listening else "|w%s|n" % player.key for player in subs])
|
string = ", ".join([account.key if account not in listening else "|w%s|n" % account.key for account in subs])
|
||||||
else:
|
else:
|
||||||
string = "<None>"
|
string = "<None>"
|
||||||
return string
|
return string
|
||||||
|
|
||||||
def mute(self, subscriber):
|
def mute(self, subscriber, **kwargs):
|
||||||
"""
|
"""
|
||||||
Adds an entity to the list of muted subscribers.
|
Adds an entity to the list of muted subscribers.
|
||||||
A muted subscriber will no longer see channel messages,
|
A muted subscriber will no longer see channel messages,
|
||||||
but may use channel commands.
|
but may use channel commands.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
subscriber (Object or Account): Subscriber to mute.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
mutelist = self.mutelist
|
mutelist = self.mutelist
|
||||||
if subscriber not in mutelist:
|
if subscriber not in mutelist:
|
||||||
|
|
@ -112,11 +118,16 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def unmute(self, subscriber):
|
def unmute(self, subscriber, **kwargs):
|
||||||
"""
|
"""
|
||||||
Removes an entity to the list of muted subscribers.
|
Removes an entity to the list of muted subscribers. A muted subscriber will no longer see channel messages,
|
||||||
A muted subscriber will no longer see channel messages,
|
|
||||||
but may use channel commands.
|
but may use channel commands.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
subscriber (Object or Account): The subscriber to unmute.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
mutelist = self.mutelist
|
mutelist = self.mutelist
|
||||||
if subscriber in mutelist:
|
if subscriber in mutelist:
|
||||||
|
|
@ -125,13 +136,15 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def connect(self, subscriber):
|
def connect(self, subscriber, **kwargs):
|
||||||
"""
|
"""
|
||||||
Connect the user to this channel. This checks access.
|
Connect the user to this channel. This checks access.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
subscriber (Player or Object): the entity to subscribe
|
subscriber (Account or Object): the entity to subscribe
|
||||||
to this channel.
|
to this channel.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
success (bool): Whether or not the addition was
|
success (bool): Whether or not the addition was
|
||||||
|
|
@ -153,13 +166,15 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
self.post_join_channel(subscriber)
|
self.post_join_channel(subscriber)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def disconnect(self, subscriber):
|
def disconnect(self, subscriber, **kwargs):
|
||||||
"""
|
"""
|
||||||
Disconnect entity from this channel.
|
Disconnect entity from this channel.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
subscriber (Player of Object): the
|
subscriber (Account of Object): the
|
||||||
entity to disconnect.
|
entity to disconnect.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
success (bool): Whether or not the removal was
|
success (bool): Whether or not the removal was
|
||||||
|
|
@ -178,7 +193,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
self.post_leave_channel(subscriber)
|
self.post_leave_channel(subscriber)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def access(self, accessing_obj, access_type='listen', default=False, no_superuser_bypass=False):
|
def access(self, accessing_obj, access_type='listen', default=False, no_superuser_bypass=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
Determines if another object has permission to access.
|
Determines if another object has permission to access.
|
||||||
|
|
||||||
|
|
@ -188,6 +203,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
default (bool, optional): What to return if no lock of access_type was found
|
default (bool, optional): What to return if no lock of access_type was found
|
||||||
no_superuser_bypass (bool, optional): Turns off superuser
|
no_superuser_bypass (bool, optional): Turns off superuser
|
||||||
lock bypass. Be careful with this one.
|
lock bypass. Be careful with this one.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
return (bool): Result of lock check.
|
return (bool): Result of lock check.
|
||||||
|
|
@ -208,7 +225,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
CHANNELHANDLER.update()
|
CHANNELHANDLER.update()
|
||||||
|
|
||||||
def message_transform(self, msgobj, emit=False, prefix=True,
|
def message_transform(self, msgobj, emit=False, prefix=True,
|
||||||
sender_strings=None, external=False):
|
sender_strings=None, external=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
Generates the formatted string sent to listeners on a channel.
|
Generates the formatted string sent to listeners on a channel.
|
||||||
|
|
||||||
|
|
@ -219,6 +236,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
prefix (bool, optional): Prefix `msg` with a text given by `self.channel_prefix`.
|
prefix (bool, optional): Prefix `msg` with a text given by `self.channel_prefix`.
|
||||||
sender_strings (list, optional): Used by bots etc, one string per external sender.
|
sender_strings (list, optional): Used by bots etc, one string per external sender.
|
||||||
external (bool, optional): If this is an external sender or not.
|
external (bool, optional): If this is an external sender or not.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if sender_strings or external:
|
if sender_strings or external:
|
||||||
|
|
@ -230,7 +249,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
msgobj.message = body
|
msgobj.message = body
|
||||||
return msgobj
|
return msgobj
|
||||||
|
|
||||||
def distribute_message(self, msgobj, online=False):
|
def distribute_message(self, msgobj, online=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
Method for grabbing all listeners that a message should be
|
Method for grabbing all listeners that a message should be
|
||||||
sent to on this channel, and sending them a message.
|
sent to on this channel, and sending them a message.
|
||||||
|
|
@ -238,12 +257,15 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
Args:
|
Args:
|
||||||
msgobj (Msg or TempMsg): Message to distribute.
|
msgobj (Msg or TempMsg): Message to distribute.
|
||||||
online (bool): Only send to receivers who are actually online
|
online (bool): Only send to receivers who are actually online
|
||||||
|
(not currently used):
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
This is also where logging happens, if enabled.
|
This is also where logging happens, if enabled.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# get all players or objects connected to this channel and send to them
|
# get all accounts or objects connected to this channel and send to them
|
||||||
if online:
|
if online:
|
||||||
subs = self.subscriptions.online()
|
subs = self.subscriptions.online()
|
||||||
else:
|
else:
|
||||||
|
|
@ -254,7 +276,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
# note our addition of the from_channel keyword here. This could be checked
|
# note our addition of the from_channel keyword here. This could be checked
|
||||||
# by a custom player.msg() to treat channel-receives differently.
|
# by a custom account.msg() to treat channel-receives differently.
|
||||||
entity.msg(msgobj.message, from_obj=msgobj.senders, options={"from_channel": self.id})
|
entity.msg(msgobj.message, from_obj=msgobj.senders, options={"from_channel": self.id})
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
logger.log_trace("%s\nCannot send msg to '%s'." % (e, entity))
|
logger.log_trace("%s\nCannot send msg to '%s'." % (e, entity))
|
||||||
|
|
@ -266,7 +288,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
def msg(self, msgobj, header=None, senders=None, sender_strings=None,
|
def msg(self, msgobj, header=None, senders=None, sender_strings=None,
|
||||||
keep_log=None, online=False, emit=False, external=False):
|
keep_log=None, online=False, emit=False, external=False):
|
||||||
"""
|
"""
|
||||||
Send the given message to all players connected to channel. Note that
|
Send the given message to all accounts connected to channel. Note that
|
||||||
no permission-checking is done here; it is assumed to have been
|
no permission-checking is done here; it is assumed to have been
|
||||||
done before calling this method. The optional keywords are not used if
|
done before calling this method. The optional keywords are not used if
|
||||||
persistent is False.
|
persistent is False.
|
||||||
|
|
@ -278,10 +300,10 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
(if persistent=False) or it will be used together with `header`
|
(if persistent=False) or it will be used together with `header`
|
||||||
and `senders` keywords to create a Msg instance on the fly.
|
and `senders` keywords to create a Msg instance on the fly.
|
||||||
header (str, optional): A header for building the message.
|
header (str, optional): A header for building the message.
|
||||||
senders (Object, Player or list, optional): Optional if persistent=False, used
|
senders (Object, Account or list, optional): Optional if persistent=False, used
|
||||||
to build senders for the message.
|
to build senders for the message.
|
||||||
sender_strings (list, optional): Name strings of senders. Used for external
|
sender_strings (list, optional): Name strings of senders. Used for external
|
||||||
connections where the sender is not a player or object.
|
connections where the sender is not an account or object.
|
||||||
When this is defined, external will be assumed.
|
When this is defined, external will be assumed.
|
||||||
keep_log (bool or None, optional): This allows to temporarily change the logging status of
|
keep_log (bool or None, optional): This allows to temporarily change the logging status of
|
||||||
this channel message. If `None`, the Channel's `keep_log` Attribute will
|
this channel message. If `None`, the Channel's `keep_log` Attribute will
|
||||||
|
|
@ -289,8 +311,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
message only (note that for unlogged channels, a `True` value here will
|
message only (note that for unlogged channels, a `True` value here will
|
||||||
create a new log file only for this message).
|
create a new log file only for this message).
|
||||||
online (bool, optional) - If this is set true, only messages people who are
|
online (bool, optional) - If this is set true, only messages people who are
|
||||||
online. Otherwise, messages all players connected. This can
|
online. Otherwise, messages all accounts connected. This can
|
||||||
make things faster, but may not trigger listeners on players
|
make things faster, but may not trigger listeners on accounts
|
||||||
that are offline.
|
that are offline.
|
||||||
emit (bool, optional) - Signals to the message formatter that this message is
|
emit (bool, optional) - Signals to the message formatter that this message is
|
||||||
not to be directly associated with a name.
|
not to be directly associated with a name.
|
||||||
|
|
@ -334,8 +356,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
|
|
||||||
# hooks
|
# hooks
|
||||||
|
|
||||||
def channel_prefix(self, msg=None, emit=False):
|
def channel_prefix(self, msg=None, emit=False, **kwargs):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Hook method. How the channel should prefix itself for users.
|
Hook method. How the channel should prefix itself for users.
|
||||||
|
|
||||||
|
|
@ -343,6 +364,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
msg (str, optional): Prefix text
|
msg (str, optional): Prefix text
|
||||||
emit (bool, optional): Switches to emit mode, which usually
|
emit (bool, optional): Switches to emit mode, which usually
|
||||||
means to not prefix the channel's info.
|
means to not prefix the channel's info.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
prefix (str): The created channel prefix.
|
prefix (str): The created channel prefix.
|
||||||
|
|
@ -350,12 +373,14 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
"""
|
"""
|
||||||
return '' if emit else '[%s] ' % self.key
|
return '' if emit else '[%s] ' % self.key
|
||||||
|
|
||||||
def format_senders(self, senders=None):
|
def format_senders(self, senders=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Hook method. Function used to format a list of sender names.
|
Hook method. Function used to format a list of sender names.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
senders (list): Sender object names.
|
senders (list): Sender object names.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
formatted_list (str): The list of names formatted appropriately.
|
formatted_list (str): The list of names formatted appropriately.
|
||||||
|
|
@ -363,14 +388,14 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
Notes:
|
Notes:
|
||||||
This function exists separately so that external sources
|
This function exists separately so that external sources
|
||||||
can use it to format source names in the same manner as
|
can use it to format source names in the same manner as
|
||||||
normal object/player names.
|
normal object/account names.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not senders:
|
if not senders:
|
||||||
return ''
|
return ''
|
||||||
return ', '.join(senders)
|
return ', '.join(senders)
|
||||||
|
|
||||||
def pose_transform(self, msgobj, sender_string):
|
def pose_transform(self, msgobj, sender_string, **kwargs):
|
||||||
"""
|
"""
|
||||||
Hook method. Detects if the sender is posing, and modifies the
|
Hook method. Detects if the sender is posing, and modifies the
|
||||||
message accordingly.
|
message accordingly.
|
||||||
|
|
@ -378,6 +403,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
Args:
|
Args:
|
||||||
msgobj (Msg or TempMsg): The message to analyze for a pose.
|
msgobj (Msg or TempMsg): The message to analyze for a pose.
|
||||||
sender_string (str): The name of the sender/poser.
|
sender_string (str): The name of the sender/poser.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
string (str): A message that combines the `sender_string`
|
string (str): A message that combines the `sender_string`
|
||||||
|
|
@ -400,17 +427,19 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
else:
|
else:
|
||||||
return '%s: %s' % (sender_string, message)
|
return '%s: %s' % (sender_string, message)
|
||||||
|
|
||||||
def format_external(self, msgobj, senders, emit=False):
|
def format_external(self, msgobj, senders, emit=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
Hook method. Used for formatting external messages. This is
|
Hook method. Used for formatting external messages. This is
|
||||||
needed as a separate operation because the senders of external
|
needed as a separate operation because the senders of external
|
||||||
messages may not be in-game objects/players, and so cannot
|
messages may not be in-game objects/accounts, and so cannot
|
||||||
have things like custom user preferences.
|
have things like custom user preferences.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msgobj (Msg or TempMsg): The message to send.
|
msgobj (Msg or TempMsg): The message to send.
|
||||||
senders (list): Strings, one per sender.
|
senders (list): Strings, one per sender.
|
||||||
emit (bool, optional): A sender-agnostic message or not.
|
emit (bool, optional): A sender-agnostic message or not.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
transformed (str): A formatted string.
|
transformed (str): A formatted string.
|
||||||
|
|
@ -421,13 +450,15 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
senders = ', '.join(senders)
|
senders = ', '.join(senders)
|
||||||
return self.pose_transform(msgobj, senders)
|
return self.pose_transform(msgobj, senders)
|
||||||
|
|
||||||
def format_message(self, msgobj, emit=False):
|
def format_message(self, msgobj, emit=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
Hook method. Formats a message body for display.
|
Hook method. Formats a message body for display.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msgobj (Msg or TempMsg): The message object to send.
|
msgobj (Msg or TempMsg): The message object to send.
|
||||||
emit (bool, optional): The message is agnostic of senders.
|
emit (bool, optional): The message is agnostic of senders.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
transformed (str): The formatted message.
|
transformed (str): The formatted message.
|
||||||
|
|
@ -445,13 +476,15 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
senders = ', '.join(senders)
|
senders = ', '.join(senders)
|
||||||
return self.pose_transform(msgobj, senders)
|
return self.pose_transform(msgobj, senders)
|
||||||
|
|
||||||
def pre_join_channel(self, joiner):
|
def pre_join_channel(self, joiner, **kwargs):
|
||||||
"""
|
"""
|
||||||
Hook method. Runs right before a channel is joined. If this
|
Hook method. Runs right before a channel is joined. If this
|
||||||
returns a false value, channel joining is aborted.
|
returns a false value, channel joining is aborted.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
joiner (object): The joining object.
|
joiner (object): The joining object.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
should_join (bool): If `False`, channel joining is aborted.
|
should_join (bool): If `False`, channel joining is aborted.
|
||||||
|
|
@ -459,23 +492,27 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def post_join_channel(self, joiner):
|
def post_join_channel(self, joiner, **kwargs):
|
||||||
"""
|
"""
|
||||||
Hook method. Runs right after an object or player joins a channel.
|
Hook method. Runs right after an object or account joins a channel.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
joiner (object): The joining object.
|
joiner (object): The joining object.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def pre_leave_channel(self, leaver):
|
def pre_leave_channel(self, leaver, **kwargs):
|
||||||
"""
|
"""
|
||||||
Hook method. Runs right before a user leaves a channel. If this returns a false
|
Hook method. Runs right before a user leaves a channel. If this returns a false
|
||||||
value, leaving the channel will be aborted.
|
value, leaving the channel will be aborted.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
leaver (object): The leaving object.
|
leaver (object): The leaving object.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
should_leave (bool): If `False`, channel parting is aborted.
|
should_leave (bool): If `False`, channel parting is aborted.
|
||||||
|
|
@ -483,17 +520,19 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def post_leave_channel(self, leaver):
|
def post_leave_channel(self, leaver, **kwargs):
|
||||||
"""
|
"""
|
||||||
Hook method. Runs right after an object or player leaves a channel.
|
Hook method. Runs right after an object or account leaves a channel.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
leaver (object): The leaving object.
|
leaver (object): The leaving object.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def pre_send_message(self, msg):
|
def pre_send_message(self, msg, **kwargs):
|
||||||
"""
|
"""
|
||||||
Hook method. Runs before a message is sent to the channel and
|
Hook method. Runs before a message is sent to the channel and
|
||||||
should return the message object, after any transformations.
|
should return the message object, after any transformations.
|
||||||
|
|
@ -501,6 +540,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (Msg or TempMsg): Message to send.
|
msg (Msg or TempMsg): Message to send.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
result (Msg, TempMsg or bool): If False, abort send.
|
result (Msg, TempMsg or bool): If False, abort send.
|
||||||
|
|
@ -508,12 +549,14 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
"""
|
"""
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def post_send_message(self, msg):
|
def post_send_message(self, msg, **kwargs):
|
||||||
"""
|
"""
|
||||||
Hook method. Run after a message is sent to the channel.
|
Hook method. Run after a message is sent to the channel.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (Msg or TempMsg): Message sent.
|
msg (Msg or TempMsg): Message sent.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,12 @@ Comm system components.
|
||||||
"""
|
"""
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
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)
|
||||||
returns_typeclass_list, returns_typeclass)
|
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
|
|
||||||
_GA = object.__getattribute__
|
_GA = object.__getattribute__
|
||||||
_PlayerDB = None
|
_AccountDB = None
|
||||||
_ObjectDB = None
|
_ObjectDB = None
|
||||||
_ChannelDB = None
|
_ChannelDB = None
|
||||||
_SESSIONS = None
|
_SESSIONS = None
|
||||||
|
|
@ -59,7 +57,7 @@ def dbref(inp, reqhash=True):
|
||||||
|
|
||||||
def identify_object(inp):
|
def identify_object(inp):
|
||||||
"""
|
"""
|
||||||
Helper function. Identifies if an object is a player or an object;
|
Helper function. Identifies if an object is an account or an object;
|
||||||
return its database model
|
return its database model
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -67,16 +65,16 @@ def identify_object(inp):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
identified (tuple): This is a tuple with (`inp`, identifier)
|
identified (tuple): This is a tuple with (`inp`, identifier)
|
||||||
where `identifier` is one of "player", "object", "channel",
|
where `identifier` is one of "account", "object", "channel",
|
||||||
"string", "dbref" or None.
|
"string", "dbref" or None.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if hasattr(inp, "__dbclass__"):
|
if hasattr(inp, "__dbclass__"):
|
||||||
clsname = inp.__dbclass__.__name__
|
clsname = inp.__dbclass__.__name__
|
||||||
if clsname == "PlayerDB":
|
if clsname == "AccountDB":
|
||||||
return inp, "player"
|
return inp, "account"
|
||||||
elif clsname == "ObjectDB":
|
elif clsname == "ObjectDB":
|
||||||
return inp ,"object"
|
return inp, "object"
|
||||||
elif clsname == "ChannelDB":
|
elif clsname == "ChannelDB":
|
||||||
return inp, "channel"
|
return inp, "channel"
|
||||||
if isinstance(inp, basestring):
|
if isinstance(inp, basestring):
|
||||||
|
|
@ -87,14 +85,14 @@ def identify_object(inp):
|
||||||
return inp, None
|
return inp, None
|
||||||
|
|
||||||
|
|
||||||
def to_object(inp, objtype='player'):
|
def to_object(inp, objtype='account'):
|
||||||
"""
|
"""
|
||||||
Locates the object related to the given playername or channel key.
|
Locates the object related to the given accountname or channel key.
|
||||||
If input was already the correct object, return it.
|
If input was already the correct object, return it.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
inp (any): The input object/string
|
inp (any): The input object/string
|
||||||
objtype (str): Either 'player' or 'channel'.
|
objtype (str): Either 'account' or 'channel'.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
obj (object): The correct object related to `inp`.
|
obj (object): The correct object related to `inp`.
|
||||||
|
|
@ -103,34 +101,35 @@ def to_object(inp, objtype='player'):
|
||||||
obj, typ = identify_object(inp)
|
obj, typ = identify_object(inp)
|
||||||
if typ == objtype:
|
if typ == objtype:
|
||||||
return obj
|
return obj
|
||||||
if objtype == 'player':
|
if objtype == 'account':
|
||||||
if typ == 'object':
|
if typ == 'object':
|
||||||
return obj.player
|
return obj.account
|
||||||
if typ == 'string':
|
if typ == 'string':
|
||||||
return _PlayerDB.objects.get(user_username__iexact=obj)
|
return _AccountDB.objects.get(user_username__iexact=obj)
|
||||||
if typ == 'dbref':
|
if typ == 'dbref':
|
||||||
return _PlayerDB.objects.get(id=obj)
|
return _AccountDB.objects.get(id=obj)
|
||||||
logger.log_err("%s %s %s %s %s", objtype, inp, obj, typ, type(inp))
|
logger.log_err("%s %s %s %s %s" % (objtype, inp, obj, typ, type(inp)))
|
||||||
raise CommError()
|
raise CommError()
|
||||||
elif objtype == 'object':
|
elif objtype == 'object':
|
||||||
if typ == 'player':
|
if typ == 'account':
|
||||||
return obj.obj
|
return obj.obj
|
||||||
if typ == 'string':
|
if typ == 'string':
|
||||||
return _ObjectDB.objects.get(db_key__iexact=obj)
|
return _ObjectDB.objects.get(db_key__iexact=obj)
|
||||||
if typ == 'dbref':
|
if typ == 'dbref':
|
||||||
return _ObjectDB.objects.get(id=obj)
|
return _ObjectDB.objects.get(id=obj)
|
||||||
logger.log_err("%s %s %s %s %s", objtype, inp, obj, typ, type(inp))
|
logger.log_err("%s %s %s %s %s" % (objtype, inp, obj, typ, type(inp)))
|
||||||
raise CommError()
|
raise CommError()
|
||||||
elif objtype == 'channel':
|
elif objtype == 'channel':
|
||||||
if typ == 'string':
|
if typ == 'string':
|
||||||
return _ChannelDB.objects.get(db_key__iexact=obj)
|
return _ChannelDB.objects.get(db_key__iexact=obj)
|
||||||
if typ == 'dbref':
|
if typ == 'dbref':
|
||||||
return _ChannelDB.objects.get(id=obj)
|
return _ChannelDB.objects.get(id=obj)
|
||||||
logger.log_err("%s %s %s %s %s", objtype, inp, obj, typ, type(inp))
|
logger.log_err("%s %s %s %s %s" % (objtype, inp, obj, typ, type(inp)))
|
||||||
raise CommError()
|
raise CommError()
|
||||||
# an unknown
|
# an unknown
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Msg manager
|
# Msg manager
|
||||||
#
|
#
|
||||||
|
|
@ -159,7 +158,7 @@ class MsgManager(TypedObjectManager):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
identified (tuple): This is a tuple with (`inp`, identifier)
|
identified (tuple): This is a tuple with (`inp`, identifier)
|
||||||
where `identifier` is one of "player", "object", "channel",
|
where `identifier` is one of "account", "object", "channel",
|
||||||
"string", "dbref" or None.
|
"string", "dbref" or None.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -184,10 +183,10 @@ class MsgManager(TypedObjectManager):
|
||||||
def get_messages_by_sender(self, sender, exclude_channel_messages=False):
|
def get_messages_by_sender(self, sender, exclude_channel_messages=False):
|
||||||
"""
|
"""
|
||||||
Get all messages sent by one entity - this could be either a
|
Get all messages sent by one entity - this could be either a
|
||||||
player or an object
|
account or an object
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sender (Player or Object): The sender of the message.
|
sender (Account or Object): The sender of the message.
|
||||||
exclude_channel_messages (bool, optional): Only return messages
|
exclude_channel_messages (bool, optional): Only return messages
|
||||||
not aimed at a channel (that is, private tells for example)
|
not aimed at a channel (that is, private tells for example)
|
||||||
|
|
||||||
|
|
@ -201,18 +200,18 @@ class MsgManager(TypedObjectManager):
|
||||||
obj, typ = identify_object(sender)
|
obj, typ = identify_object(sender)
|
||||||
if exclude_channel_messages:
|
if exclude_channel_messages:
|
||||||
# explicitly exclude channel recipients
|
# explicitly exclude channel recipients
|
||||||
if typ == 'player':
|
if typ == 'account':
|
||||||
return list(self.filter(db_sender_players=obj,
|
return list(self.filter(db_sender_accounts=obj,
|
||||||
db_receivers_channels__isnull=True).exclude(db_hide_from_players=obj))
|
db_receivers_channels__isnull=True).exclude(db_hide_from_accounts=obj))
|
||||||
elif typ == 'object':
|
elif typ == 'object':
|
||||||
return list(self.filter(db_sender_objects=obj,
|
return list(self.filter(db_sender_objects=obj,
|
||||||
db_receivers_channels__isnull=True).exclude(db_hide_from_objects=obj))
|
db_receivers_channels__isnull=True).exclude(db_hide_from_objects=obj))
|
||||||
else:
|
else:
|
||||||
raise CommError
|
raise CommError
|
||||||
else:
|
else:
|
||||||
# get everything, channel or not
|
# get everything, channel or not
|
||||||
if typ == 'player':
|
if typ == 'account':
|
||||||
return list(self.filter(db_sender_players=obj).exclude(db_hide_from_players=obj))
|
return list(self.filter(db_sender_accounts=obj).exclude(db_hide_from_accounts=obj))
|
||||||
elif typ == 'object':
|
elif typ == 'object':
|
||||||
return list(self.filter(db_sender_objects=obj).exclude(db_hide_from_objects=obj))
|
return list(self.filter(db_sender_objects=obj).exclude(db_hide_from_objects=obj))
|
||||||
else:
|
else:
|
||||||
|
|
@ -223,7 +222,7 @@ class MsgManager(TypedObjectManager):
|
||||||
Get all messages sent to one given recipient.
|
Get all messages sent to one given recipient.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
recipient (Object, Player or Channel): The recipient of the messages to search for.
|
recipient (Object, Account or Channel): The recipient of the messages to search for.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
messages (list): Matching messages.
|
messages (list): Matching messages.
|
||||||
|
|
@ -233,8 +232,8 @@ class MsgManager(TypedObjectManager):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
obj, typ = identify_object(recipient)
|
obj, typ = identify_object(recipient)
|
||||||
if typ == 'player':
|
if typ == 'account':
|
||||||
return list(self.filter(db_receivers_players=obj).exclude(db_hide_from_players=obj))
|
return list(self.filter(db_receivers_accounts=obj).exclude(db_hide_from_accounts=obj))
|
||||||
elif typ == 'object':
|
elif typ == 'object':
|
||||||
return list(self.filter(db_receivers_objects=obj).exclude(db_hide_from_objects=obj))
|
return list(self.filter(db_receivers_objects=obj).exclude(db_hide_from_objects=obj))
|
||||||
elif typ == 'channel':
|
elif typ == 'channel':
|
||||||
|
|
@ -261,9 +260,9 @@ class MsgManager(TypedObjectManager):
|
||||||
one of the arguments must be given to do a search.
|
one of the arguments must be given to do a search.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sender (Object or Player, optional): Get messages sent by a particular player or object
|
sender (Object or Account, optional): Get messages sent by a particular account or object
|
||||||
receiver (Object, Player or Channel, optional): Get messages
|
receiver (Object, Account or Channel, optional): Get messages
|
||||||
received by a certain player,object or channel
|
received by a certain account,object or channel
|
||||||
freetext (str): Search for a text string in a message. NOTE:
|
freetext (str): Search for a text string in a message. NOTE:
|
||||||
This can potentially be slow, so make sure to supply one of
|
This can potentially be slow, so make sure to supply one of
|
||||||
the other arguments to limit the search.
|
the other arguments to limit the search.
|
||||||
|
|
@ -288,16 +287,16 @@ class MsgManager(TypedObjectManager):
|
||||||
|
|
||||||
# filter by sender
|
# filter by sender
|
||||||
sender, styp = identify_object(sender)
|
sender, styp = identify_object(sender)
|
||||||
if styp == 'player':
|
if styp == 'account':
|
||||||
sender_restrict = Q(db_sender_players=sender) & ~Q(db_hide_from_players=sender)
|
sender_restrict = Q(db_sender_accounts=sender) & ~Q(db_hide_from_accounts=sender)
|
||||||
elif styp == 'object':
|
elif styp == 'object':
|
||||||
sender_restrict = Q(db_sender_objects=sender) & ~Q(db_hide_from_objects=sender)
|
sender_restrict = Q(db_sender_objects=sender) & ~Q(db_hide_from_objects=sender)
|
||||||
else:
|
else:
|
||||||
sender_restrict = Q()
|
sender_restrict = Q()
|
||||||
# filter by receiver
|
# filter by receiver
|
||||||
receiver, rtyp = identify_object(receiver)
|
receiver, rtyp = identify_object(receiver)
|
||||||
if rtyp == 'player':
|
if rtyp == 'account':
|
||||||
receiver_restrict = Q(db_receivers_players=receiver) & ~Q(db_hide_from_players=receiver)
|
receiver_restrict = Q(db_receivers_accounts=receiver) & ~Q(db_hide_from_accounts=receiver)
|
||||||
elif rtyp == 'object':
|
elif rtyp == 'object':
|
||||||
receiver_restrict = Q(db_receivers_objects=receiver) & ~Q(db_hide_from_objects=receiver)
|
receiver_restrict = Q(db_receivers_objects=receiver) & ~Q(db_hide_from_objects=receiver)
|
||||||
elif rtyp == 'channel':
|
elif rtyp == 'channel':
|
||||||
|
|
@ -314,6 +313,7 @@ class MsgManager(TypedObjectManager):
|
||||||
# back-compatibility alias
|
# back-compatibility alias
|
||||||
message_search = search_message
|
message_search = search_message
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Channel manager
|
# Channel manager
|
||||||
#
|
#
|
||||||
|
|
@ -332,7 +332,7 @@ class ChannelDBManager(TypedObjectManager):
|
||||||
subscribed to the Channel.
|
subscribed to the Channel.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@returns_typeclass_list
|
|
||||||
def get_all_channels(self):
|
def get_all_channels(self):
|
||||||
"""
|
"""
|
||||||
Get all channels.
|
Get all channels.
|
||||||
|
|
@ -343,7 +343,6 @@ class ChannelDBManager(TypedObjectManager):
|
||||||
"""
|
"""
|
||||||
return self.all()
|
return self.all()
|
||||||
|
|
||||||
@returns_typeclass
|
|
||||||
def get_channel(self, channelkey):
|
def get_channel(self, channelkey):
|
||||||
"""
|
"""
|
||||||
Return the channel object if given its key.
|
Return the channel object if given its key.
|
||||||
|
|
@ -356,36 +355,35 @@ class ChannelDBManager(TypedObjectManager):
|
||||||
channel (Channel or None): A channel match.
|
channel (Channel or None): A channel match.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# first check the channel key
|
dbref = self.dbref(channelkey)
|
||||||
channels = self.filter(db_key__iexact=channelkey)
|
if dbref:
|
||||||
if not channels:
|
try:
|
||||||
# also check aliases
|
return self.get(id=dbref)
|
||||||
channels = [channel for channel in self.all()
|
except self.model.DoesNotExist:
|
||||||
if channelkey in channel.aliases.all()]
|
pass
|
||||||
if channels:
|
results = self.filter(Q(db_key__iexact=channelkey) |
|
||||||
return channels[0]
|
Q(db_tags__db_tagtype__iexact="alias",
|
||||||
return None
|
db_tags__db_key__iexact=channelkey)).distinct()
|
||||||
|
return results[0] if results else None
|
||||||
|
|
||||||
@returns_typeclass_list
|
|
||||||
def get_subscriptions(self, subscriber):
|
def get_subscriptions(self, subscriber):
|
||||||
"""
|
"""
|
||||||
Return all channels a given entity is subscribed to.
|
Return all channels a given entity is subscribed to.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
subscriber (Object or Player): The one subscribing.
|
subscriber (Object or Account): The one subscribing.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
subscriptions (list): Channel subscribed to.
|
subscriptions (list): Channel subscribed to.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
clsname = subscriber.__dbclass__.__name__
|
clsname = subscriber.__dbclass__.__name__
|
||||||
if clsname == "PlayerDB":
|
if clsname == "AccountDB":
|
||||||
return subscriber.subscription_set.all()
|
return subscriber.account_subscription_set.all()
|
||||||
if clsname == "ObjectDB":
|
if clsname == "ObjectDB":
|
||||||
return subscriber.object_subscription_set.all()
|
return subscriber.object_subscription_set.all()
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@returns_typeclass_list
|
|
||||||
def search_channel(self, ostring, exact=True):
|
def search_channel(self, ostring, exact=True):
|
||||||
"""
|
"""
|
||||||
Search the channel database for a particular channel.
|
Search the channel database for a particular channel.
|
||||||
|
|
@ -396,34 +394,27 @@ class ChannelDBManager(TypedObjectManager):
|
||||||
case sensitive) match.
|
case sensitive) match.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
channels = []
|
dbref = self.dbref(ostring)
|
||||||
if not ostring: return channels
|
if dbref:
|
||||||
try:
|
try:
|
||||||
# try an id match first
|
return self.get(id=dbref)
|
||||||
dbref = int(ostring.strip('#'))
|
except self.model.DoesNotExist:
|
||||||
channels = self.filter(id=dbref)
|
pass
|
||||||
except Exception:
|
if exact:
|
||||||
# Usually because we couldn't convert to int - not a dbref
|
channels = self.filter(Q(db_key__iexact=ostring) |
|
||||||
pass
|
Q(db_tags__db_tagtype__iexact="alias",
|
||||||
if not channels:
|
db_tags__db_key__iexact=ostring)).distinct()
|
||||||
# no id match. Search on the key.
|
else:
|
||||||
if exact:
|
channels = self.filter(Q(db_key__icontains=ostring) |
|
||||||
channels = self.filter(db_key__iexact=ostring)
|
Q(db_tags__db_tagtype__iexact="alias",
|
||||||
else:
|
db_tags__db_key__icontains=ostring)).distinct()
|
||||||
channels = self.filter(db_key__icontains=ostring)
|
|
||||||
if not channels:
|
|
||||||
# still no match. Search by alias.
|
|
||||||
channels = [channel for channel in self.all()
|
|
||||||
if ostring.lower() in [a.lower
|
|
||||||
for a in channel.aliases.all()]]
|
|
||||||
return channels
|
return channels
|
||||||
# back-compatibility alias
|
# back-compatibility alias
|
||||||
channel_search = search_channel
|
channel_search = search_channel
|
||||||
|
|
||||||
|
|
||||||
class ChannelManager(ChannelDBManager, TypeclassManager):
|
class ChannelManager(ChannelDBManager, TypeclassManager):
|
||||||
"""
|
"""
|
||||||
Wrapper to group the typeclass manager to a consistent name.
|
Wrapper to group the typeclass manager to a consistent name.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ class Migration(migrations.Migration):
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='msg',
|
model_name='msg',
|
||||||
name='db_hide_from_players',
|
name='db_hide_from_accounts',
|
||||||
field=models.ManyToManyField(related_name=b'hide_from_players_set', null=True, to=settings.AUTH_USER_MODEL),
|
field=models.ManyToManyField(related_name=b'hide_from_accounts_set', null=True, to=settings.AUTH_USER_MODEL),
|
||||||
preserve_default=True,
|
preserve_default=True,
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
|
|
@ -35,8 +35,8 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='msg',
|
model_name='msg',
|
||||||
name='db_receivers_players',
|
name='db_receivers_accounts',
|
||||||
field=models.ManyToManyField(help_text=b'player receivers', related_name=b'receiver_player_set', null=True, to=settings.AUTH_USER_MODEL),
|
field=models.ManyToManyField(help_text=b'account receivers', related_name=b'receiver_account_set', null=True, to=settings.AUTH_USER_MODEL),
|
||||||
preserve_default=True,
|
preserve_default=True,
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
|
|
@ -47,8 +47,8 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='msg',
|
model_name='msg',
|
||||||
name='db_sender_players',
|
name='db_sender_accounts',
|
||||||
field=models.ManyToManyField(related_name=b'sender_player_set', null=True, verbose_name=b'sender(player)', to=settings.AUTH_USER_MODEL, db_index=True),
|
field=models.ManyToManyField(related_name=b'sender_account_set', null=True, verbose_name=b'sender(account)', to=settings.AUTH_USER_MODEL, db_index=True),
|
||||||
preserve_default=True,
|
preserve_default=True,
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
def convert_defaults(apps, schema_editor):
|
def convert_defaults(apps, schema_editor):
|
||||||
ChannelDB = apps.get_model("comms", "ChannelDB")
|
ChannelDB = apps.get_model("comms", "ChannelDB")
|
||||||
for channel in ChannelDB.objects.filter(db_typeclass_path="src.comms.comms.Channel"):
|
for channel in ChannelDB.objects.filter(db_typeclass_path="src.comms.comms.Channel"):
|
||||||
channel.db_typeclass_path = "typeclasses.channels.Channel"
|
channel.db_typeclass_path = "typeclasses.channels.Channel"
|
||||||
channel.save()
|
channel.save()
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|
@ -16,5 +18,5 @@ class Migration(migrations.Migration):
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RunPython(convert_defaults),
|
migrations.RunPython(convert_defaults),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
def convert_channelnames(apps, schema_editor):
|
def convert_channelnames(apps, schema_editor):
|
||||||
ChannelDB = apps.get_model("comms", "ChannelDB")
|
ChannelDB = apps.get_model("comms", "ChannelDB")
|
||||||
for chan in ChannelDB.objects.filter(db_key="MUDinfo"):
|
for chan in ChannelDB.objects.filter(db_key="MUDinfo"):
|
||||||
|
|
@ -13,6 +14,7 @@ def convert_channelnames(apps, schema_editor):
|
||||||
chan.db_key = "MudInfo"
|
chan.db_key = "MudInfo"
|
||||||
chan.save()
|
chan.save()
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|
@ -20,5 +22,5 @@ class Migration(migrations.Migration):
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RunPython(convert_channelnames),
|
migrations.RunPython(convert_channelnames),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='msg',
|
model_name='msg',
|
||||||
name='db_hide_from_players',
|
name='db_hide_from_accounts',
|
||||||
field=models.ManyToManyField(blank=True, null=True, related_name='hide_from_players_set', to=settings.AUTH_USER_MODEL),
|
field=models.ManyToManyField(blank=True, null=True, related_name='hide_from_accounts_set', to=settings.AUTH_USER_MODEL),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='msg',
|
model_name='msg',
|
||||||
|
|
@ -40,8 +40,8 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='msg',
|
model_name='msg',
|
||||||
name='db_receivers_players',
|
name='db_receivers_accounts',
|
||||||
field=models.ManyToManyField(blank=True, help_text=b'player receivers', null=True, related_name='receiver_player_set', to=settings.AUTH_USER_MODEL),
|
field=models.ManyToManyField(blank=True, help_text=b'account receivers', null=True, related_name='receiver_account_set', to=settings.AUTH_USER_MODEL),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='msg',
|
model_name='msg',
|
||||||
|
|
@ -55,8 +55,8 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='msg',
|
model_name='msg',
|
||||||
name='db_sender_players',
|
name='db_sender_accounts',
|
||||||
field=models.ManyToManyField(blank=True, db_index=True, null=True, related_name='sender_player_set', to=settings.AUTH_USER_MODEL, verbose_name=b'sender(player)'),
|
field=models.ManyToManyField(blank=True, db_index=True, null=True, related_name='sender_account_set', to=settings.AUTH_USER_MODEL, verbose_name=b'sender(account)'),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='msg',
|
model_name='msg',
|
||||||
|
|
|
||||||
26
evennia/comms/migrations/0011_auto_20170217_2039.py
Normal file
26
evennia/comms/migrations/0011_auto_20170217_2039.py
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.11 on 2017-02-17 20:39
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('scripts', '0007_auto_20150403_2339'),
|
||||||
|
('comms', '0010_auto_20161206_1912'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_receivers_scripts',
|
||||||
|
field=models.ManyToManyField(blank=True, help_text=b'script_receivers', null=True, related_name='receiver_script_set', to='scripts.ScriptDB'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_sender_scripts',
|
||||||
|
field=models.ManyToManyField(blank=True, db_index=True, null=True, related_name='sender_script_set', to='scripts.ScriptDB', verbose_name=b'sender(script)'),
|
||||||
|
),
|
||||||
|
]
|
||||||
81
evennia/comms/migrations/0011_auto_20170606_1731.py
Normal file
81
evennia/comms/migrations/0011_auto_20170606_1731.py
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.2 on 2017-06-06 17:31
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('comms', '0010_auto_20161206_1912'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='channeldb',
|
||||||
|
name='db_attributes',
|
||||||
|
field=models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='channeldb',
|
||||||
|
name='db_object_subscriptions',
|
||||||
|
field=models.ManyToManyField(blank=True, db_index=True, related_name='object_subscription_set', to='objects.ObjectDB', verbose_name=b'subscriptions'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='channeldb',
|
||||||
|
name='db_subscriptions',
|
||||||
|
field=models.ManyToManyField(blank=True, db_index=True, related_name='subscription_set', to=settings.AUTH_USER_MODEL, verbose_name=b'subscriptions'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='channeldb',
|
||||||
|
name='db_tags',
|
||||||
|
field=models.ManyToManyField(help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_hide_from_channels',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='hide_from_channels_set', to='comms.ChannelDB'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_hide_from_objects',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='hide_from_objects_set', to='objects.ObjectDB'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_hide_from_accounts',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='hide_from_accounts_set', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_receivers_channels',
|
||||||
|
field=models.ManyToManyField(blank=True, help_text=b'channel recievers', related_name='channel_set', to='comms.ChannelDB'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_receivers_objects',
|
||||||
|
field=models.ManyToManyField(blank=True, help_text=b'object receivers', related_name='receiver_object_set', to='objects.ObjectDB'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_receivers_accounts',
|
||||||
|
field=models.ManyToManyField(blank=True, help_text=b'account receivers', related_name='receiver_account_set', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_sender_objects',
|
||||||
|
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_object_set', to='objects.ObjectDB', verbose_name=b'sender(object)'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_sender_accounts',
|
||||||
|
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_account_set', to=settings.AUTH_USER_MODEL, verbose_name=b'sender(account)'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_tags',
|
||||||
|
field=models.ManyToManyField(blank=True, help_text=b'tags on this message. Tags are simple string markers to identify, group and alias messages.', to='typeclasses.Tag'),
|
||||||
|
),
|
||||||
|
]
|
||||||
16
evennia/comms/migrations/0012_merge_20170617_2017.py
Normal file
16
evennia/comms/migrations/0012_merge_20170617_2017.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.2 on 2017-06-17 20:17
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('comms', '0011_auto_20170606_1731'),
|
||||||
|
('comms', '0011_auto_20170217_2039'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
84
evennia/comms/migrations/0013_auto_20170705_1726.py
Normal file
84
evennia/comms/migrations/0013_auto_20170705_1726.py
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.2 on 2017-07-05 17:26
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models, connection
|
||||||
|
|
||||||
|
|
||||||
|
def _table_exists(db_cursor, tablename):
|
||||||
|
"Returns bool if table exists or not"
|
||||||
|
return tablename in connection.introspection.table_names()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0007_copy_player_to_account'),
|
||||||
|
('comms', '0012_merge_20170617_2017'),
|
||||||
|
]
|
||||||
|
|
||||||
|
db_cursor = connection.cursor()
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='channeldb',
|
||||||
|
name='db_account_subscriptions',
|
||||||
|
field=models.ManyToManyField(blank=True, db_index=True, related_name='account_subscription_set', to='accounts.AccountDB', verbose_name=b'account subscriptions'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='channeldb',
|
||||||
|
name='db_object_subscriptions',
|
||||||
|
field=models.ManyToManyField(blank=True, db_index=True, related_name='object_subscription_set', to='objects.ObjectDB', verbose_name=b'object subscriptions'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_receivers_scripts',
|
||||||
|
field=models.ManyToManyField(blank=True, help_text=b'script_receivers', related_name='receiver_script_set', to='scripts.ScriptDB'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_sender_scripts',
|
||||||
|
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_script_set', to='scripts.ScriptDB', verbose_name=b'sender(script)'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='channeldb',
|
||||||
|
name='db_object_subscriptions',
|
||||||
|
field=models.ManyToManyField(blank=True, db_index=True, related_name='object_subscription_set', to='objects.ObjectDB', verbose_name=b'object subscriptions'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_receivers_scripts',
|
||||||
|
field=models.ManyToManyField(blank=True, help_text=b'script_receivers', related_name='receiver_script_set', to='scripts.ScriptDB'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_sender_scripts',
|
||||||
|
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_script_set', to='scripts.ScriptDB', verbose_name=b'sender(script)'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
if _table_exists(db_cursor, 'comms_msg_db_hide_from_players'):
|
||||||
|
# OBS - this is run BEFORE migrations are run!
|
||||||
|
# not a migration of an existing database
|
||||||
|
operations += [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='channeldb',
|
||||||
|
name='db_account_subscriptions',
|
||||||
|
field=models.ManyToManyField(blank=True, db_index=True, related_name='account_subscription_set', to='accounts.AccountDB', verbose_name=b'account subscriptions'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_hide_from_accounts',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='hide_from_accounts_set', to='accounts.AccountDB'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_receivers_accounts',
|
||||||
|
field=models.ManyToManyField(blank=True, help_text=b'account receivers', related_name='receiver_account_set', to='accounts.AccountDB'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_sender_accounts',
|
||||||
|
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_account_set', to='accounts.AccountDB', verbose_name=b'sender(account)'),
|
||||||
|
),
|
||||||
|
]
|
||||||
46
evennia/comms/migrations/0014_auto_20170705_1736.py
Normal file
46
evennia/comms/migrations/0014_auto_20170705_1736.py
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.2 on 2017-07-05 17:36
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
# This migration only made sense earlier in the git history, during
|
||||||
|
# the player->account transition. Now it will do nothing since players.PlayerDB
|
||||||
|
# no longer exists.
|
||||||
|
|
||||||
|
def forwards(apps, schema_editor):
|
||||||
|
|
||||||
|
try:
|
||||||
|
apps.get_model('players', 'PlayerDB')
|
||||||
|
except LookupError:
|
||||||
|
return
|
||||||
|
AccountDB = apps.get_model('accounts', 'AccountDB')
|
||||||
|
|
||||||
|
Msg = apps.get_model('comms', 'Msg')
|
||||||
|
for msg in Msg.objects.all():
|
||||||
|
for player in msg.db_sender_players.all():
|
||||||
|
account = AccountDB.objects.get(id=player.id)
|
||||||
|
msg.db_sender_accounts.add(account)
|
||||||
|
for player in msg.db_receivers_players.all():
|
||||||
|
account = AccountDB.objects.get(id=player.id)
|
||||||
|
msg.db_receivers_accounts.add(account)
|
||||||
|
for player in msg.db_hide_from_players.all():
|
||||||
|
account = AccountDB.objects.get(id=player.id)
|
||||||
|
msg.db_hide_from_accounts.add(account)
|
||||||
|
|
||||||
|
ChannelDB = apps.get_model('comms', 'ChannelDB')
|
||||||
|
for channel in ChannelDB.objects.all():
|
||||||
|
for player in channel.db_subscriptions.all():
|
||||||
|
account = AccountDB.objects.get(id=player.id)
|
||||||
|
channel.db_account_subscriptions.add(player)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('comms', '0013_auto_20170705_1726'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(forwards)
|
||||||
|
]
|
||||||
38
evennia/comms/migrations/0015_auto_20170706_2041.py
Normal file
38
evennia/comms/migrations/0015_auto_20170706_2041.py
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.2 on 2017-07-06 20:41
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, connection
|
||||||
|
|
||||||
|
|
||||||
|
def _table_exists(db_cursor, tablename):
|
||||||
|
"Returns bool if table exists or not"
|
||||||
|
return tablename in connection.introspection.table_names()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('comms', '0014_auto_20170705_1736'),
|
||||||
|
]
|
||||||
|
|
||||||
|
db_cursor = connection.cursor()
|
||||||
|
|
||||||
|
if not _table_exists(db_cursor, "channels.channeldb_db_receivers_players"):
|
||||||
|
# OBS - this is run BEFORE migrations are run!
|
||||||
|
operations = []
|
||||||
|
else:
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='channeldb',
|
||||||
|
name='db_subscriptions', # this is now db_account_subscriptions
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_receivers_players',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='msg',
|
||||||
|
name='db_sender_players',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -13,7 +13,7 @@ For non-persistent (and slightly faster) use one can also use the
|
||||||
TempMsg, which mimics the Msg API but without actually saving to the
|
TempMsg, which mimics the Msg API but without actually saving to the
|
||||||
database.
|
database.
|
||||||
|
|
||||||
Channels are central objects that act as targets for Msgs. Players can
|
Channels are central objects that act as targets for Msgs. Accounts can
|
||||||
connect to channels by use of a ChannelConnect object (this object is
|
connect to channels by use of a ChannelConnect object (this object is
|
||||||
necessary to easily be able to delete connections on the fly).
|
necessary to easily be able to delete connections on the fly).
|
||||||
"""
|
"""
|
||||||
|
|
@ -48,16 +48,18 @@ _CHANNELHANDLER = None
|
||||||
class Msg(SharedMemoryModel):
|
class Msg(SharedMemoryModel):
|
||||||
"""
|
"""
|
||||||
A single message. This model describes all ooc messages
|
A single message. This model describes all ooc messages
|
||||||
sent in-game, both to channels and between players.
|
sent in-game, both to channels and between accounts.
|
||||||
|
|
||||||
The Msg class defines the following database fields (all
|
The Msg class defines the following database fields (all
|
||||||
accessed via specific handler methods):
|
accessed via specific handler methods):
|
||||||
|
|
||||||
- db_sender_players: Player senders
|
- db_sender_accounts: Account senders
|
||||||
- db_sender_objects: Object senders
|
- db_sender_objects: Object senders
|
||||||
|
- db_sender_scripts: Script senders
|
||||||
- db_sender_external: External senders (defined as string names)
|
- db_sender_external: External senders (defined as string names)
|
||||||
- db_receivers_players: Receiving players
|
- db_receivers_accounts: Receiving accounts
|
||||||
- db_receivers_objects: Receiving objects
|
- db_receivers_objects: Receiving objects
|
||||||
|
- db_receivers_scripts: Receiveing scripts
|
||||||
- db_receivers_channels: Receiving channels
|
- db_receivers_channels: Receiving channels
|
||||||
- db_header: Header text
|
- db_header: Header text
|
||||||
- db_message: The actual message text
|
- db_message: The actual message text
|
||||||
|
|
@ -75,25 +77,31 @@ class Msg(SharedMemoryModel):
|
||||||
# These databse fields are all set using their corresponding properties,
|
# These databse fields are all set using their corresponding properties,
|
||||||
# named same as the field, but withtout the db_* prefix.
|
# named same as the field, but withtout the db_* prefix.
|
||||||
|
|
||||||
# Sender is either a player, an object or an external sender, like
|
# Sender is either an account, an object or an external sender, like
|
||||||
# an IRC channel; normally there is only one, but if co-modification of
|
# an IRC channel; normally there is only one, but if co-modification of
|
||||||
# a message is allowed, there may be more than one "author"
|
# a message is allowed, there may be more than one "author"
|
||||||
db_sender_players = models.ManyToManyField("players.PlayerDB", related_name='sender_player_set',
|
db_sender_accounts = models.ManyToManyField("accounts.AccountDB", related_name='sender_account_set',
|
||||||
null=True, blank=True, verbose_name='sender(player)', db_index=True)
|
blank=True, verbose_name='sender(account)', db_index=True)
|
||||||
|
|
||||||
db_sender_objects = models.ManyToManyField("objects.ObjectDB", related_name='sender_object_set',
|
db_sender_objects = models.ManyToManyField("objects.ObjectDB", related_name='sender_object_set',
|
||||||
null=True, blank=True, verbose_name='sender(object)', db_index=True)
|
blank=True, verbose_name='sender(object)', db_index=True)
|
||||||
|
db_sender_scripts = models.ManyToManyField("scripts.ScriptDB", related_name='sender_script_set',
|
||||||
|
blank=True, verbose_name='sender(script)', db_index=True)
|
||||||
db_sender_external = models.CharField('external sender', max_length=255, null=True, blank=True, db_index=True,
|
db_sender_external = models.CharField('external sender', max_length=255, null=True, blank=True, db_index=True,
|
||||||
help_text="identifier for external sender, for example a sender over an "
|
help_text="identifier for external sender, for example a sender over an "
|
||||||
"IRC connection (i.e. someone who doesn't have an exixtence in-game).")
|
"IRC connection (i.e. someone who doesn't have an exixtence in-game).")
|
||||||
# The destination objects of this message. Stored as a
|
# The destination objects of this message. Stored as a
|
||||||
# comma-separated string of object dbrefs. Can be defined along
|
# comma-separated string of object dbrefs. Can be defined along
|
||||||
# with channels below.
|
# with channels below.
|
||||||
db_receivers_players = models.ManyToManyField('players.PlayerDB', related_name='receiver_player_set',
|
db_receivers_accounts = models.ManyToManyField('accounts.AccountDB', related_name='receiver_account_set',
|
||||||
null=True, blank=True, help_text="player receivers")
|
blank=True, help_text="account receivers")
|
||||||
|
|
||||||
db_receivers_objects = models.ManyToManyField('objects.ObjectDB', related_name='receiver_object_set',
|
db_receivers_objects = models.ManyToManyField('objects.ObjectDB', related_name='receiver_object_set',
|
||||||
null=True, blank=True, help_text="object receivers")
|
blank=True, help_text="object receivers")
|
||||||
|
db_receivers_scripts = models.ManyToManyField('scripts.ScriptDB', related_name='receiver_script_set',
|
||||||
|
blank=True, help_text="script_receivers")
|
||||||
db_receivers_channels = models.ManyToManyField("ChannelDB", related_name='channel_set',
|
db_receivers_channels = models.ManyToManyField("ChannelDB", related_name='channel_set',
|
||||||
null=True, blank=True, help_text="channel recievers")
|
blank=True, help_text="channel recievers")
|
||||||
|
|
||||||
# header could be used for meta-info about the message if your system needs
|
# header could be used for meta-info about the message if your system needs
|
||||||
# it, or as a separate store for the mail subject line maybe.
|
# it, or as a separate store for the mail subject line maybe.
|
||||||
|
|
@ -106,13 +114,14 @@ class Msg(SharedMemoryModel):
|
||||||
db_lock_storage = models.TextField('locks', blank=True,
|
db_lock_storage = models.TextField('locks', blank=True,
|
||||||
help_text='access locks on this message.')
|
help_text='access locks on this message.')
|
||||||
|
|
||||||
# these can be used to filter/hide a given message from supplied objects/players/channels
|
# these can be used to filter/hide a given message from supplied objects/accounts/channels
|
||||||
db_hide_from_players = models.ManyToManyField("players.PlayerDB", related_name='hide_from_players_set', null=True, blank=True)
|
db_hide_from_accounts = models.ManyToManyField("accounts.AccountDB", related_name='hide_from_accounts_set', blank=True)
|
||||||
db_hide_from_objects = models.ManyToManyField("objects.ObjectDB", related_name='hide_from_objects_set', null=True, blank=True)
|
|
||||||
db_hide_from_channels = models.ManyToManyField("ChannelDB", related_name='hide_from_channels_set', null=True, blank=True)
|
|
||||||
|
|
||||||
db_tags = models.ManyToManyField(Tag, null=True, blank=True,
|
db_hide_from_objects = models.ManyToManyField("objects.ObjectDB", related_name='hide_from_objects_set', blank=True)
|
||||||
help_text='tags on this message. Tags are simple string markers to identify, group and alias messages.')
|
db_hide_from_channels = models.ManyToManyField("ChannelDB", related_name='hide_from_channels_set', blank=True)
|
||||||
|
|
||||||
|
db_tags = models.ManyToManyField(Tag, blank=True,
|
||||||
|
help_text='tags on this message. Tags are simple string markers to identify, group and alias messages.')
|
||||||
|
|
||||||
# Database manager
|
# Database manager
|
||||||
objects = managers.MsgManager()
|
objects = managers.MsgManager()
|
||||||
|
|
@ -146,9 +155,10 @@ class Msg(SharedMemoryModel):
|
||||||
#@property
|
#@property
|
||||||
def __senders_get(self):
|
def __senders_get(self):
|
||||||
"Getter. Allows for value = self.sender"
|
"Getter. Allows for value = self.sender"
|
||||||
return list(self.db_sender_players.all()) + \
|
return list(self.db_sender_accounts.all()) + \
|
||||||
list(self.db_sender_objects.all()) + \
|
list(self.db_sender_objects.all()) + \
|
||||||
self.extra_senders
|
list(self.db_sender_scripts.all()) + \
|
||||||
|
self.extra_senders
|
||||||
|
|
||||||
#@sender.setter
|
#@sender.setter
|
||||||
def __senders_set(self, senders):
|
def __senders_set(self, senders):
|
||||||
|
|
@ -166,14 +176,17 @@ class Msg(SharedMemoryModel):
|
||||||
clsname = sender.__dbclass__.__name__
|
clsname = sender.__dbclass__.__name__
|
||||||
if clsname == "ObjectDB":
|
if clsname == "ObjectDB":
|
||||||
self.db_sender_objects.add(sender)
|
self.db_sender_objects.add(sender)
|
||||||
elif clsname == "PlayerDB":
|
elif clsname == "AccountDB":
|
||||||
self.db_sender_players.add(sender)
|
self.db_sender_accounts.add(sender)
|
||||||
|
elif clsname == "ScriptDB":
|
||||||
|
self.db_sender_scripts.add(sender)
|
||||||
|
|
||||||
#@sender.deleter
|
#@sender.deleter
|
||||||
def __senders_del(self):
|
def __senders_del(self):
|
||||||
"Deleter. Clears all senders"
|
"Deleter. Clears all senders"
|
||||||
self.db_sender_players.clear()
|
self.db_sender_accounts.clear()
|
||||||
self.db_sender_objects.clear()
|
self.db_sender_objects.clear()
|
||||||
|
self.db_sender_scripts.clear()
|
||||||
self.db_sender_external = ""
|
self.db_sender_external = ""
|
||||||
self.extra_senders = []
|
self.extra_senders = []
|
||||||
self.save()
|
self.save()
|
||||||
|
|
@ -184,7 +197,7 @@ class Msg(SharedMemoryModel):
|
||||||
Remove a single sender or a list of senders.
|
Remove a single sender or a list of senders.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
senders (Player, Object, str or list): Senders to remove.
|
senders (Account, Object, str or list): Senders to remove.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for sender in make_iter(senders):
|
for sender in make_iter(senders):
|
||||||
|
|
@ -198,17 +211,22 @@ class Msg(SharedMemoryModel):
|
||||||
clsname = sender.__dbclass__.__name__
|
clsname = sender.__dbclass__.__name__
|
||||||
if clsname == "ObjectDB":
|
if clsname == "ObjectDB":
|
||||||
self.db_sender_objects.remove(sender)
|
self.db_sender_objects.remove(sender)
|
||||||
elif clsname == "PlayerDB":
|
elif clsname == "AccountDB":
|
||||||
self.db_sender_players.remove(sender)
|
self.db_sender_accounts.remove(sender)
|
||||||
|
elif clsname == "ScriptDB":
|
||||||
|
self.db_sender_accounts.remove(sender)
|
||||||
|
|
||||||
# receivers property
|
# receivers property
|
||||||
#@property
|
#@property
|
||||||
def __receivers_get(self):
|
def __receivers_get(self):
|
||||||
"""
|
"""
|
||||||
Getter. Allows for value = self.receivers.
|
Getter. Allows for value = self.receivers.
|
||||||
Returns three lists of receivers: players, objects and channels.
|
Returns four lists of receivers: accounts, objects, scripts and channels.
|
||||||
"""
|
"""
|
||||||
return list(self.db_receivers_players.all()) + list(self.db_receivers_objects.all())
|
return list(self.db_receivers_accounts.all()) + \
|
||||||
|
list(self.db_receivers_objects.all()) + \
|
||||||
|
list(self.db_receivers_scripts.all()) + \
|
||||||
|
list(self.db_receivers_channels.all())
|
||||||
|
|
||||||
#@receivers.setter
|
#@receivers.setter
|
||||||
def __receivers_set(self, receivers):
|
def __receivers_set(self, receivers):
|
||||||
|
|
@ -224,14 +242,20 @@ class Msg(SharedMemoryModel):
|
||||||
clsname = receiver.__dbclass__.__name__
|
clsname = receiver.__dbclass__.__name__
|
||||||
if clsname == "ObjectDB":
|
if clsname == "ObjectDB":
|
||||||
self.db_receivers_objects.add(receiver)
|
self.db_receivers_objects.add(receiver)
|
||||||
elif clsname == "PlayerDB":
|
elif clsname == "AccountDB":
|
||||||
self.db_receivers_players.add(receiver)
|
self.db_receivers_accounts.add(receiver)
|
||||||
|
elif clsname == "ScriptDB":
|
||||||
|
self.db_receivers_scripts.add(receiver)
|
||||||
|
elif clsname == "ChannelDB":
|
||||||
|
self.db_receivers_channels.add(receiver)
|
||||||
|
|
||||||
#@receivers.deleter
|
#@receivers.deleter
|
||||||
def __receivers_del(self):
|
def __receivers_del(self):
|
||||||
"Deleter. Clears all receivers"
|
"Deleter. Clears all receivers"
|
||||||
self.db_receivers_players.clear()
|
self.db_receivers_accounts.clear()
|
||||||
self.db_receivers_objects.clear()
|
self.db_receivers_objects.clear()
|
||||||
|
self.db_receivers_scripts.clear()
|
||||||
|
self.db_receivers_channels.clear()
|
||||||
self.save()
|
self.save()
|
||||||
receivers = property(__receivers_get, __receivers_set, __receivers_del)
|
receivers = property(__receivers_get, __receivers_set, __receivers_del)
|
||||||
|
|
||||||
|
|
@ -240,7 +264,7 @@ class Msg(SharedMemoryModel):
|
||||||
Remove a single receiver or a list of receivers.
|
Remove a single receiver or a list of receivers.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
receivers (Player, Object, Channel or list): Receiver to remove.
|
receivers (Account, Object, Script, Channel or list): Receiver to remove.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for receiver in make_iter(receivers):
|
for receiver in make_iter(receivers):
|
||||||
|
|
@ -251,8 +275,12 @@ class Msg(SharedMemoryModel):
|
||||||
clsname = receiver.__dbclass__.__name__
|
clsname = receiver.__dbclass__.__name__
|
||||||
if clsname == "ObjectDB":
|
if clsname == "ObjectDB":
|
||||||
self.db_receivers_objects.remove(receiver)
|
self.db_receivers_objects.remove(receiver)
|
||||||
elif clsname == "PlayerDB":
|
elif clsname == "AccountDB":
|
||||||
self.db_receivers_players.remove(receiver)
|
self.db_receivers_accounts.remove(receiver)
|
||||||
|
elif clsname == "ScriptDB":
|
||||||
|
self.db_receivers_scripts.remove(receiver)
|
||||||
|
elif clsname == "ChannelDB":
|
||||||
|
self.db_receivers_channels.remove(receiver)
|
||||||
|
|
||||||
# channels property
|
# channels property
|
||||||
#@property
|
#@property
|
||||||
|
|
@ -279,9 +307,9 @@ class Msg(SharedMemoryModel):
|
||||||
def __hide_from_get(self):
|
def __hide_from_get(self):
|
||||||
"""
|
"""
|
||||||
Getter. Allows for value = self.hide_from.
|
Getter. Allows for value = self.hide_from.
|
||||||
Returns 3 lists of players, objects and channels
|
Returns 3 lists of accounts, objects and channels
|
||||||
"""
|
"""
|
||||||
return self.db_hide_from_players.all(), self.db_hide_from_objects.all(), self.db_hide_from_channels.all()
|
return self.db_hide_from_accounts.all(), self.db_hide_from_objects.all(), self.db_hide_from_channels.all()
|
||||||
|
|
||||||
#@hide_from_sender.setter
|
#@hide_from_sender.setter
|
||||||
def __hide_from_set(self, hiders):
|
def __hide_from_set(self, hiders):
|
||||||
|
|
@ -292,8 +320,8 @@ class Msg(SharedMemoryModel):
|
||||||
if not hasattr(hider, "__dbclass__"):
|
if not hasattr(hider, "__dbclass__"):
|
||||||
raise ValueError("This is a not a typeclassed object!")
|
raise ValueError("This is a not a typeclassed object!")
|
||||||
clsname = hider.__dbclass__.__name__
|
clsname = hider.__dbclass__.__name__
|
||||||
if clsname == "PlayerDB":
|
if clsname == "AccountDB":
|
||||||
self.db_hide_from_players.add(hider.__dbclass__)
|
self.db_hide_from_accounts.add(hider.__dbclass__)
|
||||||
elif clsname == "ObjectDB":
|
elif clsname == "ObjectDB":
|
||||||
self.db_hide_from_objects.add(hider.__dbclass__)
|
self.db_hide_from_objects.add(hider.__dbclass__)
|
||||||
elif clsname == "ChannelDB":
|
elif clsname == "ChannelDB":
|
||||||
|
|
@ -302,7 +330,7 @@ class Msg(SharedMemoryModel):
|
||||||
#@hide_from_sender.deleter
|
#@hide_from_sender.deleter
|
||||||
def __hide_from_del(self):
|
def __hide_from_del(self):
|
||||||
"Deleter. Allows for del self.hide_from_senders"
|
"Deleter. Allows for del self.hide_from_senders"
|
||||||
self.db_hide_from_players.clear()
|
self.db_hide_from_accounts.clear()
|
||||||
self.db_hide_from_objects.clear()
|
self.db_hide_from_objects.clear()
|
||||||
self.db_hide_from_channels.clear()
|
self.db_hide_from_channels.clear()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
@ -323,7 +351,7 @@ class Msg(SharedMemoryModel):
|
||||||
Checks lock access.
|
Checks lock access.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
accessing_obj (Object or Player): The object trying to gain access.
|
accessing_obj (Object or Account): The object trying to gain access.
|
||||||
access_type (str, optional): The type of lock access to check.
|
access_type (str, optional): The type of lock access to check.
|
||||||
default (bool): Fallback to use if `access_type` lock is not defined.
|
default (bool): Fallback to use if `access_type` lock is not defined.
|
||||||
|
|
||||||
|
|
@ -340,6 +368,7 @@ class Msg(SharedMemoryModel):
|
||||||
#
|
#
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class TempMsg(object):
|
class TempMsg(object):
|
||||||
"""
|
"""
|
||||||
This is a non-persistent object for sending temporary messages
|
This is a non-persistent object for sending temporary messages
|
||||||
|
|
@ -347,19 +376,20 @@ class TempMsg(object):
|
||||||
doesn't require sender to be given.
|
doesn't require sender to be given.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, senders=None, receivers=None, channels=None, message="", header="", type="", lockstring="", hide_from=None):
|
def __init__(self, senders=None, receivers=None, channels=None, message="", header="", type="", lockstring="", hide_from=None):
|
||||||
"""
|
"""
|
||||||
Creates the temp message.
|
Creates the temp message.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
senders (any or list, optional): Senders of the message.
|
senders (any or list, optional): Senders of the message.
|
||||||
receivers (Player, Object, Channel or list, optional): Receivers of this message.
|
receivers (Account, Object, Channel or list, optional): Receivers of this message.
|
||||||
channels (Channel or list, optional): Channels to send to.
|
channels (Channel or list, optional): Channels to send to.
|
||||||
message (str, optional): Message to send.
|
message (str, optional): Message to send.
|
||||||
header (str, optional): Header of message.
|
header (str, optional): Header of message.
|
||||||
type (str, optional): Message class, if any.
|
type (str, optional): Message class, if any.
|
||||||
lockstring (str, optional): Lock for the message.
|
lockstring (str, optional): Lock for the message.
|
||||||
hide_from (Player, Object, Channel or list, optional): Entities to hide this message from.
|
hide_from (Account, Object, Channel or list, optional): Entities to hide this message from.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.senders = senders and make_iter(senders) or []
|
self.senders = senders and make_iter(senders) or []
|
||||||
|
|
@ -389,7 +419,7 @@ class TempMsg(object):
|
||||||
Remove a sender or a list of senders.
|
Remove a sender or a list of senders.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sender (Object, Player, str or list): Senders to remove.
|
sender (Object, Account, str or list): Senders to remove.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for o in make_iter(sender):
|
for o in make_iter(sender):
|
||||||
|
|
@ -403,7 +433,7 @@ class TempMsg(object):
|
||||||
Remove a receiver or a list of receivers
|
Remove a receiver or a list of receivers
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
receiver (Object, Player, Channel, str or list): Receivers to remove.
|
receiver (Object, Account, Channel, str or list): Receivers to remove.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for o in make_iter(receiver):
|
for o in make_iter(receiver):
|
||||||
|
|
@ -417,7 +447,7 @@ class TempMsg(object):
|
||||||
Checks lock access.
|
Checks lock access.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
accessing_obj (Object or Player): The object trying to gain access.
|
accessing_obj (Object or Account): The object trying to gain access.
|
||||||
access_type (str, optional): The type of lock access to check.
|
access_type (str, optional): The type of lock access to check.
|
||||||
default (bool): Fallback to use if `access_type` lock is not defined.
|
default (bool): Fallback to use if `access_type` lock is not defined.
|
||||||
|
|
||||||
|
|
@ -439,8 +469,9 @@ class SubscriptionHandler(object):
|
||||||
"""
|
"""
|
||||||
This handler manages subscriptions to the
|
This handler manages subscriptions to the
|
||||||
channel and hides away which type of entity is
|
channel and hides away which type of entity is
|
||||||
subscribing (Player or Object)
|
subscribing (Account or Object)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
"""
|
"""
|
||||||
Initialize the handler
|
Initialize the handler
|
||||||
|
|
@ -453,8 +484,8 @@ class SubscriptionHandler(object):
|
||||||
self._cache = None
|
self._cache = None
|
||||||
|
|
||||||
def _recache(self):
|
def _recache(self):
|
||||||
self._cache = {player: True for player in self.obj.db_subscriptions.all()
|
self._cache = {account: True for account in self.obj.db_account_subscriptions.all()
|
||||||
if hasattr(player, 'pk') and player.pk}
|
if hasattr(account, 'pk') and account.pk}
|
||||||
self._cache.update({obj: True for obj in self.obj.db_object_subscriptions.all()
|
self._cache.update({obj: True for obj in self.obj.db_object_subscriptions.all()
|
||||||
if hasattr(obj, 'pk') and obj.pk})
|
if hasattr(obj, 'pk') and obj.pk})
|
||||||
|
|
||||||
|
|
@ -463,12 +494,12 @@ class SubscriptionHandler(object):
|
||||||
Check if the given entity subscribe to this channel
|
Check if the given entity subscribe to this channel
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entity (str, Player or Object): The entity to return. If
|
entity (str, Account or Object): The entity to return. If
|
||||||
a string, it assumed to be the key or the #dbref
|
a string, it assumed to be the key or the #dbref
|
||||||
of the entity.
|
of the entity.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
subscriber (Player, Object or None): The given
|
subscriber (Account, Object or None): The given
|
||||||
subscriber.
|
subscriber.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -481,7 +512,7 @@ class SubscriptionHandler(object):
|
||||||
Subscribe an entity to this channel.
|
Subscribe an entity to this channel.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entity (Player, Object or list): The entity or
|
entity (Account, Object or list): The entity or
|
||||||
list of entities to subscribe to this channel.
|
list of entities to subscribe to this channel.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
|
|
@ -499,9 +530,9 @@ class SubscriptionHandler(object):
|
||||||
# chooses the right type
|
# chooses the right type
|
||||||
if clsname == "ObjectDB":
|
if clsname == "ObjectDB":
|
||||||
self.obj.db_object_subscriptions.add(subscriber)
|
self.obj.db_object_subscriptions.add(subscriber)
|
||||||
elif clsname == "PlayerDB":
|
elif clsname == "AccountDB":
|
||||||
self.obj.db_subscriptions.add(subscriber)
|
self.obj.db_account_subscriptions.add(subscriber)
|
||||||
_CHANNELHANDLER.cached_cmdsets.pop(subscriber, None)
|
_CHANNELHANDLER._cached_cmdsets.pop(subscriber, None)
|
||||||
self._recache()
|
self._recache()
|
||||||
|
|
||||||
def remove(self, entity):
|
def remove(self, entity):
|
||||||
|
|
@ -509,7 +540,7 @@ class SubscriptionHandler(object):
|
||||||
Remove a subscriber from the channel.
|
Remove a subscriber from the channel.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entity (Player, Object or list): The entity or
|
entity (Account, Object or list): The entity or
|
||||||
entities to un-subscribe from the channel.
|
entities to un-subscribe from the channel.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -520,11 +551,11 @@ class SubscriptionHandler(object):
|
||||||
if subscriber:
|
if subscriber:
|
||||||
clsname = subscriber.__dbclass__.__name__
|
clsname = subscriber.__dbclass__.__name__
|
||||||
# chooses the right type
|
# chooses the right type
|
||||||
if clsname == "PlayerDB":
|
if clsname == "AccountDB":
|
||||||
self.obj.db_subscriptions.remove(entity)
|
self.obj.db_account_subscriptions.remove(entity)
|
||||||
elif clsname == "ObjectDB":
|
elif clsname == "ObjectDB":
|
||||||
self.obj.db_object_subscriptions.remove(entity)
|
self.obj.db_object_subscriptions.remove(entity)
|
||||||
_CHANNELHANDLER.cached_cmdsets.pop(subscriber, None)
|
_CHANNELHANDLER._cached_cmdsets.pop(subscriber, None)
|
||||||
self._recache()
|
self._recache()
|
||||||
|
|
||||||
def all(self):
|
def all(self):
|
||||||
|
|
@ -533,29 +564,30 @@ class SubscriptionHandler(object):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
subscribers (list): The subscribers. This
|
subscribers (list): The subscribers. This
|
||||||
may be a mix of Players and Objects!
|
may be a mix of Accounts and Objects!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self._cache is None:
|
if self._cache is None:
|
||||||
self._recache()
|
self._recache()
|
||||||
return self._cache
|
return self._cache
|
||||||
|
get = all # alias
|
||||||
|
|
||||||
def online(self):
|
def online(self):
|
||||||
"""
|
"""
|
||||||
Get all online players from our cache
|
Get all online accounts from our cache
|
||||||
Returns:
|
Returns:
|
||||||
subscribers (list): Subscribers who are online or
|
subscribers (list): Subscribers who are online or
|
||||||
are puppeted by an online player.
|
are puppeted by an online account.
|
||||||
"""
|
"""
|
||||||
subs = []
|
subs = []
|
||||||
recache_needed = False
|
recache_needed = False
|
||||||
for obj in self.all():
|
for obj in self.all():
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
try:
|
try:
|
||||||
if hasattr(obj, 'player'):
|
if hasattr(obj, 'account'):
|
||||||
if not obj.player:
|
if not obj.account:
|
||||||
continue
|
continue
|
||||||
obj = obj.player
|
obj = obj.account
|
||||||
if not obj.is_connected:
|
if not obj.is_connected:
|
||||||
continue
|
continue
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
|
|
@ -572,7 +604,7 @@ class SubscriptionHandler(object):
|
||||||
Remove all subscribers from channel.
|
Remove all subscribers from channel.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.obj.db_subscriptions.clear()
|
self.obj.db_account_subscriptions.clear()
|
||||||
self.obj.db_object_subscriptions.clear()
|
self.obj.db_object_subscriptions.clear()
|
||||||
self._cache = None
|
self._cache = None
|
||||||
|
|
||||||
|
|
@ -585,16 +617,15 @@ class ChannelDB(TypedObject):
|
||||||
The Channel class defines the following database fields
|
The Channel class defines the following database fields
|
||||||
beyond the ones inherited from TypedObject:
|
beyond the ones inherited from TypedObject:
|
||||||
|
|
||||||
- db_subscriptions: The Player subscriptions (this is the most
|
- db_account_subscriptions: The Account subscriptions.
|
||||||
usual case, named this way for legacy.
|
|
||||||
- db_object_subscriptions: The Object subscriptions.
|
- db_object_subscriptions: The Object subscriptions.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
db_subscriptions = models.ManyToManyField("players.PlayerDB",
|
db_account_subscriptions = models.ManyToManyField("accounts.AccountDB",
|
||||||
related_name="subscription_set", null=True, blank=True, verbose_name='subscriptions', db_index=True)
|
related_name="account_subscription_set", blank=True, verbose_name='account subscriptions', db_index=True)
|
||||||
|
|
||||||
db_object_subscriptions = models.ManyToManyField("objects.ObjectDB",
|
db_object_subscriptions = models.ManyToManyField("objects.ObjectDB",
|
||||||
related_name="object_subscription_set", null=True, blank=True, verbose_name='subscriptions', db_index=True)
|
related_name="object_subscription_set", blank=True, verbose_name='object subscriptions', db_index=True)
|
||||||
|
|
||||||
# Database manager
|
# Database manager
|
||||||
objects = managers.ChannelDBManager()
|
objects = managers.ChannelDBManager()
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,9 @@ things you want from here into your game folder and change them there.
|
||||||
for any game. Allows safe trading of any godds (including coin)
|
for any game. Allows safe trading of any godds (including coin)
|
||||||
* CharGen (Griatch 2011) - A simple Character creator for OOC mode.
|
* CharGen (Griatch 2011) - A simple Character creator for OOC mode.
|
||||||
Meant as a starting point for a more fleshed-out system.
|
Meant as a starting point for a more fleshed-out system.
|
||||||
* Clothing (BattleJenkins 2017) - A layered clothing system with
|
* Clothing (FlutterSprite 2017) - A layered clothing system with
|
||||||
slots for different types of garments auto-showing in description.
|
slots for different types of garments auto-showing in description.
|
||||||
|
* Color-markups (Griatch, 2017) - Alternative in-game color markups.
|
||||||
* Custom gametime (Griatch, vlgeoff 2017) - Implements Evennia's
|
* Custom gametime (Griatch, vlgeoff 2017) - Implements Evennia's
|
||||||
gametime module but for custom game world-specific calendars.
|
gametime module but for custom game world-specific calendars.
|
||||||
* Dice (Griatch 2012) - A fully featured dice rolling system.
|
* Dice (Griatch 2012) - A fully featured dice rolling system.
|
||||||
|
|
@ -30,8 +31,6 @@ things you want from here into your game folder and change them there.
|
||||||
multiple descriptions for time and season as well as details.
|
multiple descriptions for time and season as well as details.
|
||||||
* GenderSub (Griatch 2015) - Simple example (only) of storing gender
|
* GenderSub (Griatch 2015) - Simple example (only) of storing gender
|
||||||
on a character and access it in an emote with a custom marker.
|
on a character and access it in an emote with a custom marker.
|
||||||
* In-game Python (Vincent Le Geoff 2017) - Allow trusted builders to script
|
|
||||||
objects and events using Python from in-game.
|
|
||||||
* Mail (grungies1138 2016) - An in-game mail system for communication.
|
* Mail (grungies1138 2016) - An in-game mail system for communication.
|
||||||
* Menu login (Griatch 2011) - A login system using menus asking
|
* Menu login (Griatch 2011) - A login system using menus asking
|
||||||
for name/password rather than giving them as one command
|
for name/password rather than giving them as one command
|
||||||
|
|
@ -40,6 +39,8 @@ things you want from here into your game folder and change them there.
|
||||||
* Menu Login (Vincent-lg 2016) - Alternate login system using EvMenu.
|
* Menu Login (Vincent-lg 2016) - Alternate login system using EvMenu.
|
||||||
* Multidescer (Griatch 2016) - Advanced descriptions combined from
|
* Multidescer (Griatch 2016) - Advanced descriptions combined from
|
||||||
many separate description components, inspired by MUSH.
|
many separate description components, inspired by MUSH.
|
||||||
|
* Random String Generator (Vincent Le Goff 2017) - Simple pseudo-random
|
||||||
|
generator of strings with rules, avoiding repetitions.
|
||||||
* RPLanguage (Griatch 2015) - Dynamic obfuscation of emotes when
|
* RPLanguage (Griatch 2015) - Dynamic obfuscation of emotes when
|
||||||
speaking unfamiliar languages. Also obfuscates whispers.
|
speaking unfamiliar languages. Also obfuscates whispers.
|
||||||
* RPSystem (Griatch 2015) - Full director-style emoting system
|
* RPSystem (Griatch 2015) - Full director-style emoting system
|
||||||
|
|
@ -49,8 +50,9 @@ things you want from here into your game folder and change them there.
|
||||||
time to pass depending on if you are walking/running etc.
|
time to pass depending on if you are walking/running etc.
|
||||||
* Talking NPC (Griatch 2011) - A talking NPC object that offers a
|
* Talking NPC (Griatch 2011) - A talking NPC object that offers a
|
||||||
menu-driven conversation tree.
|
menu-driven conversation tree.
|
||||||
* Turnbattle (BattleJenkins 2017) - A turn-based combat engine meant
|
* Tree Select (FlutterSprite 2017) - A simple system for creating a
|
||||||
as a start to build from. Has attack/disengage and turn timeouts.
|
branching EvMenu with selection options sourced from a single
|
||||||
|
multi-line string.
|
||||||
* Wilderness (titeuf87 2017) - Make infinitely large wilderness areas
|
* Wilderness (titeuf87 2017) - Make infinitely large wilderness areas
|
||||||
with dynamically created locations.
|
with dynamically created locations.
|
||||||
* UnixCommand (Vincent Le Geoff 2017) - Add commands with UNIX-style syntax.
|
* UnixCommand (Vincent Le Geoff 2017) - Add commands with UNIX-style syntax.
|
||||||
|
|
@ -59,6 +61,11 @@ things you want from here into your game folder and change them there.
|
||||||
|
|
||||||
* EGI_Client (gtaylor 2016) - Client for reporting game status
|
* EGI_Client (gtaylor 2016) - Client for reporting game status
|
||||||
to the Evennia game index (games.evennia.com)
|
to the Evennia game index (games.evennia.com)
|
||||||
|
* In-game Python (Vincent Le Goff 2017) - Allow trusted builders to script
|
||||||
|
objects and events using Python from in-game.
|
||||||
|
* Turnbattle (FlutterSprite 2017) - A turn-based combat engine meant
|
||||||
|
as a start to build from. Has attack/disengage and turn timeouts,
|
||||||
|
and includes optional expansions for equipment and combat movement.
|
||||||
* Tutorial examples (Griatch 2011, 2015) - A folder of basic
|
* Tutorial examples (Griatch 2011, 2015) - A folder of basic
|
||||||
example objects, commands and scripts.
|
example objects, commands and scripts.
|
||||||
* Tutorial world (Griatch 2011, 2015) - A folder containing the
|
* Tutorial world (Griatch 2011, 2015) - A folder containing the
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ See README.md for more info.
|
||||||
# own code, so somthing needs to be done here. See issue #766. /Griatch
|
# own code, so somthing needs to be done here. See issue #766. /Griatch
|
||||||
|
|
||||||
#import evennia
|
#import evennia
|
||||||
#evennia._init()
|
# evennia._init()
|
||||||
#import barter, dice, extended_room, menu_login, talking_npc
|
#import barter, dice, extended_room, menu_login, talking_npc
|
||||||
#import chargen, email_login, gendersub, menusystem, slow_exit
|
#import chargen, email_login, gendersub, menusystem, slow_exit
|
||||||
#import tutorial_world, tutorial_examples
|
#import tutorial_world, tutorial_examples
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,7 @@ class TradeTimeout(DefaultScript):
|
||||||
"""
|
"""
|
||||||
This times out the trade request, in case player B did not reply in time.
|
This times out the trade request, in case player B did not reply in time.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def at_script_creation(self):
|
def at_script_creation(self):
|
||||||
"""
|
"""
|
||||||
Called when script is first created
|
Called when script is first created
|
||||||
|
|
@ -136,6 +137,7 @@ class TradeHandler(object):
|
||||||
Objects of this class handles the ongoing trade, notably storing the current
|
Objects of this class handles the ongoing trade, notably storing the current
|
||||||
offers from each side and wether both have accepted or not.
|
offers from each side and wether both have accepted or not.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, part_a, part_b):
|
def __init__(self, part_a, part_b):
|
||||||
"""
|
"""
|
||||||
Initializes the trade. This is called when part A tries to
|
Initializes the trade. This is called when part A tries to
|
||||||
|
|
@ -391,6 +393,7 @@ class CmdTradeBase(Command):
|
||||||
Base command for Trade commands to inherit from. Implements the
|
Base command for Trade commands to inherit from. Implements the
|
||||||
custom parsing.
|
custom parsing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def parse(self):
|
def parse(self):
|
||||||
"""
|
"""
|
||||||
Parse the relevant parts and make it easily
|
Parse the relevant parts and make it easily
|
||||||
|
|
@ -411,7 +414,7 @@ class CmdTradeBase(Command):
|
||||||
if ':' in self.args:
|
if ':' in self.args:
|
||||||
self.args, self.emote = [part.strip() for part in self.args.rsplit(":", 1)]
|
self.args, self.emote = [part.strip() for part in self.args.rsplit(":", 1)]
|
||||||
self.str_caller = 'You say, "' + self.emote + '"\n [%s]'
|
self.str_caller = 'You say, "' + self.emote + '"\n [%s]'
|
||||||
if self.caller.has_player:
|
if self.caller.has_account:
|
||||||
self.str_other = '|c%s|n says, "' % self.caller.key + self.emote + '"\n [%s]'
|
self.str_other = '|c%s|n says, "' % self.caller.key + self.emote + '"\n [%s]'
|
||||||
else:
|
else:
|
||||||
self.str_other = '%s says, "' % self.caller.key + self.emote + '"\n [%s]'
|
self.str_other = '%s says, "' % self.caller.key + self.emote + '"\n [%s]'
|
||||||
|
|
@ -766,7 +769,7 @@ class CmdTrade(Command):
|
||||||
if ':' in self.args:
|
if ':' in self.args:
|
||||||
self.args, emote = [part.strip() for part in self.args.rsplit(":", 1)]
|
self.args, emote = [part.strip() for part in self.args.rsplit(":", 1)]
|
||||||
selfemote = 'You say, "%s"\n ' % emote
|
selfemote = 'You say, "%s"\n ' % emote
|
||||||
if self.caller.has_player:
|
if self.caller.has_account:
|
||||||
theiremote = '|c%s|n says, "%s"\n ' % (self.caller.key, emote)
|
theiremote = '|c%s|n says, "%s"\n ' % (self.caller.key, emote)
|
||||||
else:
|
else:
|
||||||
theiremote = '%s says, "%s"\n ' % (self.caller.key, emote)
|
theiremote = '%s says, "%s"\n ' % (self.caller.key, emote)
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ necessary anymore - the ooclook and @charcreate commands in that mode
|
||||||
replaces this module with better functionality. This remains here for
|
replaces this module with better functionality. This remains here for
|
||||||
inspiration.
|
inspiration.
|
||||||
|
|
||||||
This is a simple character creation commandset for the Player level.
|
This is a simple character creation commandset for the Account level.
|
||||||
It shows some more info and gives the Player the option to create a
|
It shows some more info and gives the Account the option to create a
|
||||||
character without any more customizations than their name (further
|
character without any more customizations than their name (further
|
||||||
options are unique for each game anyway).
|
options are unique for each game anyway).
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@ cmdset.
|
||||||
Installation:
|
Installation:
|
||||||
|
|
||||||
Import this module to `mygame/commands/default_cmdsets.py` and
|
Import this module to `mygame/commands/default_cmdsets.py` and
|
||||||
add `chargen.OOCCMdSetCharGen` to the `PlayerCmdSet` class
|
add `chargen.OOCCMdSetCharGen` to the `AccountCmdSet` class
|
||||||
(it says where to add it). Reload.
|
(it says where to add it). Reload.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -39,7 +39,7 @@ class CmdOOCLook(default_cmds.CmdLook):
|
||||||
look
|
look
|
||||||
look <character>
|
look <character>
|
||||||
|
|
||||||
This is an OOC version of the look command. Since a Player doesn't
|
This is an OOC version of the look command. Since an Account doesn't
|
||||||
have an in-game existence, there is no concept of location or
|
have an in-game existence, there is no concept of location or
|
||||||
"self".
|
"self".
|
||||||
|
|
||||||
|
|
@ -56,24 +56,24 @@ class CmdOOCLook(default_cmds.CmdLook):
|
||||||
"""
|
"""
|
||||||
Implements the ooc look command
|
Implements the ooc look command
|
||||||
|
|
||||||
We use an attribute _character_dbrefs on the player in order
|
We use an attribute _character_dbrefs on the account in order
|
||||||
to figure out which characters are "theirs". A drawback of this
|
to figure out which characters are "theirs". A drawback of this
|
||||||
is that only the CmdCharacterCreate command adds this attribute,
|
is that only the CmdCharacterCreate command adds this attribute,
|
||||||
and thus e.g. player #1 will not be listed (although it will work).
|
and thus e.g. account #1 will not be listed (although it will work).
|
||||||
Existence in this list does not depend on puppeting rights though,
|
Existence in this list does not depend on puppeting rights though,
|
||||||
that is checked by the @ic command directly.
|
that is checked by the @ic command directly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# making sure caller is really a player
|
# making sure caller is really an account
|
||||||
self.character = None
|
self.character = None
|
||||||
if utils.inherits_from(self.caller, "evennia.objects.objects.Object"):
|
if utils.inherits_from(self.caller, "evennia.objects.objects.Object"):
|
||||||
# An object of some type is calling. Convert to player.
|
# An object of some type is calling. Convert to account.
|
||||||
self.character = self.caller
|
self.character = self.caller
|
||||||
if hasattr(self.caller, "player"):
|
if hasattr(self.caller, "account"):
|
||||||
self.caller = self.caller.player
|
self.caller = self.caller.account
|
||||||
|
|
||||||
if not self.character:
|
if not self.character:
|
||||||
# ooc mode, we are players
|
# ooc mode, we are accounts
|
||||||
|
|
||||||
avail_chars = self.caller.db._character_dbrefs
|
avail_chars = self.caller.db._character_dbrefs
|
||||||
if self.args:
|
if self.args:
|
||||||
|
|
@ -102,7 +102,7 @@ class CmdOOCLook(default_cmds.CmdLook):
|
||||||
else:
|
else:
|
||||||
charlist = "You have no Characters."
|
charlist = "You have no Characters."
|
||||||
string = \
|
string = \
|
||||||
""" You, %s, are an |wOOC ghost|n without form. The world is hidden
|
""" You, %s, are an |wOOC ghost|n without form. The world is hidden
|
||||||
from you and besides chatting on channels your options are limited.
|
from you and besides chatting on channels your options are limited.
|
||||||
You need to have a Character in order to interact with the world.
|
You need to have a Character in order to interact with the world.
|
||||||
|
|
||||||
|
|
@ -139,13 +139,13 @@ class CmdOOCCharacterCreate(Command):
|
||||||
attribute on ourselves to remember it.
|
attribute on ourselves to remember it.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# making sure caller is really a player
|
# making sure caller is really an account
|
||||||
self.character = None
|
self.character = None
|
||||||
if utils.inherits_from(self.caller, "evennia.objects.objects.Object"):
|
if utils.inherits_from(self.caller, "evennia.objects.objects.Object"):
|
||||||
# An object of some type is calling. Convert to player.
|
# An object of some type is calling. Convert to account.
|
||||||
self.character = self.caller
|
self.character = self.caller
|
||||||
if hasattr(self.caller, "player"):
|
if hasattr(self.caller, "account"):
|
||||||
self.caller = self.caller.player
|
self.caller = self.caller.account
|
||||||
|
|
||||||
if not self.args:
|
if not self.args:
|
||||||
self.caller.msg("Usage: create <character name>")
|
self.caller.msg("Usage: create <character name>")
|
||||||
|
|
@ -161,8 +161,8 @@ class CmdOOCCharacterCreate(Command):
|
||||||
if not new_character:
|
if not new_character:
|
||||||
self.caller.msg("|rThe Character couldn't be created. This is a bug. Please contact an admin.")
|
self.caller.msg("|rThe Character couldn't be created. This is a bug. Please contact an admin.")
|
||||||
return
|
return
|
||||||
# make sure to lock the character to only be puppeted by this player
|
# make sure to lock the character to only be puppeted by this account
|
||||||
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" %
|
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" %
|
||||||
(new_character.id, self.caller.id))
|
(new_character.id, self.caller.id))
|
||||||
|
|
||||||
# save dbref
|
# save dbref
|
||||||
|
|
@ -175,10 +175,11 @@ class CmdOOCCharacterCreate(Command):
|
||||||
self.caller.msg("|gThe character |c%s|g was successfully created!" % charname)
|
self.caller.msg("|gThe character |c%s|g was successfully created!" % charname)
|
||||||
|
|
||||||
|
|
||||||
class OOCCmdSetCharGen(default_cmds.PlayerCmdSet):
|
class OOCCmdSetCharGen(default_cmds.AccountCmdSet):
|
||||||
"""
|
"""
|
||||||
Extends the default OOC cmdset.
|
Extends the default OOC cmdset.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def at_cmdset_creation(self):
|
def at_cmdset_creation(self):
|
||||||
"""Install everything from the default set, then overload"""
|
"""Install everything from the default set, then overload"""
|
||||||
self.add(CmdOOCLook())
|
self.add(CmdOOCLook())
|
||||||
|
|
|
||||||
|
|
@ -88,11 +88,11 @@ CLOTHING_TYPE_ORDER = ['hat', 'jewelry', 'top', 'undershirt', 'gloves', 'fullbod
|
||||||
'underpants', 'socks', 'shoes', 'accessory']
|
'underpants', 'socks', 'shoes', 'accessory']
|
||||||
# The maximum number of each type of clothes that can be worn. Unlimited if untyped or not specified.
|
# The maximum number of each type of clothes that can be worn. Unlimited if untyped or not specified.
|
||||||
CLOTHING_TYPE_LIMIT = {
|
CLOTHING_TYPE_LIMIT = {
|
||||||
'hat': 1,
|
'hat': 1,
|
||||||
'gloves': 1,
|
'gloves': 1,
|
||||||
'socks': 1,
|
'socks': 1,
|
||||||
'shoes': 1
|
'shoes': 1
|
||||||
}
|
}
|
||||||
# The maximum number of clothing items that can be worn, or None for unlimited.
|
# The maximum number of clothing items that can be worn, or None for unlimited.
|
||||||
CLOTHING_OVERALL_LIMIT = 20
|
CLOTHING_OVERALL_LIMIT = 20
|
||||||
# What types of clothes will automatically cover what other types of clothes when worn.
|
# What types of clothes will automatically cover what other types of clothes when worn.
|
||||||
|
|
@ -100,11 +100,11 @@ CLOTHING_OVERALL_LIMIT = 20
|
||||||
# on that auto-covers it - for example, it's perfectly possible to have your underpants
|
# on that auto-covers it - for example, it's perfectly possible to have your underpants
|
||||||
# showing if you put them on after your pants!
|
# showing if you put them on after your pants!
|
||||||
CLOTHING_TYPE_AUTOCOVER = {
|
CLOTHING_TYPE_AUTOCOVER = {
|
||||||
'top': ['undershirt'],
|
'top': ['undershirt'],
|
||||||
'bottom': ['underpants'],
|
'bottom': ['underpants'],
|
||||||
'fullbody': ['undershirt', 'underpants'],
|
'fullbody': ['undershirt', 'underpants'],
|
||||||
'shoes': ['socks']
|
'shoes': ['socks']
|
||||||
}
|
}
|
||||||
# Types of clothes that can't be used to cover other clothes.
|
# Types of clothes that can't be used to cover other clothes.
|
||||||
CLOTHING_TYPE_CANT_COVER_WITH = ['jewelry']
|
CLOTHING_TYPE_CANT_COVER_WITH = ['jewelry']
|
||||||
|
|
||||||
|
|
@ -295,6 +295,7 @@ class ClothedCharacter(DefaultCharacter):
|
||||||
just copy the return_appearance hook defined below to your own game's
|
just copy the return_appearance hook defined below to your own game's
|
||||||
character typeclass.
|
character typeclass.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def return_appearance(self, looker):
|
def return_appearance(self, looker):
|
||||||
"""
|
"""
|
||||||
This formats a description. It is the hook a 'look' command
|
This formats a description. It is the hook a 'look' command
|
||||||
|
|
|
||||||
238
evennia/contrib/color_markups.py
Normal file
238
evennia/contrib/color_markups.py
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
"""
|
||||||
|
Color markups
|
||||||
|
|
||||||
|
Contribution, Griatch 2017
|
||||||
|
|
||||||
|
Additional color markup styles for Evennia (extending or replacing the default |r, |234 etc).
|
||||||
|
|
||||||
|
|
||||||
|
Installation:
|
||||||
|
|
||||||
|
Import the desired style variables from this module into mygame/server/conf/settings.py and add them
|
||||||
|
to these settings variables. Each are specified as a list, and multiple such lists can be added to
|
||||||
|
each variable to support multiple formats. Note that list order affects which regexes are applied
|
||||||
|
first. You must restart both Portal and Server for color tags to update.
|
||||||
|
|
||||||
|
Assign to the following settings variables:
|
||||||
|
|
||||||
|
COLOR_ANSI_EXTRA_MAP - a mapping between regexes and ANSI colors
|
||||||
|
COLOR_XTERM256_EXTRA_FG - regex for defining XTERM256 foreground colors
|
||||||
|
COLOR_XTERM256_EXTRA_BG - regex for defining XTERM256 background colors
|
||||||
|
COLOR_XTERM256_EXTRA_GFG - regex for defining XTERM256 grayscale foreground colors
|
||||||
|
COLOR_XTERM256_EXTRA_GBG - regex for defining XTERM256 grayscale background colors
|
||||||
|
COLOR_ANSI_BRIGHT_BG_EXTRA_MAP = ANSI does not support bright backgrounds; we fake
|
||||||
|
this by mapping ANSI markup to matching bright XTERM256 backgrounds
|
||||||
|
|
||||||
|
COLOR_NO_DEFAULT - Set True/False. If False (default), extend the default markup, otherwise
|
||||||
|
replace it completely.
|
||||||
|
|
||||||
|
|
||||||
|
To add the {- "curly-bracket" style, add the following to your settings file, then reboot both
|
||||||
|
Server and Portal:
|
||||||
|
|
||||||
|
from evennia.contrib import color_markups
|
||||||
|
COLOR_ANSI_EXTRA_MAP = color_markups.CURLY_COLOR_ANSI_EXTRA_MAP
|
||||||
|
COLOR_XTERM256_EXTRA_FG = color_markups.CURLY_COLOR_XTERM256_EXTRA_FG
|
||||||
|
COLOR_XTERM256_EXTRA_BG = color_markups.CURLY_COLOR_XTERM256_EXTRA_BG
|
||||||
|
COLOR_XTERM256_EXTRA_GFG = color_markups.CURLY_COLOR_XTERM256_EXTRA_GFG
|
||||||
|
COLOR_XTERM256_EXTRA_GBG = color_markups.CURLY_COLOR_XTERM256_EXTRA_GBG
|
||||||
|
COLOR_ANSI_BRIGHT_BG_EXTRA_MAP = color_markups.CURLY_COLOR_ANSI_BRIGHT_BG_EXTRA_MAP
|
||||||
|
|
||||||
|
|
||||||
|
To add the %c- "mux/mush" style, add the following to your settings file, then reboot both Server
|
||||||
|
and Portal:
|
||||||
|
|
||||||
|
from evennia.contrib import color_markups
|
||||||
|
COLOR_ANSI_EXTRA_MAP = color_markups.MUX_COLOR_ANSI_EXTRA_MAP
|
||||||
|
COLOR_XTERM256_EXTRA_FG = color_markups.MUX_COLOR_XTERM256_EXTRA_FG
|
||||||
|
COLOR_XTERM256_EXTRA_BG = color_markups.MUX_COLOR_XTERM256_EXTRA_BG
|
||||||
|
COLOR_XTERM256_EXTRA_GFG = color_markups.MUX_COLOR_XTERM256_EXTRA_GFG
|
||||||
|
COLOR_XTERM256_EXTRA_GBG = color_markups.MUX_COLOR_XTERM256_EXTRA_GBG
|
||||||
|
COLOR_ANSI_BRIGHT_BGS_EXTRA_MAP = color_markups.CURLY_COLOR_ANSI_BRIGHT_BGS_EXTRA_MAP
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ANSI constants (copied from evennia.utils.ansi to avoid import)
|
||||||
|
|
||||||
|
_ANSI_BEEP = "\07"
|
||||||
|
_ANSI_ESCAPE = "\033"
|
||||||
|
_ANSI_NORMAL = "\033[0m"
|
||||||
|
|
||||||
|
_ANSI_UNDERLINE = "\033[4m"
|
||||||
|
_ANSI_HILITE = "\033[1m"
|
||||||
|
_ANSI_UNHILITE = "\033[22m"
|
||||||
|
_ANSI_BLINK = "\033[5m"
|
||||||
|
_ANSI_INVERSE = "\033[7m"
|
||||||
|
_ANSI_INV_HILITE = "\033[1;7m"
|
||||||
|
_ANSI_INV_BLINK = "\033[7;5m"
|
||||||
|
_ANSI_BLINK_HILITE = "\033[1;5m"
|
||||||
|
_ANSI_INV_BLINK_HILITE = "\033[1;5;7m"
|
||||||
|
|
||||||
|
# Foreground colors
|
||||||
|
_ANSI_BLACK = "\033[30m"
|
||||||
|
_ANSI_RED = "\033[31m"
|
||||||
|
_ANSI_GREEN = "\033[32m"
|
||||||
|
_ANSI_YELLOW = "\033[33m"
|
||||||
|
_ANSI_BLUE = "\033[34m"
|
||||||
|
_ANSI_MAGENTA = "\033[35m"
|
||||||
|
_ANSI_CYAN = "\033[36m"
|
||||||
|
_ANSI_WHITE = "\033[37m"
|
||||||
|
|
||||||
|
# Background colors
|
||||||
|
_ANSI_BACK_BLACK = "\033[40m"
|
||||||
|
_ANSI_BACK_RED = "\033[41m"
|
||||||
|
_ANSI_BACK_GREEN = "\033[42m"
|
||||||
|
_ANSI_BACK_YELLOW = "\033[43m"
|
||||||
|
_ANSI_BACK_BLUE = "\033[44m"
|
||||||
|
_ANSI_BACK_MAGENTA = "\033[45m"
|
||||||
|
_ANSI_BACK_CYAN = "\033[46m"
|
||||||
|
_ANSI_BACK_WHITE = "\033[47m"
|
||||||
|
|
||||||
|
# Formatting Characters
|
||||||
|
_ANSI_RETURN = "\r\n"
|
||||||
|
_ANSI_TAB = "\t"
|
||||||
|
_ANSI_SPACE = " "
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
#
|
||||||
|
# {- style MUD markup (old Evennia default). This is
|
||||||
|
# basically identical to the default |-style except using
|
||||||
|
# a curly bracket instead. This was removed because {}
|
||||||
|
# are used in Python string formatting.
|
||||||
|
#
|
||||||
|
# {r, {R - bright/dark red foreground
|
||||||
|
# {[r, {[R - bright/dark red background
|
||||||
|
# {500, {[500 - XTERM256 red foreground/background
|
||||||
|
# {=w, {[=w - XTERM256 greyscale foreground/background
|
||||||
|
#
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
CURLY_COLOR_ANSI_EXTRA_MAP = [
|
||||||
|
(r'{n', _ANSI_NORMAL), # reset
|
||||||
|
(r'{/', _ANSI_RETURN), # line break
|
||||||
|
(r'{-', _ANSI_TAB), # tab
|
||||||
|
(r'{_', _ANSI_SPACE), # space
|
||||||
|
(r'{*', _ANSI_INVERSE), # invert
|
||||||
|
(r'{^', _ANSI_BLINK), # blinking text (very annoying and not supported by all clients)
|
||||||
|
(r'{u', _ANSI_UNDERLINE), # underline
|
||||||
|
|
||||||
|
(r'{r', _ANSI_HILITE + _ANSI_RED),
|
||||||
|
(r'{g', _ANSI_HILITE + _ANSI_GREEN),
|
||||||
|
(r'{y', _ANSI_HILITE + _ANSI_YELLOW),
|
||||||
|
(r'{b', _ANSI_HILITE + _ANSI_BLUE),
|
||||||
|
(r'{m', _ANSI_HILITE + _ANSI_MAGENTA),
|
||||||
|
(r'{c', _ANSI_HILITE + _ANSI_CYAN),
|
||||||
|
(r'{w', _ANSI_HILITE + _ANSI_WHITE), # pure white
|
||||||
|
(r'{x', _ANSI_HILITE + _ANSI_BLACK), # dark grey
|
||||||
|
|
||||||
|
(r'{R', _ANSI_HILITE + _ANSI_RED),
|
||||||
|
(r'{G', _ANSI_HILITE + _ANSI_GREEN),
|
||||||
|
(r'{Y', _ANSI_HILITE + _ANSI_YELLOW),
|
||||||
|
(r'{B', _ANSI_HILITE + _ANSI_BLUE),
|
||||||
|
(r'{M', _ANSI_HILITE + _ANSI_MAGENTA),
|
||||||
|
(r'{C', _ANSI_HILITE + _ANSI_CYAN),
|
||||||
|
(r'{W', _ANSI_HILITE + _ANSI_WHITE), # light grey
|
||||||
|
(r'{X', _ANSI_HILITE + _ANSI_BLACK), # pure black
|
||||||
|
|
||||||
|
# hilight-able colors
|
||||||
|
(r'{h', _ANSI_HILITE),
|
||||||
|
(r'{H', _ANSI_UNHILITE),
|
||||||
|
|
||||||
|
(r'{!R', _ANSI_RED),
|
||||||
|
(r'{!G', _ANSI_GREEN),
|
||||||
|
(r'{!Y', _ANSI_YELLOW),
|
||||||
|
(r'{!B', _ANSI_BLUE),
|
||||||
|
(r'{!M', _ANSI_MAGENTA),
|
||||||
|
(r'{!C', _ANSI_CYAN),
|
||||||
|
(r'{!W', _ANSI_WHITE), # light grey
|
||||||
|
(r'{!X', _ANSI_BLACK), # pure black
|
||||||
|
|
||||||
|
# normal ANSI backgrounds
|
||||||
|
(r'{[R', _ANSI_BACK_RED),
|
||||||
|
(r'{[G', _ANSI_BACK_GREEN),
|
||||||
|
(r'{[Y', _ANSI_BACK_YELLOW),
|
||||||
|
(r'{[B', _ANSI_BACK_BLUE),
|
||||||
|
(r'{[M', _ANSI_BACK_MAGENTA),
|
||||||
|
(r'{[C', _ANSI_BACK_CYAN),
|
||||||
|
(r'{[W', _ANSI_BACK_WHITE), # light grey background
|
||||||
|
(r'{[X', _ANSI_BACK_BLACK), # pure black background
|
||||||
|
]
|
||||||
|
|
||||||
|
CURLY_COLOR_XTERM256_EXTRA_FG = [r'\{([0-5])([0-5])([0-5])'] # |123 - foreground colour
|
||||||
|
CURLY_COLOR_XTERM256_EXTRA_BG = [r'\{\[([0-5])([0-5])([0-5])'] # |[123 - background colour
|
||||||
|
CURLY_COLOR_XTERM256_EXTRA_GFG = [r'\{=([a-z])'] # |=a - greyscale foreground
|
||||||
|
CURLY_COLOR_XTERM256_EXTRA_GBG = [r'\{\[=([a-z])'] # |[=a - greyscale background
|
||||||
|
|
||||||
|
CURLY_COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP = [
|
||||||
|
(r'{[r', r'{[500'),
|
||||||
|
(r'{[g', r'{[050'),
|
||||||
|
(r'{[y', r'{[550'),
|
||||||
|
(r'{[b', r'{[005'),
|
||||||
|
(r'{[m', r'{[505'),
|
||||||
|
(r'{[c', r'{[055'),
|
||||||
|
(r'{[w', r'{[555'), # white background
|
||||||
|
(r'{[x', r'{[222'), # dark grey background
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
#
|
||||||
|
# %c - MUX/MUSH style markup. This was Evennia's first
|
||||||
|
# color markup style. It was phased out due to % being used
|
||||||
|
# in Python formatting operations.
|
||||||
|
#
|
||||||
|
# %ch%cr, %cr - bright/dark red foreground
|
||||||
|
# %ch%cR, %cR- bright/dark red background
|
||||||
|
# %c500, %c[500 - XTERM256 red foreground/background
|
||||||
|
# %c=w, %c[=w - XTERM256 greyscale foreground/background
|
||||||
|
#
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
MUX_COLOR_ANSI_EXTRA_MAP = [
|
||||||
|
(r'%cn', _ANSI_NORMAL), # reset
|
||||||
|
(r'%ch', _ANSI_HILITE), # highlight
|
||||||
|
(r'%r', _ANSI_RETURN), # line break
|
||||||
|
(r'%R', _ANSI_RETURN), #
|
||||||
|
(r'%t', _ANSI_TAB), # tab
|
||||||
|
(r'%T', _ANSI_TAB), #
|
||||||
|
(r'%b', _ANSI_SPACE), # space
|
||||||
|
(r'%B', _ANSI_SPACE),
|
||||||
|
(r'%cf', _ANSI_BLINK), # annoying and not supported by all clients
|
||||||
|
(r'%ci', _ANSI_INVERSE), # invert
|
||||||
|
|
||||||
|
(r'%cr', _ANSI_RED),
|
||||||
|
(r'%cg', _ANSI_GREEN),
|
||||||
|
(r'%cy', _ANSI_YELLOW),
|
||||||
|
(r'%cb', _ANSI_BLUE),
|
||||||
|
(r'%cm', _ANSI_MAGENTA),
|
||||||
|
(r'%cc', _ANSI_CYAN),
|
||||||
|
(r'%cw', _ANSI_WHITE),
|
||||||
|
(r'%cx', _ANSI_BLACK),
|
||||||
|
|
||||||
|
(r'%cR', _ANSI_BACK_RED),
|
||||||
|
(r'%cG', _ANSI_BACK_GREEN),
|
||||||
|
(r'%cY', _ANSI_BACK_YELLOW),
|
||||||
|
(r'%cB', _ANSI_BACK_BLUE),
|
||||||
|
(r'%cM', _ANSI_BACK_MAGENTA),
|
||||||
|
(r'%cC', _ANSI_BACK_CYAN),
|
||||||
|
(r'%cW', _ANSI_BACK_WHITE),
|
||||||
|
(r'%cX', _ANSI_BACK_BLACK)
|
||||||
|
]
|
||||||
|
|
||||||
|
MUX_COLOR_XTERM256_EXTRA_FG = [r'%c([0-5])([0-5])([0-5])'] # %c123 - foreground colour
|
||||||
|
MUX_COLOR_XTERM256_EXTRA_BG = [r'%c\[([0-5])([0-5])([0-5])'] # %c[123 - background colour
|
||||||
|
MUX_COLOR_XTERM256_EXTRA_GFG = [r'%c=([a-z])'] # %c=a - greyscale foreground
|
||||||
|
MUX_COLOR_XTERM256_EXTRA_GBG = [r'%c\[=([a-z])'] # %c[=a - greyscale background
|
||||||
|
|
||||||
|
MUX_COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP = [
|
||||||
|
(r'%ch%cR', r'%c[500'),
|
||||||
|
(r'%ch%cG', r'%c[050'),
|
||||||
|
(r'%ch%cY', r'%c[550'),
|
||||||
|
(r'%ch%cB', r'%c[005'),
|
||||||
|
(r'%ch%cM', r'%c[505'),
|
||||||
|
(r'%ch%cC', r'%c[055'),
|
||||||
|
(r'%ch%cW', r'%c[555'), # white background
|
||||||
|
(r'%ch%cX', r'%c[222'), # dark grey background
|
||||||
|
]
|
||||||
|
|
@ -45,15 +45,15 @@ TIMEFACTOR = settings.TIME_FACTOR
|
||||||
# Each unit must be consistent and expressed in seconds.
|
# Each unit must be consistent and expressed in seconds.
|
||||||
UNITS = getattr(settings, "TIME_UNITS", {
|
UNITS = getattr(settings, "TIME_UNITS", {
|
||||||
# default custom calendar
|
# default custom calendar
|
||||||
"sec": 1,
|
"sec": 1,
|
||||||
"min": 60,
|
"min": 60,
|
||||||
"hr": 60 * 60,
|
"hr": 60 * 60,
|
||||||
"hour": 60 * 60,
|
"hour": 60 * 60,
|
||||||
"day": 60 * 60 * 24,
|
"day": 60 * 60 * 24,
|
||||||
"week": 60 * 60 * 24 * 7,
|
"week": 60 * 60 * 24 * 7,
|
||||||
"month": 60 * 60 * 24 * 7 * 4,
|
"month": 60 * 60 * 24 * 7 * 4,
|
||||||
"yr": 60 * 60 * 24 * 7 * 4 * 12,
|
"yr": 60 * 60 * 24 * 7 * 4 * 12,
|
||||||
"year": 60 * 60 * 24 * 7 * 4 * 12, })
|
"year": 60 * 60 * 24 * 7 * 4 * 12, })
|
||||||
|
|
||||||
|
|
||||||
def time_to_tuple(seconds, *divisors):
|
def time_to_tuple(seconds, *divisors):
|
||||||
|
|
@ -111,8 +111,8 @@ def gametime_to_realtime(format=False, **kwargs):
|
||||||
name = name[:-1]
|
name = name[:-1]
|
||||||
|
|
||||||
if name not in UNITS:
|
if name not in UNITS:
|
||||||
raise ValueError("the unit {} isn't defined as a valid " \
|
raise ValueError("the unit {} isn't defined as a valid "
|
||||||
"game time unit".format(name))
|
"game time unit".format(name))
|
||||||
rtime += value * UNITS[name]
|
rtime += value * UNITS[name]
|
||||||
rtime /= TIMEFACTOR
|
rtime /= TIMEFACTOR
|
||||||
if format:
|
if format:
|
||||||
|
|
@ -121,7 +121,7 @@ def gametime_to_realtime(format=False, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
def realtime_to_gametime(secs=0, mins=0, hrs=0, days=0, weeks=0,
|
def realtime_to_gametime(secs=0, mins=0, hrs=0, days=0, weeks=0,
|
||||||
months=0, yrs=0, format=False):
|
months=0, yrs=0, format=False):
|
||||||
"""
|
"""
|
||||||
This method calculates how much in-game time a real-world time
|
This method calculates how much in-game time a real-world time
|
||||||
interval would correspond to. This is usually a lot less
|
interval would correspond to. This is usually a lot less
|
||||||
|
|
@ -140,7 +140,7 @@ def realtime_to_gametime(secs=0, mins=0, hrs=0, days=0, weeks=0,
|
||||||
|
|
||||||
"""
|
"""
|
||||||
gtime = TIMEFACTOR * (secs + mins * 60 + hrs * 3600 + days * 86400 +
|
gtime = TIMEFACTOR * (secs + mins * 60 + hrs * 3600 + days * 86400 +
|
||||||
weeks * 604800 + months * 2628000 + yrs * 31536000)
|
weeks * 604800 + months * 2628000 + yrs * 31536000)
|
||||||
if format:
|
if format:
|
||||||
units = sorted(set(UNITS.values()), reverse=True)
|
units = sorted(set(UNITS.values()), reverse=True)
|
||||||
# Remove seconds from the tuple
|
# Remove seconds from the tuple
|
||||||
|
|
@ -149,6 +149,7 @@ def realtime_to_gametime(secs=0, mins=0, hrs=0, days=0, weeks=0,
|
||||||
return time_to_tuple(gtime, *units)
|
return time_to_tuple(gtime, *units)
|
||||||
return gtime
|
return gtime
|
||||||
|
|
||||||
|
|
||||||
def custom_gametime(absolute=False):
|
def custom_gametime(absolute=False):
|
||||||
"""
|
"""
|
||||||
Return the custom game time as a tuple of units, as defined in settings.
|
Return the custom game time as a tuple of units, as defined in settings.
|
||||||
|
|
@ -168,6 +169,7 @@ def custom_gametime(absolute=False):
|
||||||
del units[-1]
|
del units[-1]
|
||||||
return time_to_tuple(current, *units)
|
return time_to_tuple(current, *units)
|
||||||
|
|
||||||
|
|
||||||
def real_seconds_until(**kwargs):
|
def real_seconds_until(**kwargs):
|
||||||
"""
|
"""
|
||||||
Return the real seconds until game time.
|
Return the real seconds until game time.
|
||||||
|
|
@ -228,6 +230,7 @@ def real_seconds_until(**kwargs):
|
||||||
|
|
||||||
return (projected - current) / TIMEFACTOR
|
return (projected - current) / TIMEFACTOR
|
||||||
|
|
||||||
|
|
||||||
def schedule(callback, repeat=False, **kwargs):
|
def schedule(callback, repeat=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
Call the callback when the game time is up.
|
Call the callback when the game time is up.
|
||||||
|
|
@ -256,14 +259,16 @@ def schedule(callback, repeat=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
seconds = real_seconds_until(**kwargs)
|
seconds = real_seconds_until(**kwargs)
|
||||||
script = create_script("evennia.contrib.custom_gametime.GametimeScript",
|
script = create_script("evennia.contrib.custom_gametime.GametimeScript",
|
||||||
key="GametimeScript", desc="A timegame-sensitive script",
|
key="GametimeScript", desc="A timegame-sensitive script",
|
||||||
interval=seconds, start_delay=True,
|
interval=seconds, start_delay=True,
|
||||||
repeats=-1 if repeat else 1)
|
repeats=-1 if repeat else 1)
|
||||||
script.db.callback = callback
|
script.db.callback = callback
|
||||||
script.db.gametime = kwargs
|
script.db.gametime = kwargs
|
||||||
return script
|
return script
|
||||||
|
|
||||||
# Scripts dealing in gametime (use `schedule` to create it)
|
# Scripts dealing in gametime (use `schedule` to create it)
|
||||||
|
|
||||||
|
|
||||||
class GametimeScript(DefaultScript):
|
class GametimeScript(DefaultScript):
|
||||||
|
|
||||||
"""Gametime-sensitive script."""
|
"""Gametime-sensitive script."""
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,7 @@ def roll_dice(dicenum, dicetype, modifier=None, conditional=None, return_tuple=F
|
||||||
else:
|
else:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
RE_PARTS = re.compile(r"(d|\+|-|/|\*|<|>|<=|>=|!=|==)")
|
RE_PARTS = re.compile(r"(d|\+|-|/|\*|<|>|<=|>=|!=|==)")
|
||||||
RE_MOD = re.compile(r"(\+|-|/|\*)")
|
RE_MOD = re.compile(r"(\+|-|/|\*)")
|
||||||
RE_COND = re.compile(r"(<|>|<=|>=|!=|==)")
|
RE_COND = re.compile(r"(<|>|<=|>=|!=|==)")
|
||||||
|
|
@ -255,6 +256,7 @@ class DiceCmdSet(CmdSet):
|
||||||
a small cmdset for testing purposes.
|
a small cmdset for testing purposes.
|
||||||
Add with @py self.cmdset.add("contrib.dice.DiceCmdSet")
|
Add with @py self.cmdset.add("contrib.dice.DiceCmdSet")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def at_cmdset_creation(self):
|
def at_cmdset_creation(self):
|
||||||
"""Called when set is created"""
|
"""Called when set is created"""
|
||||||
self.add(CmdDice())
|
self.add(CmdDice())
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ from twisted.web.http_headers import Headers
|
||||||
from twisted.web.iweb import IBodyProducer
|
from twisted.web.iweb import IBodyProducer
|
||||||
from zope.interface import implements
|
from zope.interface import implements
|
||||||
|
|
||||||
from evennia.players.models import PlayerDB
|
from evennia.accounts.models import AccountDB
|
||||||
from evennia.server.sessionhandler import SESSIONS
|
from evennia.server.sessionhandler import SESSIONS
|
||||||
from evennia.utils import get_evennia_version, logger
|
from evennia.utils import get_evennia_version, logger
|
||||||
|
|
||||||
|
|
@ -24,6 +24,7 @@ class EvenniaGameIndexClient(object):
|
||||||
Evennia Game Index. Since EGI is in the early goings, this isn't
|
Evennia Game Index. Since EGI is in the early goings, this isn't
|
||||||
incredibly configurable as far as what is being sent.
|
incredibly configurable as far as what is being sent.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, on_bad_request=None):
|
def __init__(self, on_bad_request=None):
|
||||||
"""
|
"""
|
||||||
:param on_bad_request: Optional callable to trigger when a bad request
|
:param on_bad_request: Optional callable to trigger when a bad request
|
||||||
|
|
@ -97,8 +98,8 @@ class EvenniaGameIndexClient(object):
|
||||||
'web_client_url': egi_config.get('web_client_url') or '',
|
'web_client_url': egi_config.get('web_client_url') or '',
|
||||||
|
|
||||||
# Game stats
|
# Game stats
|
||||||
'connected_player_count': SESSIONS.player_count(),
|
'connected_account_count': SESSIONS.account_count(),
|
||||||
'total_player_count': PlayerDB.objects.num_total_players() or 0,
|
'total_account_count': AccountDB.objects.num_total_accounts() or 0,
|
||||||
|
|
||||||
# System info
|
# System info
|
||||||
'evennia_version': get_evennia_version(),
|
'evennia_version': get_evennia_version(),
|
||||||
|
|
@ -131,6 +132,7 @@ class SimpleResponseReceiver(protocol.Protocol):
|
||||||
"""
|
"""
|
||||||
Used for pulling the response body out of an HTTP response.
|
Used for pulling the response body out of an HTTP response.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, status_code, d):
|
def __init__(self, status_code, d):
|
||||||
self.status_code = status_code
|
self.status_code = status_code
|
||||||
self.buf = ''
|
self.buf = ''
|
||||||
|
|
|
||||||
|
|
@ -31,13 +31,12 @@ the module given by settings.CONNECTION_SCREEN_MODULE.
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia.players.models import PlayerDB
|
from evennia.accounts.models import AccountDB
|
||||||
from evennia.objects.models import ObjectDB
|
from evennia.objects.models import ObjectDB
|
||||||
from evennia.server.models import ServerConfig
|
from evennia.server.models import ServerConfig
|
||||||
from evennia.comms.models import ChannelDB
|
|
||||||
|
|
||||||
from evennia.commands.cmdset import CmdSet
|
from evennia.commands.cmdset import CmdSet
|
||||||
from evennia.utils import create, logger, utils, ansi
|
from evennia.utils import logger, utils, ansi
|
||||||
from evennia.commands.default.muxcommand import MuxCommand
|
from evennia.commands.default.muxcommand import MuxCommand
|
||||||
from evennia.commands.cmdhandler import CMD_LOGINSTART
|
from evennia.commands.cmdhandler import CMD_LOGINSTART
|
||||||
from evennia.commands.default import unloggedin as default_unloggedin # Used in CmdUnconnectedCreate
|
from evennia.commands.default import unloggedin as default_unloggedin # Used in CmdUnconnectedCreate
|
||||||
|
|
@ -78,7 +77,7 @@ class CmdUnconnectedConnect(MuxCommand):
|
||||||
have a unique position in that their `func()` receives
|
have a unique position in that their `func()` receives
|
||||||
a session object instead of a `source_object` like all
|
a session object instead of a `source_object` like all
|
||||||
other types of logged-in commands (this is because
|
other types of logged-in commands (this is because
|
||||||
there is no object yet before the player has logged in)
|
there is no object yet before the account has logged in)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
session = self.caller
|
session = self.caller
|
||||||
|
|
@ -91,23 +90,22 @@ class CmdUnconnectedConnect(MuxCommand):
|
||||||
password = arglist[1]
|
password = arglist[1]
|
||||||
|
|
||||||
# Match an email address to an account.
|
# Match an email address to an account.
|
||||||
player = PlayerDB.objects.get_player_from_email(email)
|
account = AccountDB.objects.get_account_from_email(email)
|
||||||
# No playername match
|
# No accountname match
|
||||||
if not player:
|
if not account:
|
||||||
string = "The email '%s' does not match any accounts." % email
|
string = "The email '%s' does not match any accounts." % email
|
||||||
string += "\n\r\n\rIf you are new you should first create a new account "
|
string += "\n\r\n\rIf you are new you should first create a new account "
|
||||||
string += "using the 'create' command."
|
string += "using the 'create' command."
|
||||||
session.msg(string)
|
session.msg(string)
|
||||||
return
|
return
|
||||||
# We have at least one result, so we can check the password.
|
# We have at least one result, so we can check the password.
|
||||||
if not player.check_password(password):
|
if not account[0].check_password(password):
|
||||||
session.msg("Incorrect password.")
|
session.msg("Incorrect password.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check IP and/or name bans
|
# Check IP and/or name bans
|
||||||
bans = ServerConfig.objects.conf("server_bans")
|
bans = ServerConfig.objects.conf("server_bans")
|
||||||
if bans and (any(tup[0] == player.name for tup in bans)
|
if bans and (any(tup[0] == account.name for tup in bans) or
|
||||||
or
|
|
||||||
any(tup[2].match(session.address[0]) for tup in bans if tup[2])):
|
any(tup[2].match(session.address[0]) for tup in bans if tup[2])):
|
||||||
# this is a banned IP or name!
|
# this is a banned IP or name!
|
||||||
string = "|rYou have been banned and cannot continue from here."
|
string = "|rYou have been banned and cannot continue from here."
|
||||||
|
|
@ -117,7 +115,7 @@ class CmdUnconnectedConnect(MuxCommand):
|
||||||
return
|
return
|
||||||
|
|
||||||
# actually do the login. This will call all hooks.
|
# actually do the login. This will call all hooks.
|
||||||
session.sessionhandler.login(session, player)
|
session.sessionhandler.login(session, account)
|
||||||
|
|
||||||
|
|
||||||
class CmdUnconnectedCreate(MuxCommand):
|
class CmdUnconnectedCreate(MuxCommand):
|
||||||
|
|
@ -125,9 +123,9 @@ class CmdUnconnectedCreate(MuxCommand):
|
||||||
Create a new account.
|
Create a new account.
|
||||||
|
|
||||||
Usage (at login screen):
|
Usage (at login screen):
|
||||||
create \"playername\" <email> <password>
|
create \"accountname\" <email> <password>
|
||||||
|
|
||||||
This creates a new player account.
|
This creates a new account account.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
key = "create"
|
key = "create"
|
||||||
|
|
@ -136,36 +134,36 @@ class CmdUnconnectedCreate(MuxCommand):
|
||||||
|
|
||||||
def parse(self):
|
def parse(self):
|
||||||
"""
|
"""
|
||||||
The parser must handle the multiple-word player
|
The parser must handle the multiple-word account
|
||||||
name enclosed in quotes:
|
name enclosed in quotes:
|
||||||
connect "Long name with many words" my@myserv.com mypassw
|
connect "Long name with many words" my@myserv.com mypassw
|
||||||
"""
|
"""
|
||||||
super(CmdUnconnectedCreate, self).parse()
|
super(CmdUnconnectedCreate, self).parse()
|
||||||
|
|
||||||
self.playerinfo = []
|
self.accountinfo = []
|
||||||
if len(self.arglist) < 3:
|
if len(self.arglist) < 3:
|
||||||
return
|
return
|
||||||
if len(self.arglist) > 3:
|
if len(self.arglist) > 3:
|
||||||
# this means we have a multi_word playername. pop from the back.
|
# this means we have a multi_word accountname. pop from the back.
|
||||||
password = self.arglist.pop()
|
password = self.arglist.pop()
|
||||||
email = self.arglist.pop()
|
email = self.arglist.pop()
|
||||||
# what remains is the playername.
|
# what remains is the accountname.
|
||||||
playername = " ".join(self.arglist)
|
accountname = " ".join(self.arglist)
|
||||||
else:
|
else:
|
||||||
playername, email, password = self.arglist
|
accountname, email, password = self.arglist
|
||||||
|
|
||||||
playername = playername.replace('"', '') # remove "
|
accountname = accountname.replace('"', '') # remove "
|
||||||
playername = playername.replace("'", "")
|
accountname = accountname.replace("'", "")
|
||||||
self.playerinfo = (playername, email, password)
|
self.accountinfo = (accountname, email, password)
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""Do checks and create account"""
|
"""Do checks and create account"""
|
||||||
|
|
||||||
session = self.caller
|
session = self.caller
|
||||||
try:
|
try:
|
||||||
playername, email, password = self.playerinfo
|
accountname, email, password = self.accountinfo
|
||||||
except ValueError:
|
except ValueError:
|
||||||
string = "\n\r Usage (without <>): create \"<playername>\" <email> <password>"
|
string = "\n\r Usage (without <>): create \"<accountname>\" <email> <password>"
|
||||||
session.msg(string)
|
session.msg(string)
|
||||||
return
|
return
|
||||||
if not email or not password:
|
if not email or not password:
|
||||||
|
|
@ -176,30 +174,30 @@ class CmdUnconnectedCreate(MuxCommand):
|
||||||
session.msg("'%s' is not a valid e-mail address." % email)
|
session.msg("'%s' is not a valid e-mail address." % email)
|
||||||
return
|
return
|
||||||
# sanity checks
|
# sanity checks
|
||||||
if not re.findall(r"^[\w. @+\-']+$", playername) or not (0 < len(playername) <= 30):
|
if not re.findall(r"^[\w. @+\-']+$", accountname) or not (0 < len(accountname) <= 30):
|
||||||
# this echoes the restrictions made by django's auth
|
# this echoes the restrictions made by django's auth
|
||||||
# module (except not allowing spaces, for convenience of
|
# module (except not allowing spaces, for convenience of
|
||||||
# logging in).
|
# logging in).
|
||||||
string = "\n\r Playername can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_/' only."
|
string = "\n\r Accountname can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_/' only."
|
||||||
session.msg(string)
|
session.msg(string)
|
||||||
return
|
return
|
||||||
# strip excessive spaces in playername
|
# strip excessive spaces in accountname
|
||||||
playername = re.sub(r"\s+", " ", playername).strip()
|
accountname = re.sub(r"\s+", " ", accountname).strip()
|
||||||
if PlayerDB.objects.filter(username__iexact=playername):
|
if AccountDB.objects.filter(username__iexact=accountname):
|
||||||
# player already exists (we also ignore capitalization here)
|
# account already exists (we also ignore capitalization here)
|
||||||
session.msg("Sorry, there is already a player with the name '%s'." % playername)
|
session.msg("Sorry, there is already an account with the name '%s'." % accountname)
|
||||||
return
|
return
|
||||||
if PlayerDB.objects.get_player_from_email(email):
|
if AccountDB.objects.get_account_from_email(email):
|
||||||
# email already set on a player
|
# email already set on an account
|
||||||
session.msg("Sorry, there is already a player with that email address.")
|
session.msg("Sorry, there is already an account with that email address.")
|
||||||
return
|
return
|
||||||
# Reserve playernames found in GUEST_LIST
|
# Reserve accountnames found in GUEST_LIST
|
||||||
if settings.GUEST_LIST and playername.lower() in (guest.lower() for guest in settings.GUEST_LIST):
|
if settings.GUEST_LIST and accountname.lower() in (guest.lower() for guest in settings.GUEST_LIST):
|
||||||
string = "\n\r That name is reserved. Please choose another Playername."
|
string = "\n\r That name is reserved. Please choose another Accountname."
|
||||||
session.msg(string)
|
session.msg(string)
|
||||||
return
|
return
|
||||||
if not re.findall(r"^[\w. @+\-']+$", password) or not (3 < len(password)):
|
if not re.findall(r"^[\w. @+\-']+$", password) or not (3 < len(password)):
|
||||||
string = "\n\r Password should be longer than 3 characers. Letters, spaces, digits and @/./+/-/_/' only." \
|
string = "\n\r Password should be longer than 3 characters. Letters, spaces, digits and @/./+/-/_/' only." \
|
||||||
"\nFor best security, make it longer than 8 characters. You can also use a phrase of" \
|
"\nFor best security, make it longer than 8 characters. You can also use a phrase of" \
|
||||||
"\nmany words if you enclose the password in double quotes."
|
"\nmany words if you enclose the password in double quotes."
|
||||||
session.msg(string)
|
session.msg(string)
|
||||||
|
|
@ -207,8 +205,7 @@ class CmdUnconnectedCreate(MuxCommand):
|
||||||
|
|
||||||
# Check IP and/or name bans
|
# Check IP and/or name bans
|
||||||
bans = ServerConfig.objects.conf("server_bans")
|
bans = ServerConfig.objects.conf("server_bans")
|
||||||
if bans and (any(tup[0] == playername.lower() for tup in bans)
|
if bans and (any(tup[0] == accountname.lower() for tup in bans) or
|
||||||
or
|
|
||||||
any(tup[2].match(session.address) for tup in bans if tup[2])):
|
any(tup[2].match(session.address) for tup in bans if tup[2])):
|
||||||
# this is a banned IP or name!
|
# this is a banned IP or name!
|
||||||
string = "|rYou have been banned and cannot continue from here." \
|
string = "|rYou have been banned and cannot continue from here." \
|
||||||
|
|
@ -219,20 +216,20 @@ class CmdUnconnectedCreate(MuxCommand):
|
||||||
|
|
||||||
# everything's ok. Create the new player account.
|
# everything's ok. Create the new player account.
|
||||||
try:
|
try:
|
||||||
permissions = settings.PERMISSION_PLAYER_DEFAULT
|
permissions = settings.PERMISSION_ACCOUNT_DEFAULT
|
||||||
typeclass = settings.BASE_CHARACTER_TYPECLASS
|
typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||||
new_player = default_unloggedin._create_player(session, playername, password, permissions, email=email)
|
new_account = default_unloggedin._create_account(session, accountname, password, permissions, email=email)
|
||||||
if new_player:
|
if new_account:
|
||||||
if MULTISESSION_MODE < 2:
|
if MULTISESSION_MODE < 2:
|
||||||
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
|
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
|
||||||
default_unloggedin._create_character(session, new_player, typeclass, default_home, permissions)
|
default_unloggedin._create_character(session, new_account, typeclass, default_home, permissions)
|
||||||
# tell the caller everything went well.
|
# tell the caller everything went well.
|
||||||
string = "A new account '%s' was created. Welcome!"
|
string = "A new account '%s' was created. Welcome!"
|
||||||
if " " in playername:
|
if " " in accountname:
|
||||||
string += "\n\nYou can now log in with the command 'connect \"%s\" <your password>'."
|
string += "\n\nYou can now log in with the command 'connect \"%s\" <your password>'."
|
||||||
else:
|
else:
|
||||||
string += "\n\nYou can now log with the command 'connect %s <your password>'."
|
string += "\n\nYou can now log with the command 'connect %s <your password>'."
|
||||||
session.msg(string % (playername, email))
|
session.msg(string % (accountname, email))
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
# We are in the middle between logged in and -not, so we have
|
# We are in the middle between logged in and -not, so we have
|
||||||
|
|
@ -246,7 +243,7 @@ class CmdUnconnectedCreate(MuxCommand):
|
||||||
class CmdUnconnectedQuit(MuxCommand):
|
class CmdUnconnectedQuit(MuxCommand):
|
||||||
"""
|
"""
|
||||||
We maintain a different version of the `quit` command
|
We maintain a different version of the `quit` command
|
||||||
here for unconnected players for the sake of simplicity. The logged in
|
here for unconnected accounts for the sake of simplicity. The logged in
|
||||||
version is a bit more complicated.
|
version is a bit more complicated.
|
||||||
"""
|
"""
|
||||||
key = "quit"
|
key = "quit"
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Extended Room
|
||||||
Evennia Contribution - Griatch 2012
|
Evennia Contribution - Griatch 2012
|
||||||
|
|
||||||
This is an extended Room typeclass for Evennia. It is supported
|
This is an extended Room typeclass for Evennia. It is supported
|
||||||
by an extended `Look` command and an extended `@desc` command, also
|
by an extended `Look` command and an extended `desc` command, also
|
||||||
in this module.
|
in this module.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ There is also a general description which is used as fallback if
|
||||||
one or more of the seasonal descriptions are not set when their
|
one or more of the seasonal descriptions are not set when their
|
||||||
time comes.
|
time comes.
|
||||||
|
|
||||||
An updated `@desc` command allows for setting seasonal descriptions.
|
An updated `desc` command allows for setting seasonal descriptions.
|
||||||
|
|
||||||
The room uses the `evennia.utils.gametime.GameTime` global script. This is
|
The room uses the `evennia.utils.gametime.GameTime` global script. This is
|
||||||
started by default, but if you have deactivated it, you need to
|
started by default, but if you have deactivated it, you need to
|
||||||
|
|
@ -45,13 +45,13 @@ at, without there having to be a database object created for it. The
|
||||||
Details are simply stored in a dictionary on the room and if the look
|
Details are simply stored in a dictionary on the room and if the look
|
||||||
command cannot find an object match for a `look <target>` command it
|
command cannot find an object match for a `look <target>` command it
|
||||||
will also look through the available details at the current location
|
will also look through the available details at the current location
|
||||||
if applicable. An extended `@desc` command is used to set details.
|
if applicable. An extended `desc` command is used to set details.
|
||||||
|
|
||||||
|
|
||||||
4) Extra commands
|
4) Extra commands
|
||||||
|
|
||||||
CmdExtendedLook - look command supporting room details
|
CmdExtendedLook - look command supporting room details
|
||||||
CmdExtendedDesc - @desc command allowing to add seasonal descs and details,
|
CmdExtendedDesc - desc command allowing to add seasonal descs and details,
|
||||||
as well as listing them
|
as well as listing them
|
||||||
CmdGameTime - A simple `time` command, displaying the current
|
CmdGameTime - A simple `time` command, displaying the current
|
||||||
time and season.
|
time and season.
|
||||||
|
|
@ -63,7 +63,7 @@ Installation/testing:
|
||||||
(see Wiki for how to do this).
|
(see Wiki for how to do this).
|
||||||
2) `@dig` a room of type `contrib.extended_room.ExtendedRoom` (or make it the
|
2) `@dig` a room of type `contrib.extended_room.ExtendedRoom` (or make it the
|
||||||
default room type)
|
default room type)
|
||||||
3) Use `@desc` and `@detail` to customize the room, then play around!
|
3) Use `desc` and `detail` to customize the room, then play around!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
|
|
@ -108,6 +108,7 @@ class ExtendedRoom(DefaultRoom):
|
||||||
time. It also allows for "details", together with a slightly modified
|
time. It also allows for "details", together with a slightly modified
|
||||||
look command.
|
look command.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def at_object_creation(self):
|
def at_object_creation(self):
|
||||||
"""Called when room is first created only."""
|
"""Called when room is first created only."""
|
||||||
self.db.spring_desc = ""
|
self.db.spring_desc = ""
|
||||||
|
|
@ -188,7 +189,7 @@ class ExtendedRoom(DefaultRoom):
|
||||||
key (str): A detail identifier.
|
key (str): A detail identifier.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
detail (str or None): A detail mathing the given key.
|
detail (str or None): A detail matching the given key.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
A detail is a way to offer more things to look at in a room
|
A detail is a way to offer more things to look at in a room
|
||||||
|
|
@ -212,37 +213,39 @@ class ExtendedRoom(DefaultRoom):
|
||||||
return detail
|
return detail
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def return_appearance(self, looker):
|
def return_appearance(self, looker, **kwargs):
|
||||||
"""
|
"""
|
||||||
This is called when e.g. the look command wants to retrieve
|
This is called when e.g. the look command wants to retrieve
|
||||||
the description of this object.
|
the description of this object.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
looker (Object): The object looking at us.
|
looker (Object): The object looking at us.
|
||||||
|
**kwargs (dict): Arbitrary, optional arguments for users
|
||||||
|
overriding the call (unused by default).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
description (str): Our description.
|
description (str): Our description.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
update = False
|
# ensures that our description is current based on time/season
|
||||||
|
self.update_current_description()
|
||||||
|
# run the normal return_appearance method, now that desc is updated.
|
||||||
|
return super(ExtendedRoom, self).return_appearance(looker, **kwargs)
|
||||||
|
|
||||||
|
def update_current_description(self):
|
||||||
|
"""
|
||||||
|
This will update the description of the room if the time or season
|
||||||
|
has changed since last checked.
|
||||||
|
"""
|
||||||
|
update = False
|
||||||
# get current time and season
|
# get current time and season
|
||||||
curr_season, curr_timeslot = self.get_time_and_season()
|
curr_season, curr_timeslot = self.get_time_and_season()
|
||||||
|
|
||||||
# compare with previously stored slots
|
# compare with previously stored slots
|
||||||
last_season = self.ndb.last_season
|
last_season = self.ndb.last_season
|
||||||
last_timeslot = self.ndb.last_timeslot
|
last_timeslot = self.ndb.last_timeslot
|
||||||
|
|
||||||
if curr_season != last_season:
|
if curr_season != last_season:
|
||||||
# season changed. Load new desc, or a fallback.
|
# season changed. Load new desc, or a fallback.
|
||||||
if curr_season == 'spring':
|
new_raw_desc = self.attributes.get("%s_desc" % curr_season)
|
||||||
new_raw_desc = self.db.spring_desc
|
|
||||||
elif curr_season == 'summer':
|
|
||||||
new_raw_desc = self.db.summer_desc
|
|
||||||
elif curr_season == 'autumn':
|
|
||||||
new_raw_desc = self.db.autumn_desc
|
|
||||||
else:
|
|
||||||
new_raw_desc = self.db.winter_desc
|
|
||||||
if new_raw_desc:
|
if new_raw_desc:
|
||||||
raw_desc = new_raw_desc
|
raw_desc = new_raw_desc
|
||||||
else:
|
else:
|
||||||
|
|
@ -251,19 +254,15 @@ class ExtendedRoom(DefaultRoom):
|
||||||
self.db.raw_desc = raw_desc
|
self.db.raw_desc = raw_desc
|
||||||
self.ndb.last_season = curr_season
|
self.ndb.last_season = curr_season
|
||||||
update = True
|
update = True
|
||||||
|
|
||||||
if curr_timeslot != last_timeslot:
|
if curr_timeslot != last_timeslot:
|
||||||
# timeslot changed. Set update flag.
|
# timeslot changed. Set update flag.
|
||||||
self.ndb.last_timeslot = curr_timeslot
|
self.ndb.last_timeslot = curr_timeslot
|
||||||
update = True
|
update = True
|
||||||
|
|
||||||
if update:
|
if update:
|
||||||
# if anything changed we have to re-parse
|
# if anything changed we have to re-parse
|
||||||
# the raw_desc for time markers
|
# the raw_desc for time markers
|
||||||
# and re-save the description again.
|
# and re-save the description again.
|
||||||
self.db.desc = self.replace_timeslots(self.db.raw_desc, curr_timeslot)
|
self.db.desc = self.replace_timeslots(self.db.raw_desc, curr_timeslot)
|
||||||
# run the normal return_appearance method, now that desc is updated.
|
|
||||||
return super(ExtendedRoom, self).return_appearance(looker)
|
|
||||||
|
|
||||||
|
|
||||||
# Custom Look command supporting Room details. Add this to
|
# Custom Look command supporting Room details. Add this to
|
||||||
|
|
@ -277,10 +276,11 @@ class CmdExtendedLook(default_cmds.CmdLook):
|
||||||
look
|
look
|
||||||
look <obj>
|
look <obj>
|
||||||
look <room detail>
|
look <room detail>
|
||||||
look *<player>
|
look *<account>
|
||||||
|
|
||||||
Observes your location, details at your location or objects in your vicinity.
|
Observes your location, details at your location or objects in your vicinity.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""
|
"""
|
||||||
Handle the looking - add fallback to details.
|
Handle the looking - add fallback to details.
|
||||||
|
|
@ -315,7 +315,7 @@ class CmdExtendedLook(default_cmds.CmdLook):
|
||||||
return
|
return
|
||||||
|
|
||||||
if not hasattr(looking_at_obj, 'return_appearance'):
|
if not hasattr(looking_at_obj, 'return_appearance'):
|
||||||
# this is likely due to us having a player instead
|
# this is likely due to us having an account instead
|
||||||
looking_at_obj = looking_at_obj.character
|
looking_at_obj = looking_at_obj.character
|
||||||
if not looking_at_obj.access(caller, "view"):
|
if not looking_at_obj.access(caller, "view"):
|
||||||
caller.msg("Could not find '%s'." % args)
|
caller.msg("Could not find '%s'." % args)
|
||||||
|
|
@ -331,26 +331,26 @@ class CmdExtendedLook(default_cmds.CmdLook):
|
||||||
|
|
||||||
class CmdExtendedDesc(default_cmds.CmdDesc):
|
class CmdExtendedDesc(default_cmds.CmdDesc):
|
||||||
"""
|
"""
|
||||||
`@desc` - describe an object or room.
|
`desc` - describe an object or room.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@desc[/switch] [<obj> =] <description>
|
desc[/switch] [<obj> =] <description>
|
||||||
@detail[/del] [<key> = <description>]
|
detail[/del] [<key> = <description>]
|
||||||
|
|
||||||
|
|
||||||
Switches for `@desc`:
|
Switches for `desc`:
|
||||||
spring - set description for <season> in current room.
|
spring - set description for <season> in current room.
|
||||||
summer
|
summer
|
||||||
autumn
|
autumn
|
||||||
winter
|
winter
|
||||||
|
|
||||||
Switch for `@detail`:
|
Switch for `detail`:
|
||||||
del - delete a named detail.
|
del - delete a named detail.
|
||||||
|
|
||||||
Sets the "desc" attribute on an object. If an object is not given,
|
Sets the "desc" attribute on an object. If an object is not given,
|
||||||
describe the current room.
|
describe the current room.
|
||||||
|
|
||||||
The alias `@detail` allows to assign a "detail" (a non-object
|
The alias `detail` allows to assign a "detail" (a non-object
|
||||||
target for the `look` command) to the current room (only).
|
target for the `look` command) to the current room (only).
|
||||||
|
|
||||||
You can also embed special time markers in your room description, like this:
|
You can also embed special time markers in your room description, like this:
|
||||||
|
|
@ -362,11 +362,11 @@ class CmdExtendedDesc(default_cmds.CmdDesc):
|
||||||
Text marked this way will only display when the server is truly at the given
|
Text marked this way will only display when the server is truly at the given
|
||||||
timeslot. The available times are night, morning, afternoon and evening.
|
timeslot. The available times are night, morning, afternoon and evening.
|
||||||
|
|
||||||
Note that `@detail`, seasons and time-of-day slots only work on rooms in this
|
Note that `detail`, seasons and time-of-day slots only work on rooms in this
|
||||||
version of the `@desc` command.
|
version of the `desc` command.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
aliases = ["@describe", "@detail"]
|
aliases = ["describe", "detail"]
|
||||||
|
|
||||||
def reset_times(self, obj):
|
def reset_times(self, obj):
|
||||||
"""By deleteting the caches we force a re-load."""
|
"""By deleteting the caches we force a re-load."""
|
||||||
|
|
@ -377,7 +377,7 @@ class CmdExtendedDesc(default_cmds.CmdDesc):
|
||||||
"""Define extended command"""
|
"""Define extended command"""
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
location = caller.location
|
location = caller.location
|
||||||
if self.cmdstring == '@detail':
|
if self.cmdname == 'detail':
|
||||||
# switch to detailing mode. This operates only on current location
|
# switch to detailing mode. This operates only on current location
|
||||||
if not location:
|
if not location:
|
||||||
caller.msg("No location to detail!")
|
caller.msg("No location to detail!")
|
||||||
|
|
@ -414,7 +414,7 @@ class CmdExtendedDesc(default_cmds.CmdDesc):
|
||||||
self.reset_times(location)
|
self.reset_times(location)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
# we are doing a @desc call
|
# we are doing a desc call
|
||||||
if not self.args:
|
if not self.args:
|
||||||
if location:
|
if location:
|
||||||
string = "|wDescriptions on %s|n:\n" % location.key
|
string = "|wDescriptions on %s|n:\n" % location.key
|
||||||
|
|
|
||||||
|
|
@ -38,22 +38,23 @@ _GENDER_PRONOUN_MAP = {"male": {"s": "he",
|
||||||
"p": "his",
|
"p": "his",
|
||||||
"a": "his"},
|
"a": "his"},
|
||||||
"female": {"s": "she",
|
"female": {"s": "she",
|
||||||
"o": "her",
|
"o": "her",
|
||||||
"p": "her",
|
"p": "her",
|
||||||
"a": "hers"},
|
"a": "hers"},
|
||||||
"neutral": {"s": "it",
|
"neutral": {"s": "it",
|
||||||
"o": "it",
|
"o": "it",
|
||||||
"p": "its",
|
"p": "its",
|
||||||
"a": "its"},
|
"a": "its"},
|
||||||
"ambiguous": {"s": "they",
|
"ambiguous": {"s": "they",
|
||||||
"o": "them",
|
"o": "them",
|
||||||
"p": "their",
|
"p": "their",
|
||||||
"a": "theirs"}
|
"a": "theirs"}
|
||||||
}
|
}
|
||||||
_RE_GENDER_PRONOUN = re.compile(r'(?<!\|)\|(?!\|)[sSoOpPaA]')
|
_RE_GENDER_PRONOUN = re.compile(r'(?<!\|)\|(?!\|)[sSoOpPaA]')
|
||||||
|
|
||||||
# in-game command for setting the gender
|
# in-game command for setting the gender
|
||||||
|
|
||||||
|
|
||||||
class SetGender(Command):
|
class SetGender(Command):
|
||||||
"""
|
"""
|
||||||
Sets gender on yourself
|
Sets gender on yourself
|
||||||
|
|
@ -72,7 +73,7 @@ class SetGender(Command):
|
||||||
"""
|
"""
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
arg = self.args.strip().lower()
|
arg = self.args.strip().lower()
|
||||||
if not arg in ("male", "female", "neutral", "ambiguous"):
|
if arg not in ("male", "female", "neutral", "ambiguous"):
|
||||||
caller.msg("Usage: @gender male||female||neutral||ambiguous")
|
caller.msg("Usage: @gender male||female||neutral||ambiguous")
|
||||||
return
|
return
|
||||||
caller.db.gender = arg
|
caller.db.gender = arg
|
||||||
|
|
@ -109,7 +110,7 @@ class GenderCharacter(DefaultCharacter):
|
||||||
- `|a`, `|A`: Absolute Possessive form: his, hers, its, His, Hers, Its, Theirs
|
- `|a`, `|A`: Absolute Possessive form: his, hers, its, His, Hers, Its, Theirs
|
||||||
|
|
||||||
"""
|
"""
|
||||||
typ = regex_match.group()[1] # "s", "O" etc
|
typ = regex_match.group()[1] # "s", "O" etc
|
||||||
gender = self.attributes.get("gender", default="ambiguous")
|
gender = self.attributes.get("gender", default="ambiguous")
|
||||||
gender = gender if gender in ("male", "female", "neutral") else "ambiguous"
|
gender = gender if gender in ("male", "female", "neutral") else "ambiguous"
|
||||||
pronoun = _GENDER_PRONOUN_MAP[gender][typ.lower()]
|
pronoun = _GENDER_PRONOUN_MAP[gender][typ.lower()]
|
||||||
|
|
|
||||||
103
evennia/contrib/health_bar.py
Normal file
103
evennia/contrib/health_bar.py
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
"""
|
||||||
|
Health Bar
|
||||||
|
|
||||||
|
Contrib - Tim Ashley Jenkins 2017
|
||||||
|
|
||||||
|
The function provided in this module lets you easily display visual
|
||||||
|
bars or meters - "health bar" is merely the most obvious use for this,
|
||||||
|
though these bars are highly customizable and can be used for any sort
|
||||||
|
of appropriate data besides player health.
|
||||||
|
|
||||||
|
Today's players may be more used to seeing statistics like health,
|
||||||
|
stamina, magic, and etc. displayed as bars rather than bare numerical
|
||||||
|
values, so using this module to present this data this way may make it
|
||||||
|
more accessible. Keep in mind, however, that players may also be using
|
||||||
|
a screen reader to connect to your game, which will not be able to
|
||||||
|
represent the colors of the bar in any way. By default, the values
|
||||||
|
represented are rendered as text inside the bar which can be read by
|
||||||
|
screen readers.
|
||||||
|
|
||||||
|
The health bar will account for current values above the maximum or
|
||||||
|
below 0, rendering them as a completely full or empty bar with the
|
||||||
|
values displayed within.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def display_meter(cur_value, max_value,
|
||||||
|
length=30, fill_color=["R", "Y", "G"],
|
||||||
|
empty_color="B", text_color="w",
|
||||||
|
align="left", pre_text="", post_text="",
|
||||||
|
show_values=True):
|
||||||
|
"""
|
||||||
|
Represents a current and maximum value given as a "bar" rendered with
|
||||||
|
ANSI or xterm256 background colors.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cur_value (int): Current value to display
|
||||||
|
max_value (int): Maximum value to display
|
||||||
|
|
||||||
|
Options:
|
||||||
|
length (int): Length of meter returned, in characters
|
||||||
|
fill_color (list): List of color codes for the full portion
|
||||||
|
of the bar, sans any sort of prefix - both ANSI and xterm256
|
||||||
|
colors are usable. When the bar is empty, colors toward the
|
||||||
|
start of the list will be chosen - when the bar is full, colors
|
||||||
|
towards the end are picked. You can adjust the 'weights' of
|
||||||
|
the changing colors by adding multiple entries of the same
|
||||||
|
color - for example, if you only want the bar to change when
|
||||||
|
it's close to empty, you could supply ['R','Y','G','G','G']
|
||||||
|
empty_color (str): Color code for the empty portion of the bar.
|
||||||
|
text_color (str): Color code for text inside the bar.
|
||||||
|
align (str): "left", "right", or "center" - alignment of text in the bar
|
||||||
|
pre_text (str): Text to put before the numbers in the bar
|
||||||
|
post_text (str): Text to put after the numbers in the bar
|
||||||
|
show_values (bool): If true, shows the numerical values represented by
|
||||||
|
the bar. It's highly recommended you keep this on, especially if
|
||||||
|
there's no info given in pre_text or post_text, as players on screen
|
||||||
|
readers will be unable to read the graphical aspect of the bar.
|
||||||
|
"""
|
||||||
|
# Start by building the base string.
|
||||||
|
num_text = ""
|
||||||
|
if show_values:
|
||||||
|
num_text = "%i / %i" % (cur_value, max_value)
|
||||||
|
bar_base_str = pre_text + num_text + post_text
|
||||||
|
# Cut down the length of the base string if needed
|
||||||
|
if len(bar_base_str) > length:
|
||||||
|
bar_base_str = bar_base_str[:length]
|
||||||
|
# Pad and align the bar base string
|
||||||
|
if align == "right":
|
||||||
|
bar_base_str = bar_base_str.rjust(length, " ")
|
||||||
|
elif align == "center":
|
||||||
|
bar_base_str = bar_base_str.center(length, " ")
|
||||||
|
else:
|
||||||
|
bar_base_str = bar_base_str.ljust(length, " ")
|
||||||
|
|
||||||
|
if max_value < 1: # Prevent divide by zero
|
||||||
|
max_value = 1
|
||||||
|
if cur_value < 0: # Prevent weirdly formatted 'negative bars'
|
||||||
|
cur_value = 0
|
||||||
|
if cur_value > max_value: # Display overfull bars correctly
|
||||||
|
cur_value = max_value
|
||||||
|
|
||||||
|
# Now it's time to determine where to put the color codes.
|
||||||
|
percent_full = float(cur_value) / float(max_value)
|
||||||
|
split_index = round(float(length) * percent_full)
|
||||||
|
# Determine point at which to split the bar
|
||||||
|
split_index = int(split_index)
|
||||||
|
|
||||||
|
# Separate the bar string into full and empty portions
|
||||||
|
full_portion = bar_base_str[:split_index]
|
||||||
|
empty_portion = bar_base_str[split_index:]
|
||||||
|
|
||||||
|
# Pick which fill color to use based on how full the bar is
|
||||||
|
fillcolor_index = (float(len(fill_color)) * percent_full)
|
||||||
|
fillcolor_index = int(round(fillcolor_index)) - 1
|
||||||
|
fillcolor_code = "|[" + fill_color[fillcolor_index]
|
||||||
|
|
||||||
|
# Make color codes for empty bar portion and text_color
|
||||||
|
emptycolor_code = "|[" + empty_color
|
||||||
|
textcolor_code = "|" + text_color
|
||||||
|
|
||||||
|
# Assemble the final bar
|
||||||
|
final_bar = fillcolor_code + textcolor_code + full_portion + "|n" + emptycolor_code + textcolor_code + empty_portion + "|n"
|
||||||
|
|
||||||
|
return final_bar
|
||||||
|
|
@ -4,6 +4,7 @@ Module containing the CallbackHandler for individual objects.
|
||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
|
|
||||||
class CallbackHandler(object):
|
class CallbackHandler(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -88,7 +89,7 @@ class CallbackHandler(object):
|
||||||
Args:
|
Args:
|
||||||
callback_name (str): the name of the callback to add.
|
callback_name (str): the name of the callback to add.
|
||||||
code (str): the Python code associated with this callback.
|
code (str): the Python code associated with this callback.
|
||||||
author (Character or Player, optional): the author of the callback.
|
author (Character or Account, optional): the author of the callback.
|
||||||
valid (bool, optional): should the callback be connected?
|
valid (bool, optional): should the callback be connected?
|
||||||
parameters (str, optional): optional parameters.
|
parameters (str, optional): optional parameters.
|
||||||
|
|
||||||
|
|
@ -99,7 +100,7 @@ class CallbackHandler(object):
|
||||||
handler = type(self).script
|
handler = type(self).script
|
||||||
if handler:
|
if handler:
|
||||||
return self.format_callback(handler.add_callback(self.obj, callback_name, code,
|
return self.format_callback(handler.add_callback(self.obj, callback_name, code,
|
||||||
author=author, valid=valid, parameters=parameters))
|
author=author, valid=valid, parameters=parameters))
|
||||||
|
|
||||||
def edit(self, callback_name, number, code, author=None, valid=False):
|
def edit(self, callback_name, number, code, author=None, valid=False):
|
||||||
"""
|
"""
|
||||||
|
|
@ -109,7 +110,7 @@ class CallbackHandler(object):
|
||||||
callback_name (str): the name of the callback to edit.
|
callback_name (str): the name of the callback to edit.
|
||||||
number (int): the callback number to be changed.
|
number (int): the callback number to be changed.
|
||||||
code (str): the Python code associated with this callback.
|
code (str): the Python code associated with this callback.
|
||||||
author (Character or Player, optional): the author of the callback.
|
author (Character or Account, optional): the author of the callback.
|
||||||
valid (bool, optional): should the callback be connected?
|
valid (bool, optional): should the callback be connected?
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -122,7 +123,7 @@ class CallbackHandler(object):
|
||||||
handler = type(self).script
|
handler = type(self).script
|
||||||
if handler:
|
if handler:
|
||||||
return self.format_callback(handler.edit_callback(self.obj, callback_name,
|
return self.format_callback(handler.edit_callback(self.obj, callback_name,
|
||||||
number, code, author=author, valid=valid))
|
number, code, author=author, valid=valid))
|
||||||
|
|
||||||
def remove(self, callback_name, number):
|
def remove(self, callback_name, number):
|
||||||
"""
|
"""
|
||||||
|
|
@ -200,5 +201,6 @@ class CallbackHandler(object):
|
||||||
|
|
||||||
return Callback(**callback)
|
return Callback(**callback)
|
||||||
|
|
||||||
|
|
||||||
Callback = namedtuple("Callback", ("obj", "name", "number", "code", "author",
|
Callback = namedtuple("Callback", ("obj", "name", "number", "code", "author",
|
||||||
"valid", "parameters", "created_on", "updated_by", "updated_on"))
|
"valid", "parameters", "created_on", "updated_by", "updated_on"))
|
||||||
|
|
|
||||||
|
|
@ -17,18 +17,18 @@ COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||||
# Permissions
|
# Permissions
|
||||||
WITH_VALIDATION = getattr(settings, "callbackS_WITH_VALIDATION", None)
|
WITH_VALIDATION = getattr(settings, "callbackS_WITH_VALIDATION", None)
|
||||||
WITHOUT_VALIDATION = getattr(settings, "callbackS_WITHOUT_VALIDATION",
|
WITHOUT_VALIDATION = getattr(settings, "callbackS_WITHOUT_VALIDATION",
|
||||||
"immortals")
|
"developer")
|
||||||
VALIDATING = getattr(settings, "callbackS_VALIDATING", "immortals")
|
VALIDATING = getattr(settings, "callbackS_VALIDATING", "developer")
|
||||||
|
|
||||||
# Split help text
|
# Split help text
|
||||||
BASIC_HELP = "Add, edit or delete callbacks."
|
BASIC_HELP = "Add, edit or delete callbacks."
|
||||||
|
|
||||||
BASIC_USAGES = [
|
BASIC_USAGES = [
|
||||||
"@call <object name> [= <callback name>]",
|
"@call <object name> [= <callback name>]",
|
||||||
"@call/add <object name> = <callback name> [parameters]",
|
"@call/add <object name> = <callback name> [parameters]",
|
||||||
"@call/edit <object name> = <callback name> [callback number]",
|
"@call/edit <object name> = <callback name> [callback number]",
|
||||||
"@call/del <object name> = <callback name> [callback number]",
|
"@call/del <object name> = <callback name> [callback number]",
|
||||||
"@call/tasks [object name [= <callback name>]]",
|
"@call/tasks [object name [= <callback name>]]",
|
||||||
]
|
]
|
||||||
|
|
||||||
BASIC_SWITCHES = [
|
BASIC_SWITCHES = [
|
||||||
|
|
@ -39,7 +39,7 @@ BASIC_SWITCHES = [
|
||||||
]
|
]
|
||||||
|
|
||||||
VALIDATOR_USAGES = [
|
VALIDATOR_USAGES = [
|
||||||
"@call/accept [object name = <callback name> [callback number]]",
|
"@call/accept [object name = <callback name> [callback number]]",
|
||||||
]
|
]
|
||||||
|
|
||||||
VALIDATOR_SWITCHES = [
|
VALIDATOR_SWITCHES = [
|
||||||
|
|
@ -73,6 +73,7 @@ them and when. You can then accept a specific callback:
|
||||||
Use the /del switch to remove callbacks that should not be connected.
|
Use the /del switch to remove callbacks that should not be connected.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class CmdCallback(COMMAND_DEFAULT_CLASS):
|
class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -94,7 +95,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
on user permission.
|
on user permission.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
caller (Object or Player): the caller asking for help on the command.
|
caller (Object or Account): the caller asking for help on the command.
|
||||||
cmdset (CmdSet): the command set (if you need additional commands).
|
cmdset (CmdSet): the command set (if you need additional commands).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -141,8 +142,8 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
self.is_validator = validator
|
self.is_validator = validator
|
||||||
self.autovalid = autovalid
|
self.autovalid = autovalid
|
||||||
if self.handler is None:
|
if self.handler is None:
|
||||||
caller.msg("The event handler is not running, can't " \
|
caller.msg("The event handler is not running, can't "
|
||||||
"access the event system.")
|
"access the event system.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Before the equal sign, there is an object name or nothing
|
# Before the equal sign, there is an object name or nothing
|
||||||
|
|
@ -170,8 +171,8 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
elif switch in ["tasks", "task"]:
|
elif switch in ["tasks", "task"]:
|
||||||
self.list_tasks()
|
self.list_tasks()
|
||||||
else:
|
else:
|
||||||
caller.msg("Mutually exclusive or invalid switches were " \
|
caller.msg("Mutually exclusive or invalid switches were "
|
||||||
"used, cannot proceed.")
|
"used, cannot proceed.")
|
||||||
|
|
||||||
def list_callbacks(self):
|
def list_callbacks(self):
|
||||||
"""Display the list of callbacks connected to the object."""
|
"""Display the list of callbacks connected to the object."""
|
||||||
|
|
@ -186,7 +187,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
created = callbacks.get(callback_name)
|
created = callbacks.get(callback_name)
|
||||||
if created is None:
|
if created is None:
|
||||||
self.msg("No callback {} has been set on {}.".format(callback_name,
|
self.msg("No callback {} has been set on {}.".format(callback_name,
|
||||||
obj))
|
obj))
|
||||||
return
|
return
|
||||||
|
|
||||||
if parameters:
|
if parameters:
|
||||||
|
|
@ -197,7 +198,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
callback = callbacks[callback_name][number]
|
callback = callbacks[callback_name][number]
|
||||||
except (ValueError, AssertionError, IndexError):
|
except (ValueError, AssertionError, IndexError):
|
||||||
self.msg("The callback {} {} cannot be found in {}.".format(
|
self.msg("The callback {} {} cannot be found in {}.".format(
|
||||||
callback_name, parameters, obj))
|
callback_name, parameters, obj))
|
||||||
return
|
return
|
||||||
|
|
||||||
# Display the callback's details
|
# Display the callback's details
|
||||||
|
|
@ -241,8 +242,8 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
if updated_on:
|
if updated_on:
|
||||||
updated_on = "{} ago".format(time_format(
|
updated_on = "{} ago".format(time_format(
|
||||||
(now - updated_on).total_seconds(),
|
(now - updated_on).total_seconds(),
|
||||||
4).capitalize())
|
4).capitalize())
|
||||||
else:
|
else:
|
||||||
updated_on = "|gUnknown|n"
|
updated_on = "|gUnknown|n"
|
||||||
parameters = callback.get("parameters", "")
|
parameters = callback.get("parameters", "")
|
||||||
|
|
@ -256,7 +257,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
else:
|
else:
|
||||||
names = list(set(list(types.keys()) + list(callbacks.keys())))
|
names = list(set(list(types.keys()) + list(callbacks.keys())))
|
||||||
table = EvTable("Callback name", "Number", "Description",
|
table = EvTable("Callback name", "Number", "Description",
|
||||||
valign="t", width=78)
|
valign="t", width=78)
|
||||||
table.reformat_column(0, width=20)
|
table.reformat_column(0, width=20)
|
||||||
table.reformat_column(1, width=10, align="r")
|
table.reformat_column(1, width=10, align="r")
|
||||||
table.reformat_column(2, width=48)
|
table.reformat_column(2, width=48)
|
||||||
|
|
@ -277,9 +278,9 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
types = self.handler.get_events(obj)
|
types = self.handler.get_events(obj)
|
||||||
|
|
||||||
# Check that the callback exists
|
# Check that the callback exists
|
||||||
if not callback_name.startswith("chain_") and not callback_name in types:
|
if not callback_name.startswith("chain_") and callback_name not in types:
|
||||||
self.msg("The callback name {} can't be found in {} of " \
|
self.msg("The callback name {} can't be found in {} of "
|
||||||
"typeclass {}.".format(callback_name, obj, type(obj)))
|
"typeclass {}.".format(callback_name, obj, type(obj)))
|
||||||
return
|
return
|
||||||
|
|
||||||
definition = types.get(callback_name, (None, "Chained event."))
|
definition = types.get(callback_name, (None, "Chained event."))
|
||||||
|
|
@ -288,7 +289,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
# Open the editor
|
# Open the editor
|
||||||
callback = self.handler.add_callback(obj, callback_name, "",
|
callback = self.handler.add_callback(obj, callback_name, "",
|
||||||
self.caller, False, parameters=self.parameters)
|
self.caller, False, parameters=self.parameters)
|
||||||
|
|
||||||
# Lock this callback right away
|
# Lock this callback right away
|
||||||
self.handler.db.locked.append((obj, callback_name, callback["number"]))
|
self.handler.db.locked.append((obj, callback_name, callback["number"]))
|
||||||
|
|
@ -296,8 +297,8 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
# Open the editor for this callback
|
# Open the editor for this callback
|
||||||
self.caller.db._callback = callback
|
self.caller.db._callback = callback
|
||||||
EvEditor(self.caller, loadfunc=_ev_load, savefunc=_ev_save,
|
EvEditor(self.caller, loadfunc=_ev_load, savefunc=_ev_save,
|
||||||
quitfunc=_ev_quit, key="Callback {} of {}".format(
|
quitfunc=_ev_quit, key="Callback {} of {}".format(
|
||||||
callback_name, obj), persistent=True, codefunc=_ev_save)
|
callback_name, obj), persistent=True, codefunc=_ev_save)
|
||||||
|
|
||||||
def edit_callback(self):
|
def edit_callback(self):
|
||||||
"""Edit a callback."""
|
"""Edit a callback."""
|
||||||
|
|
@ -313,9 +314,9 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check that the callback exists
|
# Check that the callback exists
|
||||||
if not callback_name in callbacks:
|
if callback_name not in callbacks:
|
||||||
self.msg("The callback name {} can't be found in {}.".format(
|
self.msg("The callback name {} can't be found in {}.".format(
|
||||||
callback_name, obj))
|
callback_name, obj))
|
||||||
return
|
return
|
||||||
|
|
||||||
# If there's only one callback, just edit it
|
# If there's only one callback, just edit it
|
||||||
|
|
@ -335,7 +336,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
callback = callbacks[callback_name][number]
|
callback = callbacks[callback_name][number]
|
||||||
except (ValueError, AssertionError, IndexError):
|
except (ValueError, AssertionError, IndexError):
|
||||||
self.msg("The callback {} {} cannot be found in {}.".format(
|
self.msg("The callback {} {} cannot be found in {}.".format(
|
||||||
callback_name, parameters, obj))
|
callback_name, parameters, obj))
|
||||||
return
|
return
|
||||||
|
|
||||||
# If caller can't edit without validation, forbid editing
|
# If caller can't edit without validation, forbid editing
|
||||||
|
|
@ -360,8 +361,8 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
callback = dict(callback)
|
callback = dict(callback)
|
||||||
self.caller.db._callback = callback
|
self.caller.db._callback = callback
|
||||||
EvEditor(self.caller, loadfunc=_ev_load, savefunc=_ev_save,
|
EvEditor(self.caller, loadfunc=_ev_load, savefunc=_ev_save,
|
||||||
quitfunc=_ev_quit, key="Callback {} of {}".format(
|
quitfunc=_ev_quit, key="Callback {} of {}".format(
|
||||||
callback_name, obj), persistent=True, codefunc=_ev_save)
|
callback_name, obj), persistent=True, codefunc=_ev_save)
|
||||||
|
|
||||||
def del_callback(self):
|
def del_callback(self):
|
||||||
"""Delete a callback."""
|
"""Delete a callback."""
|
||||||
|
|
@ -377,9 +378,9 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check that the callback exists
|
# Check that the callback exists
|
||||||
if not callback_name in callbacks:
|
if callback_name not in callbacks:
|
||||||
self.msg("The callback name {} can't be found in {}.".format(
|
self.msg("The callback name {} can't be found in {}.".format(
|
||||||
callback_name, obj))
|
callback_name, obj))
|
||||||
return
|
return
|
||||||
|
|
||||||
# If there's only one callback, just delete it
|
# If there's only one callback, just delete it
|
||||||
|
|
@ -388,8 +389,8 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
callback = callbacks[callback_name][0]
|
callback = callbacks[callback_name][0]
|
||||||
else:
|
else:
|
||||||
if not parameters:
|
if not parameters:
|
||||||
self.msg("Which callback do you wish to delete? Specify " \
|
self.msg("Which callback do you wish to delete? Specify "
|
||||||
"a number.")
|
"a number.")
|
||||||
self.list_callbacks()
|
self.list_callbacks()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -400,7 +401,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
callback = callbacks[callback_name][number]
|
callback = callbacks[callback_name][number]
|
||||||
except (ValueError, AssertionError, IndexError):
|
except (ValueError, AssertionError, IndexError):
|
||||||
self.msg("The callback {} {} cannot be found in {}.".format(
|
self.msg("The callback {} {} cannot be found in {}.".format(
|
||||||
callback_name, parameters, obj))
|
callback_name, parameters, obj))
|
||||||
return
|
return
|
||||||
|
|
||||||
# If caller can't edit without validation, forbid deleting
|
# If caller can't edit without validation, forbid deleting
|
||||||
|
|
@ -417,7 +418,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
# Delete the callback
|
# Delete the callback
|
||||||
self.handler.del_callback(obj, callback_name, number)
|
self.handler.del_callback(obj, callback_name, number)
|
||||||
self.msg("The callback {}[{}] of {} was deleted.".format(
|
self.msg("The callback {}[{}] of {} was deleted.".format(
|
||||||
callback_name, number + 1, obj))
|
callback_name, number + 1, obj))
|
||||||
|
|
||||||
def accept_callback(self):
|
def accept_callback(self):
|
||||||
"""Accept a callback."""
|
"""Accept a callback."""
|
||||||
|
|
@ -428,7 +429,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
# If no object, display the list of callbacks to be checked
|
# If no object, display the list of callbacks to be checked
|
||||||
if obj is None:
|
if obj is None:
|
||||||
table = EvTable("ID", "Type", "Object", "Name", "Updated by",
|
table = EvTable("ID", "Type", "Object", "Name", "Updated by",
|
||||||
"On", width=78)
|
"On", width=78)
|
||||||
table.reformat_column(0, align="r")
|
table.reformat_column(0, align="r")
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
for obj, name, number in self.handler.db.to_valid:
|
for obj, name, number in self.handler.db.to_valid:
|
||||||
|
|
@ -450,8 +451,8 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
if updated_on:
|
if updated_on:
|
||||||
updated_on = "{} ago".format(time_format(
|
updated_on = "{} ago".format(time_format(
|
||||||
(now - updated_on).total_seconds(),
|
(now - updated_on).total_seconds(),
|
||||||
4).capitalize())
|
4).capitalize())
|
||||||
else:
|
else:
|
||||||
updated_on = "|gUnknown|n"
|
updated_on = "|gUnknown|n"
|
||||||
|
|
||||||
|
|
@ -469,9 +470,9 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check that the callback exists
|
# Check that the callback exists
|
||||||
if not callback_name in callbacks:
|
if callback_name not in callbacks:
|
||||||
self.msg("The callback name {} can't be found in {}.".format(
|
self.msg("The callback name {} can't be found in {}.".format(
|
||||||
callback_name, obj))
|
callback_name, obj))
|
||||||
return
|
return
|
||||||
|
|
||||||
if not parameters:
|
if not parameters:
|
||||||
|
|
@ -486,7 +487,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
callback = callbacks[callback_name][number]
|
callback = callbacks[callback_name][number]
|
||||||
except (ValueError, AssertionError, IndexError):
|
except (ValueError, AssertionError, IndexError):
|
||||||
self.msg("The callback {} {} cannot be found in {}.".format(
|
self.msg("The callback {} {} cannot be found in {}.".format(
|
||||||
callback_name, parameters, obj))
|
callback_name, parameters, obj))
|
||||||
return
|
return
|
||||||
|
|
||||||
# Accept the callback
|
# Accept the callback
|
||||||
|
|
@ -495,7 +496,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
else:
|
else:
|
||||||
self.handler.accept_callback(obj, callback_name, number)
|
self.handler.accept_callback(obj, callback_name, number)
|
||||||
self.msg("The callback {} {} of {} has been accepted.".format(
|
self.msg("The callback {} {} of {} has been accepted.".format(
|
||||||
callback_name, parameters, obj))
|
callback_name, parameters, obj))
|
||||||
|
|
||||||
def list_tasks(self):
|
def list_tasks(self):
|
||||||
"""List the active tasks."""
|
"""List the active tasks."""
|
||||||
|
|
@ -520,40 +521,44 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||||
self.msg(unicode(table))
|
self.msg(unicode(table))
|
||||||
|
|
||||||
# Private functions to handle editing
|
# Private functions to handle editing
|
||||||
|
|
||||||
|
|
||||||
def _ev_load(caller):
|
def _ev_load(caller):
|
||||||
return caller.db._callback and caller.db._callback.get("code", "") or ""
|
return caller.db._callback and caller.db._callback.get("code", "") or ""
|
||||||
|
|
||||||
|
|
||||||
def _ev_save(caller, buf):
|
def _ev_save(caller, buf):
|
||||||
"""Save and add the callback."""
|
"""Save and add the callback."""
|
||||||
lock = "perm({}) or perm(events_without_validation)".format(
|
lock = "perm({}) or perm(events_without_validation)".format(
|
||||||
WITHOUT_VALIDATION)
|
WITHOUT_VALIDATION)
|
||||||
autovalid = caller.locks.check_lockstring(caller, lock)
|
autovalid = caller.locks.check_lockstring(caller, lock)
|
||||||
callback = caller.db._callback
|
callback = caller.db._callback
|
||||||
handler = get_event_handler()
|
handler = get_event_handler()
|
||||||
if not handler or not callback or not all(key in callback for key in \
|
if not handler or not callback or not all(key in callback for key in
|
||||||
("obj", "name", "number", "valid")):
|
("obj", "name", "number", "valid")):
|
||||||
caller.msg("Couldn't save this callback.")
|
caller.msg("Couldn't save this callback.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if (callback["obj"], callback["name"], callback["number"]) in handler.db.locked:
|
if (callback["obj"], callback["name"], callback["number"]) in handler.db.locked:
|
||||||
handler.db.locked.remove((callback["obj"], callback["name"],
|
handler.db.locked.remove((callback["obj"], callback["name"],
|
||||||
callback["number"]))
|
callback["number"]))
|
||||||
|
|
||||||
handler.edit_callback(callback["obj"], callback["name"], callback["number"], buf,
|
handler.edit_callback(callback["obj"], callback["name"], callback["number"], buf,
|
||||||
caller, valid=autovalid)
|
caller, valid=autovalid)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _ev_quit(caller):
|
def _ev_quit(caller):
|
||||||
callback = caller.db._callback
|
callback = caller.db._callback
|
||||||
handler = get_event_handler()
|
handler = get_event_handler()
|
||||||
if not handler or not callback or not all(key in callback for key in \
|
if not handler or not callback or not all(key in callback for key in
|
||||||
("obj", "name", "number", "valid")):
|
("obj", "name", "number", "valid")):
|
||||||
caller.msg("Couldn't save this callback.")
|
caller.msg("Couldn't save this callback.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if (callback["obj"], callback["name"], callback["number"]) in handler.db.locked:
|
if (callback["obj"], callback["name"], callback["number"]) in handler.db.locked:
|
||||||
handler.db.locked.remove((callback["obj"], callback["name"],
|
handler.db.locked.remove((callback["obj"], callback["name"],
|
||||||
callback["number"]))
|
callback["number"]))
|
||||||
|
|
||||||
del caller.db._callback
|
del caller.db._callback
|
||||||
caller.msg("Exited the code editor.")
|
caller.msg("Exited the code editor.")
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ Eventfuncs are just Python functions that can be used inside of calllbacks.
|
||||||
from evennia import ObjectDB, ScriptDB
|
from evennia import ObjectDB, ScriptDB
|
||||||
from evennia.contrib.ingame_python.utils import InterruptEvent
|
from evennia.contrib.ingame_python.utils import InterruptEvent
|
||||||
|
|
||||||
|
|
||||||
def deny():
|
def deny():
|
||||||
"""
|
"""
|
||||||
Deny, that is stop, the callback here.
|
Deny, that is stop, the callback here.
|
||||||
|
|
@ -22,6 +23,7 @@ def deny():
|
||||||
"""
|
"""
|
||||||
raise InterruptEvent
|
raise InterruptEvent
|
||||||
|
|
||||||
|
|
||||||
def get(**kwargs):
|
def get(**kwargs):
|
||||||
"""
|
"""
|
||||||
Return an object with the given search option or None if None is found.
|
Return an object with the given search option or None if None is found.
|
||||||
|
|
@ -53,6 +55,7 @@ def get(**kwargs):
|
||||||
|
|
||||||
return object
|
return object
|
||||||
|
|
||||||
|
|
||||||
def call_event(obj, event_name, seconds=0):
|
def call_event(obj, event_name, seconds=0):
|
||||||
"""
|
"""
|
||||||
Call the specified event in X seconds.
|
Call the specified event in X seconds.
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import traceback
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia import DefaultObject, DefaultScript, ChannelDB, ScriptDB
|
from evennia import DefaultObject, DefaultScript, ChannelDB, ScriptDB
|
||||||
from evennia import logger
|
from evennia import logger, ObjectDB
|
||||||
from evennia.utils.ansi import raw
|
from evennia.utils.ansi import raw
|
||||||
from evennia.utils.create import create_channel
|
from evennia.utils.create import create_channel
|
||||||
from evennia.utils.dbserialize import dbserialize
|
from evennia.utils.dbserialize import dbserialize
|
||||||
|
|
@ -21,6 +21,7 @@ from evennia.contrib.ingame_python.utils import get_next_wait, EVENTS, Interrupt
|
||||||
# Constants
|
# Constants
|
||||||
RE_LINE_ERROR = re.compile(r'^ File "\<string\>", line (\d+)')
|
RE_LINE_ERROR = re.compile(r'^ File "\<string\>", line (\d+)')
|
||||||
|
|
||||||
|
|
||||||
class EventHandler(DefaultScript):
|
class EventHandler(DefaultScript):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -94,28 +95,36 @@ class EventHandler(DefaultScript):
|
||||||
self.ndb.channel = ChannelDB.objects.get(db_key="everror")
|
self.ndb.channel = ChannelDB.objects.get(db_key="everror")
|
||||||
except ChannelDB.DoesNotExist:
|
except ChannelDB.DoesNotExist:
|
||||||
self.ndb.channel = create_channel("everror", desc="Event errors",
|
self.ndb.channel = create_channel("everror", desc="Event errors",
|
||||||
locks="control:false();listen:perm(Builders);send:false()")
|
locks="control:false();listen:perm(Builders);send:false()")
|
||||||
|
|
||||||
def get_events(self, obj):
|
def get_events(self, obj):
|
||||||
"""
|
"""
|
||||||
Return a dictionary of events on this object.
|
Return a dictionary of events on this object.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
obj (Object): the connected object.
|
obj (Object or typeclass): the connected object or a general typeclass.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A dictionary of the object's events.
|
A dictionary of the object's events.
|
||||||
|
|
||||||
Note:
|
Notes:
|
||||||
Events would define what the object can have as
|
Events would define what the object can have as
|
||||||
callbacks. Note, however, that chained callbacks will not
|
callbacks. Note, however, that chained callbacks will not
|
||||||
appear in events and are handled separately.
|
appear in events and are handled separately.
|
||||||
|
|
||||||
|
You can also request the events of a typeclass, not a
|
||||||
|
connected object. This is useful to get the global list
|
||||||
|
of events for a typeclass that has no object yet.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
events = {}
|
events = {}
|
||||||
all_events = self.ndb.events
|
all_events = self.ndb.events
|
||||||
classes = Queue()
|
classes = Queue()
|
||||||
classes.put(type(obj))
|
if isinstance(obj, type):
|
||||||
|
classes.put(obj)
|
||||||
|
else:
|
||||||
|
classes.put(type(obj))
|
||||||
|
|
||||||
invalid = []
|
invalid = []
|
||||||
while not classes.empty():
|
while not classes.empty():
|
||||||
typeclass = classes.get()
|
typeclass = classes.get()
|
||||||
|
|
@ -123,7 +132,7 @@ class EventHandler(DefaultScript):
|
||||||
for key, etype in all_events.get(typeclass_name, {}).items():
|
for key, etype in all_events.get(typeclass_name, {}).items():
|
||||||
if key in invalid:
|
if key in invalid:
|
||||||
continue
|
continue
|
||||||
if etype[0] is None: # Invalidate
|
if etype[0] is None: # Invalidate
|
||||||
invalid.append(key)
|
invalid.append(key)
|
||||||
continue
|
continue
|
||||||
if key not in events:
|
if key not in events:
|
||||||
|
|
@ -192,7 +201,7 @@ class EventHandler(DefaultScript):
|
||||||
return callbacks
|
return callbacks
|
||||||
|
|
||||||
def add_callback(self, obj, callback_name, code, author=None, valid=False,
|
def add_callback(self, obj, callback_name, code, author=None, valid=False,
|
||||||
parameters=""):
|
parameters=""):
|
||||||
"""
|
"""
|
||||||
Add the specified callback.
|
Add the specified callback.
|
||||||
|
|
||||||
|
|
@ -200,7 +209,7 @@ class EventHandler(DefaultScript):
|
||||||
obj (Object): the Evennia typeclassed object to be extended.
|
obj (Object): the Evennia typeclassed object to be extended.
|
||||||
callback_name (str): the name of the callback to add.
|
callback_name (str): the name of the callback to add.
|
||||||
code (str): the Python code associated with this callback.
|
code (str): the Python code associated with this callback.
|
||||||
author (Character or Player, optional): the author of the callback.
|
author (Character or Account, optional): the author of the callback.
|
||||||
valid (bool, optional): should the callback be connected?
|
valid (bool, optional): should the callback be connected?
|
||||||
parameters (str, optional): optional parameters.
|
parameters (str, optional): optional parameters.
|
||||||
|
|
||||||
|
|
@ -220,11 +229,11 @@ class EventHandler(DefaultScript):
|
||||||
|
|
||||||
# Add the callback in the list
|
# Add the callback in the list
|
||||||
callbacks.append({
|
callbacks.append({
|
||||||
"created_on": datetime.now(),
|
"created_on": datetime.now(),
|
||||||
"author": author,
|
"author": author,
|
||||||
"valid": valid,
|
"valid": valid,
|
||||||
"code": code,
|
"code": code,
|
||||||
"parameters": parameters,
|
"parameters": parameters,
|
||||||
})
|
})
|
||||||
|
|
||||||
# If not valid, set it in 'to_valid'
|
# If not valid, set it in 'to_valid'
|
||||||
|
|
@ -233,7 +242,7 @@ class EventHandler(DefaultScript):
|
||||||
|
|
||||||
# Call the custom_add if needed
|
# Call the custom_add if needed
|
||||||
custom_add = self.get_events(obj).get(
|
custom_add = self.get_events(obj).get(
|
||||||
callback_name, [None, None, None, None])[3]
|
callback_name, [None, None, None, None])[3]
|
||||||
if custom_add:
|
if custom_add:
|
||||||
custom_add(obj, callback_name, len(callbacks) - 1, parameters)
|
custom_add(obj, callback_name, len(callbacks) - 1, parameters)
|
||||||
|
|
||||||
|
|
@ -245,7 +254,7 @@ class EventHandler(DefaultScript):
|
||||||
return definition
|
return definition
|
||||||
|
|
||||||
def edit_callback(self, obj, callback_name, number, code, author=None,
|
def edit_callback(self, obj, callback_name, number, code, author=None,
|
||||||
valid=False):
|
valid=False):
|
||||||
"""
|
"""
|
||||||
Edit the specified callback.
|
Edit the specified callback.
|
||||||
|
|
||||||
|
|
@ -254,7 +263,7 @@ class EventHandler(DefaultScript):
|
||||||
callback_name (str): the name of the callback to edit.
|
callback_name (str): the name of the callback to edit.
|
||||||
number (int): the callback number to be changed.
|
number (int): the callback number to be changed.
|
||||||
code (str): the Python code associated with this callback.
|
code (str): the Python code associated with this callback.
|
||||||
author (Character or Player, optional): the author of the callback.
|
author (Character or Account, optional): the author of the callback.
|
||||||
valid (bool, optional): should the callback be connected?
|
valid (bool, optional): should the callback be connected?
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
|
@ -280,10 +289,10 @@ class EventHandler(DefaultScript):
|
||||||
|
|
||||||
# Edit the callback
|
# Edit the callback
|
||||||
callbacks[number].update({
|
callbacks[number].update({
|
||||||
"updated_on": datetime.now(),
|
"updated_on": datetime.now(),
|
||||||
"updated_by": author,
|
"updated_by": author,
|
||||||
"valid": valid,
|
"valid": valid,
|
||||||
"code": code,
|
"code": code,
|
||||||
})
|
})
|
||||||
|
|
||||||
# If not valid, set it in 'to_valid'
|
# If not valid, set it in 'to_valid'
|
||||||
|
|
@ -326,7 +335,7 @@ class EventHandler(DefaultScript):
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
logger.log_info("Deleting callback {} {} of {}:\n{}".format(
|
logger.log_info("Deleting callback {} {} of {}:\n{}".format(
|
||||||
callback_name, number, obj, code))
|
callback_name, number, obj, code))
|
||||||
del callbacks[number]
|
del callbacks[number]
|
||||||
|
|
||||||
# Change IDs of callbacks to be validated
|
# Change IDs of callbacks to be validated
|
||||||
|
|
@ -341,7 +350,7 @@ class EventHandler(DefaultScript):
|
||||||
elif t_number > number:
|
elif t_number > number:
|
||||||
# Change the ID for this callback
|
# Change the ID for this callback
|
||||||
self.db.to_valid.insert(i, (t_obj, t_callback_name,
|
self.db.to_valid.insert(i, (t_obj, t_callback_name,
|
||||||
t_number - 1))
|
t_number - 1))
|
||||||
del self.db.to_valid[i + 1]
|
del self.db.to_valid[i + 1]
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
|
|
@ -406,13 +415,13 @@ class EventHandler(DefaultScript):
|
||||||
# Errors should not pass silently
|
# Errors should not pass silently
|
||||||
allowed = ("number", "parameters", "locals")
|
allowed = ("number", "parameters", "locals")
|
||||||
if any(k for k in kwargs if k not in allowed):
|
if any(k for k in kwargs if k not in allowed):
|
||||||
raise TypeError("Unknown keyword arguments were specified " \
|
raise TypeError("Unknown keyword arguments were specified "
|
||||||
"to call callbacks: {}".format(kwargs))
|
"to call callbacks: {}".format(kwargs))
|
||||||
|
|
||||||
event = self.get_events(obj).get(callback_name)
|
event = self.get_events(obj).get(callback_name)
|
||||||
if locals is None and not event:
|
if locals is None and not event:
|
||||||
logger.log_err("The callback {} for the object {} (typeclass " \
|
logger.log_err("The callback {} for the object {} (typeclass "
|
||||||
"{}) can't be found".format(callback_name, obj, type(obj)))
|
"{}) can't be found".format(callback_name, obj, type(obj)))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Prepare the locals if necessary
|
# Prepare the locals if necessary
|
||||||
|
|
@ -422,9 +431,9 @@ class EventHandler(DefaultScript):
|
||||||
try:
|
try:
|
||||||
locals[variable] = args[i]
|
locals[variable] = args[i]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
logger.log_trace("callback {} of {} ({}): need variable " \
|
logger.log_trace("callback {} of {} ({}): need variable "
|
||||||
"{} in position {}".format(callback_name, obj,
|
"{} in position {}".format(callback_name, obj,
|
||||||
type(obj), variable, i))
|
type(obj), variable, i))
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
locals = {key: value for key, value in locals.items()}
|
locals = {key: value for key, value in locals.items()}
|
||||||
|
|
@ -474,9 +483,9 @@ class EventHandler(DefaultScript):
|
||||||
number = callback["number"]
|
number = callback["number"]
|
||||||
obj = callback["obj"]
|
obj = callback["obj"]
|
||||||
oid = obj.id
|
oid = obj.id
|
||||||
logger.log_err("An error occurred during the callback {} of " \
|
logger.log_err("An error occurred during the callback {} of "
|
||||||
"{} (#{}), number {}\n{}".format(callback_name, obj,
|
"{} (#{}), number {}\n{}".format(callback_name, obj,
|
||||||
oid, number + 1, "\n".join(trace)))
|
oid, number + 1, "\n".join(trace)))
|
||||||
|
|
||||||
# Create the error message
|
# Create the error message
|
||||||
line = "|runknown|n"
|
line = "|runknown|n"
|
||||||
|
|
@ -497,8 +506,8 @@ class EventHandler(DefaultScript):
|
||||||
|
|
||||||
exc = raw(trace[-1].strip("\n").splitlines()[-1])
|
exc = raw(trace[-1].strip("\n").splitlines()[-1])
|
||||||
err_msg = "Error in {} of {} (#{})[{}], line {}:" \
|
err_msg = "Error in {} of {} (#{})[{}], line {}:" \
|
||||||
" {}\n{}".format(callback_name, obj,
|
" {}\n{}".format(callback_name, obj,
|
||||||
oid, number + 1, lineno, line, exc)
|
oid, number + 1, lineno, line, exc)
|
||||||
|
|
||||||
# Inform the last updater if connected
|
# Inform the last updater if connected
|
||||||
updater = callback.get("updated_by")
|
updater = callback.get("updated_by")
|
||||||
|
|
@ -509,8 +518,8 @@ class EventHandler(DefaultScript):
|
||||||
updater.msg(err_msg)
|
updater.msg(err_msg)
|
||||||
else:
|
else:
|
||||||
err_msg = "Error in {} of {} (#{})[{}], line {}:" \
|
err_msg = "Error in {} of {} (#{})[{}], line {}:" \
|
||||||
" {}\n {}".format(callback_name, obj,
|
" {}\n {}".format(callback_name, obj,
|
||||||
oid, number + 1, lineno, line, exc)
|
oid, number + 1, lineno, line, exc)
|
||||||
self.ndb.channel.msg(err_msg)
|
self.ndb.channel.msg(err_msg)
|
||||||
|
|
||||||
def add_event(self, typeclass, name, variables, help_text, custom_call, custom_add):
|
def add_event(self, typeclass, name, variables, help_text, custom_call, custom_add):
|
||||||
|
|
@ -647,8 +656,8 @@ def complete_task(task_id):
|
||||||
return
|
return
|
||||||
|
|
||||||
if task_id not in script.db.tasks:
|
if task_id not in script.db.tasks:
|
||||||
logger.log_err("The task #{} was scheduled, but it cannot be " \
|
logger.log_err("The task #{} was scheduled, but it cannot be "
|
||||||
"found".format(task_id))
|
"found".format(task_id))
|
||||||
return
|
return
|
||||||
|
|
||||||
delta, obj, callback_name, locals = script.db.tasks.pop(task_id)
|
delta, obj, callback_name, locals = script.db.tasks.pop(task_id)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ settings.EVENTS_CALENDAR = "standard"
|
||||||
# Constants
|
# Constants
|
||||||
OLD_EVENTS = {}
|
OLD_EVENTS = {}
|
||||||
|
|
||||||
|
|
||||||
class TestEventHandler(EvenniaTest):
|
class TestEventHandler(EvenniaTest):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -31,7 +32,7 @@ class TestEventHandler(EvenniaTest):
|
||||||
"""Create the event handler."""
|
"""Create the event handler."""
|
||||||
super(TestEventHandler, self).setUp()
|
super(TestEventHandler, self).setUp()
|
||||||
self.handler = create_script(
|
self.handler = create_script(
|
||||||
"evennia.contrib.ingame_python.scripts.EventHandler")
|
"evennia.contrib.ingame_python.scripts.EventHandler")
|
||||||
|
|
||||||
# Copy old events if necessary
|
# Copy old events if necessary
|
||||||
if OLD_EVENTS:
|
if OLD_EVENTS:
|
||||||
|
|
@ -64,7 +65,7 @@ class TestEventHandler(EvenniaTest):
|
||||||
"""Add a callback while needing validation."""
|
"""Add a callback while needing validation."""
|
||||||
author = self.char1
|
author = self.char1
|
||||||
self.handler.add_callback(self.room1, "dummy",
|
self.handler.add_callback(self.room1, "dummy",
|
||||||
"character.db.strength = 40", author=author, valid=False)
|
"character.db.strength = 40", author=author, valid=False)
|
||||||
callback = self.handler.get_callbacks(self.room1).get("dummy")
|
callback = self.handler.get_callbacks(self.room1).get("dummy")
|
||||||
callback = callback[0]
|
callback = callback[0]
|
||||||
self.assertIsNotNone(callback)
|
self.assertIsNotNone(callback)
|
||||||
|
|
@ -78,18 +79,18 @@ class TestEventHandler(EvenniaTest):
|
||||||
self.char1.db.strength = 10
|
self.char1.db.strength = 10
|
||||||
locals = {"character": self.char1}
|
locals = {"character": self.char1}
|
||||||
self.assertTrue(self.handler.call(
|
self.assertTrue(self.handler.call(
|
||||||
self.room1, "dummy", locals=locals))
|
self.room1, "dummy", locals=locals))
|
||||||
self.assertEqual(self.char1.db.strength, 10)
|
self.assertEqual(self.char1.db.strength, 10)
|
||||||
|
|
||||||
def test_edit(self):
|
def test_edit(self):
|
||||||
"""Test editing a callback."""
|
"""Test editing a callback."""
|
||||||
author = self.char1
|
author = self.char1
|
||||||
self.handler.add_callback(self.room1, "dummy",
|
self.handler.add_callback(self.room1, "dummy",
|
||||||
"character.db.strength = 60", author=author, valid=True)
|
"character.db.strength = 60", author=author, valid=True)
|
||||||
|
|
||||||
# Edit it right away
|
# Edit it right away
|
||||||
self.handler.edit_callback(self.room1, "dummy", 0,
|
self.handler.edit_callback(self.room1, "dummy", 0,
|
||||||
"character.db.strength = 65", author=self.char2, valid=True)
|
"character.db.strength = 65", author=self.char2, valid=True)
|
||||||
|
|
||||||
# Check that the callback was written
|
# Check that the callback was written
|
||||||
callback = self.handler.get_callbacks(self.room1).get("dummy")
|
callback = self.handler.get_callbacks(self.room1).get("dummy")
|
||||||
|
|
@ -103,35 +104,35 @@ class TestEventHandler(EvenniaTest):
|
||||||
self.char1.db.strength = 10
|
self.char1.db.strength = 10
|
||||||
locals = {"character": self.char1}
|
locals = {"character": self.char1}
|
||||||
self.assertTrue(self.handler.call(
|
self.assertTrue(self.handler.call(
|
||||||
self.room1, "dummy", locals=locals))
|
self.room1, "dummy", locals=locals))
|
||||||
self.assertEqual(self.char1.db.strength, 65)
|
self.assertEqual(self.char1.db.strength, 65)
|
||||||
|
|
||||||
def test_edit_validation(self):
|
def test_edit_validation(self):
|
||||||
"""Edit a callback when validation isn't automatic."""
|
"""Edit a callback when validation isn't automatic."""
|
||||||
author = self.char1
|
author = self.char1
|
||||||
self.handler.add_callback(self.room1, "dummy",
|
self.handler.add_callback(self.room1, "dummy",
|
||||||
"character.db.strength = 70", author=author, valid=True)
|
"character.db.strength = 70", author=author, valid=True)
|
||||||
|
|
||||||
# Edit it right away
|
# Edit it right away
|
||||||
self.handler.edit_callback(self.room1, "dummy", 0,
|
self.handler.edit_callback(self.room1, "dummy", 0,
|
||||||
"character.db.strength = 80", author=self.char2, valid=False)
|
"character.db.strength = 80", author=self.char2, valid=False)
|
||||||
|
|
||||||
# Run this dummy callback (shouldn't do anything)
|
# Run this dummy callback (shouldn't do anything)
|
||||||
self.char1.db.strength = 10
|
self.char1.db.strength = 10
|
||||||
locals = {"character": self.char1}
|
locals = {"character": self.char1}
|
||||||
self.assertTrue(self.handler.call(
|
self.assertTrue(self.handler.call(
|
||||||
self.room1, "dummy", locals=locals))
|
self.room1, "dummy", locals=locals))
|
||||||
self.assertEqual(self.char1.db.strength, 10)
|
self.assertEqual(self.char1.db.strength, 10)
|
||||||
|
|
||||||
def test_del(self):
|
def test_del(self):
|
||||||
"""Try to delete a callback."""
|
"""Try to delete a callback."""
|
||||||
# Add 3 callbacks
|
# Add 3 callbacks
|
||||||
self.handler.add_callback(self.room1, "dummy",
|
self.handler.add_callback(self.room1, "dummy",
|
||||||
"character.db.strength = 5", author=self.char1, valid=True)
|
"character.db.strength = 5", author=self.char1, valid=True)
|
||||||
self.handler.add_callback(self.room1, "dummy",
|
self.handler.add_callback(self.room1, "dummy",
|
||||||
"character.db.strength = 8", author=self.char2, valid=False)
|
"character.db.strength = 8", author=self.char2, valid=False)
|
||||||
self.handler.add_callback(self.room1, "dummy",
|
self.handler.add_callback(self.room1, "dummy",
|
||||||
"character.db.strength = 9", author=self.char1, valid=True)
|
"character.db.strength = 9", author=self.char1, valid=True)
|
||||||
|
|
||||||
# Note that the second callback isn't valid
|
# Note that the second callback isn't valid
|
||||||
self.assertIn((self.room1, "dummy", 1), self.handler.db.to_valid)
|
self.assertIn((self.room1, "dummy", 1), self.handler.db.to_valid)
|
||||||
|
|
@ -160,16 +161,16 @@ class TestEventHandler(EvenniaTest):
|
||||||
self.char1.db.strength = 10
|
self.char1.db.strength = 10
|
||||||
locals = {"character": self.char1}
|
locals = {"character": self.char1}
|
||||||
self.assertTrue(self.handler.call(
|
self.assertTrue(self.handler.call(
|
||||||
self.room1, "dummy", locals=locals))
|
self.room1, "dummy", locals=locals))
|
||||||
self.assertEqual(self.char1.db.strength, 9)
|
self.assertEqual(self.char1.db.strength, 9)
|
||||||
|
|
||||||
def test_accept(self):
|
def test_accept(self):
|
||||||
"""Accept an callback."""
|
"""Accept an callback."""
|
||||||
# Add 2 callbacks
|
# Add 2 callbacks
|
||||||
self.handler.add_callback(self.room1, "dummy",
|
self.handler.add_callback(self.room1, "dummy",
|
||||||
"character.db.strength = 5", author=self.char1, valid=True)
|
"character.db.strength = 5", author=self.char1, valid=True)
|
||||||
self.handler.add_callback(self.room1, "dummy",
|
self.handler.add_callback(self.room1, "dummy",
|
||||||
"character.db.strength = 8", author=self.char2, valid=False)
|
"character.db.strength = 8", author=self.char2, valid=False)
|
||||||
|
|
||||||
# Note that the second callback isn't valid
|
# Note that the second callback isn't valid
|
||||||
self.assertIn((self.room1, "dummy", 1), self.handler.db.to_valid)
|
self.assertIn((self.room1, "dummy", 1), self.handler.db.to_valid)
|
||||||
|
|
@ -185,7 +186,7 @@ class TestEventHandler(EvenniaTest):
|
||||||
self.char1.db.strength = 10
|
self.char1.db.strength = 10
|
||||||
locals = {"character": self.char1}
|
locals = {"character": self.char1}
|
||||||
self.assertTrue(self.handler.call(
|
self.assertTrue(self.handler.call(
|
||||||
self.room1, "dummy", locals=locals))
|
self.room1, "dummy", locals=locals))
|
||||||
self.assertEqual(self.char1.db.strength, 8)
|
self.assertEqual(self.char1.db.strength, 8)
|
||||||
|
|
||||||
def test_call(self):
|
def test_call(self):
|
||||||
|
|
@ -201,14 +202,14 @@ class TestEventHandler(EvenniaTest):
|
||||||
character.db.health = 0
|
character.db.health = 0
|
||||||
""".strip("\n"))
|
""".strip("\n"))
|
||||||
self.handler.add_callback(self.room1, "dummy", code,
|
self.handler.add_callback(self.room1, "dummy", code,
|
||||||
author=self.char1, valid=True)
|
author=self.char1, valid=True)
|
||||||
|
|
||||||
# Call the dummy callback
|
# Call the dummy callback
|
||||||
self.assertTrue(self.handler.call(
|
self.assertTrue(self.handler.call(
|
||||||
self.room1, "dummy", locals={"character": self.char1}))
|
self.room1, "dummy", locals={"character": self.char1}))
|
||||||
self.assertEqual(self.char1.db.health, 50)
|
self.assertEqual(self.char1.db.health, 50)
|
||||||
self.assertTrue(self.handler.call(
|
self.assertTrue(self.handler.call(
|
||||||
self.room1, "dummy", locals={"character": self.char2}))
|
self.room1, "dummy", locals={"character": self.char2}))
|
||||||
self.assertEqual(self.char2.db.health, 0)
|
self.assertEqual(self.char2.db.health, 0)
|
||||||
|
|
||||||
def test_handler(self):
|
def test_handler(self):
|
||||||
|
|
@ -217,7 +218,7 @@ class TestEventHandler(EvenniaTest):
|
||||||
|
|
||||||
# Add an callback
|
# Add an callback
|
||||||
callback = self.room1.callbacks.add("dummy", "pass", author=self.char1,
|
callback = self.room1.callbacks.add("dummy", "pass", author=self.char1,
|
||||||
valid=True)
|
valid=True)
|
||||||
self.assertEqual(callback.obj, self.room1)
|
self.assertEqual(callback.obj, self.room1)
|
||||||
self.assertEqual(callback.name, "dummy")
|
self.assertEqual(callback.name, "dummy")
|
||||||
self.assertEqual(callback.code, "pass")
|
self.assertEqual(callback.code, "pass")
|
||||||
|
|
@ -227,13 +228,13 @@ class TestEventHandler(EvenniaTest):
|
||||||
|
|
||||||
# Edit this very callback
|
# Edit this very callback
|
||||||
new = self.room1.callbacks.edit("dummy", 0, "character.db.say = True",
|
new = self.room1.callbacks.edit("dummy", 0, "character.db.say = True",
|
||||||
author=self.char1, valid=True)
|
author=self.char1, valid=True)
|
||||||
self.assertIn([new], self.room1.callbacks.all().values())
|
self.assertIn([new], self.room1.callbacks.all().values())
|
||||||
self.assertNotIn([callback], self.room1.callbacks.all().values())
|
self.assertNotIn([callback], self.room1.callbacks.all().values())
|
||||||
|
|
||||||
# Try to call this callback
|
# Try to call this callback
|
||||||
self.assertTrue(self.room1.callbacks.call("dummy",
|
self.assertTrue(self.room1.callbacks.call("dummy",
|
||||||
locals={"character": self.char2}))
|
locals={"character": self.char2}))
|
||||||
self.assertTrue(self.char2.db.say)
|
self.assertTrue(self.char2.db.say)
|
||||||
|
|
||||||
# Delete the callback
|
# Delete the callback
|
||||||
|
|
@ -249,7 +250,7 @@ class TestCmdCallback(CommandTest):
|
||||||
"""Create the callback handler."""
|
"""Create the callback handler."""
|
||||||
super(TestCmdCallback, self).setUp()
|
super(TestCmdCallback, self).setUp()
|
||||||
self.handler = create_script(
|
self.handler = create_script(
|
||||||
"evennia.contrib.ingame_python.scripts.EventHandler")
|
"evennia.contrib.ingame_python.scripts.EventHandler")
|
||||||
|
|
||||||
# Copy old events if necessary
|
# Copy old events if necessary
|
||||||
if OLD_EVENTS:
|
if OLD_EVENTS:
|
||||||
|
|
@ -287,7 +288,7 @@ class TestCmdCallback(CommandTest):
|
||||||
|
|
||||||
# Add some callback
|
# Add some callback
|
||||||
self.handler.add_callback(self.exit, "traverse", "pass",
|
self.handler.add_callback(self.exit, "traverse", "pass",
|
||||||
author=self.char1, valid=True)
|
author=self.char1, valid=True)
|
||||||
|
|
||||||
# Try to obtain more details on a specific callback on exit
|
# Try to obtain more details on a specific callback on exit
|
||||||
table = self.call(CmdCallback(), "out = traverse")
|
table = self.call(CmdCallback(), "out = traverse")
|
||||||
|
|
@ -355,18 +356,18 @@ class TestCmdCallback(CommandTest):
|
||||||
def test_del(self):
|
def test_del(self):
|
||||||
"""Add and remove an callback."""
|
"""Add and remove an callback."""
|
||||||
self.handler.add_callback(self.exit, "traverse", "pass",
|
self.handler.add_callback(self.exit, "traverse", "pass",
|
||||||
author=self.char1, valid=True)
|
author=self.char1, valid=True)
|
||||||
|
|
||||||
# Try to delete the callback
|
# Try to delete the callback
|
||||||
# char2 shouldn't be allowed to do so (that's not HIS callback)
|
# char2 shouldn't be allowed to do so (that's not HIS callback)
|
||||||
self.call(CmdCallback(), "/del out = traverse 1", caller=self.char2)
|
self.call(CmdCallback(), "/del out = traverse 1", caller=self.char2)
|
||||||
self.assertTrue(len(self.handler.get_callbacks(self.exit).get(
|
self.assertTrue(len(self.handler.get_callbacks(self.exit).get(
|
||||||
"traverse", [])) == 1)
|
"traverse", [])) == 1)
|
||||||
|
|
||||||
# Now, char1 should be allowed to delete it
|
# Now, char1 should be allowed to delete it
|
||||||
self.call(CmdCallback(), "/del out = traverse 1")
|
self.call(CmdCallback(), "/del out = traverse 1")
|
||||||
self.assertTrue(len(self.handler.get_callbacks(self.exit).get(
|
self.assertTrue(len(self.handler.get_callbacks(self.exit).get(
|
||||||
"traverse", [])) == 0)
|
"traverse", [])) == 0)
|
||||||
|
|
||||||
def test_lock(self):
|
def test_lock(self):
|
||||||
"""Test the lock of multiple editing."""
|
"""Test the lock of multiple editing."""
|
||||||
|
|
@ -414,7 +415,7 @@ class TestDefaultCallbacks(CommandTest):
|
||||||
"""Create the callback handler."""
|
"""Create the callback handler."""
|
||||||
super(TestDefaultCallbacks, self).setUp()
|
super(TestDefaultCallbacks, self).setUp()
|
||||||
self.handler = create_script(
|
self.handler = create_script(
|
||||||
"evennia.contrib.ingame_python.scripts.EventHandler")
|
"evennia.contrib.ingame_python.scripts.EventHandler")
|
||||||
|
|
||||||
# Copy old events if necessary
|
# Copy old events if necessary
|
||||||
if OLD_EVENTS:
|
if OLD_EVENTS:
|
||||||
|
|
@ -450,7 +451,7 @@ class TestDefaultCallbacks(CommandTest):
|
||||||
|
|
||||||
# Try the can_traverse callback
|
# Try the can_traverse callback
|
||||||
self.handler.add_callback(self.exit, "can_traverse", code,
|
self.handler.add_callback(self.exit, "can_traverse", code,
|
||||||
author=self.char1, valid=True)
|
author=self.char1, valid=True)
|
||||||
|
|
||||||
# Have char1 move through the exit
|
# Have char1 move through the exit
|
||||||
self.call(ExitCommand(), "", "You can leave.", obj=self.exit)
|
self.call(ExitCommand(), "", "You can leave.", obj=self.exit)
|
||||||
|
|
@ -458,13 +459,13 @@ class TestDefaultCallbacks(CommandTest):
|
||||||
|
|
||||||
# Have char2 move through this exit
|
# Have char2 move through this exit
|
||||||
self.call(ExitCommand(), "", "You cannot leave.", obj=self.exit,
|
self.call(ExitCommand(), "", "You cannot leave.", obj=self.exit,
|
||||||
caller=self.char2)
|
caller=self.char2)
|
||||||
self.assertIs(self.char2.location, self.room1)
|
self.assertIs(self.char2.location, self.room1)
|
||||||
|
|
||||||
# Try the traverse callback
|
# Try the traverse callback
|
||||||
self.handler.del_callback(self.exit, "can_traverse", 0)
|
self.handler.del_callback(self.exit, "can_traverse", 0)
|
||||||
self.handler.add_callback(self.exit, "traverse", "character.msg('Fine!')",
|
self.handler.add_callback(self.exit, "traverse", "character.msg('Fine!')",
|
||||||
author=self.char1, valid=True)
|
author=self.char1, valid=True)
|
||||||
|
|
||||||
# Have char2 move through the exit
|
# Have char2 move through the exit
|
||||||
self.call(ExitCommand(), "", obj=self.exit, caller=self.char2)
|
self.call(ExitCommand(), "", obj=self.exit, caller=self.char2)
|
||||||
|
|
@ -478,15 +479,15 @@ class TestDefaultCallbacks(CommandTest):
|
||||||
# Test msg_arrive and msg_leave
|
# Test msg_arrive and msg_leave
|
||||||
code = 'message = "{character} goes out."'
|
code = 'message = "{character} goes out."'
|
||||||
self.handler.add_callback(self.exit, "msg_leave", code,
|
self.handler.add_callback(self.exit, "msg_leave", code,
|
||||||
author=self.char1, valid=True)
|
author=self.char1, valid=True)
|
||||||
|
|
||||||
# Have char1 move through the exit
|
# Have char1 move through the exit
|
||||||
old_msg = self.char2.msg
|
old_msg = self.char2.msg
|
||||||
try:
|
try:
|
||||||
self.char2.msg = Mock()
|
self.char2.msg = Mock()
|
||||||
self.call(ExitCommand(), "", obj=self.exit)
|
self.call(ExitCommand(), "", obj=self.exit)
|
||||||
stored_msg = [args[0] if args and args[0] else kwargs.get("text",utils.to_str(kwargs, force_string=True))
|
stored_msg = [args[0] if args and args[0] else kwargs.get("text", utils.to_str(kwargs, force_string=True))
|
||||||
for name, args, kwargs in self.char2.msg.mock_calls]
|
for name, args, kwargs in self.char2.msg.mock_calls]
|
||||||
# Get the first element of a tuple if msg received a tuple instead of a string
|
# Get the first element of a tuple if msg received a tuple instead of a string
|
||||||
stored_msg = [smsg[0] if isinstance(smsg, tuple) else smsg for smsg in stored_msg]
|
stored_msg = [smsg[0] if isinstance(smsg, tuple) else smsg for smsg in stored_msg]
|
||||||
returned_msg = ansi.parse_ansi("\n".join(stored_msg), strip_ansi=True)
|
returned_msg = ansi.parse_ansi("\n".join(stored_msg), strip_ansi=True)
|
||||||
|
|
@ -496,18 +497,18 @@ class TestDefaultCallbacks(CommandTest):
|
||||||
|
|
||||||
# Create a return exit
|
# Create a return exit
|
||||||
back = create_object("evennia.objects.objects.DefaultExit",
|
back = create_object("evennia.objects.objects.DefaultExit",
|
||||||
key="in", location=self.room2, destination=self.room1)
|
key="in", location=self.room2, destination=self.room1)
|
||||||
code = 'message = "{character} goes in."'
|
code = 'message = "{character} goes in."'
|
||||||
self.handler.add_callback(self.exit, "msg_arrive", code,
|
self.handler.add_callback(self.exit, "msg_arrive", code,
|
||||||
author=self.char1, valid=True)
|
author=self.char1, valid=True)
|
||||||
|
|
||||||
# Have char1 move through the exit
|
# Have char1 move through the exit
|
||||||
old_msg = self.char2.msg
|
old_msg = self.char2.msg
|
||||||
try:
|
try:
|
||||||
self.char2.msg = Mock()
|
self.char2.msg = Mock()
|
||||||
self.call(ExitCommand(), "", obj=back)
|
self.call(ExitCommand(), "", obj=back)
|
||||||
stored_msg = [args[0] if args and args[0] else kwargs.get("text",utils.to_str(kwargs, force_string=True))
|
stored_msg = [args[0] if args and args[0] else kwargs.get("text", utils.to_str(kwargs, force_string=True))
|
||||||
for name, args, kwargs in self.char2.msg.mock_calls]
|
for name, args, kwargs in self.char2.msg.mock_calls]
|
||||||
# Get the first element of a tuple if msg received a tuple instead of a string
|
# Get the first element of a tuple if msg received a tuple instead of a string
|
||||||
stored_msg = [smsg[0] if isinstance(smsg, tuple) else smsg for smsg in stored_msg]
|
stored_msg = [smsg[0] if isinstance(smsg, tuple) else smsg for smsg in stored_msg]
|
||||||
returned_msg = ansi.parse_ansi("\n".join(stored_msg), strip_ansi=True)
|
returned_msg = ansi.parse_ansi("\n".join(stored_msg), strip_ansi=True)
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ This event is called when another character arrives in the location
|
||||||
where the current character is. For instance, a puppeted character
|
where the current character is. For instance, a puppeted character
|
||||||
arrives in the shop of a shopkeeper (assuming the shopkeeper is
|
arrives in the shop of a shopkeeper (assuming the shopkeeper is
|
||||||
a character). As its name suggests, this event can be very useful
|
a character). As its name suggests, this event can be very useful
|
||||||
to have NPC greeting one another, or players, who come to visit.
|
to have NPC greeting one another, or accounts, who come to visit.
|
||||||
|
|
||||||
Variables you can use in this event:
|
Variables you can use in this event:
|
||||||
character: the character connected to this event.
|
character: the character connected to this event.
|
||||||
|
|
@ -100,9 +100,9 @@ Variables you can use in this event:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
CHARACTER_PUPPETED = """
|
CHARACTER_PUPPETED = """
|
||||||
When the character has been puppeted by a player.
|
When the character has been puppeted by an account.
|
||||||
This event is called when a player has just puppeted this character.
|
This event is called when an account has just puppeted this character.
|
||||||
This can commonly happen when a player connects onto this character,
|
This can commonly happen when an account connects onto this character,
|
||||||
or when puppeting to a NPC or free character.
|
or when puppeting to a NPC or free character.
|
||||||
|
|
||||||
Variables you can use in this event:
|
Variables you can use in this event:
|
||||||
|
|
@ -151,14 +151,15 @@ Variables you can use in this event:
|
||||||
|
|
||||||
CHARACTER_UNPUPPETED = """
|
CHARACTER_UNPUPPETED = """
|
||||||
When the character is about to be un-puppeted.
|
When the character is about to be un-puppeted.
|
||||||
This event is called when a player is about to un-puppet the
|
This event is called when an account is about to un-puppet the
|
||||||
character, which can happen if the player is disconnecting or
|
character, which can happen if the account is disconnecting or
|
||||||
changing puppets.
|
changing puppets.
|
||||||
|
|
||||||
Variables you can use in this event:
|
Variables you can use in this event:
|
||||||
character: the character connected to this event.
|
character: the character connected to this event.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@register_events
|
@register_events
|
||||||
class EventCharacter(DefaultCharacter):
|
class EventCharacter(DefaultCharacter):
|
||||||
|
|
||||||
|
|
@ -208,12 +209,12 @@ class EventCharacter(DefaultCharacter):
|
||||||
exits = [o for o in location.contents if o.location is location and o.destination is destination]
|
exits = [o for o in location.contents if o.location is location and o.destination is destination]
|
||||||
mapping = mapping or {}
|
mapping = mapping or {}
|
||||||
mapping.update({
|
mapping.update({
|
||||||
"character": self,
|
"character": self,
|
||||||
})
|
})
|
||||||
|
|
||||||
if exits:
|
if exits:
|
||||||
exits[0].callbacks.call("msg_leave", self, exits[0],
|
exits[0].callbacks.call("msg_leave", self, exits[0],
|
||||||
location, destination, string, mapping)
|
location, destination, string, mapping)
|
||||||
string = exits[0].callbacks.get_variable("message")
|
string = exits[0].callbacks.get_variable("message")
|
||||||
mapping = exits[0].callbacks.get_variable("mapping")
|
mapping = exits[0].callbacks.get_variable("mapping")
|
||||||
|
|
||||||
|
|
@ -244,8 +245,8 @@ class EventCharacter(DefaultCharacter):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not source_location and self.location.has_player:
|
if not source_location and self.location.has_account:
|
||||||
# This was created from nowhere and added to a player's
|
# This was created from nowhere and added to an account's
|
||||||
# inventory; it's probably the result of a create command.
|
# inventory; it's probably the result of a create command.
|
||||||
string = "You now have %s in your possession." % self.get_display_name(self.location)
|
string = "You now have %s in your possession." % self.get_display_name(self.location)
|
||||||
self.location.msg(string)
|
self.location.msg(string)
|
||||||
|
|
@ -261,14 +262,14 @@ class EventCharacter(DefaultCharacter):
|
||||||
exits = []
|
exits = []
|
||||||
mapping = mapping or {}
|
mapping = mapping or {}
|
||||||
mapping.update({
|
mapping.update({
|
||||||
"character": self,
|
"character": self,
|
||||||
})
|
})
|
||||||
|
|
||||||
if origin:
|
if origin:
|
||||||
exits = [o for o in destination.contents if o.location is destination and o.destination is origin]
|
exits = [o for o in destination.contents if o.location is destination and o.destination is origin]
|
||||||
if exits:
|
if exits:
|
||||||
exits[0].callbacks.call("msg_arrive", self, exits[0],
|
exits[0].callbacks.call("msg_arrive", self, exits[0],
|
||||||
origin, destination, string, mapping)
|
origin, destination, string, mapping)
|
||||||
string = exits[0].callbacks.get_variable("message")
|
string = exits[0].callbacks.get_variable("message")
|
||||||
mapping = exits[0].callbacks.get_variable("mapping")
|
mapping = exits[0].callbacks.get_variable("mapping")
|
||||||
|
|
||||||
|
|
@ -299,7 +300,7 @@ class EventCharacter(DefaultCharacter):
|
||||||
Room = DefaultRoom
|
Room = DefaultRoom
|
||||||
if isinstance(origin, Room) and isinstance(destination, Room):
|
if isinstance(origin, Room) and isinstance(destination, Room):
|
||||||
can = self.callbacks.call("can_move", self,
|
can = self.callbacks.call("can_move", self,
|
||||||
origin, destination)
|
origin, destination)
|
||||||
if can:
|
if can:
|
||||||
can = origin.callbacks.call("can_move", self, origin)
|
can = origin.callbacks.call("can_move", self, origin)
|
||||||
if can:
|
if can:
|
||||||
|
|
@ -357,11 +358,11 @@ class EventCharacter(DefaultCharacter):
|
||||||
def at_post_puppet(self):
|
def at_post_puppet(self):
|
||||||
"""
|
"""
|
||||||
Called just after puppeting has been completed and all
|
Called just after puppeting has been completed and all
|
||||||
Player<->Object links have been established.
|
Account<->Object links have been established.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
You can use `self.player` and `self.sessions.get()` to get
|
You can use `self.account` and `self.sessions.get()` to get
|
||||||
player and sessions at this point; the last entry in the
|
account and sessions at this point; the last entry in the
|
||||||
list from `self.sessions.get()` is the latest Session
|
list from `self.sessions.get()` is the latest Session
|
||||||
puppeting this Object.
|
puppeting this Object.
|
||||||
|
|
||||||
|
|
@ -378,11 +379,11 @@ class EventCharacter(DefaultCharacter):
|
||||||
def at_pre_unpuppet(self):
|
def at_pre_unpuppet(self):
|
||||||
"""
|
"""
|
||||||
Called just before beginning to un-connect a puppeting from
|
Called just before beginning to un-connect a puppeting from
|
||||||
this Player.
|
this Account.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
You can use `self.player` and `self.sessions.get()` to get
|
You can use `self.account` and `self.sessions.get()` to get
|
||||||
player and sessions at this point; the last entry in the
|
account and sessions at this point; the last entry in the
|
||||||
list from `self.sessions.get()` is the latest Session
|
list from `self.sessions.get()` is the latest Session
|
||||||
puppeting this Object.
|
puppeting this Object.
|
||||||
|
|
||||||
|
|
@ -396,6 +397,104 @@ class EventCharacter(DefaultCharacter):
|
||||||
|
|
||||||
super(EventCharacter, self).at_pre_unpuppet()
|
super(EventCharacter, self).at_pre_unpuppet()
|
||||||
|
|
||||||
|
def at_before_say(self, message, **kwargs):
|
||||||
|
"""
|
||||||
|
Before the object says something.
|
||||||
|
|
||||||
|
This hook is by default used by the 'say' and 'whisper'
|
||||||
|
commands as used by this command it is called before the text
|
||||||
|
is said/whispered and can be used to customize the outgoing
|
||||||
|
text from the object. Returning `None` aborts the command.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message (str): The suggested say/whisper text spoken by self.
|
||||||
|
Kwargs:
|
||||||
|
whisper (bool): If True, this is a whisper rather than
|
||||||
|
a say. This is sent by the whisper command by default.
|
||||||
|
Other verbal commands could use this hook in similar
|
||||||
|
ways.
|
||||||
|
receiver (Object): If set, this is a target for the say/whisper.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
message (str): The (possibly modified) text to be spoken.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# First, try the location
|
||||||
|
location = getattr(self, "location", None)
|
||||||
|
location = location if location and inherits_from(location, "evennia.objects.objects.DefaultRoom") else None
|
||||||
|
if location and not kwargs.get("whisper", False):
|
||||||
|
allow = location.callbacks.call("can_say", self, location, message, parameters=message)
|
||||||
|
message = location.callbacks.get_variable("message")
|
||||||
|
if not allow or not message:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Browse all the room's other characters
|
||||||
|
for obj in location.contents:
|
||||||
|
if obj is self or not inherits_from(obj, "evennia.objects.objects.DefaultCharacter"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
allow = obj.callbacks.call("can_say", self, obj, message, parameters=message)
|
||||||
|
message = obj.callbacks.get_variable("message")
|
||||||
|
if not allow or not message:
|
||||||
|
return
|
||||||
|
|
||||||
|
return message
|
||||||
|
|
||||||
|
def at_say(self, message, **kwargs):
|
||||||
|
"""
|
||||||
|
Display the actual say (or whisper) of self.
|
||||||
|
|
||||||
|
This hook should display the actual say/whisper of the object in its
|
||||||
|
location. It should both alert the object (self) and its
|
||||||
|
location that some text is spoken. The overriding of messages or
|
||||||
|
`mapping` allows for simple customization of the hook without
|
||||||
|
re-writing it completely.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message (str): The text to be conveyed by self.
|
||||||
|
msg_self (str, optional): The message to echo to self.
|
||||||
|
msg_location (str, optional): The message to echo to self's location.
|
||||||
|
receiver (Object, optional): An eventual receiver of the message
|
||||||
|
(by default only used by whispers).
|
||||||
|
msg_receiver(str, optional): Specific message for receiver only.
|
||||||
|
mapping (dict, optional): Additional mapping in messages.
|
||||||
|
Kwargs:
|
||||||
|
whisper (bool): If this is a whisper rather than a say. Kwargs
|
||||||
|
can be used by other verbal commands in a similar way.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
Messages can contain {} markers, which must
|
||||||
|
If used, `msg_self`, `msg_receiver` and `msg_location` should contain
|
||||||
|
references to other objects between braces, the way `location.msg_contents`
|
||||||
|
would allow. For instance:
|
||||||
|
msg_self = 'You say: "{speech}"'
|
||||||
|
msg_location = '{object} says: "{speech}"'
|
||||||
|
msg_receiver = '{object} whispers: "{speech}"'
|
||||||
|
|
||||||
|
The following mappings can be used in both messages:
|
||||||
|
object: the object speaking.
|
||||||
|
location: the location where object is.
|
||||||
|
speech: the text spoken by self.
|
||||||
|
|
||||||
|
You can use additional mappings if you want to add other
|
||||||
|
information in your messages.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
super(EventCharacter, self).at_say(message, **kwargs)
|
||||||
|
location = getattr(self, "location", None)
|
||||||
|
location = location if location and inherits_from(location, "evennia.objects.objects.DefaultRoom") else None
|
||||||
|
|
||||||
|
if location and not kwargs.get("whisper", False):
|
||||||
|
location.callbacks.call("say", self, location, message,
|
||||||
|
parameters=message)
|
||||||
|
|
||||||
|
# Call the other characters' "say" event
|
||||||
|
presents = [obj for obj in location.contents if obj is not self and inherits_from(obj, "evennia.objects.objects.DefaultCharacter")]
|
||||||
|
for present in presents:
|
||||||
|
present.callbacks.call("say", self, present, message, parameters=message)
|
||||||
|
|
||||||
|
|
||||||
# Exit help
|
# Exit help
|
||||||
EXIT_CAN_TRAVERSE = """
|
EXIT_CAN_TRAVERSE = """
|
||||||
|
|
@ -489,6 +588,7 @@ Variables you can use in this event:
|
||||||
destination: the character's location after moving.
|
destination: the character's location after moving.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@register_events
|
@register_events
|
||||||
class EventExit(DefaultExit):
|
class EventExit(DefaultExit):
|
||||||
|
|
||||||
|
|
@ -520,7 +620,7 @@ class EventExit(DefaultExit):
|
||||||
is_character = inherits_from(traversing_object, DefaultCharacter)
|
is_character = inherits_from(traversing_object, DefaultCharacter)
|
||||||
if is_character:
|
if is_character:
|
||||||
allow = self.callbacks.call("can_traverse", traversing_object,
|
allow = self.callbacks.call("can_traverse", traversing_object,
|
||||||
self, self.location)
|
self, self.location)
|
||||||
if not allow:
|
if not allow:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -529,7 +629,7 @@ class EventExit(DefaultExit):
|
||||||
# After traversing
|
# After traversing
|
||||||
if is_character:
|
if is_character:
|
||||||
self.callbacks.call("traverse", traversing_object,
|
self.callbacks.call("traverse", traversing_object,
|
||||||
self, self.location, self.destination)
|
self, self.location, self.destination)
|
||||||
|
|
||||||
|
|
||||||
# Object help
|
# Object help
|
||||||
|
|
@ -573,6 +673,7 @@ Variables you can use in this event:
|
||||||
object: the object connected to this event.
|
object: the object connected to this event.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@register_events
|
@register_events
|
||||||
class EventObject(DefaultObject):
|
class EventObject(DefaultObject):
|
||||||
|
|
||||||
|
|
@ -621,6 +722,7 @@ class EventObject(DefaultObject):
|
||||||
super(EventObject, self).at_drop(dropper)
|
super(EventObject, self).at_drop(dropper)
|
||||||
self.callbacks.call("drop", dropper, self)
|
self.callbacks.call("drop", dropper, self)
|
||||||
|
|
||||||
|
|
||||||
# Room help
|
# Room help
|
||||||
ROOM_CAN_DELETE = """
|
ROOM_CAN_DELETE = """
|
||||||
Can the room be deleted?
|
Can the room be deleted?
|
||||||
|
|
@ -683,7 +785,7 @@ Variables you can use in this event:
|
||||||
ROOM_PUPPETED_IN = """
|
ROOM_PUPPETED_IN = """
|
||||||
After the character has been puppeted in this room.
|
After the character has been puppeted in this room.
|
||||||
This event is called after a character has been puppeted in this
|
This event is called after a character has been puppeted in this
|
||||||
room. This can happen when a player, having connected, begins
|
room. This can happen when an account, having connected, begins
|
||||||
to puppet a character. The character's location at this point,
|
to puppet a character. The character's location at this point,
|
||||||
if it's a room, will see this event fire.
|
if it's a room, will see this event fire.
|
||||||
|
|
||||||
|
|
@ -733,7 +835,7 @@ Variables you can use in this event:
|
||||||
ROOM_UNPUPPETED_IN = """
|
ROOM_UNPUPPETED_IN = """
|
||||||
Before the character is un-puppeted in this room.
|
Before the character is un-puppeted in this room.
|
||||||
This event is called before a character is un-puppeted in this
|
This event is called before a character is un-puppeted in this
|
||||||
room. This can happen when a player, puppeting a character, is
|
room. This can happen when an account, puppeting a character, is
|
||||||
disconnecting. The character's location at this point, if it's a
|
disconnecting. The character's location at this point, if it's a
|
||||||
room, will see this event fire.
|
room, will see this event fire.
|
||||||
|
|
||||||
|
|
@ -742,6 +844,7 @@ Variables you can use in this event:
|
||||||
room: the room connected to this event.
|
room: the room connected to this event.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@register_events
|
@register_events
|
||||||
class EventRoom(DefaultRoom):
|
class EventRoom(DefaultRoom):
|
||||||
|
|
||||||
|
|
@ -771,50 +874,3 @@ class EventRoom(DefaultRoom):
|
||||||
|
|
||||||
self.callbacks.call("delete", self)
|
self.callbacks.call("delete", self)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def at_say(self, speaker, message):
|
|
||||||
"""
|
|
||||||
Called on this object if an object inside this object speaks.
|
|
||||||
The string returned from this method is the final form of the
|
|
||||||
speech.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
speaker (Object): The object speaking.
|
|
||||||
message (str): The words spoken.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The message to be said (str) or None.
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
You should not need to add things like 'you say: ' or
|
|
||||||
similar here, that should be handled by the say command before
|
|
||||||
this.
|
|
||||||
|
|
||||||
"""
|
|
||||||
allow = self.callbacks.call("can_say", speaker, self, message,
|
|
||||||
parameters=message)
|
|
||||||
if not allow:
|
|
||||||
return
|
|
||||||
|
|
||||||
message = self.callbacks.get_variable("message")
|
|
||||||
|
|
||||||
# Call the event "can_say" of other characters in the location
|
|
||||||
for present in [o for o in self.contents if isinstance(
|
|
||||||
o, DefaultCharacter) and o is not speaker]:
|
|
||||||
allow = present.callbacks.call("can_say", speaker, present,
|
|
||||||
message, parameters=message)
|
|
||||||
if not allow:
|
|
||||||
return
|
|
||||||
|
|
||||||
message = present.callbacks.get_variable("message")
|
|
||||||
|
|
||||||
# We force the next event to be called after the message
|
|
||||||
# This will have to change when the Evennia API adds new hooks
|
|
||||||
delay(0, self.callbacks.call, "say", speaker, self, message,
|
|
||||||
parameters=message)
|
|
||||||
for present in [o for o in self.contents if isinstance(
|
|
||||||
o, DefaultCharacter) and o is not speaker]:
|
|
||||||
delay(0, present.callbacks.call, "say", speaker, present, message,
|
|
||||||
parameters=message)
|
|
||||||
|
|
||||||
return message
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ from evennia.contrib.custom_gametime import real_seconds_until as custom_rsu
|
||||||
# Temporary storage for events waiting for the script to be started
|
# Temporary storage for events waiting for the script to be started
|
||||||
EVENTS = []
|
EVENTS = []
|
||||||
|
|
||||||
|
|
||||||
def get_event_handler():
|
def get_event_handler():
|
||||||
"""Return the event handler or None."""
|
"""Return the event handler or None."""
|
||||||
try:
|
try:
|
||||||
|
|
@ -30,6 +31,7 @@ def get_event_handler():
|
||||||
|
|
||||||
return script
|
return script
|
||||||
|
|
||||||
|
|
||||||
def register_events(path_or_typeclass):
|
def register_events(path_or_typeclass):
|
||||||
"""
|
"""
|
||||||
Register the events in this typeclass.
|
Register the events in this typeclass.
|
||||||
|
|
@ -84,6 +86,8 @@ def register_events(path_or_typeclass):
|
||||||
return typeclass
|
return typeclass
|
||||||
|
|
||||||
# Custom callbacks for specific event types
|
# Custom callbacks for specific event types
|
||||||
|
|
||||||
|
|
||||||
def get_next_wait(format):
|
def get_next_wait(format):
|
||||||
"""
|
"""
|
||||||
Get the length of time in seconds before format.
|
Get the length of time in seconds before format.
|
||||||
|
|
@ -104,8 +108,8 @@ def get_next_wait(format):
|
||||||
"""
|
"""
|
||||||
calendar = getattr(settings, "EVENTS_CALENDAR", None)
|
calendar = getattr(settings, "EVENTS_CALENDAR", None)
|
||||||
if calendar is None:
|
if calendar is None:
|
||||||
logger.log_err("A time-related event has been set whereas " \
|
logger.log_err("A time-related event has been set whereas "
|
||||||
"the gametime calendar has not been set in the settings.")
|
"the gametime calendar has not been set in the settings.")
|
||||||
return
|
return
|
||||||
elif calendar == "standard":
|
elif calendar == "standard":
|
||||||
rsu = standard_rsu
|
rsu = standard_rsu
|
||||||
|
|
@ -131,8 +135,8 @@ def get_next_wait(format):
|
||||||
break
|
break
|
||||||
|
|
||||||
if not piece.isdigit():
|
if not piece.isdigit():
|
||||||
logger.log_trace("The time specified '{}' in {} isn't " \
|
logger.log_trace("The time specified '{}' in {} isn't "
|
||||||
"a valid number".format(piece, format))
|
"a valid number".format(piece, format))
|
||||||
return
|
return
|
||||||
|
|
||||||
# Convert the piece to int
|
# Convert the piece to int
|
||||||
|
|
@ -154,6 +158,7 @@ def get_next_wait(format):
|
||||||
usual = gametime_to_realtime(**kwargs)
|
usual = gametime_to_realtime(**kwargs)
|
||||||
return until, usual, details
|
return until, usual, details
|
||||||
|
|
||||||
|
|
||||||
def time_event(obj, event_name, number, parameters):
|
def time_event(obj, event_name, number, parameters):
|
||||||
"""
|
"""
|
||||||
Create a time-related event.
|
Create a time-related event.
|
||||||
|
|
@ -173,6 +178,7 @@ def time_event(obj, event_name, number, parameters):
|
||||||
script.db.number = number
|
script.db.number = number
|
||||||
script.ndb.usual = usual
|
script.ndb.usual = usual
|
||||||
|
|
||||||
|
|
||||||
def keyword_event(callbacks, parameters):
|
def keyword_event(callbacks, parameters):
|
||||||
"""
|
"""
|
||||||
Custom call for events with keywords (like push, or pull, or turn...).
|
Custom call for events with keywords (like push, or pull, or turn...).
|
||||||
|
|
@ -201,6 +207,7 @@ def keyword_event(callbacks, parameters):
|
||||||
|
|
||||||
return to_call
|
return to_call
|
||||||
|
|
||||||
|
|
||||||
def phrase_event(callbacks, parameters):
|
def phrase_event(callbacks, parameters):
|
||||||
"""
|
"""
|
||||||
Custom call for events with keywords in sentences (like say or whisper).
|
Custom call for events with keywords in sentences (like say or whisper).
|
||||||
|
|
@ -236,6 +243,7 @@ def phrase_event(callbacks, parameters):
|
||||||
|
|
||||||
return to_call
|
return to_call
|
||||||
|
|
||||||
|
|
||||||
class InterruptEvent(RuntimeError):
|
class InterruptEvent(RuntimeError):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@ A simple Brandymail style @mail system that uses the Msg class from Evennia Core
|
||||||
|
|
||||||
Installation:
|
Installation:
|
||||||
import CmdMail from this module (from evennia.contrib.mail import CmdMail),
|
import CmdMail from this module (from evennia.contrib.mail import CmdMail),
|
||||||
and add into the default Player or Character command set (self.add(CmdMail)).
|
and add into the default Account or Character command set (self.add(CmdMail)).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from evennia import ObjectDB, PlayerDB
|
from evennia import ObjectDB, AccountDB
|
||||||
from evennia import default_cmds
|
from evennia import default_cmds
|
||||||
from evennia.utils import create, evtable, make_iter
|
from evennia.utils import create, evtable, make_iter
|
||||||
from evennia.comms.models import Msg
|
from evennia.comms.models import Msg
|
||||||
|
|
@ -22,22 +22,23 @@ _HEAD_CHAR = "|015-|n"
|
||||||
_SUB_HEAD_CHAR = "-"
|
_SUB_HEAD_CHAR = "-"
|
||||||
_WIDTH = 78
|
_WIDTH = 78
|
||||||
|
|
||||||
|
|
||||||
class CmdMail(default_cmds.MuxCommand):
|
class CmdMail(default_cmds.MuxCommand):
|
||||||
"""
|
"""
|
||||||
Commands that allow either IC or OOC communications
|
Commands that allow either IC or OOC communications
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@mail - Displays all the mail a player has in their mailbox
|
@mail - Displays all the mail an account has in their mailbox
|
||||||
|
|
||||||
@mail <#> - Displays a specific message
|
@mail <#> - Displays a specific message
|
||||||
|
|
||||||
@mail <players>=<subject>/<message>
|
@mail <accounts>=<subject>/<message>
|
||||||
- Sends a message to the comma separated list of players.
|
- Sends a message to the comma separated list of accounts.
|
||||||
|
|
||||||
@mail/delete <#> - Deletes a specific message
|
@mail/delete <#> - Deletes a specific message
|
||||||
|
|
||||||
@mail/forward <player list>=<#>[/<Message>]
|
@mail/forward <account list>=<#>[/<Message>]
|
||||||
- Forwards an existing message to the specified list of players,
|
- Forwards an existing message to the specified list of accounts,
|
||||||
original message is delivered with optional Message prepended.
|
original message is delivered with optional Message prepended.
|
||||||
|
|
||||||
@mail/reply <#>=<message>
|
@mail/reply <#>=<message>
|
||||||
|
|
@ -54,6 +55,7 @@ class CmdMail(default_cmds.MuxCommand):
|
||||||
@mail/delete 6
|
@mail/delete 6
|
||||||
@mail/forward feend78 Griatch=4/You guys should read this.
|
@mail/forward feend78 Griatch=4/You guys should read this.
|
||||||
@mail/reply 9=Thanks for the info!
|
@mail/reply 9=Thanks for the info!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
key = "@mail"
|
key = "@mail"
|
||||||
aliases = ["mail"]
|
aliases = ["mail"]
|
||||||
|
|
@ -65,7 +67,7 @@ class CmdMail(default_cmds.MuxCommand):
|
||||||
Search a list of targets of the same type as caller.
|
Search a list of targets of the same type as caller.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
caller (Object or Player): The type of object to search.
|
caller (Object or Account): The type of object to search.
|
||||||
namelist (list): List of strings for objects to search for.
|
namelist (list): List of strings for objects to search for.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -73,10 +75,10 @@ class CmdMail(default_cmds.MuxCommand):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
nameregex = r"|".join(r"^%s$" % re.escape(name) for name in make_iter(namelist))
|
nameregex = r"|".join(r"^%s$" % re.escape(name) for name in make_iter(namelist))
|
||||||
if hasattr(self.caller, "player") and self.caller.player:
|
if hasattr(self.caller, "account") and self.caller.account:
|
||||||
matches = list(ObjectDB.objects.filter(db_key__iregex=nameregex))
|
matches = list(ObjectDB.objects.filter(db_key__iregex=nameregex))
|
||||||
else:
|
else:
|
||||||
matches = list(PlayerDB.objects.filter(username__iregex=nameregex))
|
matches = list(AccountDB.objects.filter(username__iregex=nameregex))
|
||||||
return matches
|
return matches
|
||||||
|
|
||||||
def get_all_mail(self):
|
def get_all_mail(self):
|
||||||
|
|
@ -85,14 +87,15 @@ class CmdMail(default_cmds.MuxCommand):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
messages (list): list of Msg objects.
|
messages (list): list of Msg objects.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# mail_messages = Msg.objects.get_by_tag(category="mail")
|
# mail_messages = Msg.objects.get_by_tag(category="mail")
|
||||||
# messages = []
|
# messages = []
|
||||||
try:
|
try:
|
||||||
player = self.caller.player
|
account = self.caller.account
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
player = self.caller
|
account = self.caller
|
||||||
messages = Msg.objects.get_by_tag(category="mail", raw_queryset=True).filter(db_receivers_players=player)
|
messages = Msg.objects.get_by_tag(category="mail").filter(db_receivers_accounts=account)
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
def send_mail(self, recipients, subject, message, caller):
|
def send_mail(self, recipients, subject, message, caller):
|
||||||
|
|
@ -100,10 +103,11 @@ class CmdMail(default_cmds.MuxCommand):
|
||||||
Function for sending new mail. Also useful for sending notifications from objects or systems.
|
Function for sending new mail. Also useful for sending notifications from objects or systems.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
recipients (list): list of Player or character objects to receive the newly created mails.
|
recipients (list): list of Account or character objects to receive the newly created mails.
|
||||||
subject (str): The header or subject of the message to be delivered.
|
subject (str): The header or subject of the message to be delivered.
|
||||||
message (str): The body of the message being sent.
|
message (str): The body of the message being sent.
|
||||||
caller (obj): The object (or Player or Character) that is sending the message.
|
caller (obj): The object (or Account or Character) that is sending the message.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for recipient in recipients:
|
for recipient in recipients:
|
||||||
recipient.msg("You have received a new @mail from %s" % caller)
|
recipient.msg("You have received a new @mail from %s" % caller)
|
||||||
|
|
@ -114,7 +118,7 @@ class CmdMail(default_cmds.MuxCommand):
|
||||||
caller.msg("You sent your message.")
|
caller.msg("You sent your message.")
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
caller.msg("No valid players found. Cannot send message.")
|
caller.msg("No valid accounts found. Cannot send message.")
|
||||||
return
|
return
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
@ -129,7 +133,8 @@ class CmdMail(default_cmds.MuxCommand):
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
all_mail = self.get_all_mail()
|
all_mail = self.get_all_mail()
|
||||||
mind = int(self.lhs) - 1
|
mind_max = max(0, all_mail.count() - 1)
|
||||||
|
mind = max(0, min(mind_max, int(self.lhs) - 1))
|
||||||
if all_mail[mind]:
|
if all_mail[mind]:
|
||||||
all_mail[mind].delete()
|
all_mail[mind].delete()
|
||||||
self.caller.msg("Message %s deleted" % self.lhs)
|
self.caller.msg("Message %s deleted" % self.lhs)
|
||||||
|
|
@ -142,16 +147,17 @@ class CmdMail(default_cmds.MuxCommand):
|
||||||
elif "forward" in self.switches:
|
elif "forward" in self.switches:
|
||||||
try:
|
try:
|
||||||
if not self.rhs:
|
if not self.rhs:
|
||||||
self.caller.msg("Cannot forward a message without a player list. Please try again.")
|
self.caller.msg("Cannot forward a message without an account list. Please try again.")
|
||||||
return
|
return
|
||||||
elif not self.lhs:
|
elif not self.lhs:
|
||||||
self.caller.msg("You must define a message to forward.")
|
self.caller.msg("You must define a message to forward.")
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
all_mail = self.get_all_mail()
|
all_mail = self.get_all_mail()
|
||||||
|
mind_max = max(0, all_mail.count() - 1)
|
||||||
if "/" in self.rhs:
|
if "/" in self.rhs:
|
||||||
message_number, message = self.rhs.split("/")
|
message_number, message = self.rhs.split("/", 1)
|
||||||
mind = int(message_number) - 1
|
mind = max(0, min(mind_max, int(message_number) - 1))
|
||||||
|
|
||||||
if all_mail[mind]:
|
if all_mail[mind]:
|
||||||
old_message = all_mail[mind]
|
old_message = all_mail[mind]
|
||||||
|
|
@ -163,7 +169,7 @@ class CmdMail(default_cmds.MuxCommand):
|
||||||
else:
|
else:
|
||||||
raise IndexError
|
raise IndexError
|
||||||
else:
|
else:
|
||||||
mind = int(self.rhs) - 1
|
mind = max(0, min(mind_max, int(self.rhs) - 1))
|
||||||
if all_mail[mind]:
|
if all_mail[mind]:
|
||||||
old_message = all_mail[mind]
|
old_message = all_mail[mind]
|
||||||
self.send_mail(self.search_targets(self.lhslist), "FWD: " + old_message.header,
|
self.send_mail(self.search_targets(self.lhslist), "FWD: " + old_message.header,
|
||||||
|
|
@ -176,7 +182,7 @@ class CmdMail(default_cmds.MuxCommand):
|
||||||
except IndexError:
|
except IndexError:
|
||||||
self.caller.msg("Message does not exixt.")
|
self.caller.msg("Message does not exixt.")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.caller.msg("Usage: @mail/forward <player list>=<#>[/<Message>]")
|
self.caller.msg("Usage: @mail/forward <account list>=<#>[/<Message>]")
|
||||||
elif "reply" in self.switches:
|
elif "reply" in self.switches:
|
||||||
try:
|
try:
|
||||||
if not self.rhs:
|
if not self.rhs:
|
||||||
|
|
@ -187,7 +193,8 @@ class CmdMail(default_cmds.MuxCommand):
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
all_mail = self.get_all_mail()
|
all_mail = self.get_all_mail()
|
||||||
mind = int(self.lhs) - 1
|
mind_max = max(0, all_mail.count() - 1)
|
||||||
|
mind = max(0, min(mind_max, int(self.lhs) - 1))
|
||||||
if all_mail[mind]:
|
if all_mail[mind]:
|
||||||
old_message = all_mail[mind]
|
old_message = all_mail[mind]
|
||||||
self.send_mail(old_message.senders, "RE: " + old_message.header,
|
self.send_mail(old_message.senders, "RE: " + old_message.header,
|
||||||
|
|
@ -210,8 +217,11 @@ class CmdMail(default_cmds.MuxCommand):
|
||||||
body = self.rhs
|
body = self.rhs
|
||||||
self.send_mail(self.search_targets(self.lhslist), subject, body, self.caller)
|
self.send_mail(self.search_targets(self.lhslist), subject, body, self.caller)
|
||||||
else:
|
else:
|
||||||
|
all_mail = self.get_all_mail()
|
||||||
|
mind_max = max(0, all_mail.count() - 1)
|
||||||
try:
|
try:
|
||||||
message = self.get_all_mail()[int(self.lhs) - 1]
|
mind = max(0, min(mind_max, int(self.lhs) - 1))
|
||||||
|
message = all_mail[mind]
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
self.caller.msg("'%s' is not a valid mail id." % self.lhs)
|
self.caller.msg("'%s' is not a valid mail id." % self.lhs)
|
||||||
return
|
return
|
||||||
|
|
@ -253,4 +263,3 @@ class CmdMail(default_cmds.MuxCommand):
|
||||||
self.caller.msg(_HEAD_CHAR * _WIDTH)
|
self.caller.msg(_HEAD_CHAR * _WIDTH)
|
||||||
else:
|
else:
|
||||||
self.caller.msg("There are no messages in your inbox.")
|
self.caller.msg("There are no messages in your inbox.")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ called and so on until the map is completed. Building instructions are passed
|
||||||
the following arguments:
|
the following arguments:
|
||||||
x - The rooms position on the maps x axis
|
x - The rooms position on the maps x axis
|
||||||
y - The rooms position on the maps y axis
|
y - The rooms position on the maps y axis
|
||||||
caller - The player calling the command
|
caller - The account calling the command
|
||||||
iteration - The current iterations number (0, 1 or 2)
|
iteration - The current iterations number (0, 1 or 2)
|
||||||
room_dict - A dictionary containing room references returned by build
|
room_dict - A dictionary containing room references returned by build
|
||||||
functions where tuple coordinates are the keys (x, y).
|
functions where tuple coordinates are the keys (x, y).
|
||||||
|
|
@ -119,7 +119,7 @@ def example1_build_forest(x, y, **kwargs):
|
||||||
room = create_object(rooms.Room, key="forest" + str(x) + str(y))
|
room = create_object(rooms.Room, key="forest" + str(x) + str(y))
|
||||||
room.db.desc = "Basic forest room."
|
room.db.desc = "Basic forest room."
|
||||||
|
|
||||||
# Send a message to the player
|
# Send a message to the account
|
||||||
kwargs["caller"].msg(room.key + " " + room.dbref)
|
kwargs["caller"].msg(room.key + " " + room.dbref)
|
||||||
|
|
||||||
# This is generally mandatory.
|
# This is generally mandatory.
|
||||||
|
|
@ -143,7 +143,7 @@ def example1_build_mountains(x, y, **kwargs):
|
||||||
rock = create_object(key="Rock", location=room)
|
rock = create_object(key="Rock", location=room)
|
||||||
rock.db.desc = "An ordinary rock."
|
rock.db.desc = "An ordinary rock."
|
||||||
|
|
||||||
# Send a message to the player
|
# Send a message to the account
|
||||||
kwargs["caller"].msg(room.key + " " + room.dbref)
|
kwargs["caller"].msg(room.key + " " + room.dbref)
|
||||||
|
|
||||||
# This is generally mandatory.
|
# This is generally mandatory.
|
||||||
|
|
@ -167,12 +167,13 @@ def example1_build_temple(x, y, **kwargs):
|
||||||
"keeping the sound level only just below thunderous. "
|
"keeping the sound level only just below thunderous. "
|
||||||
"This is a rare spot of mirth on this dread moor.")
|
"This is a rare spot of mirth on this dread moor.")
|
||||||
|
|
||||||
# Send a message to the player
|
# Send a message to the account
|
||||||
kwargs["caller"].msg(room.key + " " + room.dbref)
|
kwargs["caller"].msg(room.key + " " + room.dbref)
|
||||||
|
|
||||||
# This is generally mandatory.
|
# This is generally mandatory.
|
||||||
return room
|
return room
|
||||||
|
|
||||||
|
|
||||||
# Include your trigger characters and build functions in a legend dict.
|
# Include your trigger characters and build functions in a legend dict.
|
||||||
EXAMPLE1_LEGEND = {("♣", "♠"): example1_build_forest,
|
EXAMPLE1_LEGEND = {("♣", "♠"): example1_build_forest,
|
||||||
("∩", "n"): example1_build_mountains,
|
("∩", "n"): example1_build_mountains,
|
||||||
|
|
@ -225,17 +226,17 @@ def example2_build_verticle_exit(x, y, **kwargs):
|
||||||
if kwargs["iteration"] == 0:
|
if kwargs["iteration"] == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
north_room = kwargs["room_dict"][(x, y-1)]
|
north_room = kwargs["room_dict"][(x, y - 1)]
|
||||||
south_room = kwargs["room_dict"][(x, y+1)]
|
south_room = kwargs["room_dict"][(x, y + 1)]
|
||||||
|
|
||||||
# create exits in the rooms
|
# create exits in the rooms
|
||||||
create_object(exits.Exit, key="south",
|
create_object(exits.Exit, key="south",
|
||||||
aliases=["s"], location=north_room,
|
aliases=["s"], location=north_room,
|
||||||
destination=south_room)
|
destination=south_room)
|
||||||
|
|
||||||
create_object(exits.Exit, key="north",
|
create_object(exits.Exit, key="north",
|
||||||
aliases=["n"], location=south_room,
|
aliases=["n"], location=south_room,
|
||||||
destination=north_room)
|
destination=north_room)
|
||||||
|
|
||||||
kwargs["caller"].msg("Connected: " + north_room.key +
|
kwargs["caller"].msg("Connected: " + north_room.key +
|
||||||
" & " + south_room.key)
|
" & " + south_room.key)
|
||||||
|
|
@ -247,20 +248,21 @@ def example2_build_horizontal_exit(x, y, **kwargs):
|
||||||
if kwargs["iteration"] == 0:
|
if kwargs["iteration"] == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
west_room = kwargs["room_dict"][(x-1, y)]
|
west_room = kwargs["room_dict"][(x - 1, y)]
|
||||||
east_room = kwargs["room_dict"][(x+1, y)]
|
east_room = kwargs["room_dict"][(x + 1, y)]
|
||||||
|
|
||||||
create_object(exits.Exit, key="east",
|
create_object(exits.Exit, key="east",
|
||||||
aliases=["e"], location=west_room,
|
aliases=["e"], location=west_room,
|
||||||
destination=east_room)
|
destination=east_room)
|
||||||
|
|
||||||
create_object(exits.Exit, key="west",
|
create_object(exits.Exit, key="west",
|
||||||
aliases=["w"], location=east_room,
|
aliases=["w"], location=east_room,
|
||||||
destination=west_room)
|
destination=west_room)
|
||||||
|
|
||||||
kwargs["caller"].msg("Connected: " + west_room.key +
|
kwargs["caller"].msg("Connected: " + west_room.key +
|
||||||
" & " + east_room.key)
|
" & " + east_room.key)
|
||||||
|
|
||||||
|
|
||||||
# Include your trigger characters and build functions in a legend dict.
|
# Include your trigger characters and build functions in a legend dict.
|
||||||
EXAMPLE2_LEGEND = {("♣", "♠"): example2_build_forest,
|
EXAMPLE2_LEGEND = {("♣", "♠"): example2_build_forest,
|
||||||
("|"): example2_build_verticle_exit,
|
("|"): example2_build_verticle_exit,
|
||||||
|
|
@ -339,37 +341,38 @@ def build_map(caller, game_map, legend, iterations=1, build_exits=True):
|
||||||
y = loc_key[1]
|
y = loc_key[1]
|
||||||
|
|
||||||
# north
|
# north
|
||||||
if (x, y-1) in room_dict:
|
if (x, y - 1) in room_dict:
|
||||||
if room_dict[(x, y-1)]:
|
if room_dict[(x, y - 1)]:
|
||||||
create_object(exits.Exit, key="north",
|
create_object(exits.Exit, key="north",
|
||||||
aliases=["n"], location=location,
|
aliases=["n"], location=location,
|
||||||
destination=room_dict[(x, y-1)])
|
destination=room_dict[(x, y - 1)])
|
||||||
|
|
||||||
# east
|
# east
|
||||||
if (x+1, y) in room_dict:
|
if (x + 1, y) in room_dict:
|
||||||
if room_dict[(x+1, y)]:
|
if room_dict[(x + 1, y)]:
|
||||||
create_object(exits.Exit, key="east",
|
create_object(exits.Exit, key="east",
|
||||||
aliases=["e"], location=location,
|
aliases=["e"], location=location,
|
||||||
destination=room_dict[(x+1, y)])
|
destination=room_dict[(x + 1, y)])
|
||||||
|
|
||||||
# south
|
# south
|
||||||
if (x, y+1) in room_dict:
|
if (x, y + 1) in room_dict:
|
||||||
if room_dict[(x, y+1)]:
|
if room_dict[(x, y + 1)]:
|
||||||
create_object(exits.Exit, key="south",
|
create_object(exits.Exit, key="south",
|
||||||
aliases=["s"], location=location,
|
aliases=["s"], location=location,
|
||||||
destination=room_dict[(x, y+1)])
|
destination=room_dict[(x, y + 1)])
|
||||||
|
|
||||||
# west
|
# west
|
||||||
if (x-1, y) in room_dict:
|
if (x - 1, y) in room_dict:
|
||||||
if room_dict[(x-1, y)]:
|
if room_dict[(x - 1, y)]:
|
||||||
create_object(exits.Exit, key="west",
|
create_object(exits.Exit, key="west",
|
||||||
aliases=["w"], location=location,
|
aliases=["w"], location=location,
|
||||||
destination=room_dict[(x-1, y)])
|
destination=room_dict[(x - 1, y)])
|
||||||
|
|
||||||
caller.msg("Map Created.")
|
caller.msg("Map Created.")
|
||||||
|
|
||||||
# access command
|
# access command
|
||||||
|
|
||||||
|
|
||||||
class CmdMapBuilder(COMMAND_DEFAULT_CLASS):
|
class CmdMapBuilder(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
Build a map from a 2D ASCII map.
|
Build a map from a 2D ASCII map.
|
||||||
|
|
@ -478,4 +481,3 @@ class CmdMapBuilder(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
# Pass map and legend to the build function.
|
# Pass map and legend to the build function.
|
||||||
build_map(caller, game_map, legend, iterations, build_exits)
|
build_map(caller, game_map, legend, iterations, build_exits)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ CMDSET_UNLOGGEDIN = "contrib.menu_login.UnloggedinCmdSet"
|
||||||
When you'll reload the server, new sessions will connect to the new
|
When you'll reload the server, new sessions will connect to the new
|
||||||
login system, where they will be able to:
|
login system, where they will be able to:
|
||||||
|
|
||||||
* Enter their username, assuming they have an existing player.
|
* Enter their username, assuming they have an existing account.
|
||||||
* Enter 'NEW' to create a new player.
|
* Enter 'NEW' to create a new account.
|
||||||
|
|
||||||
The top-level functions in this file are menu nodes (as described in
|
The top-level functions in this file are menu nodes (as described in
|
||||||
evennia.utils.evmenu.py). Each one of these functions is responsible
|
evennia.utils.evmenu.py). Each one of these functions is responsible
|
||||||
|
|
@ -61,7 +61,7 @@ def start(caller):
|
||||||
a session has been created OR if an error occurs further
|
a session has been created OR if an error occurs further
|
||||||
down the menu tree. From there, users can either enter a
|
down the menu tree. From there, users can either enter a
|
||||||
username (if this username exists) or type NEW (capitalized
|
username (if this username exists) or type NEW (capitalized
|
||||||
or not) to create a new player.
|
or not) to create a new account.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
text = random_string_from_module(CONNECTION_SCREEN_MODULE)
|
text = random_string_from_module(CONNECTION_SCREEN_MODULE)
|
||||||
|
|
@ -79,7 +79,7 @@ def start(caller):
|
||||||
|
|
||||||
|
|
||||||
def username(caller, string_input):
|
def username(caller, string_input):
|
||||||
"""Check that the username leads to an existing player.
|
"""Check that the username leads to an existing account.
|
||||||
|
|
||||||
Check that the specified username exists. If the username doesn't
|
Check that the specified username exists. If the username doesn't
|
||||||
exist, display an error message and ask the user to try again. If
|
exist, display an error message and ask the user to try again. If
|
||||||
|
|
@ -88,8 +88,8 @@ def username(caller, string_input):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
string_input = string_input.strip()
|
string_input = string_input.strip()
|
||||||
player = managers.players.get_player_from_name(string_input)
|
account = managers.accounts.get_account_from_name(string_input)
|
||||||
if player is None:
|
if account is None:
|
||||||
text = dedent("""
|
text = dedent("""
|
||||||
|rThe username '{}' doesn't exist. Have you created it?|n
|
|rThe username '{}' doesn't exist. Have you created it?|n
|
||||||
Try another name or leave empty to go back.
|
Try another name or leave empty to go back.
|
||||||
|
|
@ -100,8 +100,8 @@ def username(caller, string_input):
|
||||||
{"key": "_default",
|
{"key": "_default",
|
||||||
"goto": "username"})
|
"goto": "username"})
|
||||||
else:
|
else:
|
||||||
caller.ndb._menutree.player = player
|
caller.ndb._menutree.account = account
|
||||||
text = "Enter the password for the {} account.".format(player.name)
|
text = "Enter the password for the {} account.".format(account.name)
|
||||||
# Disables echo for the password
|
# Disables echo for the password
|
||||||
caller.msg("", options={"echo": False})
|
caller.msg("", options={"echo": False})
|
||||||
options = (
|
options = (
|
||||||
|
|
@ -115,7 +115,7 @@ def username(caller, string_input):
|
||||||
|
|
||||||
|
|
||||||
def ask_password(caller, string_input):
|
def ask_password(caller, string_input):
|
||||||
"""Ask the user to enter the password to this player.
|
"""Ask the user to enter the password to this account.
|
||||||
|
|
||||||
This is assuming the user exists (see 'create_username' and
|
This is assuming the user exists (see 'create_username' and
|
||||||
'create_password'). This node "loops" if needed: if the
|
'create_password'). This node "loops" if needed: if the
|
||||||
|
|
@ -129,14 +129,14 @@ def ask_password(caller, string_input):
|
||||||
|
|
||||||
# Check the password and login is correct; also check for bans
|
# Check the password and login is correct; also check for bans
|
||||||
|
|
||||||
player = menutree.player
|
account = menutree.account
|
||||||
password_attempts = menutree.password_attempts \
|
password_attempts = menutree.password_attempts \
|
||||||
if hasattr(menutree, "password_attempts") else 0
|
if hasattr(menutree, "password_attempts") else 0
|
||||||
bans = ServerConfig.objects.conf("server_bans")
|
bans = ServerConfig.objects.conf("server_bans")
|
||||||
banned = bans and (any(tup[0] == player.name.lower() for tup in bans) or
|
banned = bans and (any(tup[0] == account.name.lower() for tup in bans) or
|
||||||
any(tup[2].match(caller.address) for tup in bans if tup[2]))
|
any(tup[2].match(caller.address) for tup in bans if tup[2]))
|
||||||
|
|
||||||
if not player.check_password(string_input):
|
if not account.check_password(string_input):
|
||||||
# Didn't enter a correct password
|
# Didn't enter a correct password
|
||||||
password_attempts += 1
|
password_attempts += 1
|
||||||
if password_attempts > 2:
|
if password_attempts > 2:
|
||||||
|
|
@ -173,7 +173,7 @@ def ask_password(caller, string_input):
|
||||||
text = ""
|
text = ""
|
||||||
options = {}
|
options = {}
|
||||||
caller.msg("", options={"echo": True})
|
caller.msg("", options={"echo": True})
|
||||||
caller.sessionhandler.login(caller, player)
|
caller.sessionhandler.login(caller, account)
|
||||||
|
|
||||||
return text, options
|
return text, options
|
||||||
|
|
||||||
|
|
@ -201,10 +201,10 @@ def create_username(caller, string_input):
|
||||||
"""
|
"""
|
||||||
menutree = caller.ndb._menutree
|
menutree = caller.ndb._menutree
|
||||||
string_input = string_input.strip()
|
string_input = string_input.strip()
|
||||||
player = managers.players.get_player_from_name(string_input)
|
account = managers.accounts.get_account_from_name(string_input)
|
||||||
|
|
||||||
# If a player with that name exists, a new one will not be created
|
# If an account with that name exists, a new one will not be created
|
||||||
if player:
|
if account:
|
||||||
text = dedent("""
|
text = dedent("""
|
||||||
|rThe account {} already exists.|n
|
|rThe account {} already exists.|n
|
||||||
Enter another username or leave blank to go back.
|
Enter another username or leave blank to go back.
|
||||||
|
|
@ -229,7 +229,7 @@ def create_username(caller, string_input):
|
||||||
"goto": "create_username"})
|
"goto": "create_username"})
|
||||||
else:
|
else:
|
||||||
# a valid username - continue getting the password
|
# a valid username - continue getting the password
|
||||||
menutree.playername = string_input
|
menutree.accountname = string_input
|
||||||
# Disables echo for entering password
|
# Disables echo for entering password
|
||||||
caller.msg("", options={"echo": False})
|
caller.msg("", options={"echo": False})
|
||||||
# Redirects to the creation of a password
|
# Redirects to the creation of a password
|
||||||
|
|
@ -259,7 +259,7 @@ def create_password(caller, string_input):
|
||||||
"goto": "create_password"})
|
"goto": "create_password"})
|
||||||
|
|
||||||
password = string_input.strip()
|
password = string_input.strip()
|
||||||
playername = menutree.playername
|
accountname = menutree.accountname
|
||||||
|
|
||||||
if len(password) < LEN_PASSWD:
|
if len(password) < LEN_PASSWD:
|
||||||
# The password is too short
|
# The password is too short
|
||||||
|
|
@ -273,15 +273,15 @@ def create_password(caller, string_input):
|
||||||
from evennia.commands.default import unloggedin
|
from evennia.commands.default import unloggedin
|
||||||
# We make use of the helper functions from the default set here.
|
# We make use of the helper functions from the default set here.
|
||||||
try:
|
try:
|
||||||
permissions = settings.PERMISSION_PLAYER_DEFAULT
|
permissions = settings.PERMISSION_ACCOUNT_DEFAULT
|
||||||
typeclass = settings.BASE_CHARACTER_TYPECLASS
|
typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||||
new_player = unloggedin._create_player(caller, playername,
|
new_account = unloggedin._create_account(caller, accountname,
|
||||||
password, permissions)
|
password, permissions)
|
||||||
if new_player:
|
if new_account:
|
||||||
if settings.MULTISESSION_MODE < 2:
|
if settings.MULTISESSION_MODE < 2:
|
||||||
default_home = ObjectDB.objects.get_id(
|
default_home = ObjectDB.objects.get_id(
|
||||||
settings.DEFAULT_HOME)
|
settings.DEFAULT_HOME)
|
||||||
unloggedin._create_character(caller, new_player,
|
unloggedin._create_character(caller, new_account,
|
||||||
typeclass, default_home, permissions)
|
typeclass, default_home, permissions)
|
||||||
except Exception:
|
except Exception:
|
||||||
# We are in the middle between logged in and -not, so we have
|
# We are in the middle between logged in and -not, so we have
|
||||||
|
|
@ -297,7 +297,7 @@ def create_password(caller, string_input):
|
||||||
text = ""
|
text = ""
|
||||||
caller.msg("|gWelcome, your new account has been created!|n")
|
caller.msg("|gWelcome, your new account has been created!|n")
|
||||||
caller.msg("", options={"echo": True})
|
caller.msg("", options={"echo": True})
|
||||||
caller.sessionhandler.login(caller, new_player)
|
caller.sessionhandler.login(caller, new_account)
|
||||||
|
|
||||||
return text, options
|
return text, options
|
||||||
|
|
||||||
|
|
@ -335,7 +335,7 @@ class UnloggedinCmdSet(CmdSet):
|
||||||
class CmdUnloggedinLook(Command):
|
class CmdUnloggedinLook(Command):
|
||||||
"""
|
"""
|
||||||
An unloggedin version of the look command. This is called by the server
|
An unloggedin version of the look command. This is called by the server
|
||||||
when the player first connects. It sets up the menu before handing off
|
when the account first connects. It sets up the menu before handing off
|
||||||
to the menu's own look command.
|
to the menu's own look command.
|
||||||
"""
|
"""
|
||||||
key = syscmdkeys.CMD_LOGINSTART
|
key = syscmdkeys.CMD_LOGINSTART
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ also adds the short descriptions and the `sdesc` command).
|
||||||
Installation:
|
Installation:
|
||||||
|
|
||||||
Edit `mygame/commands/default_cmdsets.py` and add
|
Edit `mygame/commands/default_cmdsets.py` and add
|
||||||
`from contrib.multidesc import CmdMultiDesc` to the top.
|
`from evennia.contrib.multidescer import CmdMultiDesc` to the top.
|
||||||
|
|
||||||
Next, look up the `at_cmdset_create` method of the `CharacterCmdSet`
|
Next, look up the `at_cmdset_create` method of the `CharacterCmdSet`
|
||||||
class and add a line `self.add(CmdMultiDesc())` to the end
|
class and add a line `self.add(CmdMultiDesc())` to the end
|
||||||
|
|
@ -97,6 +97,7 @@ def _update_store(caller, key=None, desc=None, delete=False, swapkey=None):
|
||||||
|
|
||||||
# eveditor save/load/quit functions
|
# eveditor save/load/quit functions
|
||||||
|
|
||||||
|
|
||||||
def _save_editor(caller, buffer):
|
def _save_editor(caller, buffer):
|
||||||
"Called when the editor saves its contents"
|
"Called when the editor saves its contents"
|
||||||
key = caller.db._multidesc_editkey
|
key = caller.db._multidesc_editkey
|
||||||
|
|
@ -104,6 +105,7 @@ def _save_editor(caller, buffer):
|
||||||
caller.msg("Saved description to key '%s'." % key)
|
caller.msg("Saved description to key '%s'." % key)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _load_editor(caller):
|
def _load_editor(caller):
|
||||||
"Called when the editor loads contents"
|
"Called when the editor loads contents"
|
||||||
key = caller.db._multidesc_editkey
|
key = caller.db._multidesc_editkey
|
||||||
|
|
@ -112,6 +114,7 @@ def _load_editor(caller):
|
||||||
return caller.db.multidesc[match[0]][1]
|
return caller.db.multidesc[match[0]][1]
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _quit_editor(caller):
|
def _quit_editor(caller):
|
||||||
"Called when the editor quits"
|
"Called when the editor quits"
|
||||||
del caller.db._multidesc_editkey
|
del caller.db._multidesc_editkey
|
||||||
|
|
@ -161,13 +164,13 @@ class CmdMultiDesc(default_cmds.MuxCommand):
|
||||||
# list all stored descriptions, either in full or cropped.
|
# list all stored descriptions, either in full or cropped.
|
||||||
# Note that we list starting from 1, not from 0.
|
# Note that we list starting from 1, not from 0.
|
||||||
_update_store(caller)
|
_update_store(caller)
|
||||||
do_crop = not "full" in switches
|
do_crop = "full" not in switches
|
||||||
if do_crop:
|
if do_crop:
|
||||||
outtext = ["|w%s:|n %s" % (key, crop(desc))
|
outtext = ["|w%s:|n %s" % (key, crop(desc))
|
||||||
for key, desc in caller.db.multidesc]
|
for key, desc in caller.db.multidesc]
|
||||||
else:
|
else:
|
||||||
outtext = ["\n|w%s:|n|n\n%s\n%s" % (key, "-" * (len(key) + 1), desc)
|
outtext = ["\n|w%s:|n|n\n%s\n%s" % (key, "-" * (len(key) + 1), desc)
|
||||||
for key, desc in caller.db.multidesc]
|
for key, desc in caller.db.multidesc]
|
||||||
|
|
||||||
caller.msg("|wStored descs:|n\n" + "\n".join(outtext))
|
caller.msg("|wStored descs:|n\n" + "\n".join(outtext))
|
||||||
return
|
return
|
||||||
|
|
@ -249,6 +252,6 @@ class CmdMultiDesc(default_cmds.MuxCommand):
|
||||||
else:
|
else:
|
||||||
caller.msg("|wCurrent desc:|n\n%s" % caller.db.desc)
|
caller.msg("|wCurrent desc:|n\n%s" % caller.db.desc)
|
||||||
|
|
||||||
except DescValidateError, err:
|
except DescValidateError as err:
|
||||||
# This is triggered by _key_to_index
|
# This is triggered by _key_to_index
|
||||||
caller.msg(err)
|
caller.msg(err)
|
||||||
|
|
|
||||||
346
evennia/contrib/random_string_generator.py
Normal file
346
evennia/contrib/random_string_generator.py
Normal file
|
|
@ -0,0 +1,346 @@
|
||||||
|
"""
|
||||||
|
Pseudo-random generator and registry
|
||||||
|
|
||||||
|
Evennia contribution - Vincent Le Goff 2017
|
||||||
|
|
||||||
|
This contrib can be used to generate pseudo-random strings of information
|
||||||
|
with specific criteria. You could, for instance, use it to generate
|
||||||
|
phone numbers, license plate numbers, validation codes, non-sensivite
|
||||||
|
passwords and so on. The strings generated by the generator will be
|
||||||
|
stored and won't be available again in order to avoid repetition.
|
||||||
|
Here's a very simple example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from evennia.contrib.random_string_generator import RandomStringGenerator
|
||||||
|
# Create a generator for phone numbers
|
||||||
|
phone_generator = RandomStringGenerator("phone number", r"555-[0-9]{3}-[0-9]{4}")
|
||||||
|
# Generate a phone number (555-XXX-XXXX with X as numbers)
|
||||||
|
number = phone_generator.get()
|
||||||
|
# `number` will contain something like: "555-981-2207"
|
||||||
|
# If you call `phone_generator.get`, it won't give the same anymore.phone_generator.all()
|
||||||
|
# Will return a list of all currently-used phone numbers
|
||||||
|
phone_generator.remove("555-981-2207")
|
||||||
|
# The number can be generated again
|
||||||
|
```
|
||||||
|
|
||||||
|
To use it, you will need to:
|
||||||
|
|
||||||
|
1. Import the `RandomStringGenerator` class from the contrib.
|
||||||
|
2. Create an instance of this class taking two arguments:
|
||||||
|
- The name of the gemerator (like "phone number", "license plate"...).
|
||||||
|
- The regular expression representing the expected results.
|
||||||
|
3. Use the generator's `all`, `get` and `remove` methods as shown above.
|
||||||
|
|
||||||
|
To understand how to read and create regular expressions, you can refer to
|
||||||
|
[the documentation on the re module](https://docs.python.org/2/library/re.html).
|
||||||
|
Some examples of regular expressions you could use:
|
||||||
|
|
||||||
|
- `r"555-\d{3}-\d{4}"`: 555, a dash, 3 digits, another dash, 4 digits.
|
||||||
|
- `r"[0-9]{3}[A-Z][0-9]{3}"`: 3 digits, a capital letter, 3 digits.
|
||||||
|
- `r"[A-Za-z0-9]{8,15}"`: between 8 and 15 letters and digits.
|
||||||
|
- ...
|
||||||
|
|
||||||
|
Behind the scenes, a script is created to store the generated information
|
||||||
|
for a single generator. The `RandomStringGenerator` object will also
|
||||||
|
read the regular expression you give to it to see what information is
|
||||||
|
required (letters, digits, a more restricted class, simple characters...)...
|
||||||
|
More complex regular expressions (with branches for instance) might not be
|
||||||
|
available.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from random import choice, randint, seed
|
||||||
|
import re
|
||||||
|
import string
|
||||||
|
import time
|
||||||
|
|
||||||
|
from evennia import DefaultScript, ScriptDB
|
||||||
|
from evennia.utils.create import create_script
|
||||||
|
|
||||||
|
|
||||||
|
class RejectedRegex(RuntimeError):
|
||||||
|
|
||||||
|
"""The provided regular expression has been rejected.
|
||||||
|
|
||||||
|
More details regarding why this error occurred will be provided in
|
||||||
|
the message. The usual reason is the provided regular expression is
|
||||||
|
not specific enough and could lead to inconsistent generating.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ExhaustedGenerator(RuntimeError):
|
||||||
|
|
||||||
|
"""The generator hasn't any available strings to generate anymore."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RandomStringGeneratorScript(DefaultScript):
|
||||||
|
|
||||||
|
"""
|
||||||
|
The global script to hold all generators.
|
||||||
|
|
||||||
|
It will be automatically created the first time `generate` is called
|
||||||
|
on a RandomStringGenerator object.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_script_creation(self):
|
||||||
|
"""Hook called when the script is created."""
|
||||||
|
self.key = "generator_script"
|
||||||
|
self.desc = "Global generator script"
|
||||||
|
self.persistent = True
|
||||||
|
|
||||||
|
# Permanent data to be stored
|
||||||
|
self.db.generated = {}
|
||||||
|
|
||||||
|
|
||||||
|
class RandomStringGenerator(object):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A generator class to generate pseudo-random strings with a rule.
|
||||||
|
|
||||||
|
The "rule" defining what the generator should provide in terms of
|
||||||
|
string is given as a regular expression when creating instances of
|
||||||
|
this class. You can use the `all` method to get all generated strings,
|
||||||
|
the `get` method to generate a new string, the `remove` method
|
||||||
|
to remove a generated string, or the `clear` method to remove all
|
||||||
|
generated strings.
|
||||||
|
|
||||||
|
Bear in mind, however, that while the generated strings will be
|
||||||
|
stored to avoid repetition, the generator will not concern itself
|
||||||
|
with how the string is stored on the object you use. You probably
|
||||||
|
want to create a tag to mark this object. This is outside of the scope
|
||||||
|
of this class.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# We keep the script as a class variable to optimize querying
|
||||||
|
# with multiple instandces
|
||||||
|
script = None
|
||||||
|
|
||||||
|
def __init__(self, name, regex):
|
||||||
|
"""
|
||||||
|
Create a new generator.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): name of the generator to create.
|
||||||
|
regex (str): regular expression describing the generator.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
`name` should be an explicit name. If you use more than one
|
||||||
|
generator in your game, be sure to give them different names.
|
||||||
|
This name will be used to store the generated information
|
||||||
|
in the global script, and in case of errors.
|
||||||
|
|
||||||
|
The regular expression should describe the generator, what
|
||||||
|
it should generate: a phone number, a license plate, a password
|
||||||
|
or something else. Regular expressions allow you to use
|
||||||
|
pretty advanced criteria, but be aware that some regular
|
||||||
|
expressions will be rejected if not specific enough.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RejectedRegex: the provided regular expression couldn't be
|
||||||
|
accepted as a valid generator description.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.name = name
|
||||||
|
self.elements = []
|
||||||
|
self.total = 1
|
||||||
|
|
||||||
|
# Analyze the regex if any
|
||||||
|
if regex:
|
||||||
|
self._find_elements(regex)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<evennia.contrib.random_string_generator.RandomStringGenerator for {}>".format(self.name)
|
||||||
|
|
||||||
|
def _get_script(self):
|
||||||
|
"""Get or create the script."""
|
||||||
|
if type(self).script:
|
||||||
|
return type(self).script
|
||||||
|
|
||||||
|
try:
|
||||||
|
script = ScriptDB.objects.get(db_key="generator_script")
|
||||||
|
except ScriptDB.DoesNotExist:
|
||||||
|
script = create_script("contrib.random_string_generator.RandomStringGeneratorScript")
|
||||||
|
|
||||||
|
type(self).script = script
|
||||||
|
return script
|
||||||
|
|
||||||
|
def _find_elements(self, regex):
|
||||||
|
"""
|
||||||
|
Find the elements described in the regular expression. This will
|
||||||
|
analyze the provided regular expression and try to find elements.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
regex (str): the regular expression.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.total = 1
|
||||||
|
self.elements = []
|
||||||
|
tree = re.sre_parse.parse(regex).data
|
||||||
|
# `tree` contains a list of elements in the regular expression
|
||||||
|
for element in tree:
|
||||||
|
# `eleemnt` is also a list, the first element is a string
|
||||||
|
name = element[0]
|
||||||
|
desc = {"min": 1, "max": 1}
|
||||||
|
|
||||||
|
# If `.`, break here
|
||||||
|
if name == "any":
|
||||||
|
raise RejectedRegex("the . definition is too broad, specify what you need more precisely")
|
||||||
|
elif name == "at":
|
||||||
|
# Either the beginning or end, we ignore it
|
||||||
|
continue
|
||||||
|
elif name == "min_repeat":
|
||||||
|
raise RejectedRegex("you have to provide a maximum number of this character class")
|
||||||
|
elif name == "max_repeat":
|
||||||
|
desc["min"] = element[1][0]
|
||||||
|
desc["max"] = element[1][1]
|
||||||
|
desc["chars"] = self._find_literal(element[1][2][0])
|
||||||
|
elif name == "in":
|
||||||
|
desc["chars"] = self._find_literal(element)
|
||||||
|
elif name == "literal":
|
||||||
|
desc["chars"] = self._find_literal(element)
|
||||||
|
else:
|
||||||
|
raise RejectedRegex("unhandled regex syntax:: {}".format(repr(name)))
|
||||||
|
|
||||||
|
self.elements.append(desc)
|
||||||
|
self.total *= len(desc["chars"]) ** desc["max"]
|
||||||
|
|
||||||
|
def _find_literal(self, element):
|
||||||
|
"""Find the literal corresponding to a piece of regular expression."""
|
||||||
|
chars = []
|
||||||
|
if element[0] == "literal":
|
||||||
|
chars.append(chr(element[1]))
|
||||||
|
elif element[0] == "in":
|
||||||
|
negate = False
|
||||||
|
if element[1][0][0] == "negate":
|
||||||
|
negate = True
|
||||||
|
chars = list(string.ascii_letters + string.digits)
|
||||||
|
|
||||||
|
for part in element[1]:
|
||||||
|
if part[0] == "negate":
|
||||||
|
continue
|
||||||
|
|
||||||
|
sublist = self._find_literal(part)
|
||||||
|
for char in sublist:
|
||||||
|
if negate:
|
||||||
|
if char in chars:
|
||||||
|
chars.remove(char)
|
||||||
|
else:
|
||||||
|
chars.append(char)
|
||||||
|
elif element[0] == "range":
|
||||||
|
chars = [chr(i) for i in range(element[1][0], element[1][1] + 1)]
|
||||||
|
elif element[0] == "category":
|
||||||
|
category = element[1]
|
||||||
|
if category == "category_digit":
|
||||||
|
chars = list(string.digits)
|
||||||
|
elif category == "category_word":
|
||||||
|
chars = list(string.letters)
|
||||||
|
else:
|
||||||
|
raise RejectedRegex("unknown category: {}".format(category))
|
||||||
|
else:
|
||||||
|
raise RejectedRegex("cannot find the literal: {}".format(element[0]))
|
||||||
|
|
||||||
|
return chars
|
||||||
|
|
||||||
|
def all(self):
|
||||||
|
"""
|
||||||
|
Return all generated strings for this generator.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
strings (list of strr): the list of strings that are already
|
||||||
|
used. The strings that were generated first come first in the list.
|
||||||
|
|
||||||
|
"""
|
||||||
|
script = self._get_script()
|
||||||
|
generated = list(script.db.generated.get(self.name, []))
|
||||||
|
return generated
|
||||||
|
|
||||||
|
def get(self, store=True, unique=True):
|
||||||
|
"""
|
||||||
|
Generate a pseudo-random string according to the regular expression.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
store (bool, optional): store the generated string in the script.
|
||||||
|
unique (bool, optional): keep on trying if the string is already used.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The newly-generated string.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ExhaustedGenerator: if there's no available string in this generator.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Unless asked explicitly, the returned string can't repeat itself.
|
||||||
|
|
||||||
|
"""
|
||||||
|
script = self._get_script()
|
||||||
|
generated = script.db.generated.get(self.name)
|
||||||
|
if generated is None:
|
||||||
|
script.db.generated[self.name] = []
|
||||||
|
generated = script.db.generated[self.name]
|
||||||
|
|
||||||
|
if len(generated) >= self.total:
|
||||||
|
raise ExhaustedGenerator
|
||||||
|
|
||||||
|
# Generate a pseudo-random string that might be used already
|
||||||
|
result = ""
|
||||||
|
for element in self.elements:
|
||||||
|
number = randint(element["min"], element["max"])
|
||||||
|
chars = element["chars"]
|
||||||
|
for index in range(number):
|
||||||
|
char = choice(chars)
|
||||||
|
result += char
|
||||||
|
|
||||||
|
# If the string has already been generated, try again
|
||||||
|
if result in generated and unique:
|
||||||
|
# Change the random seed, incrementing it slowly
|
||||||
|
epoch = time.time()
|
||||||
|
while result in generated:
|
||||||
|
epoch += 1
|
||||||
|
seed(epoch)
|
||||||
|
result = self.get(store=False, unique=False)
|
||||||
|
|
||||||
|
if store:
|
||||||
|
generated.append(result)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def remove(self, element):
|
||||||
|
"""
|
||||||
|
Remove a generated string from the list of stored strings.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
element (str): the string to remove from the list of generated strings.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: the specified value hasn't been generated and is not present.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
The specified string has to be present in the script (so
|
||||||
|
has to have been generated). It will remove this entry
|
||||||
|
from the script, so this string could be generated again by
|
||||||
|
calling the `get` method.
|
||||||
|
|
||||||
|
"""
|
||||||
|
script = self._get_script()
|
||||||
|
generated = script.db.generated.get(self.name, [])
|
||||||
|
if element not in generated:
|
||||||
|
raise ValueError("the string {} isn't stored as generated by the generator {}".format(
|
||||||
|
element, self.name))
|
||||||
|
|
||||||
|
generated.remove(element)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""
|
||||||
|
Clear the generator of all generated strings.
|
||||||
|
|
||||||
|
"""
|
||||||
|
script = self._get_script()
|
||||||
|
generated = script.db.generated.get(self.name, [])
|
||||||
|
generated[:] = []
|
||||||
|
|
@ -21,30 +21,30 @@ in the game in various ways:
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from evennia.contrib import rplanguages
|
from evennia.contrib import rplanguage
|
||||||
|
|
||||||
# need to be done once, here we create the "default" lang
|
# need to be done once, here we create the "default" lang
|
||||||
rplanguages.add_language()
|
rplanguage.add_language()
|
||||||
|
|
||||||
say = "This is me talking."
|
say = "This is me talking."
|
||||||
whisper = "This is me whispering.
|
whisper = "This is me whispering.
|
||||||
|
|
||||||
print rplanguages.obfuscate_language(say, level=0.0)
|
print rplanguage.obfuscate_language(say, level=0.0)
|
||||||
<<< "This is me talking."
|
<<< "This is me talking."
|
||||||
print rplanguages.obfuscate_language(say, level=0.5)
|
print rplanguage.obfuscate_language(say, level=0.5)
|
||||||
<<< "This is me byngyry."
|
<<< "This is me byngyry."
|
||||||
print rplanguages.obfuscate_language(say, level=1.0)
|
print rplanguage.obfuscate_language(say, level=1.0)
|
||||||
<<< "Daly ly sy byngyry."
|
<<< "Daly ly sy byngyry."
|
||||||
|
|
||||||
result = rplanguages.obfuscate_whisper(whisper, level=0.0)
|
result = rplanguage.obfuscate_whisper(whisper, level=0.0)
|
||||||
<<< "This is me whispering"
|
<<< "This is me whispering"
|
||||||
result = rplanguages.obfuscate_whisper(whisper, level=0.2)
|
result = rplanguage.obfuscate_whisper(whisper, level=0.2)
|
||||||
<<< "This is m- whisp-ring"
|
<<< "This is m- whisp-ring"
|
||||||
result = rplanguages.obfuscate_whisper(whisper, level=0.5)
|
result = rplanguage.obfuscate_whisper(whisper, level=0.5)
|
||||||
<<< "---s -s -- ---s------"
|
<<< "---s -s -- ---s------"
|
||||||
result = rplanguages.obfuscate_whisper(whisper, level=0.7)
|
result = rplanguage.obfuscate_whisper(whisper, level=0.7)
|
||||||
<<< "---- -- -- ----------"
|
<<< "---- -- -- ----------"
|
||||||
result = rplanguages.obfuscate_whisper(whisper, level=1.0)
|
result = rplanguage.obfuscate_whisper(whisper, level=1.0)
|
||||||
<<< "..."
|
<<< "..."
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
@ -71,7 +71,7 @@ Usage:
|
||||||
manual_translations = {"the":"y'e", "we":"uyi", "she":"semi", "he":"emi",
|
manual_translations = {"the":"y'e", "we":"uyi", "she":"semi", "he":"emi",
|
||||||
"you": "do", 'me':'mi','i':'me', 'be':"hy'e", 'and':'y'}
|
"you": "do", 'me':'mi','i':'me', 'be':"hy'e", 'and':'y'}
|
||||||
|
|
||||||
rplanguages.add_language(key="elvish", phonemes=phonemes, grammar=grammar,
|
rplanguage.add_language(key="elvish", phonemes=phonemes, grammar=grammar,
|
||||||
word_length_variance=word_length_variance,
|
word_length_variance=word_length_variance,
|
||||||
noun_postfix=noun_postfix, vowels=vowels,
|
noun_postfix=noun_postfix, vowels=vowels,
|
||||||
manual_translations=manual_translations
|
manual_translations=manual_translations
|
||||||
|
|
@ -96,6 +96,7 @@ import re
|
||||||
from random import choice, randint
|
from random import choice, randint
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from evennia import DefaultScript
|
from evennia import DefaultScript
|
||||||
|
from evennia.utils import logger
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
|
|
@ -105,21 +106,26 @@ from evennia import DefaultScript
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
|
|
||||||
# default language grammar
|
# default language grammar
|
||||||
_PHONEMES = "ea oh ae aa eh ah ao aw ai er ey ow ia ih iy oy ua uh uw a e i u y p b t d f v t dh s z sh zh ch jh k ng g m n l r w"
|
_PHONEMES = "ea oh ae aa eh ah ao aw ai er ey ow ia ih iy oy ua uh uw a e i u y p b t d f v t dh " \
|
||||||
|
"s z sh zh ch jh k ng g m n l r w"
|
||||||
_VOWELS = "eaoiuy"
|
_VOWELS = "eaoiuy"
|
||||||
# these must be able to be constructed from phonemes (so for example,
|
# these must be able to be constructed from phonemes (so for example,
|
||||||
# if you have v here, there must exixt at least one single-character
|
# if you have v here, there must exist at least one single-character
|
||||||
# vowel phoneme defined above)
|
# vowel phoneme defined above)
|
||||||
_GRAMMAR = "v cv vc cvv vcc vcv cvcc vccv cvccv cvcvcc cvccvcv vccvccvc cvcvccvv cvcvcvcvv"
|
_GRAMMAR = "v cv vc cvv vcc vcv cvcc vccv cvccv cvcvcc cvccvcv vccvccvc cvcvccvv cvcvcvcvv"
|
||||||
|
|
||||||
_RE_FLAGS = re.MULTILINE + re.IGNORECASE + re.UNICODE
|
_RE_FLAGS = re.MULTILINE + re.IGNORECASE + re.UNICODE
|
||||||
_RE_GRAMMAR = re.compile(r"vv|cc|v|c", _RE_FLAGS)
|
_RE_GRAMMAR = re.compile(r"vv|cc|v|c", _RE_FLAGS)
|
||||||
_RE_WORD = re.compile(r'\w+', _RE_FLAGS)
|
_RE_WORD = re.compile(r'\w+', _RE_FLAGS)
|
||||||
|
_RE_EXTRA_CHARS = re.compile(r'\s+(?=\W)|[,.?;](?=[,.?;]|\s+[,.?;])', _RE_FLAGS)
|
||||||
|
|
||||||
|
|
||||||
class LanguageExistsError(Exception):
|
class LanguageError(RuntimeError):
|
||||||
message = "Language is already created. Re-adding it will re-build" \
|
pass
|
||||||
" its dictionary map. Use 'force=True' keyword if you are sure."
|
|
||||||
|
|
||||||
|
class LanguageExistsError(LanguageError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LanguageHandler(DefaultScript):
|
class LanguageHandler(DefaultScript):
|
||||||
|
|
@ -148,16 +154,19 @@ class LanguageHandler(DefaultScript):
|
||||||
don't know the language well enough).
|
don't know the language well enough).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def at_script_creation(self):
|
def at_script_creation(self):
|
||||||
"Called when script is first started"
|
"Called when script is first started"
|
||||||
self.key = "language_handler"
|
self.key = "language_handler"
|
||||||
self.persistent = True
|
self.persistent = True
|
||||||
self.db.language_storage = {}
|
self.db.language_storage = {}
|
||||||
|
|
||||||
|
|
||||||
def add(self, key="default", phonemes=_PHONEMES,
|
def add(self, key="default", phonemes=_PHONEMES,
|
||||||
grammar=_GRAMMAR, word_length_variance=0, noun_prefix="",
|
grammar=_GRAMMAR, word_length_variance=0,
|
||||||
noun_postfix="", vowels=_VOWELS, manual_translations=None,
|
noun_translate=False,
|
||||||
|
noun_prefix="",
|
||||||
|
noun_postfix="",
|
||||||
|
vowels=_VOWELS, manual_translations=None,
|
||||||
auto_translations=None, force=False):
|
auto_translations=None, force=False):
|
||||||
"""
|
"""
|
||||||
Add a new language. Note that you generally only need to do
|
Add a new language. Note that you generally only need to do
|
||||||
|
|
@ -170,14 +179,21 @@ class LanguageHandler(DefaultScript):
|
||||||
will be used as an identifier for the language so it
|
will be used as an identifier for the language so it
|
||||||
should be short and unique.
|
should be short and unique.
|
||||||
phonemes (str, optional): Space-separated string of all allowed
|
phonemes (str, optional): Space-separated string of all allowed
|
||||||
phonemes in this language.
|
phonemes in this language. If either of the base phonemes
|
||||||
|
(c, v, cc, vv) are present in the grammar, the phoneme list must
|
||||||
|
at least include one example of each.
|
||||||
grammar (str): All allowed consonant (c) and vowel (v) combinations
|
grammar (str): All allowed consonant (c) and vowel (v) combinations
|
||||||
allowed to build up words. For example cvv would be a consonant
|
allowed to build up words. Grammars are broken into the base phonemes
|
||||||
followed by two vowels (would allow for a word like 'die').
|
(c, v, cc, vv) prioritizing the longer bases. So cvv would be a
|
||||||
|
the c + vv (would allow for a word like 'die' whereas
|
||||||
|
cvcvccc would be c+v+c+v+cc+c (a word like 'galosch').
|
||||||
word_length_variance (real): The variation of length of words.
|
word_length_variance (real): The variation of length of words.
|
||||||
0 means a minimal variance, higher variance may mean words
|
0 means a minimal variance, higher variance may mean words
|
||||||
have wildly varying length; this strongly affects how the
|
have wildly varying length; this strongly affects how the
|
||||||
language "looks".
|
language "looks".
|
||||||
|
noun_translate (bool, optional): If a proper noun, identified as a
|
||||||
|
capitalized word, should be translated or not. By default they
|
||||||
|
will not, allowing for e.g. the names of characters to be understandable.
|
||||||
noun_prefix (str, optional): A prefix to go before every noun
|
noun_prefix (str, optional): A prefix to go before every noun
|
||||||
in this language (if any).
|
in this language (if any).
|
||||||
noun_postfix (str, optuonal): A postfix to go after every noun
|
noun_postfix (str, optuonal): A postfix to go after every noun
|
||||||
|
|
@ -213,21 +229,28 @@ class LanguageHandler(DefaultScript):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if key in self.db.language_storage and not force:
|
if key in self.db.language_storage and not force:
|
||||||
raise LanguageExistsError
|
raise LanguageExistsError(
|
||||||
|
"Language is already created. Re-adding it will re-build"
|
||||||
# allowed grammar are grouped by length
|
" its dictionary map. Use 'force=True' keyword if you are sure.")
|
||||||
gramdict = defaultdict(list)
|
|
||||||
for gram in grammar.split():
|
|
||||||
gramdict[len(gram)].append(gram)
|
|
||||||
grammar = dict(gramdict)
|
|
||||||
|
|
||||||
# create grammar_component->phoneme mapping
|
# create grammar_component->phoneme mapping
|
||||||
# {"vv": ["ea", "oh", ...], ...}
|
# {"vv": ["ea", "oh", ...], ...}
|
||||||
grammar2phonemes = defaultdict(list)
|
grammar2phonemes = defaultdict(list)
|
||||||
for phoneme in phonemes.split():
|
for phoneme in phonemes.split():
|
||||||
|
if re.search("\W", phoneme):
|
||||||
|
raise LanguageError("The phoneme '%s' contains an invalid character" % phoneme)
|
||||||
gram = "".join(["v" if char in vowels else "c" for char in phoneme])
|
gram = "".join(["v" if char in vowels else "c" for char in phoneme])
|
||||||
grammar2phonemes[gram].append(phoneme)
|
grammar2phonemes[gram].append(phoneme)
|
||||||
|
|
||||||
|
# allowed grammar are grouped by length
|
||||||
|
gramdict = defaultdict(list)
|
||||||
|
for gram in grammar.split():
|
||||||
|
if re.search("\W|(!=[cv])", gram):
|
||||||
|
raise LanguageError("The grammar '%s' is invalid (only 'c' and 'v' are allowed)" % gram)
|
||||||
|
gramdict[len(gram)].append(gram)
|
||||||
|
grammar = dict(gramdict)
|
||||||
|
|
||||||
|
|
||||||
# create automatic translation
|
# create automatic translation
|
||||||
translation = {}
|
translation = {}
|
||||||
|
|
||||||
|
|
@ -240,8 +263,8 @@ class LanguageHandler(DefaultScript):
|
||||||
word = word.strip()
|
word = word.strip()
|
||||||
lword = len(word)
|
lword = len(word)
|
||||||
new_word = ""
|
new_word = ""
|
||||||
wlen = max(0, lword + sum(randint(-1,1) for i
|
wlen = max(0, lword + sum(randint(-1, 1) for i
|
||||||
in range(word_length_variance)))
|
in range(word_length_variance)))
|
||||||
if wlen not in grammar:
|
if wlen not in grammar:
|
||||||
# always create a translation, use random length
|
# always create a translation, use random length
|
||||||
structure = choice(grammar[choice(list(grammar))])
|
structure = choice(grammar[choice(list(grammar))])
|
||||||
|
|
@ -257,10 +280,11 @@ class LanguageHandler(DefaultScript):
|
||||||
translation.update(dict((key.lower(), value.lower()) for key, value in manual_translations.items()))
|
translation.update(dict((key.lower(), value.lower()) for key, value in manual_translations.items()))
|
||||||
|
|
||||||
# store data
|
# store data
|
||||||
storage = {"translation" : translation,
|
storage = {"translation": translation,
|
||||||
"grammar": grammar,
|
"grammar": grammar,
|
||||||
"grammar2phonemes": dict(grammar2phonemes),
|
"grammar2phonemes": dict(grammar2phonemes),
|
||||||
"word_length_variance": word_length_variance,
|
"word_length_variance": word_length_variance,
|
||||||
|
"noun_translate": noun_translate,
|
||||||
"noun_prefix": noun_prefix,
|
"noun_prefix": noun_prefix,
|
||||||
"noun_postfix": noun_postfix}
|
"noun_postfix": noun_postfix}
|
||||||
self.db.language_storage[key] = storage
|
self.db.language_storage[key] = storage
|
||||||
|
|
@ -282,34 +306,63 @@ class LanguageHandler(DefaultScript):
|
||||||
"""
|
"""
|
||||||
word = match.group()
|
word = match.group()
|
||||||
lword = len(word)
|
lword = len(word)
|
||||||
|
|
||||||
if len(word) <= self.level:
|
if len(word) <= self.level:
|
||||||
# below level. Don't translate
|
# below level. Don't translate
|
||||||
new_word = word
|
new_word = word
|
||||||
else:
|
else:
|
||||||
# translate the word
|
# try to translate the word from dictionary
|
||||||
new_word = self.language["translation"].get(word.lower(), "")
|
new_word = self.language["translation"].get(word.lower(), "")
|
||||||
if not new_word:
|
if not new_word:
|
||||||
if word.istitle():
|
# no dictionary translation. Generate one
|
||||||
# capitalized word we don't have a translation for -
|
|
||||||
# treat as a name (don't translate)
|
# find out what preceeded this word
|
||||||
new_word = "%s%s%s" % (self.language["noun_prefix"], word, self.language["noun_postfix"])
|
wpos = match.start()
|
||||||
else:
|
preceeding = match.string[:wpos].strip()
|
||||||
# make up translation on the fly. Length can
|
start_sentence = preceeding.endswith(".") or not preceeding
|
||||||
# vary from un-translated word.
|
|
||||||
wlen = max(0, lword + sum(randint(-1,1) for i
|
# make up translation on the fly. Length can
|
||||||
in range(self.language["word_length_variance"])))
|
# vary from un-translated word.
|
||||||
grammar = self.language["grammar"]
|
wlen = max(0, lword + sum(randint(-1, 1) for i
|
||||||
if wlen not in grammar:
|
in range(self.language["word_length_variance"])))
|
||||||
|
grammar = self.language["grammar"]
|
||||||
|
if wlen not in grammar:
|
||||||
|
if randint(0, 1) == 0:
|
||||||
# this word has no direct translation!
|
# this word has no direct translation!
|
||||||
return ""
|
wlen = 0
|
||||||
|
new_word = ''
|
||||||
|
else:
|
||||||
|
# use random word length
|
||||||
|
wlen = choice(grammar.keys())
|
||||||
|
|
||||||
|
if wlen:
|
||||||
structure = choice(grammar[wlen])
|
structure = choice(grammar[wlen])
|
||||||
grammar2phonemes = self.language["grammar2phonemes"]
|
grammar2phonemes = self.language["grammar2phonemes"]
|
||||||
for match in _RE_GRAMMAR.finditer(structure):
|
for match in _RE_GRAMMAR.finditer(structure):
|
||||||
# there are only four combinations: vv,cc,c,v
|
# there are only four combinations: vv,cc,c,v
|
||||||
new_word += choice(grammar2phonemes[match.group()])
|
try:
|
||||||
if word.istitle():
|
new_word += choice(grammar2phonemes[match.group()])
|
||||||
# capitalize words the same way
|
except KeyError:
|
||||||
new_word = new_word.capitalize()
|
logger.log_trace("You need to supply at least one example of each of "
|
||||||
|
"the four base phonemes (c, v, cc, vv)")
|
||||||
|
# abort translation here
|
||||||
|
new_word = ''
|
||||||
|
break
|
||||||
|
|
||||||
|
if word.istitle():
|
||||||
|
title_word = ''
|
||||||
|
if not start_sentence and not self.language.get("noun_translate", False):
|
||||||
|
# don't translate what we identify as proper nouns (names)
|
||||||
|
title_word = word
|
||||||
|
elif new_word:
|
||||||
|
title_word = new_word
|
||||||
|
|
||||||
|
if title_word:
|
||||||
|
# Regardless of if we translate or not, we will add the custom prefix/postfixes
|
||||||
|
new_word = "%s%s%s" % (self.language["noun_prefix"],
|
||||||
|
title_word.capitalize(),
|
||||||
|
self.language["noun_postfix"])
|
||||||
|
|
||||||
if len(word) > 1 and word.isupper():
|
if len(word) > 1 and word.isupper():
|
||||||
# keep LOUD words loud also when translated
|
# keep LOUD words loud also when translated
|
||||||
new_word = new_word.upper()
|
new_word = new_word.upper()
|
||||||
|
|
@ -341,12 +394,16 @@ class LanguageHandler(DefaultScript):
|
||||||
|
|
||||||
# configuring the translation
|
# configuring the translation
|
||||||
self.level = int(10 * (1.0 - max(0, min(level, 1.0))))
|
self.level = int(10 * (1.0 - max(0, min(level, 1.0))))
|
||||||
return _RE_WORD.sub(self._translate_sub, text)
|
translation = _RE_WORD.sub(self._translate_sub, text)
|
||||||
|
# the substitution may create too long empty spaces, remove those
|
||||||
|
return _RE_EXTRA_CHARS.sub("", translation)
|
||||||
|
|
||||||
|
|
||||||
# Language access functions
|
# Language access functions
|
||||||
|
|
||||||
_LANGUAGE_HANDLER = None
|
_LANGUAGE_HANDLER = None
|
||||||
|
|
||||||
|
|
||||||
def obfuscate_language(text, level=0.0, language="default"):
|
def obfuscate_language(text, level=0.0, language="default"):
|
||||||
"""
|
"""
|
||||||
Main access method for the language parser.
|
Main access method for the language parser.
|
||||||
|
|
@ -412,7 +469,6 @@ def available_languages():
|
||||||
return list(_LANGUAGE_HANDLER.attributes.get("language_storage", {}))
|
return list(_LANGUAGE_HANDLER.attributes.get("language_storage", {}))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# Whisper obscuration
|
# Whisper obscuration
|
||||||
|
|
@ -427,6 +483,7 @@ def available_languages():
|
||||||
#
|
#
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
_RE_WHISPER_OBSCURE = [
|
_RE_WHISPER_OBSCURE = [
|
||||||
re.compile(r"^$", _RE_FLAGS), # This is a Test! #0 full whisper
|
re.compile(r"^$", _RE_FLAGS), # This is a Test! #0 full whisper
|
||||||
re.compile(r"[ae]", _RE_FLAGS), # This -s - Test! #1 add uy
|
re.compile(r"[ae]", _RE_FLAGS), # This -s - Test! #1 add uy
|
||||||
|
|
@ -434,7 +491,7 @@ _RE_WHISPER_OBSCURE = [
|
||||||
re.compile(r"[aeiouy]", _RE_FLAGS), # Th-s -s - T-st! #3 add all consonants
|
re.compile(r"[aeiouy]", _RE_FLAGS), # Th-s -s - T-st! #3 add all consonants
|
||||||
re.compile(r"[aeiouybdhjlmnpqrv]", _RE_FLAGS), # T--s -s - T-st! #4 add hard consonants
|
re.compile(r"[aeiouybdhjlmnpqrv]", _RE_FLAGS), # T--s -s - T-st! #4 add hard consonants
|
||||||
re.compile(r"[a-eg-rt-z]", _RE_FLAGS), # T--s -s - T-s-! #5 add all capitals
|
re.compile(r"[a-eg-rt-z]", _RE_FLAGS), # T--s -s - T-s-! #5 add all capitals
|
||||||
re.compile(r"[A-EG-RT-Za-eg-rt-z]", _RE_FLAGS), # ---s -s - --s-! #6 add f
|
re.compile(r"[A-EG-RT-Za-eg-rt-z]", _RE_FLAGS), # ---s -s - --s-! #6 add f
|
||||||
re.compile(r"[A-EG-RT-Za-rt-z]", _RE_FLAGS), # ---s -s - --s-! #7 add s
|
re.compile(r"[A-EG-RT-Za-rt-z]", _RE_FLAGS), # ---s -s - --s-! #7 add s
|
||||||
re.compile(r"[A-EG-RT-Za-z]", _RE_FLAGS), # ---- -- - ----! #8 add capital F
|
re.compile(r"[A-EG-RT-Za-z]", _RE_FLAGS), # ---- -- - ----! #8 add capital F
|
||||||
re.compile(r"[A-RT-Za-z]", _RE_FLAGS), # ---- -- - ----! #9 add capital S
|
re.compile(r"[A-RT-Za-z]", _RE_FLAGS), # ---- -- - ----! #9 add capital S
|
||||||
|
|
@ -460,4 +517,3 @@ def obfuscate_whisper(whisper, level=0.0):
|
||||||
level = min(max(0.0, level), 1.0)
|
level = min(max(0.0, level), 1.0)
|
||||||
olevel = int(13.0 * level)
|
olevel = int(13.0 * level)
|
||||||
return _RE_WHISPER_OBSCURE[olevel].sub('...' if olevel == 13.0 else '-', whisper)
|
return _RE_WHISPER_OBSCURE[olevel].sub('...' if olevel == 13.0 else '-', whisper)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,14 +81,14 @@ Verbose Installation Instructions:
|
||||||
Import the `ContribRPRoom` class:
|
Import the `ContribRPRoom` class:
|
||||||
`from evennia.contrib.rpsystem import ContribRPRoom`
|
`from evennia.contrib.rpsystem import ContribRPRoom`
|
||||||
Inherit `ContribRPRoom`:
|
Inherit `ContribRPRoom`:
|
||||||
Change `class Character(DefaultRoom):` to
|
Change `class Room(DefaultRoom):` to
|
||||||
`class Character(ContribRPRoom):`
|
`class Room(ContribRPRoom):`
|
||||||
3. In `typeclasses/objects.py`
|
3. In `typeclasses/objects.py`
|
||||||
Import the `ContribRPObject` class:
|
Import the `ContribRPObject` class:
|
||||||
`from evennia.contrib.rpsystem import ContribRPObject`
|
`from evennia.contrib.rpsystem import ContribRPObject`
|
||||||
Inherit `ContribRPObject`:
|
Inherit `ContribRPObject`:
|
||||||
Change `class Character(DefaultObject):` to
|
Change `class Object(DefaultObject):` to
|
||||||
`class Character(ContribRPObject):`
|
`class Object(ContribRPObject):`
|
||||||
4. Reload the server (@reload or from console: "evennia reload")
|
4. Reload the server (@reload or from console: "evennia reload")
|
||||||
5. Force typeclass updates as required. Example for your character:
|
5. Force typeclass updates as required. Example for your character:
|
||||||
@type/reset/force me = typeclasses.characters.Character
|
@type/reset/force me = typeclasses.characters.Character
|
||||||
|
|
@ -127,10 +127,10 @@ _NUM_SEP = "-"
|
||||||
# Texts
|
# Texts
|
||||||
|
|
||||||
_EMOTE_NOMATCH_ERROR = \
|
_EMOTE_NOMATCH_ERROR = \
|
||||||
"""|RNo match for |r{ref}|R.|n"""
|
"""|RNo match for |r{ref}|R.|n"""
|
||||||
|
|
||||||
_EMOTE_MULTIMATCH_ERROR = \
|
_EMOTE_MULTIMATCH_ERROR = \
|
||||||
"""|RMultiple possibilities for {ref}:
|
"""|RMultiple possibilities for {ref}:
|
||||||
|r{reflist}|n"""
|
|r{reflist}|n"""
|
||||||
|
|
||||||
_RE_FLAGS = re.MULTILINE + re.IGNORECASE + re.UNICODE
|
_RE_FLAGS = re.MULTILINE + re.IGNORECASE + re.UNICODE
|
||||||
|
|
@ -142,7 +142,7 @@ _RE_PREFIX = re.compile(r"^%s" % _PREFIX, re.UNICODE)
|
||||||
# marker. So entering "/tall man" will return groups ("", "tall")
|
# marker. So entering "/tall man" will return groups ("", "tall")
|
||||||
# and "/2-tall man" will return groups ("2", "tall").
|
# and "/2-tall man" will return groups ("2", "tall").
|
||||||
_RE_OBJ_REF_START = re.compile(r"%s(?:([0-9]+)%s)*(\w+)" %
|
_RE_OBJ_REF_START = re.compile(r"%s(?:([0-9]+)%s)*(\w+)" %
|
||||||
(_PREFIX, _NUM_SEP), _RE_FLAGS)
|
(_PREFIX, _NUM_SEP), _RE_FLAGS)
|
||||||
|
|
||||||
_RE_LEFT_BRACKETS = re.compile(r"\{+", _RE_FLAGS)
|
_RE_LEFT_BRACKETS = re.compile(r"\{+", _RE_FLAGS)
|
||||||
_RE_RIGHT_BRACKETS = re.compile(r"\}+", _RE_FLAGS)
|
_RE_RIGHT_BRACKETS = re.compile(r"\}+", _RE_FLAGS)
|
||||||
|
|
@ -168,6 +168,7 @@ _RE_LANGUAGE = re.compile(r"(?:\((\w+)\))*(\".+?\")")
|
||||||
# 2) for every person seeing the emote, parse this
|
# 2) for every person seeing the emote, parse this
|
||||||
# intermediary form into the one valid for that char.
|
# intermediary form into the one valid for that char.
|
||||||
|
|
||||||
|
|
||||||
class EmoteError(Exception):
|
class EmoteError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -240,6 +241,7 @@ def ordered_permutation_regex(sentence):
|
||||||
regex = r"|".join(sorted(set(solution), key=len, reverse=True))
|
regex = r"|".join(sorted(set(solution), key=len, reverse=True))
|
||||||
return regex
|
return regex
|
||||||
|
|
||||||
|
|
||||||
def regex_tuple_from_key_alias(obj):
|
def regex_tuple_from_key_alias(obj):
|
||||||
"""
|
"""
|
||||||
This will build a regex tuple for any object, not just from those
|
This will build a regex tuple for any object, not just from those
|
||||||
|
|
@ -358,13 +360,13 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False):
|
||||||
"""
|
"""
|
||||||
# Load all candidate regex tuples [(regex, obj, sdesc/recog),...]
|
# Load all candidate regex tuples [(regex, obj, sdesc/recog),...]
|
||||||
candidate_regexes = \
|
candidate_regexes = \
|
||||||
([(_RE_SELF_REF, sender, sender.sdesc.get())] if hasattr(sender, "sdesc") else [])+ \
|
([(_RE_SELF_REF, sender, sender.sdesc.get())] if hasattr(sender, "sdesc") else []) + \
|
||||||
([sender.recog.get_regex_tuple(obj) for obj in candidates] if hasattr(sender, "recog") else []) + \
|
([sender.recog.get_regex_tuple(obj) for obj in candidates] if hasattr(sender, "recog") else []) + \
|
||||||
[obj.sdesc.get_regex_tuple()
|
[obj.sdesc.get_regex_tuple()
|
||||||
for obj in candidates if hasattr(obj, "sdesc")] + \
|
for obj in candidates if hasattr(obj, "sdesc")] + \
|
||||||
[regex_tuple_from_key_alias(obj) # handle objects without sdescs
|
[regex_tuple_from_key_alias(obj) # handle objects without sdescs
|
||||||
for obj in candidates if not (hasattr(obj, "recog") and
|
for obj in candidates if not (hasattr(obj, "recog") and
|
||||||
hasattr(obj, "sdesc"))]
|
hasattr(obj, "sdesc"))]
|
||||||
|
|
||||||
# filter out non-found data
|
# filter out non-found data
|
||||||
candidate_regexes = [tup for tup in candidate_regexes if tup]
|
candidate_regexes = [tup for tup in candidate_regexes if tup]
|
||||||
|
|
@ -387,7 +389,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False):
|
||||||
# start index forward for all candidates.
|
# start index forward for all candidates.
|
||||||
|
|
||||||
# first see if there is a number given (e.g. 1-tall)
|
# first see if there is a number given (e.g. 1-tall)
|
||||||
num_identifier, _ = marker_match.groups("") # return "" if no match, rather than None
|
num_identifier, _ = marker_match.groups("") # return "" if no match, rather than None
|
||||||
istart0 = marker_match.start()
|
istart0 = marker_match.start()
|
||||||
istart = istart0
|
istart = istart0
|
||||||
|
|
||||||
|
|
@ -418,7 +420,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False):
|
||||||
else:
|
else:
|
||||||
# multi-match.
|
# multi-match.
|
||||||
# was a numerical identifier given to help us separate the multi-match?
|
# was a numerical identifier given to help us separate the multi-match?
|
||||||
inum = min(max(0, int(num_identifier) - 1), nmatches-1) if num_identifier else None
|
inum = min(max(0, int(num_identifier) - 1), nmatches - 1) if num_identifier else None
|
||||||
if inum is not None:
|
if inum is not None:
|
||||||
# A valid inum is given. Use this to separate data.
|
# A valid inum is given. Use this to separate data.
|
||||||
obj = bestmatches[inum][0]
|
obj = bestmatches[inum][0]
|
||||||
|
|
@ -438,10 +440,10 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False):
|
||||||
mapping[key] = obj
|
mapping[key] = obj
|
||||||
else:
|
else:
|
||||||
refname = marker_match.group()
|
refname = marker_match.group()
|
||||||
reflist = ["%s%s%s (%s%s)" % (inum+1, _NUM_SEP,
|
reflist = ["%s%s%s (%s%s)" % (inum + 1, _NUM_SEP,
|
||||||
_RE_PREFIX.sub("", refname), text,
|
_RE_PREFIX.sub("", refname), text,
|
||||||
" (%s)" % sender.key if sender == ob else "")
|
" (%s)" % sender.key if sender == ob else "")
|
||||||
for inum, (ob, text) in enumerate(obj)]
|
for inum, (ob, text) in enumerate(obj)]
|
||||||
errors.append(_EMOTE_MULTIMATCH_ERROR.format(
|
errors.append(_EMOTE_MULTIMATCH_ERROR.format(
|
||||||
ref=marker_match.group(), reflist="\n ".join(reflist)))
|
ref=marker_match.group(), reflist="\n ".join(reflist)))
|
||||||
if search_mode:
|
if search_mode:
|
||||||
|
|
@ -532,8 +534,8 @@ def send_emote(sender, receivers, emote, anonymous_add="first"):
|
||||||
receiver_sdesc_mapping = dict((ref, process_recog(recog_get(obj), obj)) for ref, obj in obj_mapping.items())
|
receiver_sdesc_mapping = dict((ref, process_recog(recog_get(obj), obj)) for ref, obj in obj_mapping.items())
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
receiver_sdesc_mapping = dict((ref, process_sdesc(obj.sdesc.get(), obj)
|
receiver_sdesc_mapping = dict((ref, process_sdesc(obj.sdesc.get(), obj)
|
||||||
if hasattr(obj, "sdesc") else process_sdesc(obj.key, obj))
|
if hasattr(obj, "sdesc") else process_sdesc(obj.key, obj))
|
||||||
for ref, obj in obj_mapping.items())
|
for ref, obj in obj_mapping.items())
|
||||||
# make sure receiver always sees their real name
|
# make sure receiver always sees their real name
|
||||||
rkey = "#%i" % receiver.id
|
rkey = "#%i" % receiver.id
|
||||||
if rkey in receiver_sdesc_mapping:
|
if rkey in receiver_sdesc_mapping:
|
||||||
|
|
@ -546,6 +548,7 @@ def send_emote(sender, receivers, emote, anonymous_add="first"):
|
||||||
# Handlers for sdesc and recog
|
# Handlers for sdesc and recog
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class SdescHandler(object):
|
class SdescHandler(object):
|
||||||
"""
|
"""
|
||||||
This Handler wraps all operations with sdescs. We
|
This Handler wraps all operations with sdescs. We
|
||||||
|
|
@ -559,6 +562,7 @@ class SdescHandler(object):
|
||||||
_regex - an empty dictionary
|
_regex - an empty dictionary
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
"""
|
"""
|
||||||
Initialize the handler
|
Initialize the handler
|
||||||
|
|
@ -600,10 +604,10 @@ class SdescHandler(object):
|
||||||
"""
|
"""
|
||||||
# strip emote components from sdesc
|
# strip emote components from sdesc
|
||||||
sdesc = _RE_REF.sub(r"\1",
|
sdesc = _RE_REF.sub(r"\1",
|
||||||
_RE_REF_LANG.sub(r"\1",
|
_RE_REF_LANG.sub(r"\1",
|
||||||
_RE_SELF_REF.sub(r"",
|
_RE_SELF_REF.sub(r"",
|
||||||
_RE_LANGUAGE.sub(r"",
|
_RE_LANGUAGE.sub(r"",
|
||||||
_RE_OBJ_REF_START.sub(r"", sdesc)))))
|
_RE_OBJ_REF_START.sub(r"", sdesc)))))
|
||||||
|
|
||||||
# make an sdesc clean of ANSI codes
|
# make an sdesc clean of ANSI codes
|
||||||
cleaned_sdesc = ansi.strip_ansi(sdesc)
|
cleaned_sdesc = ansi.strip_ansi(sdesc)
|
||||||
|
|
@ -656,6 +660,7 @@ class RecogHandler(object):
|
||||||
_recog_obj2regex
|
_recog_obj2regex
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
"""
|
"""
|
||||||
Initialize the handler
|
Initialize the handler
|
||||||
|
|
@ -679,9 +684,9 @@ class RecogHandler(object):
|
||||||
obj2regex = self.obj.attributes.get("_recog_obj2regex", default={})
|
obj2regex = self.obj.attributes.get("_recog_obj2regex", default={})
|
||||||
obj2recog = self.obj.attributes.get("_recog_obj2recog", default={})
|
obj2recog = self.obj.attributes.get("_recog_obj2recog", default={})
|
||||||
self.obj2regex = dict((obj, re.compile(regex, _RE_FLAGS))
|
self.obj2regex = dict((obj, re.compile(regex, _RE_FLAGS))
|
||||||
for obj, regex in obj2regex.items() if obj)
|
for obj, regex in obj2regex.items() if obj)
|
||||||
self.obj2recog = dict((obj, recog)
|
self.obj2recog = dict((obj, recog)
|
||||||
for obj, recog in obj2recog.items() if obj)
|
for obj, recog in obj2recog.items() if obj)
|
||||||
|
|
||||||
def add(self, obj, recog, max_length=60):
|
def add(self, obj, recog, max_length=60):
|
||||||
"""
|
"""
|
||||||
|
|
@ -705,10 +710,10 @@ class RecogHandler(object):
|
||||||
"""
|
"""
|
||||||
# strip emote components from recog
|
# strip emote components from recog
|
||||||
recog = _RE_REF.sub(r"\1",
|
recog = _RE_REF.sub(r"\1",
|
||||||
_RE_REF_LANG.sub(r"\1",
|
_RE_REF_LANG.sub(r"\1",
|
||||||
_RE_SELF_REF.sub(r"",
|
_RE_SELF_REF.sub(r"",
|
||||||
_RE_LANGUAGE.sub(r"",
|
_RE_LANGUAGE.sub(r"",
|
||||||
_RE_OBJ_REF_START.sub(r"", recog)))))
|
_RE_OBJ_REF_START.sub(r"", recog)))))
|
||||||
|
|
||||||
# make an recog clean of ANSI codes
|
# make an recog clean of ANSI codes
|
||||||
cleaned_recog = ansi.strip_ansi(recog)
|
cleaned_recog = ansi.strip_ansi(recog)
|
||||||
|
|
@ -751,7 +756,7 @@ class RecogHandler(object):
|
||||||
# to avoid revealing masked characters. If lock
|
# to avoid revealing masked characters. If lock
|
||||||
# does not exist, pass automatically.
|
# does not exist, pass automatically.
|
||||||
return self.obj2recog.get(obj, obj.sdesc.get()
|
return self.obj2recog.get(obj, obj.sdesc.get()
|
||||||
if hasattr(obj, "sdesc") else obj.key)
|
if hasattr(obj, "sdesc") else obj.key)
|
||||||
else:
|
else:
|
||||||
# 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
|
||||||
|
|
@ -785,6 +790,7 @@ class RecogHandler(object):
|
||||||
|
|
||||||
class RPCommand(Command):
|
class RPCommand(Command):
|
||||||
"simple parent"
|
"simple parent"
|
||||||
|
|
||||||
def parse(self):
|
def parse(self):
|
||||||
"strip extra whitespace"
|
"strip extra whitespace"
|
||||||
self.args = self.args.strip()
|
self.args = self.args.strip()
|
||||||
|
|
@ -828,7 +834,7 @@ class CmdEmote(RPCommand): # replaces the main emote
|
||||||
send_emote(self.caller, targets, emote, anonymous_add='first')
|
send_emote(self.caller, targets, emote, anonymous_add='first')
|
||||||
|
|
||||||
|
|
||||||
class CmdSay(RPCommand): # replaces standard say
|
class CmdSay(RPCommand): # replaces standard say
|
||||||
"""
|
"""
|
||||||
speak as your character
|
speak as your character
|
||||||
|
|
||||||
|
|
@ -843,7 +849,6 @@ class CmdSay(RPCommand): # replaces standard say
|
||||||
locks = "cmd:all()"
|
locks = "cmd:all()"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
||||||
"Run the say command"
|
"Run the say command"
|
||||||
|
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
|
|
@ -853,14 +858,14 @@ class CmdSay(RPCommand): # replaces standard say
|
||||||
return
|
return
|
||||||
|
|
||||||
# calling the speech hook on the location
|
# calling the speech hook on the location
|
||||||
speech = caller.location.at_say(caller, self.args)
|
speech = caller.location.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)
|
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)
|
||||||
|
|
||||||
|
|
||||||
class CmdSdesc(RPCommand): # set/look at own sdesc
|
class CmdSdesc(RPCommand): # set/look at own sdesc
|
||||||
"""
|
"""
|
||||||
Assign yourself a short description (sdesc).
|
Assign yourself a short description (sdesc).
|
||||||
|
|
||||||
|
|
@ -884,13 +889,13 @@ class CmdSdesc(RPCommand): # set/look at own sdesc
|
||||||
sdesc = _RE_CHAREND.sub("", self.args)
|
sdesc = _RE_CHAREND.sub("", self.args)
|
||||||
try:
|
try:
|
||||||
sdesc = caller.sdesc.add(sdesc)
|
sdesc = caller.sdesc.add(sdesc)
|
||||||
except SdescError, err:
|
except SdescError as err:
|
||||||
caller.msg(err)
|
caller.msg(err)
|
||||||
return
|
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))
|
||||||
|
|
||||||
|
|
||||||
class CmdPose(RPCommand): # set current pose and default pose
|
class CmdPose(RPCommand): # set current pose and default pose
|
||||||
"""
|
"""
|
||||||
Set a static pose
|
Set a static pose
|
||||||
|
|
||||||
|
|
@ -976,7 +981,7 @@ class CmdPose(RPCommand): # set current pose and default pose
|
||||||
# set the pose. We do one-time ref->sdesc mapping here.
|
# set the pose. We do one-time ref->sdesc mapping here.
|
||||||
parsed, mapping = parse_sdescs_and_recogs(caller, caller.location.contents, pose)
|
parsed, mapping = parse_sdescs_and_recogs(caller, caller.location.contents, pose)
|
||||||
mapping = dict((ref, obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key)
|
mapping = dict((ref, obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key)
|
||||||
for ref, obj in mapping.iteritems())
|
for ref, obj in mapping.iteritems())
|
||||||
pose = parsed.format(**mapping)
|
pose = parsed.format(**mapping)
|
||||||
|
|
||||||
if len(target_name) + len(pose) > 60:
|
if len(target_name) + len(pose) > 60:
|
||||||
|
|
@ -988,7 +993,7 @@ class CmdPose(RPCommand): # set current pose and default pose
|
||||||
caller.msg("Pose will read '%s %s'." % (target_name, pose))
|
caller.msg("Pose will read '%s %s'." % (target_name, pose))
|
||||||
|
|
||||||
|
|
||||||
class CmdRecog(RPCommand): # assign personal alias to object in room
|
class CmdRecog(RPCommand): # assign personal alias to object in room
|
||||||
"""
|
"""
|
||||||
Recognize another person in the same room.
|
Recognize another person in the same room.
|
||||||
|
|
||||||
|
|
@ -1034,11 +1039,11 @@ class CmdRecog(RPCommand): # assign personal alias to object in room
|
||||||
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 = ["%s%s%s (%s%s)" % (inum+1, _NUM_SEP,
|
reflist = ["%s%s%s (%s%s)" % (inum + 1, _NUM_SEP,
|
||||||
_RE_PREFIX.sub("", sdesc), caller.recog.get(obj),
|
_RE_PREFIX.sub("", sdesc), caller.recog.get(obj),
|
||||||
" (%s)" % caller.key if caller == obj else "")
|
" (%s)" % caller.key if caller == obj else "")
|
||||||
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:
|
||||||
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):
|
||||||
|
|
@ -1053,7 +1058,7 @@ class CmdRecog(RPCommand): # assign personal alias to object in room
|
||||||
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)
|
||||||
except RecogError, err:
|
except RecogError as err:
|
||||||
caller.msg(err)
|
caller.msg(err)
|
||||||
return
|
return
|
||||||
caller.msg("%s will now remember |w%s|n as |w%s|n." % (caller.key, sdesc, alias))
|
caller.msg("%s will now remember |w%s|n as |w%s|n." % (caller.key, sdesc, alias))
|
||||||
|
|
@ -1095,7 +1100,7 @@ class CmdMask(RPCommand):
|
||||||
caller.sdesc.add(sdesc)
|
caller.sdesc.add(sdesc)
|
||||||
caller.msg("You wear a mask as '%s'." % sdesc)
|
caller.msg("You wear a mask as '%s'." % sdesc)
|
||||||
else:
|
else:
|
||||||
#unmask
|
# unmask
|
||||||
old_sdesc = caller.db.unmasked_sdesc
|
old_sdesc = caller.db.unmasked_sdesc
|
||||||
if not old_sdesc:
|
if not old_sdesc:
|
||||||
caller.msg("You are not wearing a mask.")
|
caller.msg("You are not wearing a mask.")
|
||||||
|
|
@ -1110,6 +1115,7 @@ class RPSystemCmdSet(CmdSet):
|
||||||
"""
|
"""
|
||||||
Mix-in for adding rp-commands to default cmdset.
|
Mix-in for adding rp-commands to default cmdset.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def at_cmdset_creation(self):
|
def at_cmdset_creation(self):
|
||||||
self.add(CmdEmote())
|
self.add(CmdEmote())
|
||||||
self.add(CmdSay())
|
self.add(CmdSay())
|
||||||
|
|
@ -1194,7 +1200,7 @@ class ContribRPObject(DefaultObject):
|
||||||
below.
|
below.
|
||||||
exact (bool): if unset (default) - prefers to match to beginning of
|
exact (bool): if unset (default) - prefers to match to beginning of
|
||||||
string rather than not matching at all. If set, requires
|
string rather than not matching at all. If set, requires
|
||||||
exact mathing of entire string.
|
exact matching of entire string.
|
||||||
candidates (list of objects): this is an optional custom list of objects
|
candidates (list of objects): this is an optional custom list of objects
|
||||||
to search (filter) between. It is ignored if `global_search`
|
to search (filter) between. It is ignored if `global_search`
|
||||||
is given. If not set, this list will automatically be defined
|
is given. If not set, this list will automatically be defined
|
||||||
|
|
@ -1203,14 +1209,14 @@ class ContribRPObject(DefaultObject):
|
||||||
nofound_string (str): optional custom string for not-found error message.
|
nofound_string (str): optional custom string for not-found error message.
|
||||||
multimatch_string (str): optional custom string for multimatch error header.
|
multimatch_string (str): optional custom string for multimatch error header.
|
||||||
use_dbref (bool or None): If None, only turn off use_dbref if we are of a lower
|
use_dbref (bool or None): If None, only turn off use_dbref if we are of a lower
|
||||||
permission than Builders. Otherwise, honor the True/False value.
|
permission than Builder. Otherwise, honor the True/False value.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
match (Object, None or list): will return an Object/None if `quiet=False`,
|
match (Object, None or list): will return an Object/None if `quiet=False`,
|
||||||
otherwise it will return a list of 0, 1 or more matches.
|
otherwise it will return a list of 0, 1 or more matches.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
To find Players, use eg. `evennia.player_search`. If
|
To find Accounts, use eg. `evennia.account_search`. If
|
||||||
`quiet=False`, error messages will be handled by
|
`quiet=False`, error messages will be handled by
|
||||||
`settings.SEARCH_AT_RESULT` and echoed automatically (on
|
`settings.SEARCH_AT_RESULT` and echoed automatically (on
|
||||||
error, return will be `None`). If `quiet=True`, the error
|
error, return will be `None`). If `quiet=True`, the error
|
||||||
|
|
@ -1228,10 +1234,10 @@ class ContribRPObject(DefaultObject):
|
||||||
|
|
||||||
if use_nicks:
|
if use_nicks:
|
||||||
# do nick-replacement on search
|
# do nick-replacement on search
|
||||||
searchdata = self.nicks.nickreplace(searchdata, categories=("object", "player"), include_player=True)
|
searchdata = self.nicks.nickreplace(searchdata, categories=("object", "account"), include_account=True)
|
||||||
|
|
||||||
if(global_search or (is_string and searchdata.startswith("#") and
|
if(global_search or (is_string and searchdata.startswith("#") and
|
||||||
len(searchdata) > 1 and searchdata[1:].isdigit())):
|
len(searchdata) > 1 and searchdata[1:].isdigit())):
|
||||||
# only allow exact matching if searching the entire database
|
# only allow exact matching if searching the entire database
|
||||||
# or unique #dbrefs
|
# or unique #dbrefs
|
||||||
exact = True
|
exact = True
|
||||||
|
|
@ -1256,18 +1262,19 @@ class ContribRPObject(DefaultObject):
|
||||||
candidates.append(self)
|
candidates.append(self)
|
||||||
|
|
||||||
# the sdesc-related substitution
|
# the sdesc-related substitution
|
||||||
is_builder = self.locks.check_lockstring(self, "perm(Builders)")
|
is_builder = self.locks.check_lockstring(self, "perm(Builder)")
|
||||||
use_dbref = is_builder if use_dbref is None else use_dbref
|
use_dbref = is_builder if use_dbref is None else use_dbref
|
||||||
search_obj = lambda string: ObjectDB.objects.object_search(string,
|
|
||||||
attribute_name=attribute_name,
|
def search_obj(string): return ObjectDB.objects.object_search(string,
|
||||||
typeclass=typeclass,
|
attribute_name=attribute_name,
|
||||||
candidates=candidates,
|
typeclass=typeclass,
|
||||||
exact=exact,
|
candidates=candidates,
|
||||||
use_dbref=use_dbref)
|
exact=exact,
|
||||||
|
use_dbref=use_dbref)
|
||||||
|
|
||||||
if candidates:
|
if candidates:
|
||||||
candidates = parse_sdescs_and_recogs(self, candidates,
|
candidates = parse_sdescs_and_recogs(self, candidates,
|
||||||
_PREFIX + searchdata, search_mode=True)
|
_PREFIX + searchdata, search_mode=True)
|
||||||
results = []
|
results = []
|
||||||
for candidate in candidates:
|
for candidate in candidates:
|
||||||
# we search by candidate keys here; this allows full error
|
# we search by candidate keys here; this allows full error
|
||||||
|
|
@ -1288,15 +1295,15 @@ class ContribRPObject(DefaultObject):
|
||||||
|
|
||||||
if quiet:
|
if quiet:
|
||||||
return results
|
return results
|
||||||
return _AT_SEARCH_RESULT(results, self, query=searchdata,
|
return _AT_SEARCH_RESULT(results, self, query=searchdata,
|
||||||
nofound_string=nofound_string, multimatch_string=multimatch_string)
|
nofound_string=nofound_string, multimatch_string=multimatch_string)
|
||||||
|
|
||||||
def get_display_name(self, looker, **kwargs):
|
def get_display_name(self, looker, **kwargs):
|
||||||
"""
|
"""
|
||||||
Displays the name of the object in a viewer-aware manner.
|
Displays the name of the object in a viewer-aware manner.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
looker (TypedObject): The object or player that is looking
|
looker (TypedObject): The object or account that is looking
|
||||||
at/getting inforamtion for this object.
|
at/getting inforamtion for this object.
|
||||||
|
|
||||||
Kwargs:
|
Kwargs:
|
||||||
|
|
@ -1336,13 +1343,13 @@ class ContribRPObject(DefaultObject):
|
||||||
return ""
|
return ""
|
||||||
# get and identify all objects
|
# get and identify all objects
|
||||||
visible = (con for con in self.contents if con != looker and
|
visible = (con for con in self.contents if con != looker and
|
||||||
con.access(looker, "view"))
|
con.access(looker, "view"))
|
||||||
exits, users, things = [], [], []
|
exits, users, things = [], [], []
|
||||||
for con in visible:
|
for con in visible:
|
||||||
key = con.get_display_name(looker, pose=True)
|
key = con.get_display_name(looker, pose=True)
|
||||||
if con.destination:
|
if con.destination:
|
||||||
exits.append(key)
|
exits.append(key)
|
||||||
elif con.has_player:
|
elif con.has_account:
|
||||||
users.append(key)
|
users.append(key)
|
||||||
else:
|
else:
|
||||||
things.append(key)
|
things.append(key)
|
||||||
|
|
@ -1383,7 +1390,7 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject):
|
||||||
Displays the name of the object in a viewer-aware manner.
|
Displays the name of the object in a viewer-aware manner.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
looker (TypedObject): The object or player that is looking
|
looker (TypedObject): The object or account that is looking
|
||||||
at/getting inforamtion for this object.
|
at/getting inforamtion for this object.
|
||||||
|
|
||||||
Kwargs:
|
Kwargs:
|
||||||
|
|
@ -1488,4 +1495,4 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject):
|
||||||
return "%s|w%s|n" % ("|W(%s)" % language if language else "", text)
|
return "%s|w%s|n" % ("|W(%s)" % language if language else "", text)
|
||||||
|
|
||||||
#from evennia.contrib import rplanguage
|
#from evennia.contrib import rplanguage
|
||||||
#return "|w%s|n" % rplanguage.obfuscate_language(text, level=1.0)
|
# return "|w%s|n" % rplanguage.obfuscate_language(text, level=1.0)
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ class SimpleDoor(DefaultExit):
|
||||||
sides using `exitname.setlock("traverse:false())`
|
sides using `exitname.setlock("traverse:false())`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def at_object_creation(self):
|
def at_object_creation(self):
|
||||||
"""
|
"""
|
||||||
Called the very first time the door is created.
|
Called the very first time the door is created.
|
||||||
|
|
@ -115,7 +116,7 @@ class CmdOpen(default_cmds.CmdOpen):
|
||||||
self.caller.msg("Note: A door-type exit was created - ignored eventual custom return-exit type.")
|
self.caller.msg("Note: A door-type exit was created - ignored eventual custom return-exit type.")
|
||||||
self.return_exit_already_created = True
|
self.return_exit_already_created = True
|
||||||
back_exit = self.create_exit(exit_name, destination, location,
|
back_exit = self.create_exit(exit_name, destination, location,
|
||||||
exit_aliases=exit_aliases, typeclass=typeclass)
|
exit_aliases=exit_aliases, typeclass=typeclass)
|
||||||
new_exit.db.return_exit = back_exit
|
new_exit.db.return_exit = back_exit
|
||||||
back_exit.db.return_exit = new_exit
|
back_exit.db.return_exit = new_exit
|
||||||
return new_exit
|
return new_exit
|
||||||
|
|
@ -159,10 +160,9 @@ class CmdOpenCloseDoor(default_cmds.MuxCommand):
|
||||||
else:
|
else:
|
||||||
door.setlock("traverse:true()")
|
door.setlock("traverse:true()")
|
||||||
self.caller.msg("You open %s." % door.key)
|
self.caller.msg("You open %s." % door.key)
|
||||||
else: # close
|
else: # close
|
||||||
if not door.locks.check(self.caller, "traverse"):
|
if not door.locks.check(self.caller, "traverse"):
|
||||||
self.caller.msg("%s is already closed." % door.key)
|
self.caller.msg("%s is already closed." % door.key)
|
||||||
else:
|
else:
|
||||||
door.setlock("traverse:false()")
|
door.setlock("traverse:false()")
|
||||||
self.caller.msg("You close %s." % door.key)
|
self.caller.msg("You close %s." % door.key)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,12 @@ MOVE_DELAY = {"stroll": 6,
|
||||||
"run": 2,
|
"run": 2,
|
||||||
"sprint": 1}
|
"sprint": 1}
|
||||||
|
|
||||||
|
|
||||||
class SlowExit(DefaultExit):
|
class SlowExit(DefaultExit):
|
||||||
"""
|
"""
|
||||||
This overloads the way moving happens.
|
This overloads the way moving happens.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def at_traverse(self, traversing_object, target_location):
|
def at_traverse(self, traversing_object, target_location):
|
||||||
"""
|
"""
|
||||||
Implements the actual traversal, using utils.delay to delay the move_to.
|
Implements the actual traversal, using utils.delay to delay the move_to.
|
||||||
|
|
@ -87,6 +89,7 @@ SPEED_DESCS = {"stroll": "strolling",
|
||||||
"run": "running",
|
"run": "running",
|
||||||
"sprint": "sprinting"}
|
"sprint": "sprinting"}
|
||||||
|
|
||||||
|
|
||||||
class CmdSetSpeed(Command):
|
class CmdSetSpeed(Command):
|
||||||
"""
|
"""
|
||||||
set your movement speed
|
set your movement speed
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ def info1(caller):
|
||||||
|
|
||||||
return text, options
|
return text, options
|
||||||
|
|
||||||
|
|
||||||
def info2(caller):
|
def info2(caller):
|
||||||
text = "'My name is not really important ... I'm just an NPC after all.'"
|
text = "'My name is not really important ... I'm just an NPC after all.'"
|
||||||
|
|
||||||
|
|
@ -67,7 +68,6 @@ def info3(caller):
|
||||||
{"desc": "Wait, why don't you tell me your name first?",
|
{"desc": "Wait, why don't you tell me your name first?",
|
||||||
"goto": "info2"})
|
"goto": "info2"})
|
||||||
|
|
||||||
|
|
||||||
return text, options
|
return text, options
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -82,6 +82,7 @@ def END(caller):
|
||||||
# The talk command (sits on the NPC)
|
# The talk command (sits on the NPC)
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
class CmdTalk(default_cmds.MuxCommand):
|
class CmdTalk(default_cmds.MuxCommand):
|
||||||
"""
|
"""
|
||||||
Talks to an npc
|
Talks to an npc
|
||||||
|
|
@ -112,6 +113,7 @@ class CmdTalk(default_cmds.MuxCommand):
|
||||||
class TalkingCmdSet(CmdSet):
|
class TalkingCmdSet(CmdSet):
|
||||||
"Stores the talk command."
|
"Stores the talk command."
|
||||||
key = "talkingcmdset"
|
key = "talkingcmdset"
|
||||||
|
|
||||||
def at_cmdset_creation(self):
|
def at_cmdset_creation(self):
|
||||||
"populates the cmdset"
|
"populates the cmdset"
|
||||||
self.add(CmdTalk())
|
self.add(CmdTalk())
|
||||||
|
|
@ -122,6 +124,7 @@ class TalkingNPC(DefaultObject):
|
||||||
This implements a simple Object using the talk command and using
|
This implements a simple Object using the talk command and using
|
||||||
the conversation defined above.
|
the conversation defined above.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def at_object_creation(self):
|
def at_object_creation(self):
|
||||||
"This is called when object is first created."
|
"This is called when object is first created."
|
||||||
self.db.desc = "This is a talkative NPC."
|
self.db.desc = "This is a talkative NPC."
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
535
evennia/contrib/tree_select.py
Normal file
535
evennia/contrib/tree_select.py
Normal file
|
|
@ -0,0 +1,535 @@
|
||||||
|
"""
|
||||||
|
Easy menu selection tree
|
||||||
|
|
||||||
|
Contrib - Tim Ashley Jenkins 2017
|
||||||
|
|
||||||
|
This module allows you to create and initialize an entire branching EvMenu
|
||||||
|
instance with nothing but a multi-line string passed to one function.
|
||||||
|
|
||||||
|
EvMenu is incredibly powerful and flexible, but using it for simple menus
|
||||||
|
can often be fairly cumbersome - a simple menu that can branch into five
|
||||||
|
categories would require six nodes, each with options represented as a list
|
||||||
|
of dictionaries.
|
||||||
|
|
||||||
|
This module provides a function, init_tree_selection, which acts as a frontend
|
||||||
|
for EvMenu, dynamically sourcing the options from a multi-line string you provide.
|
||||||
|
For example, if you define a string as such:
|
||||||
|
|
||||||
|
TEST_MENU = '''Foo
|
||||||
|
Bar
|
||||||
|
Baz
|
||||||
|
Qux'''
|
||||||
|
|
||||||
|
And then use TEST_MENU as the 'treestr' source when you call init_tree_selection
|
||||||
|
on a player:
|
||||||
|
|
||||||
|
init_tree_selection(TEST_MENU, caller, callback)
|
||||||
|
|
||||||
|
The player will be presented with an EvMenu, like so:
|
||||||
|
|
||||||
|
___________________________
|
||||||
|
|
||||||
|
Make your selection:
|
||||||
|
___________________________
|
||||||
|
|
||||||
|
Foo
|
||||||
|
Bar
|
||||||
|
Baz
|
||||||
|
Qux
|
||||||
|
|
||||||
|
Making a selection will pass the selection's key to the specified callback as a
|
||||||
|
string along with the caller, as well as the index of the selection (the line number
|
||||||
|
on the source string) along with the source string for the tree itself.
|
||||||
|
|
||||||
|
In addition to specifying selections on the menu, you can also specify categories.
|
||||||
|
Categories are indicated by putting options below it preceded with a '-' character.
|
||||||
|
If a selection is a category, then choosing it will bring up a new menu node, prompting
|
||||||
|
the player to select between those options, or to go back to the previous menu. In
|
||||||
|
addition, categories are marked by default with a '[+]' at the end of their key. Both
|
||||||
|
this marker and the option to go back can be disabled.
|
||||||
|
|
||||||
|
Categories can be nested in other categories as well - just go another '-' deeper. You
|
||||||
|
can do this as many times as you like. There's no hard limit to the number of
|
||||||
|
categories you can go down.
|
||||||
|
|
||||||
|
For example, let's add some more options to our menu, turning 'Bar' into a category.
|
||||||
|
|
||||||
|
TEST_MENU = '''Foo
|
||||||
|
Bar
|
||||||
|
-You've got to know
|
||||||
|
--When to hold em
|
||||||
|
--When to fold em
|
||||||
|
--When to walk away
|
||||||
|
Baz
|
||||||
|
Qux'''
|
||||||
|
|
||||||
|
Now when we call the menu, we can see that 'Bar' has become a category instead of a
|
||||||
|
selectable option.
|
||||||
|
|
||||||
|
_______________________________
|
||||||
|
|
||||||
|
Make your selection:
|
||||||
|
_______________________________
|
||||||
|
|
||||||
|
Foo
|
||||||
|
Bar [+]
|
||||||
|
Baz
|
||||||
|
Qux
|
||||||
|
|
||||||
|
Note the [+] next to 'Bar'. If we select 'Bar', it'll show us the option listed under it.
|
||||||
|
|
||||||
|
________________________________________________________________
|
||||||
|
|
||||||
|
Bar
|
||||||
|
________________________________________________________________
|
||||||
|
|
||||||
|
You've got to know [+]
|
||||||
|
<< Go Back: Return to the previous menu.
|
||||||
|
|
||||||
|
Just the one option, which is a category itself, and the option to go back, which will
|
||||||
|
take us back to the previous menu. Let's select 'You've got to know'.
|
||||||
|
|
||||||
|
________________________________________________________________
|
||||||
|
|
||||||
|
You've got to know
|
||||||
|
________________________________________________________________
|
||||||
|
|
||||||
|
When to hold em
|
||||||
|
When to fold em
|
||||||
|
When to walk away
|
||||||
|
<< Go Back: Return to the previous menu.
|
||||||
|
|
||||||
|
Now we see the three options listed under it, too. We can select one of them or use 'Go
|
||||||
|
Back' to return to the 'Bar' menu we were just at before. It's very simple to make a
|
||||||
|
branching tree of selections!
|
||||||
|
|
||||||
|
One last thing - you can set the descriptions for the various options simply by adding a
|
||||||
|
':' character followed by the description to the option's line. For example, let's add a
|
||||||
|
description to 'Baz' in our menu:
|
||||||
|
|
||||||
|
TEST_MENU = '''Foo
|
||||||
|
Bar
|
||||||
|
-You've got to know
|
||||||
|
--When to hold em
|
||||||
|
--When to fold em
|
||||||
|
--When to walk away
|
||||||
|
Baz: Look at this one: the best option.
|
||||||
|
Qux'''
|
||||||
|
|
||||||
|
Now we see that the Baz option has a description attached that's separate from its key:
|
||||||
|
|
||||||
|
_______________________________________________________________
|
||||||
|
|
||||||
|
Make your selection:
|
||||||
|
_______________________________________________________________
|
||||||
|
|
||||||
|
Foo
|
||||||
|
Bar [+]
|
||||||
|
Baz: Look at this one: the best option.
|
||||||
|
Qux
|
||||||
|
|
||||||
|
Once the player makes a selection - let's say, 'Foo' - the menu will terminate and call
|
||||||
|
your specified callback with the selection, like so:
|
||||||
|
|
||||||
|
callback(caller, TEST_MENU, 0, "Foo")
|
||||||
|
|
||||||
|
The index of the selection is given along with a string containing the selection's key.
|
||||||
|
That way, if you have two selections in the menu with the same key, you can still
|
||||||
|
differentiate between them.
|
||||||
|
|
||||||
|
And that's all there is to it! For simple branching-tree selections, using this system is
|
||||||
|
much easier than manually creating EvMenu nodes. It also makes generating menus with dynamic
|
||||||
|
options much easier - since the source of the menu tree is just a string, you could easily
|
||||||
|
generate that string procedurally before passing it to the init_tree_selection function.
|
||||||
|
For example, if a player casts a spell or does an attack without specifying a target, instead
|
||||||
|
of giving them an error, you could present them with a list of valid targets to select by
|
||||||
|
generating a multi-line string of targets and passing it to init_tree_selection, with the
|
||||||
|
callable performing the maneuver once a selection is made.
|
||||||
|
|
||||||
|
This selection system only works for simple branching trees - doing anything really complicated
|
||||||
|
like jumping between categories or prompting for arbitrary input would still require a full
|
||||||
|
EvMenu implementation. For simple selections, however, I'm sure you will find using this function
|
||||||
|
to be much easier!
|
||||||
|
|
||||||
|
Included in this module is a sample menu and function which will let a player change the color
|
||||||
|
of their name - feel free to mess with it to get a feel for how this system works by importing
|
||||||
|
this module in your game's default_cmdsets.py module and adding CmdNameColor to your default
|
||||||
|
character's command set.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia.utils import evmenu
|
||||||
|
from evennia.utils.logger import log_trace
|
||||||
|
from evennia import Command
|
||||||
|
|
||||||
|
def init_tree_selection(treestr, caller, callback,
|
||||||
|
index=None, mark_category=True, go_back=True,
|
||||||
|
cmd_on_exit="look",
|
||||||
|
start_text="Make your selection:"):
|
||||||
|
"""
|
||||||
|
Prompts a player to select an option from a menu tree given as a multi-line string.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
treestr (str): Multi-lne string representing menu options
|
||||||
|
caller (obj): Player to initialize the menu for
|
||||||
|
callback (callable): Function to run when a selection is made. Must take 4 args:
|
||||||
|
caller (obj): Caller given above
|
||||||
|
treestr (str): Menu tree string given above
|
||||||
|
index (int): Index of final selection
|
||||||
|
selection (str): Key of final selection
|
||||||
|
|
||||||
|
Options:
|
||||||
|
index (int or None): Index to start the menu at, or None for top level
|
||||||
|
mark_category (bool): If True, marks categories with a [+] symbol in the menu
|
||||||
|
go_back (bool): If True, present an option to go back to previous categories
|
||||||
|
start_text (str): Text to display at the top level of the menu
|
||||||
|
cmd_on_exit(str): Command to enter when the menu exits - 'look' by default
|
||||||
|
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
This function will initialize an instance of EvMenu with options generated
|
||||||
|
dynamically from the source string, and passes the menu user's selection to
|
||||||
|
a function of your choosing. The EvMenu is made of a single, repeating node,
|
||||||
|
which will call itself over and over at different levels of the menu tree as
|
||||||
|
categories are selected.
|
||||||
|
|
||||||
|
Once a non-category selection is made, the user's selection will be passed to
|
||||||
|
the given callable, both as a string and as an index number. The index is given
|
||||||
|
to ensure every selection has a unique identifier, so that selections with the
|
||||||
|
same key in different categories can be distinguished between.
|
||||||
|
|
||||||
|
The menus called by this function are not persistent and cannot perform
|
||||||
|
complicated tasks like prompt for arbitrary input or jump multiple category
|
||||||
|
levels at once - you'll have to use EvMenu itself if you want to take full
|
||||||
|
advantage of its features.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Pass kwargs to store data needed in the menu
|
||||||
|
kwargs = {
|
||||||
|
"index":index,
|
||||||
|
"mark_category":mark_category,
|
||||||
|
"go_back":go_back,
|
||||||
|
"treestr":treestr,
|
||||||
|
"callback":callback,
|
||||||
|
"start_text":start_text
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initialize menu of selections
|
||||||
|
evmenu.EvMenu(caller, "evennia.contrib.tree_select", startnode="menunode_treeselect",
|
||||||
|
startnode_input=None, cmd_on_exit=cmd_on_exit, **kwargs)
|
||||||
|
|
||||||
|
def dashcount(entry):
|
||||||
|
"""
|
||||||
|
Counts the number of dashes at the beginning of a string. This
|
||||||
|
is needed to determine the depth of options in categories.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entry (str): String to count the dashes at the start of
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dashes (int): Number of dashes at the start
|
||||||
|
"""
|
||||||
|
dashes = 0
|
||||||
|
for char in entry:
|
||||||
|
if char == "-":
|
||||||
|
dashes += 1
|
||||||
|
else:
|
||||||
|
return dashes
|
||||||
|
return dashes
|
||||||
|
|
||||||
|
def is_category(treestr, index):
|
||||||
|
"""
|
||||||
|
Determines whether an option in a tree string is a category by
|
||||||
|
whether or not there are additional options below it.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
treestr (str): Multi-line string representing menu options
|
||||||
|
index (int): Which line of the string to test
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
is_category (bool): Whether the option is a category
|
||||||
|
"""
|
||||||
|
opt_list = treestr.split('\n')
|
||||||
|
# Not a category if it's the last one in the list
|
||||||
|
if index == len(opt_list) - 1:
|
||||||
|
return False
|
||||||
|
# Not a category if next option is not one level deeper
|
||||||
|
return not bool(dashcount(opt_list[index+1]) != dashcount(opt_list[index]) + 1)
|
||||||
|
|
||||||
|
def parse_opts(treestr, category_index=None):
|
||||||
|
"""
|
||||||
|
Parses a tree string and given index into a list of options. If
|
||||||
|
category_index is none, returns all the options at the top level of
|
||||||
|
the menu. If category_index corresponds to a category, returns a list
|
||||||
|
of options under that category. If category_index corresponds to
|
||||||
|
an option that is not a category, it's a selection and returns True.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
treestr (str): Multi-line string representing menu options
|
||||||
|
category_index (int): Index of category or None for top level
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
kept_opts (list or True): Either a list of options in the selected
|
||||||
|
category or True if a selection was made
|
||||||
|
"""
|
||||||
|
dash_depth = 0
|
||||||
|
opt_list = treestr.split('\n')
|
||||||
|
kept_opts = []
|
||||||
|
|
||||||
|
# If a category index is given
|
||||||
|
if category_index != None:
|
||||||
|
# If given index is not a category, it's a selection - return True.
|
||||||
|
if not is_category(treestr, category_index):
|
||||||
|
return True
|
||||||
|
# Otherwise, change the dash depth to match the new category.
|
||||||
|
dash_depth = dashcount(opt_list[category_index]) + 1
|
||||||
|
# Delete everything before the category index
|
||||||
|
opt_list = opt_list [category_index+1:]
|
||||||
|
|
||||||
|
# Keep every option (referenced by index) at the appropriate depth
|
||||||
|
cur_index = 0
|
||||||
|
for option in opt_list:
|
||||||
|
if dashcount(option) == dash_depth:
|
||||||
|
if category_index == None:
|
||||||
|
kept_opts.append((cur_index, option[dash_depth:]))
|
||||||
|
else:
|
||||||
|
kept_opts.append((cur_index + category_index + 1, option[dash_depth:]))
|
||||||
|
# Exits the loop if leaving a category
|
||||||
|
if dashcount(option) < dash_depth:
|
||||||
|
return kept_opts
|
||||||
|
cur_index += 1
|
||||||
|
return kept_opts
|
||||||
|
|
||||||
|
def index_to_selection(treestr, index, desc=False):
|
||||||
|
"""
|
||||||
|
Given a menu tree string and an index, returns the corresponding selection's
|
||||||
|
name as a string. If 'desc' is set to True, will return the selection's
|
||||||
|
description as a string instead.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
treestr (str): Multi-line string representing menu options
|
||||||
|
index (int): Index to convert to selection key or description
|
||||||
|
|
||||||
|
Options:
|
||||||
|
desc (bool): If true, returns description instead of key
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
selection (str): Selection key or description if 'desc' is set
|
||||||
|
"""
|
||||||
|
opt_list = treestr.split('\n')
|
||||||
|
# Fetch the given line
|
||||||
|
selection = opt_list[index]
|
||||||
|
# Strip out the dashes at the start
|
||||||
|
selection = selection[dashcount(selection):]
|
||||||
|
# Separate out description, if any
|
||||||
|
if ":" in selection:
|
||||||
|
# Split string into key and description
|
||||||
|
selection = selection.split(':', 1)
|
||||||
|
selection[1] = selection[1].strip(" ")
|
||||||
|
else:
|
||||||
|
# If no description given, set description to None
|
||||||
|
selection = [selection, None]
|
||||||
|
if not desc:
|
||||||
|
return selection[0]
|
||||||
|
else:
|
||||||
|
return selection[1]
|
||||||
|
|
||||||
|
def go_up_one_category(treestr, index):
|
||||||
|
"""
|
||||||
|
Given a menu tree string and an index, returns the category that the given option
|
||||||
|
belongs to. Used for the 'go back' option.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
treestr (str): Multi-line string representing menu options
|
||||||
|
index (int): Index to determine the parent category of
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
parent_category (int): Index of parent category
|
||||||
|
"""
|
||||||
|
opt_list = treestr.split('\n')
|
||||||
|
# Get the number of dashes deep the given index is
|
||||||
|
dash_level = dashcount(opt_list[index])
|
||||||
|
# Delete everything after the current index
|
||||||
|
opt_list = opt_list[:index+1]
|
||||||
|
|
||||||
|
|
||||||
|
# If there's no dash, return 'None' to return to base menu
|
||||||
|
if dash_level == 0:
|
||||||
|
return None
|
||||||
|
current_index = index
|
||||||
|
# Go up through each option until we find one that's one category above
|
||||||
|
for selection in reversed(opt_list):
|
||||||
|
if dashcount(selection) == dash_level - 1:
|
||||||
|
return current_index
|
||||||
|
current_index -= 1
|
||||||
|
|
||||||
|
def optlist_to_menuoptions(treestr, optlist, index, mark_category, go_back):
|
||||||
|
"""
|
||||||
|
Takes a list of options processed by parse_opts and turns it into
|
||||||
|
a list/dictionary of menu options for use in menunode_treeselect.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
treestr (str): Multi-line string representing menu options
|
||||||
|
optlist (list): List of options to convert to EvMenu's option format
|
||||||
|
index (int): Index of current category
|
||||||
|
mark_category (bool): Whether or not to mark categories with [+]
|
||||||
|
go_back (bool): Whether or not to add an option to go back in the menu
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
menuoptions (list of dicts): List of menu options formatted for use
|
||||||
|
in EvMenu, each passing a different "newindex" kwarg that changes
|
||||||
|
the menu level or makes a selection
|
||||||
|
"""
|
||||||
|
|
||||||
|
menuoptions = []
|
||||||
|
cur_index = 0
|
||||||
|
for option in optlist:
|
||||||
|
index_to_add = optlist[cur_index][0]
|
||||||
|
menuitem = {}
|
||||||
|
keystr = index_to_selection(treestr, index_to_add)
|
||||||
|
if mark_category and is_category(treestr, index_to_add):
|
||||||
|
# Add the [+] to the key if marking categories, and the key by itself as an alias
|
||||||
|
menuitem["key"] = [keystr + " [+]", keystr]
|
||||||
|
else:
|
||||||
|
menuitem["key"] = keystr
|
||||||
|
# Get the option's description
|
||||||
|
desc = index_to_selection(treestr, index_to_add, desc=True)
|
||||||
|
if desc:
|
||||||
|
menuitem["desc"] = desc
|
||||||
|
# Passing 'newindex' as a kwarg to the node is how we move through the menu!
|
||||||
|
menuitem["goto"] = ["menunode_treeselect", {"newindex":index_to_add}]
|
||||||
|
menuoptions.append(menuitem)
|
||||||
|
cur_index += 1
|
||||||
|
# Add option to go back, if needed
|
||||||
|
if index != None and go_back == True:
|
||||||
|
gobackitem = {"key":["<< Go Back", "go back", "back"],
|
||||||
|
"desc":"Return to the previous menu.",
|
||||||
|
"goto":["menunode_treeselect", {"newindex":go_up_one_category(treestr, index)}]}
|
||||||
|
menuoptions.append(gobackitem)
|
||||||
|
return menuoptions
|
||||||
|
|
||||||
|
def menunode_treeselect(caller, raw_string, **kwargs):
|
||||||
|
"""
|
||||||
|
This is the repeating menu node that handles the tree selection.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# If 'newindex' is in the kwargs, change the stored index.
|
||||||
|
if "newindex" in kwargs:
|
||||||
|
caller.ndb._menutree.index = kwargs["newindex"]
|
||||||
|
|
||||||
|
# Retrieve menu info
|
||||||
|
index = caller.ndb._menutree.index
|
||||||
|
mark_category = caller.ndb._menutree.mark_category
|
||||||
|
go_back = caller.ndb._menutree.go_back
|
||||||
|
treestr = caller.ndb._menutree.treestr
|
||||||
|
callback = caller.ndb._menutree.callback
|
||||||
|
start_text = caller.ndb._menutree.start_text
|
||||||
|
|
||||||
|
# List of options if index is 'None' or category, or 'True' if a selection
|
||||||
|
optlist = parse_opts(treestr, category_index=index)
|
||||||
|
|
||||||
|
# If given index returns optlist as 'True', it's a selection. Pass to callback and end the menu.
|
||||||
|
if optlist == True:
|
||||||
|
selection = index_to_selection(treestr, index)
|
||||||
|
try:
|
||||||
|
callback(caller, treestr, index, selection)
|
||||||
|
except Exception:
|
||||||
|
log_trace("Error in tree selection callback.")
|
||||||
|
|
||||||
|
# Returning None, None ends the menu.
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
# Otherwise, convert optlist to a list of menu options.
|
||||||
|
else:
|
||||||
|
options = optlist_to_menuoptions(treestr, optlist, index, mark_category, go_back)
|
||||||
|
if index == None:
|
||||||
|
# Use start_text for the menu text on the top level
|
||||||
|
text = start_text
|
||||||
|
else:
|
||||||
|
# Use the category name and description (if any) as the menu text
|
||||||
|
if index_to_selection(treestr, index, desc=True) != None:
|
||||||
|
text = "|w" + index_to_selection(treestr, index) + "|n: " + index_to_selection(treestr, index, desc=True)
|
||||||
|
else:
|
||||||
|
text = "|w" + index_to_selection(treestr, index) + "|n"
|
||||||
|
return text, options
|
||||||
|
|
||||||
|
# The rest of this module is for the example menu and command! It'll change the color of your name.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Here's an example string that you can initialize a menu from. Note the dashes at
|
||||||
|
the beginning of each line - that's how menu option depth and hierarchy is determined.
|
||||||
|
"""
|
||||||
|
|
||||||
|
NAMECOLOR_MENU = """Set name color: Choose a color for your name!
|
||||||
|
-Red shades: Various shades of |511red|n
|
||||||
|
--Red: |511Set your name to Red|n
|
||||||
|
--Pink: |533Set your name to Pink|n
|
||||||
|
--Maroon: |301Set your name to Maroon|n
|
||||||
|
-Orange shades: Various shades of |531orange|n
|
||||||
|
--Orange: |531Set your name to Orange|n
|
||||||
|
--Brown: |321Set your name to Brown|n
|
||||||
|
--Sienna: |420Set your name to Sienna|n
|
||||||
|
-Yellow shades: Various shades of |551yellow|n
|
||||||
|
--Yellow: |551Set your name to Yellow|n
|
||||||
|
--Gold: |540Set your name to Gold|n
|
||||||
|
--Dandelion: |553Set your name to Dandelion|n
|
||||||
|
-Green shades: Various shades of |141green|n
|
||||||
|
--Green: |141Set your name to Green|n
|
||||||
|
--Lime: |350Set your name to Lime|n
|
||||||
|
--Forest: |032Set your name to Forest|n
|
||||||
|
-Blue shades: Various shades of |115blue|n
|
||||||
|
--Blue: |115Set your name to Blue|n
|
||||||
|
--Cyan: |155Set your name to Cyan|n
|
||||||
|
--Navy: |113Set your name to Navy|n
|
||||||
|
-Purple shades: Various shades of |415purple|n
|
||||||
|
--Purple: |415Set your name to Purple|n
|
||||||
|
--Lavender: |535Set your name to Lavender|n
|
||||||
|
--Fuchsia: |503Set your name to Fuchsia|n
|
||||||
|
Remove name color: Remove your name color, if any"""
|
||||||
|
|
||||||
|
class CmdNameColor(Command):
|
||||||
|
"""
|
||||||
|
Set or remove a special color on your name. Just an example for the
|
||||||
|
easy menu selection tree contrib.
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = "namecolor"
|
||||||
|
|
||||||
|
def func(self):
|
||||||
|
# This is all you have to do to initialize a menu!
|
||||||
|
init_tree_selection(NAMECOLOR_MENU, self.caller,
|
||||||
|
change_name_color,
|
||||||
|
start_text="Name color options:")
|
||||||
|
|
||||||
|
def change_name_color(caller, treestr, index, selection):
|
||||||
|
"""
|
||||||
|
Changes a player's name color.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
caller (obj): Character whose name to color.
|
||||||
|
treestr (str): String for the color change menu - unused
|
||||||
|
index (int): Index of menu selection - unused
|
||||||
|
selection (str): Selection made from the name color menu - used
|
||||||
|
to determine the color the player chose.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Store the caller's uncolored name
|
||||||
|
if not caller.db.uncolored_name:
|
||||||
|
caller.db.uncolored_name = caller.key
|
||||||
|
|
||||||
|
# Dictionary matching color selection names to color codes
|
||||||
|
colordict = { "Red":"|511", "Pink":"|533", "Maroon":"|301",
|
||||||
|
"Orange":"|531", "Brown":"|321", "Sienna":"|420",
|
||||||
|
"Yellow":"|551", "Gold":"|540", "Dandelion":"|553",
|
||||||
|
"Green":"|141", "Lime":"|350", "Forest":"|032",
|
||||||
|
"Blue":"|115", "Cyan":"|155", "Navy":"|113",
|
||||||
|
"Purple":"|415", "Lavender":"|535", "Fuchsia":"|503"}
|
||||||
|
|
||||||
|
# I know this probably isn't the best way to do this. It's just an example!
|
||||||
|
if selection == "Remove name color": # Player chose to remove their name color
|
||||||
|
caller.key = caller.db.uncolored_name
|
||||||
|
caller.msg("Name color removed.")
|
||||||
|
elif selection in colordict:
|
||||||
|
newcolor = colordict[selection] # Retrieve color code based on menu selection
|
||||||
|
caller.key = newcolor + caller.db.uncolored_name + "|n" # Add color code to caller's name
|
||||||
|
caller.msg(newcolor + ("Name color changed to %s!" % selection) + "|n")
|
||||||
|
|
||||||
42
evennia/contrib/turnbattle/README.md
Normal file
42
evennia/contrib/turnbattle/README.md
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Turn based battle system framework
|
||||||
|
|
||||||
|
Contrib - Tim Ashley Jenkins 2017
|
||||||
|
|
||||||
|
This is a framework for a simple turn-based combat system, similar
|
||||||
|
to those used in D&D-style tabletop role playing games. It allows
|
||||||
|
any character to start a fight in a room, at which point initiative
|
||||||
|
is rolled and a turn order is established. Each participant in combat
|
||||||
|
has a limited time to decide their action for that turn (30 seconds by
|
||||||
|
default), and combat progresses through the turn order, looping through
|
||||||
|
the participants until the fight ends.
|
||||||
|
|
||||||
|
This folder contains multiple examples of how such a system can be
|
||||||
|
implemented and customized:
|
||||||
|
|
||||||
|
tb_basic.py - The simplest system, which implements initiative and turn
|
||||||
|
order, attack rolls against defense values, and damage to hit
|
||||||
|
points. Only very basic game mechanics are included.
|
||||||
|
|
||||||
|
tb_equip.py - Adds weapons and armor to the basic implementation of
|
||||||
|
the battle system, including commands for wielding weapons and
|
||||||
|
donning armor, and modifiers to accuracy and damage based on
|
||||||
|
currently used equipment.
|
||||||
|
|
||||||
|
tb_range.py - Adds a system for abstract positioning and movement, which
|
||||||
|
tracks the distance between different characters and objects in
|
||||||
|
combat, as well as differentiates between melee and ranged
|
||||||
|
attacks.
|
||||||
|
|
||||||
|
This system is meant as a basic framework to start from, and is modeled
|
||||||
|
after the combat systems of popular tabletop role playing games rather than
|
||||||
|
the real-time battle systems that many MMOs and some MUDs use. As such, it
|
||||||
|
may be better suited to role-playing or more story-oriented games, or games
|
||||||
|
meant to closely emulate the experience of playing a tabletop RPG.
|
||||||
|
|
||||||
|
Each of these modules contains the full functionality of the battle system
|
||||||
|
with different customizations added in - the instructions to install each
|
||||||
|
one is contained in the module itself. It's recommended that you install
|
||||||
|
and test tb_basic first, so you can better understand how the other
|
||||||
|
modules expand on it and get a better idea of how you can customize the
|
||||||
|
system to your liking and integrate the subsystems presented here into
|
||||||
|
your own combat system.
|
||||||
1
evennia/contrib/turnbattle/__init__.py
Normal file
1
evennia/contrib/turnbattle/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -16,26 +16,26 @@ is easily extensible and can be used as the foundation for implementing
|
||||||
the rules from your turn-based tabletop game of choice or making your
|
the rules from your turn-based tabletop game of choice or making your
|
||||||
own battle system.
|
own battle system.
|
||||||
|
|
||||||
To install and test, import this module's BattleCharacter object into
|
To install and test, import this module's TBBasicCharacter object into
|
||||||
your game's character.py module:
|
your game's character.py module:
|
||||||
|
|
||||||
from evennia.contrib.turnbattle import BattleCharacter
|
from evennia.contrib.turnbattle.tb_basic import TBBasicCharacter
|
||||||
|
|
||||||
And change your game's character typeclass to inherit from BattleCharacter
|
And change your game's character typeclass to inherit from TBBasicCharacter
|
||||||
instead of the default:
|
instead of the default:
|
||||||
|
|
||||||
class Character(BattleCharacter):
|
class Character(TBBasicCharacter):
|
||||||
|
|
||||||
Next, import this module into your default_cmdsets.py module:
|
Next, import this module into your default_cmdsets.py module:
|
||||||
|
|
||||||
from evennia.contrib import turnbattle
|
from evennia.contrib.turnbattle import tb_basic
|
||||||
|
|
||||||
And add the battle command set to your default command set:
|
And add the battle command set to your default command set:
|
||||||
|
|
||||||
#
|
#
|
||||||
# any commands you add below will overload the default ones.
|
# any commands you add below will overload the default ones.
|
||||||
#
|
#
|
||||||
self.add(turnbattle.BattleCmdSet())
|
self.add(tb_basic.BattleCmdSet())
|
||||||
|
|
||||||
This module is meant to be heavily expanded on, so you may want to copy it
|
This module is meant to be heavily expanded on, so you may want to copy it
|
||||||
to your game's 'world' folder and modify it there rather than importing it
|
to your game's 'world' folder and modify it there rather than importing it
|
||||||
|
|
@ -48,10 +48,18 @@ from evennia.commands.default.help import CmdHelp
|
||||||
|
|
||||||
"""
|
"""
|
||||||
----------------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
COMBAT FUNCTIONS START HERE
|
OPTIONS
|
||||||
----------------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
TURN_TIMEOUT = 30 # Time before turns automatically end, in seconds
|
||||||
|
ACTIONS_PER_TURN = 1 # Number of actions allowed per turn
|
||||||
|
|
||||||
|
"""
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
COMBAT FUNCTIONS START HERE
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
"""
|
||||||
|
|
||||||
def roll_init(character):
|
def roll_init(character):
|
||||||
"""
|
"""
|
||||||
|
|
@ -167,6 +175,20 @@ def apply_damage(defender, damage):
|
||||||
if defender.db.hp <= 0:
|
if defender.db.hp <= 0:
|
||||||
defender.db.hp = 0
|
defender.db.hp = 0
|
||||||
|
|
||||||
|
def at_defeat(defeated):
|
||||||
|
"""
|
||||||
|
Announces the defeat of a fighter in combat.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
defeated (obj): Fighter that's been defeated.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
All this does is announce a defeat message by default, but if you
|
||||||
|
want anything else to happen to defeated fighters (like putting them
|
||||||
|
into a dying state or something similar) then this is the place to
|
||||||
|
do it.
|
||||||
|
"""
|
||||||
|
defeated.location.msg_contents("%s has been defeated!" % defeated)
|
||||||
|
|
||||||
def resolve_attack(attacker, defender, attack_value=None, defense_value=None):
|
def resolve_attack(attacker, defender, attack_value=None, defense_value=None):
|
||||||
"""
|
"""
|
||||||
|
|
@ -195,10 +217,9 @@ def resolve_attack(attacker, defender, attack_value=None, defense_value=None):
|
||||||
# Announce damage dealt and apply damage.
|
# Announce damage dealt and apply damage.
|
||||||
attacker.location.msg_contents("%s hits %s for %i damage!" % (attacker, defender, damage_value))
|
attacker.location.msg_contents("%s hits %s for %i damage!" % (attacker, defender, damage_value))
|
||||||
apply_damage(defender, damage_value)
|
apply_damage(defender, damage_value)
|
||||||
# If defender HP is reduced to 0 or less, announce defeat.
|
# If defender HP is reduced to 0 or less, call at_defeat.
|
||||||
if defender.db.hp <= 0:
|
if defender.db.hp <= 0:
|
||||||
attacker.location.msg_contents("%s has been defeated!" % defender)
|
at_defeat(defender)
|
||||||
|
|
||||||
|
|
||||||
def combat_cleanup(character):
|
def combat_cleanup(character):
|
||||||
"""
|
"""
|
||||||
|
|
@ -226,9 +247,7 @@ def is_in_combat(character):
|
||||||
Returns:
|
Returns:
|
||||||
(bool): True if in combat or False if not in combat
|
(bool): True if in combat or False if not in combat
|
||||||
"""
|
"""
|
||||||
if character.db.Combat_TurnHandler:
|
return bool(character.db.combat_turnhandler)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def is_turn(character):
|
def is_turn(character):
|
||||||
|
|
@ -241,11 +260,9 @@ def is_turn(character):
|
||||||
Returns:
|
Returns:
|
||||||
(bool): True if it is their turn or False otherwise
|
(bool): True if it is their turn or False otherwise
|
||||||
"""
|
"""
|
||||||
turnhandler = character.db.Combat_TurnHandler
|
turnhandler = character.db.combat_turnhandler
|
||||||
currentchar = turnhandler.db.fighters[turnhandler.db.turn]
|
currentchar = turnhandler.db.fighters[turnhandler.db.turn]
|
||||||
if character == currentchar:
|
return bool(character == currentchar)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def spend_action(character, actions, action_name=None):
|
def spend_action(character, actions, action_name=None):
|
||||||
|
|
@ -261,14 +278,14 @@ def spend_action(character, actions, action_name=None):
|
||||||
combat to provided string
|
combat to provided string
|
||||||
"""
|
"""
|
||||||
if action_name:
|
if action_name:
|
||||||
character.db.Combat_LastAction = action_name
|
character.db.combat_lastaction = action_name
|
||||||
if actions == 'all': # If spending all actions
|
if actions == 'all': # If spending all actions
|
||||||
character.db.Combat_ActionsLeft = 0 # Set actions to 0
|
character.db.combat_actionsleft = 0 # Set actions to 0
|
||||||
else:
|
else:
|
||||||
character.db.Combat_ActionsLeft -= actions # Use up actions.
|
character.db.combat_actionsleft -= actions # Use up actions.
|
||||||
if character.db.Combat_ActionsLeft < 0:
|
if character.db.combat_actionsleft < 0:
|
||||||
character.db.Combat_ActionsLeft = 0 # Can't have fewer than 0 actions
|
character.db.combat_actionsleft = 0 # Can't have fewer than 0 actions
|
||||||
character.db.Combat_TurnHandler.turn_end_check(character) # Signal potential end of turn.
|
character.db.combat_turnhandler.turn_end_check(character) # Signal potential end of turn.
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -278,7 +295,7 @@ CHARACTER TYPECLASS
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class BattleCharacter(DefaultCharacter):
|
class TBBasicCharacter(DefaultCharacter):
|
||||||
"""
|
"""
|
||||||
A character able to participate in turn-based combat. Has attributes for current
|
A character able to participate in turn-based combat. Has attributes for current
|
||||||
and maximum HP, and access to combat commands.
|
and maximum HP, and access to combat commands.
|
||||||
|
|
@ -324,7 +341,182 @@ class BattleCharacter(DefaultCharacter):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
"""
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
SCRIPTS START HERE
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TBBasicTurnHandler(DefaultScript):
|
||||||
|
"""
|
||||||
|
This is the script that handles the progression of combat through turns.
|
||||||
|
On creation (when a fight is started) it adds all combat-ready characters
|
||||||
|
to its roster and then sorts them into a turn order. There can only be one
|
||||||
|
fight going on in a single room at a time, so the script is assigned to a
|
||||||
|
room as its object.
|
||||||
|
|
||||||
|
Fights persist until only one participant is left with any HP or all
|
||||||
|
remaining participants choose to end the combat with the 'disengage' command.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_script_creation(self):
|
||||||
|
"""
|
||||||
|
Called once, when the script is created.
|
||||||
|
"""
|
||||||
|
self.key = "Combat Turn Handler"
|
||||||
|
self.interval = 5 # Once every 5 seconds
|
||||||
|
self.persistent = True
|
||||||
|
self.db.fighters = []
|
||||||
|
|
||||||
|
# Add all fighters in the room with at least 1 HP to the combat."
|
||||||
|
for thing in self.obj.contents:
|
||||||
|
if thing.db.hp:
|
||||||
|
self.db.fighters.append(thing)
|
||||||
|
|
||||||
|
# Initialize each fighter for combat
|
||||||
|
for fighter in self.db.fighters:
|
||||||
|
self.initialize_for_combat(fighter)
|
||||||
|
|
||||||
|
# Add a reference to this script to the room
|
||||||
|
self.obj.db.combat_turnhandler = self
|
||||||
|
|
||||||
|
# Roll initiative and sort the list of fighters depending on who rolls highest to determine turn order.
|
||||||
|
# The initiative roll is determined by the roll_init function and can be customized easily.
|
||||||
|
ordered_by_roll = sorted(self.db.fighters, key=roll_init, reverse=True)
|
||||||
|
self.db.fighters = ordered_by_roll
|
||||||
|
|
||||||
|
# Announce the turn order.
|
||||||
|
self.obj.msg_contents("Turn order is: %s " % ", ".join(obj.key for obj in self.db.fighters))
|
||||||
|
|
||||||
|
# Start first fighter's turn.
|
||||||
|
self.start_turn(self.db.fighters[0])
|
||||||
|
|
||||||
|
# Set up the current turn and turn timeout delay.
|
||||||
|
self.db.turn = 0
|
||||||
|
self.db.timer = TURN_TIMEOUT # Set timer to turn timeout specified in options
|
||||||
|
|
||||||
|
def at_stop(self):
|
||||||
|
"""
|
||||||
|
Called at script termination.
|
||||||
|
"""
|
||||||
|
for fighter in self.db.fighters:
|
||||||
|
combat_cleanup(fighter) # Clean up the combat attributes for every fighter.
|
||||||
|
self.obj.db.combat_turnhandler = None # Remove reference to turn handler in location
|
||||||
|
|
||||||
|
def at_repeat(self):
|
||||||
|
"""
|
||||||
|
Called once every self.interval seconds.
|
||||||
|
"""
|
||||||
|
currentchar = self.db.fighters[self.db.turn] # Note the current character in the turn order.
|
||||||
|
self.db.timer -= self.interval # Count down the timer.
|
||||||
|
|
||||||
|
if self.db.timer <= 0:
|
||||||
|
# Force current character to disengage if timer runs out.
|
||||||
|
self.obj.msg_contents("%s's turn timed out!" % currentchar)
|
||||||
|
spend_action(currentchar, 'all', action_name="disengage") # Spend all remaining actions.
|
||||||
|
return
|
||||||
|
elif self.db.timer <= 10 and not self.db.timeout_warning_given: # 10 seconds left
|
||||||
|
# Warn the current character if they're about to time out.
|
||||||
|
currentchar.msg("WARNING: About to time out!")
|
||||||
|
self.db.timeout_warning_given = True
|
||||||
|
|
||||||
|
def initialize_for_combat(self, character):
|
||||||
|
"""
|
||||||
|
Prepares a character for combat when starting or entering a fight.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
character (obj): Character to initialize for combat.
|
||||||
|
"""
|
||||||
|
combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case.
|
||||||
|
character.db.combat_actionsleft = 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0
|
||||||
|
character.db.combat_turnhandler = self # Add a reference to this turn handler script to the character
|
||||||
|
character.db.combat_lastaction = "null" # Track last action taken in combat
|
||||||
|
|
||||||
|
def start_turn(self, character):
|
||||||
|
"""
|
||||||
|
Readies a character for the start of their turn by replenishing their
|
||||||
|
available actions and notifying them that their turn has come up.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
character (obj): Character to be readied.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Here, you only get one action per turn, but you might want to allow more than
|
||||||
|
one per turn, or even grant a number of actions based on a character's
|
||||||
|
attributes. You can even add multiple different kinds of actions, I.E. actions
|
||||||
|
separated for movement, by adding "character.db.combat_movesleft = 3" or
|
||||||
|
something similar.
|
||||||
|
"""
|
||||||
|
character.db.combat_actionsleft = ACTIONS_PER_TURN # Replenish actions
|
||||||
|
# Prompt the character for their turn and give some information.
|
||||||
|
character.msg("|wIt's your turn! You have %i HP remaining.|n" % character.db.hp)
|
||||||
|
|
||||||
|
def next_turn(self):
|
||||||
|
"""
|
||||||
|
Advances to the next character in the turn order.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check to see if every character disengaged as their last action. If so, end combat.
|
||||||
|
disengage_check = True
|
||||||
|
for fighter in self.db.fighters:
|
||||||
|
if fighter.db.combat_lastaction != "disengage": # If a character has done anything but disengage
|
||||||
|
disengage_check = False
|
||||||
|
if disengage_check: # All characters have disengaged
|
||||||
|
self.obj.msg_contents("All fighters have disengaged! Combat is over!")
|
||||||
|
self.stop() # Stop this script and end combat.
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check to see if only one character is left standing. If so, end combat.
|
||||||
|
defeated_characters = 0
|
||||||
|
for fighter in self.db.fighters:
|
||||||
|
if fighter.db.HP == 0:
|
||||||
|
defeated_characters += 1 # Add 1 for every fighter with 0 HP left (defeated)
|
||||||
|
if defeated_characters == (len(self.db.fighters) - 1): # If only one character isn't defeated
|
||||||
|
for fighter in self.db.fighters:
|
||||||
|
if fighter.db.HP != 0:
|
||||||
|
LastStanding = fighter # Pick the one fighter left with HP remaining
|
||||||
|
self.obj.msg_contents("Only %s remains! Combat is over!" % LastStanding)
|
||||||
|
self.stop() # Stop this script and end combat.
|
||||||
|
return
|
||||||
|
|
||||||
|
# Cycle to the next turn.
|
||||||
|
currentchar = self.db.fighters[self.db.turn]
|
||||||
|
self.db.turn += 1 # Go to the next in the turn order.
|
||||||
|
if self.db.turn > len(self.db.fighters) - 1:
|
||||||
|
self.db.turn = 0 # Go back to the first in the turn order once you reach the end.
|
||||||
|
newchar = self.db.fighters[self.db.turn] # Note the new character
|
||||||
|
self.db.timer = TURN_TIMEOUT + self.time_until_next_repeat() # Reset the timer.
|
||||||
|
self.db.timeout_warning_given = False # Reset the timeout warning.
|
||||||
|
self.obj.msg_contents("%s's turn ends - %s's turn begins!" % (currentchar, newchar))
|
||||||
|
self.start_turn(newchar) # Start the new character's turn.
|
||||||
|
|
||||||
|
def turn_end_check(self, character):
|
||||||
|
"""
|
||||||
|
Tests to see if a character's turn is over, and cycles to the next turn if it is.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
character (obj): Character to test for end of turn
|
||||||
|
"""
|
||||||
|
if not character.db.combat_actionsleft: # Character has no actions remaining
|
||||||
|
self.next_turn()
|
||||||
|
return
|
||||||
|
|
||||||
|
def join_fight(self, character):
|
||||||
|
"""
|
||||||
|
Adds a new character to a fight already in progress.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
character (obj): Character to be added to the fight.
|
||||||
|
"""
|
||||||
|
# Inserts the fighter to the turn order, right behind whoever's turn it currently is.
|
||||||
|
self.db.fighters.insert(self.db.turn, character)
|
||||||
|
# Tick the turn counter forward one to compensate.
|
||||||
|
self.db.turn += 1
|
||||||
|
# Initialize the character like you do at the start.
|
||||||
|
self.initialize_for_combat(character)
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
----------------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
COMMANDS START HERE
|
COMMANDS START HERE
|
||||||
|
|
@ -365,13 +557,13 @@ class CmdFight(Command):
|
||||||
if len(fighters) <= 1: # If you're the only able fighter in the room
|
if len(fighters) <= 1: # If you're the only able fighter in the room
|
||||||
self.caller.msg("There's nobody here to fight!")
|
self.caller.msg("There's nobody here to fight!")
|
||||||
return
|
return
|
||||||
if here.db.Combat_TurnHandler: # If there's already a fight going on...
|
if here.db.combat_turnhandler: # If there's already a fight going on...
|
||||||
here.msg_contents("%s joins the fight!" % self.caller)
|
here.msg_contents("%s joins the fight!" % self.caller)
|
||||||
here.db.Combat_TurnHandler.join_fight(self.caller) # Join the fight!
|
here.db.combat_turnhandler.join_fight(self.caller) # Join the fight!
|
||||||
return
|
return
|
||||||
here.msg_contents("%s starts a fight!" % self.caller)
|
here.msg_contents("%s starts a fight!" % self.caller)
|
||||||
# Add a turn handler script to the room, which starts combat.
|
# Add a turn handler script to the room, which starts combat.
|
||||||
here.scripts.add("contrib.turnbattle.TurnHandler")
|
here.scripts.add("contrib.turnbattle.tb_basic.TBBasicTurnHandler")
|
||||||
# Remember you'll have to change the path to the script if you copy this code to your own modules!
|
# Remember you'll have to change the path to the script if you copy this code to your own modules!
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -559,177 +751,4 @@ class BattleCmdSet(default_cmds.CharacterCmdSet):
|
||||||
self.add(CmdRest())
|
self.add(CmdRest())
|
||||||
self.add(CmdPass())
|
self.add(CmdPass())
|
||||||
self.add(CmdDisengage())
|
self.add(CmdDisengage())
|
||||||
self.add(CmdCombatHelp())
|
self.add(CmdCombatHelp())
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
----------------------------------------------------------------------------
|
|
||||||
SCRIPTS START HERE
|
|
||||||
----------------------------------------------------------------------------
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class TurnHandler(DefaultScript):
|
|
||||||
"""
|
|
||||||
This is the script that handles the progression of combat through turns.
|
|
||||||
On creation (when a fight is started) it adds all combat-ready characters
|
|
||||||
to its roster and then sorts them into a turn order. There can only be one
|
|
||||||
fight going on in a single room at a time, so the script is assigned to a
|
|
||||||
room as its object.
|
|
||||||
|
|
||||||
Fights persist until only one participant is left with any HP or all
|
|
||||||
remaining participants choose to end the combat with the 'disengage' command.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def at_script_creation(self):
|
|
||||||
"""
|
|
||||||
Called once, when the script is created.
|
|
||||||
"""
|
|
||||||
self.key = "Combat Turn Handler"
|
|
||||||
self.interval = 5 # Once every 5 seconds
|
|
||||||
self.persistent = True
|
|
||||||
self.db.fighters = []
|
|
||||||
|
|
||||||
# Add all fighters in the room with at least 1 HP to the combat."
|
|
||||||
for object in self.obj.contents:
|
|
||||||
if object.db.hp:
|
|
||||||
self.db.fighters.append(object)
|
|
||||||
|
|
||||||
# Initialize each fighter for combat
|
|
||||||
for fighter in self.db.fighters:
|
|
||||||
self.initialize_for_combat(fighter)
|
|
||||||
|
|
||||||
# Add a reference to this script to the room
|
|
||||||
self.obj.db.Combat_TurnHandler = self
|
|
||||||
|
|
||||||
# Roll initiative and sort the list of fighters depending on who rolls highest to determine turn order.
|
|
||||||
# The initiative roll is determined by the roll_init function and can be customized easily.
|
|
||||||
ordered_by_roll = sorted(self.db.fighters, key=roll_init, reverse=True)
|
|
||||||
self.db.fighters = ordered_by_roll
|
|
||||||
|
|
||||||
# Announce the turn order.
|
|
||||||
self.obj.msg_contents("Turn order is: %s " % ", ".join(obj.key for obj in self.db.fighters))
|
|
||||||
|
|
||||||
# Set up the current turn and turn timeout delay.
|
|
||||||
self.db.turn = 0
|
|
||||||
self.db.timer = 30 # 30 seconds
|
|
||||||
|
|
||||||
def at_stop(self):
|
|
||||||
"""
|
|
||||||
Called at script termination.
|
|
||||||
"""
|
|
||||||
for fighter in self.db.fighters:
|
|
||||||
combat_cleanup(fighter) # Clean up the combat attributes for every fighter.
|
|
||||||
self.obj.db.Combat_TurnHandler = None # Remove reference to turn handler in location
|
|
||||||
|
|
||||||
def at_repeat(self):
|
|
||||||
"""
|
|
||||||
Called once every self.interval seconds.
|
|
||||||
"""
|
|
||||||
currentchar = self.db.fighters[self.db.turn] # Note the current character in the turn order.
|
|
||||||
self.db.timer -= self.interval # Count down the timer.
|
|
||||||
|
|
||||||
if self.db.timer <= 0:
|
|
||||||
# Force current character to disengage if timer runs out.
|
|
||||||
self.obj.msg_contents("%s's turn timed out!" % currentchar)
|
|
||||||
spend_action(currentchar, 'all', action_name="disengage") # Spend all remaining actions.
|
|
||||||
return
|
|
||||||
elif self.db.timer <= 10 and not self.db.timeout_warning_given: # 10 seconds left
|
|
||||||
# Warn the current character if they're about to time out.
|
|
||||||
currentchar.msg("WARNING: About to time out!")
|
|
||||||
self.db.timeout_warning_given = True
|
|
||||||
|
|
||||||
def initialize_for_combat(self, character):
|
|
||||||
"""
|
|
||||||
Prepares a character for combat when starting or entering a fight.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
character (obj): Character to initialize for combat.
|
|
||||||
"""
|
|
||||||
combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case.
|
|
||||||
character.db.Combat_ActionsLeft = 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0
|
|
||||||
character.db.Combat_TurnHandler = self # Add a reference to this turn handler script to the character
|
|
||||||
character.db.Combat_LastAction = "null" # Track last action taken in combat
|
|
||||||
|
|
||||||
def start_turn(self, character):
|
|
||||||
"""
|
|
||||||
Readies a character for the start of their turn by replenishing their
|
|
||||||
available actions and notifying them that their turn has come up.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
character (obj): Character to be readied.
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
Here, you only get one action per turn, but you might want to allow more than
|
|
||||||
one per turn, or even grant a number of actions based on a character's
|
|
||||||
attributes. You can even add multiple different kinds of actions, I.E. actions
|
|
||||||
separated for movement, by adding "character.db.Combat_MovesLeft = 3" or
|
|
||||||
something similar.
|
|
||||||
"""
|
|
||||||
character.db.Combat_ActionsLeft = 1 # 1 action per turn.
|
|
||||||
# Prompt the character for their turn and give some information.
|
|
||||||
character.msg("|wIt's your turn! You have %i HP remaining.|n" % character.db.hp)
|
|
||||||
|
|
||||||
def next_turn(self):
|
|
||||||
"""
|
|
||||||
Advances to the next character in the turn order.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Check to see if every character disengaged as their last action. If so, end combat.
|
|
||||||
disengage_check = True
|
|
||||||
for fighter in self.db.fighters:
|
|
||||||
if fighter.db.Combat_LastAction != "disengage": # If a character has done anything but disengage
|
|
||||||
disengage_check = False
|
|
||||||
if disengage_check: # All characters have disengaged
|
|
||||||
self.obj.msg_contents("All fighters have disengaged! Combat is over!")
|
|
||||||
self.stop() # Stop this script and end combat.
|
|
||||||
return
|
|
||||||
|
|
||||||
# Check to see if only one character is left standing. If so, end combat.
|
|
||||||
defeated_characters = 0
|
|
||||||
for fighter in self.db.fighters:
|
|
||||||
if fighter.db.HP == 0:
|
|
||||||
defeated_characters += 1 # Add 1 for every fighter with 0 HP left (defeated)
|
|
||||||
if defeated_characters == (len(self.db.fighters) - 1): # If only one character isn't defeated
|
|
||||||
for fighter in self.db.fighters:
|
|
||||||
if fighter.db.HP != 0:
|
|
||||||
LastStanding = fighter # Pick the one fighter left with HP remaining
|
|
||||||
self.obj.msg_contents("Only %s remains! Combat is over!" % LastStanding)
|
|
||||||
self.stop() # Stop this script and end combat.
|
|
||||||
return
|
|
||||||
|
|
||||||
# Cycle to the next turn.
|
|
||||||
currentchar = self.db.fighters[self.db.turn]
|
|
||||||
self.db.turn += 1 # Go to the next in the turn order.
|
|
||||||
if self.db.turn > len(self.db.fighters) - 1:
|
|
||||||
self.db.turn = 0 # Go back to the first in the turn order once you reach the end.
|
|
||||||
newchar = self.db.fighters[self.db.turn] # Note the new character
|
|
||||||
self.db.timer = 30 + self.time_until_next_repeat() # Reset the timer.
|
|
||||||
self.db.timeout_warning_given = False # Reset the timeout warning.
|
|
||||||
self.obj.msg_contents("%s's turn ends - %s's turn begins!" % (currentchar, newchar))
|
|
||||||
self.start_turn(newchar) # Start the new character's turn.
|
|
||||||
|
|
||||||
def turn_end_check(self, character):
|
|
||||||
"""
|
|
||||||
Tests to see if a character's turn is over, and cycles to the next turn if it is.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
character (obj): Character to test for end of turn
|
|
||||||
"""
|
|
||||||
if not character.db.Combat_ActionsLeft: # Character has no actions remaining
|
|
||||||
self.next_turn()
|
|
||||||
return
|
|
||||||
|
|
||||||
def join_fight(self, character):
|
|
||||||
"""
|
|
||||||
Adds a new character to a fight already in progress.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
character (obj): Character to be added to the fight.
|
|
||||||
"""
|
|
||||||
# Inserts the fighter to the turn order, right behind whoever's turn it currently is.
|
|
||||||
self.db.fighters.insert(self.db.turn, character)
|
|
||||||
# Tick the turn counter forward one to compensate.
|
|
||||||
self.db.turn += 1
|
|
||||||
# Initialize the character like you do at the start.
|
|
||||||
self.initialize_for_combat(character)
|
|
||||||
1086
evennia/contrib/turnbattle/tb_equip.py
Normal file
1086
evennia/contrib/turnbattle/tb_equip.py
Normal file
File diff suppressed because it is too large
Load diff
1383
evennia/contrib/turnbattle/tb_range.py
Normal file
1383
evennia/contrib/turnbattle/tb_range.py
Normal file
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue