This commit is contained in:
Kenneth Aalberg 2022-12-20 19:08:36 +01:00
commit eff8b4aa66
1346 changed files with 170356 additions and 49772 deletions

View file

@ -1,27 +0,0 @@
---
name: Bug report (branch-develop)
about: Use this to report errors in the Evennia `develop` branch
title: "[BUG - Develop] (Enter a brief description here)"
labels: bug, branch-develop
assignees: ''
---
#### Describe the bug
<!--(Summarize your bug as clearly as possible here)-->
#### To Reproduce
Steps to reproduce the behavior:
1.
2.
3.
4. See error
#### Expected behavior
<!--(Add a clear and concise description of what you expected to happen.)-->
#### Develop-branch commit
<!-- (The develop-branch commit-hash. If unsure, run `evennia -v` or get the first few lines of the `about` command in-game.) -->
#### Additional context
<!--(Add with any other context about the problem, or ideas on how to solve.)-->

View file

@ -2,26 +2,26 @@
name: Bug report
about: Create a report to help us improve
title: "[BUG] (Enter a brief description here)"
labels: bug
labels: bug, needs-triage
assignees: ''
---
#### Describe the bug
<!--(Write with a clear and concise description of what the bug is.)-->
<!--(Replace with a clear and concise description of what the bug is.)-->
#### To Reproduce
Steps to reproduce the behavior:
1.
1.
2.
3.
4. See error
#### Expected behavior
<!--(Write a clear and concise description of what you expected to happen.)-->
<!--(Replace with a clear and concise description of what you expected to happen.)-->
#### Environment, Evennia version, OS etc
<!--(Add Evennia version and ideally commit hash. If unsure, run `evennia -v` or get the first few lines of the `about` command in-game.)-->
<!--(Replace with info. If unsure, run `evennia -v` or get the first few lines of the `about` command in-game.)-->
#### Additional context
<!--(Add with any other context about the problem, or ideas on how to solve.)-->
<!--(Replace with any other context about the problem, or ideas on how to solve.)-->

View file

@ -7,11 +7,11 @@ assignees: ''
---
#### Documentation page name and link
<!--(Give the name and link to the documentation page that needs changes, or the proposed name of a new page)-->
#### Existing page / new
<!--(Link to existing documentation page or proposed name of new page)-->
#### Reason for issue
<!--(Motivate why you suggest this change)-->
#### Documentation issue
<!--(Replace with the description of what the issue is or motivate a changes/additions)-->
#### Suggested change
<!--(Enter the suggested change here)-->

View file

@ -2,19 +2,19 @@
name: Feature request
about: Suggest an idea for this project
title: "[Feature Request] Enter a brief description here"
labels: feature-request
labels: feature-request, needs-triage
assignees: ''
---
#### Is your feature request related to a problem? Please describe.
<!--(Optional - a concise description of what the problem is. Ex. I'm always frustrated when [...])-->
<!--A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]-->
#### Describe the solution you'd like
<!--(A clear and concise description of what you want to happen after this feature is added)-->
<!--A clear and concise description of what you want to happen.-->
#### Alternatives you've considered
<!--(Describe any alternatives you've already considered/tried, if any)-->
#### Describe alternatives you've considered
<!--A clear and concise description of any alternative solutions or features you've considered.-->
#### Additional context
<!--(Any other info, links, images etc that can help with implementing or motivate adding this feature)-->
<!--Add any other context or screenshots about the feature request here.-->

View file

@ -0,0 +1,86 @@
# evennia/setup-database
# Use this action in a workflow for when you need to do initialization of database services
# (such as with PostgreSQL and MySQL) before you initiate unit tests that will employ that
# database service.
# NOTE: This action was intended for use with the core Evennia workflows ONLY.
name: Set up Evennia database service
author: dvoraen
description: "Activates the database server for the passed in service and ensures it's ready for use."
inputs:
database:
description: "Database service being initialized."
required: true
runs:
using: "composite"
steps:
- name: Set up PostgreSQL server
if: ${{ inputs.database == 'postgresql' }}
uses: harmon758/postgresql-action@v1
with:
postgresql version: "11"
postgresql db: "evennia"
postgresql user: "evennia"
postgresql password: "password"
- name: Wait for PostgreSQL to activate
if: ${{ inputs.database == 'postgresql' }}
run: |
while ! pg_isready -h 127.0.0.1 -q >/dev/null 2>&1
do
sleep 1
echo -n .
done
echo
shell: bash
- name: Set up MySQL server
if: ${{ inputs.database == 'mysql' }}
uses: mirromutth/mysql-action@v1.1
with:
host port: 3306
# character set server: "utf8mb4"
# collation server: "utf8mb4_unicode_ci"
character set server: "utf8"
collation server: "utf8_general_ci"
mysql database: "evennia"
mysql user: "evennia"
mysql password: "password"
mysql root password: root_password
- name: Wait for MySQL to activate
if: ${{ inputs.database == 'mysql' }}
run: |
while ! mysqladmin ping -h 127.0.0.1 -u root -proot_password -s >/dev/null 2>&1
do
sleep 1
echo -n .
done
echo
shell: bash
- name: Set up MySQL Privileges
if: ${{ inputs.database == 'mysql' }}
run: |
cat <<EOF | mysql -u root -proot_password -h 127.0.0.1 mysql
create user 'evennia'@'%' identified by 'password';
grant all on \`evennia%\`.* to 'evennia'@'%';
grant process on *.* to 'evennia'@'%';
flush privileges
EOF
shell: bash
# get logs from db start
- name: Database container logs
if: ${{ inputs.database == 'postgresql' || inputs.database == 'mysql' }}
uses: jwalton/gh-docker-logs@v2
- name: Check running containers
if: ${{ inputs.database == 'postgresql' || inputs.database == 'mysql' }}
run: docker ps -a
shell: bash

View file

@ -9,7 +9,7 @@
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
name: "CodeQL code scanning action"
on:
push:
@ -39,11 +39,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@ -54,7 +54,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -68,4 +68,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View file

@ -4,13 +4,15 @@ name: documentation
on:
push:
branches: [ master, develop ]
branches: [ main, develop ]
paths:
- 'docs/**'
- 'evennia/contrib/**'
pull_request:
branches: [ master, develop ]
branches: [ main, develop ]
paths:
- 'docs/**'
- 'evennia/contrib/**'
jobs:
build:
@ -18,15 +20,17 @@ jobs:
strategy:
matrix:
python-version: [3.7]
python-version: ['3.10']
steps:
- uses: actions/checkout@v2
- name: Checkout ${{ github.ref }} branch
uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: pip
- name: Install doc-building dependencies
run: |
@ -34,26 +38,21 @@ jobs:
cd docs/
make install
# fail early here, run quickstrict with aborts also on warnings
- name: Quick-test docs (no autodocs)
run: |
cd docs/
make quickstrict
# full game dir needed for mv-local
# - name: Set up evennia game dir
# run: |
# pip install -e .
# cd ..
# evennia --init gamedir
# cd gamedir
# evennia migrate
- name: Set up evennia game dir
run: |
pip install .
pip install .[extra]
cd ..
evennia --init gamedir
cd gamedir
evennia migrate
- name: Deploy docs (only from master/develop branch)
if: ${{ github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master'}}
- name: Build and deploy docs (only from main/develop branch)
if: ${{ github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main'}}
run: |
git config --global user.email "docbuilder@evennia.com"
git config --global user.name "Doc builder mechanism"
git config --global user.name "Evennia docbuilder action"
git branch
cd docs
make mv-local
echo "Would deploy here!"
make release

View file

@ -5,138 +5,132 @@ name: test-suite
on:
push:
branches: [ master, develop ]
branches: [main, develop]
paths-ignore:
- 'docs/**'
- "docs/**"
pull_request:
branches: [ master, develop ]
branches: [main, develop]
jobs:
build:
test:
name: Test
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: [3.7, 3.8, 3.9]
TESTING_DB: ['sqlite3', 'postgresql', 'mysql']
python-version: ["3.10", "3.11"]
TESTING_DB: ["sqlite3", "postgresql", "mysql"]
include:
- python-version: "3.10"
TESTING_DB: "sqlite3"
coverage-test: true
timeout-minutes: 35
env:
UNIT_TEST_SETTINGS: "--settings=settings --keepdb --parallel 4 --timing"
COVERAGE_TEST_SETTINGS: "--settings=settings --timing"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Set up database (${{ matrix.TESTING_DB }})
uses: ./.github/actions/setup-database
with:
database: ${{ matrix.TESTING_DB }}
timeout-minutes: 5
- name: Set up PostgreSQL server
uses: harmon758/postgresql-action@v1
if: ${{ matrix.TESTING_DB == 'postgresql' }}
with:
postgresql version: '10.7'
postgresql db: 'evennia'
postgresql user: 'evennia'
postgresql password: 'password'
- name: Set up MySQL server
uses: mirromutth/mysql-action@v1.1
if: ${{ matrix.TESTING_DB == 'mysql'}}
with:
host port: 3306
character set server: 'utf8mb4'
collation server: 'utf8mb4_unicode_ci'
mysql database: 'evennia'
mysql user: 'evennia'
mysql password: 'password'
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: |
pyproject.toml
# wait for db to activate, get logs from their start
- name: Wait / sleep
uses: jakejarvis/wait-action@v0.1.0
if: ${{ matrix.TESTING_DB == 'postgresql' || matrix.TESTING_DB == 'mysql' }}
with:
time: '10s'
- name: Database container logs
uses: jwalton/gh-docker-logs@v1.0.0
- name: Check running containers
run: docker ps -a
- name: Install package dependencies
run: |
python -m pip install --upgrade pip
pip install wheel
pip install psycopg2-binary==2.9.5 # req by postgresql
pip install mysqlclient
pip install coveralls
pip install tblib
pip install .
pip install .[extra]
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Initialize evennia
run: |
evennia --init testing_mygame
cp .github/workflows/${{ matrix.TESTING_DB }}_settings.py testing_mygame/server/conf/settings.py
cd testing_mygame
evennia migrate
evennia collectstatic --noinput
- name: Install package dependencies
run: |
python -m pip install --upgrade pip
pip install wheel
pip install psycopg2-binary==2.8.6 # fix issue for Django 2.2
pip install mysqlclient
pip install coveralls
pip install codacy-coverage
pip install -e .
# For non-coverage tests, run them in parallel.
- name: Run test suite
if: ${{ ! matrix.coverage-test }}
working-directory: testing_mygame
run: |
evennia test ${{ env.UNIT_TEST_SETTINGS }} evennia
- name: Install extra dependencies # Only develop branch right now
if: ${{ github.ref == 'refs/heads/develop' }}
run: pip install -r requirements_extra.txt
# OBS - it's important to not run the coverage tests with --parallel, it messes up the coverage
# calculation!
- name: Run test suite with coverage
if: ${{ matrix.coverage-test }}
working-directory: testing_mygame
run: |
coverage run --rcfile=../pyproject.toml ../bin/unix/evennia test ${{ env.COVERAGE_TEST_SETTINGS }} evennia
coverage combine
coverage xml
coverage --version
coverage report | grep TOTAL
- name: Initialize evennia
run: |
evennia --init testing_mygame
cp .github/workflows/${{ matrix.TESTING_DB }}_settings.py testing_mygame/server/conf/settings.py
cd testing_mygame
evennia migrate
evennia collectstatic --noinput
- name: Run test suite
run: |
cd testing_mygame
coverage run --source=../evennia --omit=*/migrations/*,*/urls.py,*/test*.py,*.sh,*.txt,*.md,*.pyc,*.service ../bin/unix/evennia test --settings=settings --keepdb evennia
coverage xml
# we only want to run coverall/codacy once, so we only do it for one of the matrix combinations
# it's also not critical if pushing to either service fails (happens for PRs since env is not
# available outside of the evennia org)
- name: Send data to Coveralls
if: ${{ matrix.TESTING_DB == 'sqlite3' && matrix.python-version == 3.7 }}
continue-on-error: true
env:
# we only want to run coverall once, so we only do it for the designated matrix combination(s)
# it's also not critical if pushing to either service fails (happens for PRs since env is not
# available outside of the evennia org)
- name: Send data to Coveralls
if: ${{ matrix.coverage-test && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') }}
continue-on-error: true
env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
run: |
cd testing_mygame
coveralls
run: |
cd testing_mygame
coveralls
- name: Send data to Codacy
if: ${{ matrix.TESTING_DB == 'sqlite3' && matrix.python-version == 3.7 }}
continue-on-error: true
uses: codacy/codacy-coverage-reporter-action@master
with:
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
coverage-reports: ./testing_mygame/coverage.xml
deploy:
name: Deploy Docker Image
needs: test
runs-on: ubuntu-latest
if: ${{ github.repository == 'evennia/evennia' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') }}
steps:
- uses: actions/checkout@v3
# docker setup and push
-
name: Set up QEMU
if: matrix.TESTING_DB == 'sqlite3' && matrix.python-version == 3.7
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
if: matrix.TESTING_DB == 'sqlite3' && matrix.python-version == 3.7
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
if: matrix.TESTING_DB == 'sqlite3' && matrix.python-version == 3.7 && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop')
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push for master
if: matrix.TESTING_DB == 'sqlite3' && matrix.python-version == 3.7 && github.ref == 'refs/heads/master'
id: docker_build_master
uses: docker/build-push-action@v2
with:
push: true
tags: evennia/evennia:latest
-
name: Build and push for develop
if: matrix.TESTING_DB == 'sqlite3' && matrix.python-version == 3.7 && github.ref == 'refs/heads/develop'
id: docker_build_develop
uses: docker/build-push-action@v2
with:
push: true
tags: evennia/evennia:develop
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push for main
if: ${{ github.ref == 'refs/heads/main' }}
id: docker_build_main
uses: docker/build-push-action@v3
with:
push: true
tags: evennia/evennia:latest
- name: Build and push for develop
if: ${{ github.ref == 'refs/heads/develop' }}
id: docker_build_develop
uses: docker/build-push-action@v3
with:
push: true
tags: evennia/evennia:develop

16
.gitignore vendored
View file

@ -50,3 +50,19 @@ twistd.bat
# never commit docs/build
docs/build
# For users of Atom
.remote-sync.json
# Visual Studio Code (VS-Code)
.vscode/
# Obsidian
.obsidian
# Virtual environments
.venv/
.env/
# Testing folder
.test_game_dir

44
.release.sh Executable file
View file

@ -0,0 +1,44 @@
# Release helper
VERSION=$(cat evennia/VERSION.txt)
echo "This will release Evennia $VERSION (based on evennia/VERSION.txt)."
echo "Before continuing:"
echo " 1. Make sure you have Evennia upload credentials."
echo " 2. Determine if CHANGELOG.md should be updated and rebuilt."
echo " 3. Make sure VERSION.txt and pyproject.toml both show version $VERSION."
echo " 4. Make sure all changes are committed (all un-staged files will be wiped)."
echo " 5. Make sure all unit tests pass."
read -p "Continue? [Y/n]> " yn
case $yn in
[nN] ) echo "Aborting.";
exit;;
* ) echo "Starting release ...";;
esac
# clean and build the pypi distribution
echo "Preparing and Building PyPi package ..."
rm -Rf dist/
git clean -xdf
pip install --upgrade pip
pip install build twine
python -m build --sdist --wheel --outdir dist/ .
echo "Uploading PyPi package (requires credentials) ..."
python -m twine upload dist/*
# tag the latest git commit
echo "Creating and pushing release tag tag v$VERSION ..."
git tag -a v$VERSION -m "Evennia release v$VERSION"
git push --tags
echo "... Release complete."
echo ""
echo "Post-release actions:"
echo " 1. Make sure to push all commits."
echo " 2. Update github discussions to report on release."
echo " 2. Make post in discord #announcements channel pointing to discussion post."
echo " 3. Any other announcements as needed."

View file

@ -1,9 +1,32 @@
# Changelog
## Evennia 1.0-dev (2019-) (WIP)
### Evennia 1.0
2019-2022
_Changed to using `main` branch to follow github standard. Old `master` branch remains
for now but will not be used anymore, so as to not break installs during transition._
Increase requirements: Django 4.1+, Twisted 22.10+ Python 3.10, 3.11. PostgreSQL 11+.
- New `drop:holds()` lock default to limit dropping nonsensical things. Access check
defaults to True for backwards-compatibility in 0.9, will be False in 1.0
- REST API allows you external access to db objects through HTTP requests (Tehom)
- `Object.normalize_name` and `.validate_name` added to (by default) enforce latinify
on character name and avoid potential exploits using clever Unicode chars (trhr)
- New `utils.format_grid` for easily displaying long lists of items in a block.
- Using `lunr` search indexing for better `help` matching and suggestions. Also improve
the main help command's default listing output.
- Added `content_types` indexing to DefaultObject's ContentsHandler. (volund)
- Made most of the networking classes such as Protocols and the SessionHandlers
replaceable via `settings.py` for modding enthusiasts. (volund)
- The `initial_setup.py` file can now be substituted in `settings.py` to customize
initial game database state. (volund)
- Added new Traits contrib, converted and expanded from Ainneve project.
- Added new `requirements_extra.txt` file for easily getting all optional dependencies.
- Change default multi-match syntax from 1-obj, 2-obj to obj-1, obj-2.
- Make `object.search` support 'stacks=0' keyword - if ``>0``, the method will return
N identical matches instead of triggering a multi-match error.
- Add `tags.has()` method for checking if an object has a tag or tags (PR by ChrisLR)
- Make IP throttle use Django-based cache system for optional persistence (PR by strikaco)
- Renamed Tutorial classes "Weapon" and "WeaponRack" to "TutorialWeapon" and
@ -15,16 +38,201 @@
- Fix typo in UnixCommand contrib, where `help` was given as `--hel`.
- Latin (la) i18n translation (jamalainm)
- Made the `evennia` dir possible to use without gamedir for purpose of doc generation.
- Make Scripts' timer component independent from script object deletion; can now start/stop
timer without deleting Script. The `.persistent` flag now only controls if timer survives
reload - Script has to be removed with `.delete()` like other typeclassed entities.
- Add `utils.repeat` and `utils.unrepeat` as shortcuts to TickerHandler add/remove, similar
to how `utils.delay` is a shortcut for TaskHandler add.
- Refactor the classic `red_button` example to use `utils.delay/repeat` and modern recommended
code style and paradigms instead of relying on `Scripts` for everything.
- Expand `CommandTest` with ability to check multiple message-receivers; inspired by PR by
user davewiththenicehat. Also add new doc string.
- Add central `FuncParser` as a much more powerful replacement for the old `parse_inlinefunc`
function.
- Attribute/NAttribute got a homogenous representation, using intefaces, both
`AttributeHandler` and `NAttributeHandler` has same api now.
- Add `evennia/utils/verb_conjugation` for automatic verb conjugation (English only). This
is useful for implementing actor-stance emoting for sending a string to different targets.
- New version of Italian translation (rpolve)
- `utils.evmenu.ask_yes_no` is a helper function that makes it easy to ask a yes/no question
to the user and respond to their input. This complements the existing `get_input` helper.
- Allow sending messages with `page/tell` without a `=` if target name contains no spaces.
- New FileHelpStorage system allows adding help entries via external files.
- `sethelp` command now warns if shadowing other help-types when creating a new
entry.
- Help command now uses `view` lock to determine if cmd/entry shows in index and
`read` lock to determine if it can be read. It used to be `view` in the role
of the latter. Migration swaps these around.
- In modules given by `settings.PROTOTYPE_MODULES`, spawner will now first look for a global
list `PROTOTYPE_LIST` of dicts before loading all dicts in the module as prototypes.
- New Channel-System using the `channel` command and nicks. Removed the `ChannelHandler` and the
concept of a dynamically created `ChannelCmdSet`.
- Add `Msg.db_receiver_external` field to allowe external, string-id message-receivers.
- Renamed `app.css` to `website.css` for consistency. Removed old prosimii-css files.
- Remove `mygame/web/static_overrides` and -`template_overrides`, reorganize website/admin/client/api
into a more consistent structure for overriding. Expanded webpage documentation considerably.
- REST API list-view was shortened (#2401). New CSS/HTML. Add ReDoc for API autodoc page.
- Update and fix dummyrunner with cleaner code and setup.
- Made `iter_to_str` format prettier strings, using Oxford comma.
- Added an MXP anchor tag to also support clickable web links.
- New `tasks` command for managing tasks started with `utils.delay` (PR by davewiththenicehat)
- Make `help` index output clickable for webclient/clients with MXP (PR by davewiththenicehat)
- Custom `evennia` launcher commands (e.g. `evennia mycmd foo bar`). Add new commands as callables
accepting `*args`, as `settings.EXTRA_LAUNCHER_COMMANDS = {'mycmd': 'path.to.callable', ...}`.
- New `XYZGrid` contrib, adding x,y,z grid coordinates with in-game map and
pathfinding. Controlled outside of the game via custom evennia launcher command.
- `Script.delete` has new kwarg `stop_task=True`, that can be used to avoid
infinite recursion when wanting to set up Script to delete-on-stop.
- Command executions now done on copies to make sure `yield` don't cause crossovers. Add
`Command.retain_instance` flag for reusing the same command instance.
- The `typeclass` command will now correctly search the correct database-table for the target
obj (avoids mistakenly assigning an AccountDB-typeclass to a Character etc).
- Merged `script` and `scripts` commands into one, for both managing global- and
on-object Scripts. Moved `CmdScripts` and `CmdObjects` to `commands/default/building.py`.
- Keep GMCP function case if outputfunc starts with capital letter (so `cmd_name` -> `Cmd.Name`
but `Cmd_nAmE` -> `Cmd.nAmE`). This helps e.g Mudlet's legacy `Client_GUI` implementation)
- Prototypes now allow setting `prototype_parent` directly to a prototype-dict.
This makes it easier when dynamically building in-module prototypes.
- `RPSystem contrib` was expanded to support case, so /tall becomes 'tall man'
while /Tall becomes 'Tall man'. One can turn this off if wanting the old style.
- Change `EvTable` fixed-height rebalance algorithm to fill with empty lines at end of
column instead of inserting rows based on cell-size (could be mistaken for a bug).
- Split `return_appearance` hook with helper methods and have it use a template
string in order to make it easier to override.
- Add validation question to default account creation.
- Add `LOCALECHO` client option to add server-side echo for clients that does
not support this (useful for getting a complete log).
- Make `@lazy_property` decorator create read/delete-protected properties. This is
because it's used for handlers, and e.g. self.locks=[] is a common beginner mistake.
- Add `$pron()` inlinefunc for pronoun parsing in actor-stance strings using
`msg_contents`.
- Update defauklt website to show Telnet/SSL/SSH connect info. Added new
`SERVER_HOSTNAME` setting for use in the server:port stanza.
- Changed all `at_before/after_*` hooks to `at_pre/post_*` for consistency
across Evennia (the old names still work but are deprecated)
- Change `settings.COMMAND_DEFAULT_ARG_REGEX` default from `None` to a regex meaning that
a space or `/` must separate the cmdname and args. This better fits common expectations.
- Add confirmation question to `ban`/`unban` commands.
- Check new `teleport` and `teleport_here` lock-types in `teleport` command to optionally
allow to limit teleportation of an object or to a specific destination.
- Add `settings.MXP_ENABLED=True` and `settings.MXP_OUTGOING_ONLY=True` as sane defaults,
to avoid known security issues with players entering MXP links.
- Add browser name to webclient `CLIENT_NAME` in `session.protocol_flags`, e.g.
`"Evennia webclient (websocket:firefox)"` or `"evennia webclient (ajax:chrome)"`.
- `TagHandler.add/has(tag=...)` kwarg changed to `add/has(key=...)` for consistency
with other handlers.
- Make `DefaultScript.delete`, `DefaultChannel.delete` and `DefaultAccount.delete` return
bool True/False if deletion was successful (like `DefaultObject.delete` before them)
- `contrib.custom_gametime` days/weeks/months now always starts from 1 (to match
the standard calendar form ... there is no month 0 every year after all).
- `AttributeProperty`/`NAttributeProperty` to allow managing Attributes/NAttributes
on typeclasses in the same way as Django fields.
- Give build/system commands a `@name` to fall back to if the non-@ name is used
by another command (like `open` and `@open`. If no duplicate, @ is optional.
- Move legacy channel-management commands (`ccreate`, `addcom` etc) to a contrib
since their work is now fully handled by the single `channel` command.
- Expand `examine` command's code to much more extensible and modular. Show
attribute categories and value types (when not strings).
- `AttributeHandler.remove(key, return_exception=False, category=None, ...)` changed
to `.remove(key, category=None, return_exception=False, ...)` for consistency.
- New `command cooldown` contrib for making it easier to manage commands using
dynamic cooldowns between uses (owllex)
- Restructured `contrib/` folder, placing all contribs as separate packages under
subfolders. All imports will need to be updated.
- Made `MonitorHandler.add/remove` support `category` for monitoring Attributes
with a category (before only key was used, ignoring category entirely).
- Move `create_*` functions into db managers, leaving `utils.create` only being
wrapper functions (consistent with `utils.search`). No change of api otherwise.
- Add support for `$dbref()` and `$search` when assigning an Attribute value
with the `set` command. This allows assigning real objects from in-game.
- Add ability to examine `/script` and `/channel` entities with `examine` command.
- Homogenize manager search methods to return querysets and not lists.
- Restructure unit tests to always honor default settings; make new parents in
on location for easy use in game dir.
- The `Lunr` search engine used by help excludes common words; the settings-list
`LUNR_STOP_WORD_FILTER_EXCEPTIONS` can be extended to make sure common names are included.
- Add `.deserialize()` method to `_Saver*` structures to help completely
decouple structures from database without needing separate import.
- Add `run_in_main_thread` as a helper for those wanting to code server code
from a web view.
- Update `evennia.utils.logger` to use Twisted's new logging API. No change in Evennia API
except more standard aliases logger.error/info/exception/debug etc can now be used.
- Have `type/force` default to `update`-mode rather than `reset`mode and add more verbose
warning when using reset mode.
- Attribute storage support defaultdics (Hendher)
- Add ObjectParent mixin to default game folder template as an easy, ready-made
way to override features on all ObjectDB-inheriting objects easily.
source location, mimicking behavior of `at_pre_move` hook - returning False will abort move.
- Add `TagProperty`, `AliasProperty` and `PermissionProperty` to assign these
data in a similar way to django fields.
- New `at_pre_object_receive(obj, source_location)` method on Objects. Called on
destination, mimicking behavior of `at_pre_move` hook - returning False will abort move.
- New `at_pre_object_leave(obj, destination)` method on Objects. Called on
- The db pickle-serializer now checks for methods `__serialize_dbobjs__` and `__deserialize_dbobjs__`
to allow custom packing/unpacking of nested dbobjs, to allow storing in Attribute.
- Optimizations to rpsystem contrib performance. Breaking change: `.get_sdesc()` will
now return `None` instead of `.db.desc` if no sdesc is set; fallback in hook (inspectorCaracal)
- Reworked text2html parser to avoid problems with stateful color tags (inspectorCaracal)
- Simplified `EvMenu.options_formatter` hook to use `EvColumn` and f-strings (inspectorcaracal)
- Allow `# CODE`, `# HEADER` etc as well as `#CODE`/`#HEADER` in batchcode
files - this works better with black linting.
- Added `move_type` str kwarg to `move_to()` calls, optionally identifying the type of
move being done ('teleport', 'disembark', 'give' etc). (volund)
- Made RPSystem contrib msg calls pass `pose` or `say` as msg-`type` for use in
e.g. webclient pane filtering where desired. (volund)
- Added `Account.uses_screenreader(session=None)` as a quick shortcut for
finding if a user uses a screenreader (and adjust display accordingly).
- Fixed bug in `cmdset.remove()` where a command could not be deleted by `key`,
even though doc suggested one could (ChrisLR)
- New contrib `name_generator` for building random real-world based or fantasy-names
based on phonetic rules.
- Enable proper serialization of dict subclasses in Attributes (aogier)
- `object.search` fuzzy-matching now uses `icontains` instead of `istartswith`
to better match how search works elsewhere (volund)
- The `.at_traverse` hook now receives a `exit_obj` kwarg, linking back to the
exit triggering the hook (volund)
- Contrib `buffs` for managing temporary and permanent RPG status buffs effects (tegiminis)
- New `at_server_init()` hook called before all other startup hooks for all
startup modes. Used for more generic overriding (volund)
- New `search` lock type used to completely hide an object from being found by
the `DefaultObject.search` (`caller.search`) method. (CloudKeeper)
- Change setting `MULTISESSION_MODE` to now only control sessions, not how many
characters can be puppeted simultaneously. New settings now control that.
- Add new setting `AUTO_CREATE_CHARACTER_WITH_ACCOUNT`, a boolean deciding if
the new account should also get a matching character (legacy MUD style).
- Add new setting `AUTO_PUPPET_ON_LOGIN`, boolean deciding if one should
automatically puppet the last/available character on connection (legacy MUD style)
- Add new setting `MAX_NR_SIMULTANEUS_PUPPETS` - how many puppets the account
can run at the same time. Used to limit multi-playing.
- Make setting `MAX_NR_CHARACTERS` interact better with the new settings above.
- Allow `$search` funcparser func to search tags and to accept kwargs for more
powerful searches passed into the regular search functions.
- `spawner.spawn` and linked methods now has a kwarg `protfunc_raise_errors`
(default True) to disable strict errors on malformed/not-found protfuncs
- Improve search performance when having many DB-based prototypes via caching.
- Remove the `return_parents` kwarg of `evennia.prototypes.spawner.spawn` since it
was inefficient and unused.
- Made all id fields BigAutoField for all databases. (owllex)
- `EvForm` refactored. New `literals` mapping, for literal mappings into the
main template (e.g. for single-character replacements).
- `EvForm` `cells` kwarg now accepts `EvCells` with custom formatting options
(mainly for custom align/valign). `EvCells` now makes use of `utils.justify`.
- `utils.justify` now supports `align="a"` (absolute alignments. This keeps
the given left indent but crops/fills to the width. Used in EvCells.
- `EvTable` now supports passing `EvColumn`s as a list directly, (`EvTable(table=[colA,colB])`)
- Add `tags=` search criterion to `DefaultObject.search`.
- Add `AT_EXIT_TRAVERSE` signal, firing when an exit is traversed.
- Add integration between Evennia and Discord channels (PR by Inspector Cararacal)
- Support for using a Godot-powered client with Evennia (PR by ChrisLR)
- Added German translation (patch by Zhuraj)
### Backports from 1.0 to 0.9.5 since 0.9.5 release
## Evennia 0.9.5
- Fix to TaskHandler to complate api and allow manipulation of `utils.delay`
return as originall intended.
- Support for Python 3.9.
> 2019-2020
> Released 2020-11-14.
> Transitional release, including new doc system.
### Evennia 0.9.5 (Nov 2020)
A transitional release, including new doc system.
Backported from develop: Python 3.8, 3.9 support. Django 3.2+ support, Twisted 21+ support.
- `is_typeclass(obj (Object), exact (bool))` now defaults to exact=False
- `py` command now reroutes stdout to output results in-game client. `py`
@ -91,8 +299,7 @@ without arguments starts a full interactive Python console.
- Make `cmd.at_post_cmd()` always run after `cmd.func()`, even when the latter uses delays
with yield.
- `EvMore` support for db queries and django paginators as well as easier to override for custom
pagination (e.g. to create EvTables for every page instead of splitting one table).
- New `EvMore` methods `.init_pages`, `paginator` and `page_formatter` for easily customize pagination.
pagination (e.g. to create EvTables for every page instead of splittine one table)
- Using `EvMore pagination`, dramatically improves performance of `spawn/list` and `scripts` listings
(100x speed increase for displaying 1000+ prototypes/scripts).
- `EvMenu` now uses the more logically named `.ndb._evmenu` instead of `.ndb._menutree` to store itself.
@ -104,10 +311,16 @@ without arguments starts a full interactive Python console.
- Include more Web-client info in `session.protocol_flags`.
- Fixes in multi-match situations - don't allow finding/listing multimatches for 3-box when
only two boxes in location.
- Made the `evennia` dir possible to use without gamedir for purpose of doc generation.
- Fix for TaskHandler with proper deferred returns/ability to cancel etc (PR by davewiththenicehat)
- Add `PermissionHandler.check` method for straight string perm-checks without needing lockstrings.
- Add `evennia.utils.utils.strip_unsafe_input` for removing html/newlines/tags from user input. The
`INPUT_CLEANUP_BYPASS_PERMISSIONS` is a list of perms that bypass this safety stripping.
- Make default `set` and `examine` commands aware of Attribute categories.
## Evennia 0.9
## Evennia 0.9 (2018-2019)
> 2018-2019
> Released Oct 2019
### Distribution
@ -305,7 +518,10 @@ without arguments starts a full interactive Python console.
- Simplified chinese, courtesy of user MaxAlex.
## Evennia 0.8 (2018)
## Evennia 0.8
> 2017-2018
> Released Nov 2018
### Requirements
@ -432,7 +648,9 @@ without arguments starts a full interactive Python console.
- Polish translation by user ogotai
# Overviews
# Overview-Changelogs
> These are changelogs from a time before we used formal version numbers.
## Sept 2017:
Release of Evennia 0.7; upgrade to Django 1.11, change 'Player' to
@ -554,24 +772,9 @@ to Events, Commands and Permissions.
Griatch takes over Maintainership of the Evennia project from
the original creator Greg Taylor.
(Earlier revisions, with previous maintainer, go back to 2005)
# Older
Earlier revisions, with previous maintainer, used SVN on Google Code
and have no changelogs.
# Contact, Support and Development
Make a post to the mailing list or chat us up on IRC. We also have a
bug tracker if you want to report bugs. Finally, if you are willing to
help with the code work, we much appreciate all help! Visit either of
the following resources:
* Evennia Webpage
http://evennia.com
* Evennia manual (wiki)
https://github.com/evennia/evennia/wiki
* Evennia Code Page (See INSTALL text for installation)
https://github.com/evennia/evennia
* Bug tracker
https://github.com/evennia/evennia/issues
* IRC channel
visit channel #evennia on irc.freenode.com
or the webclient: http://tinyurl.com/evchat
First commit (Evennia's birthday) was November 20, 2006.

View file

@ -2,11 +2,17 @@
## 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.
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:
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
@ -16,31 +22,54 @@ Examples of behavior that contributes to creating a positive environment include
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* 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
* 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 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.
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.
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.
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.
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]
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/

View file

@ -1,32 +1,34 @@
# Evennia Code Style
All code submitted or committed to the Evennia project should aim to
follow the guidelines outlined in [Python PEP 8][pep8]. Keeping the code style
uniform makes it much easier for people to collaborate and read the
code.
All code submitted or committed to the Evennia project should aim to follow the
guidelines outlined in [Python PEP 8][pep8]. Keeping the code style uniform
makes it much easier for people to collaborate and read the code.
A good way to check if your code follows PEP8 is to use the [PEP8 tool][pep8tool]
on your sources.
## A quick list of code style points
## Main code style specification
* 4-space indentation, NO TABS!
* Unix line endings.
* 100 character line widths
* CamelCase is only used for classes, nothing else.
* All non-global variable names and all function names are to be
lowercase, words separated by underscores. Variable names should
always be more than two letters long.
* Module-level global variables (only) are to be in CAPITAL letters.
* (Evennia-specific): Imports should normally be done in this order:
* Imports should be done in this order:
- Python modules (builtins and standard library)
- Twisted modules
- Django modules
- Evennia library modules (`evennia`)
- Evennia contrib modules (`evennia.contrib`)
* All modules, classes, functions and modules should have doc
strings formatted as described below
* All modules, classes, functions and methods should have doc strings formatted
as outlined below.
* All default commands should have a consistent docstring formatted as
outlined below.
## Doc strings
## Code Docstrings
All modules, classes, functions and methods should have docstrings
formatted with [Google style][googlestyle] -inspired indents, using
@ -52,7 +54,7 @@ are useful in the game ...
Sectioning (`# title`, `## subtile` etc) should not be used in
freeform docstrings - this will confuse the sectioning of the auto
documentation page and the auto-api will create this automatically.
Write just the section name bolded on its own line to mark a section.
Write just the section name bolded on its own line to mark a section.
Beyond sections markdown should be used as needed to format
the text.
@ -68,7 +70,7 @@ other text, only code).
### Class docstrings
The root class docstring should describe the over-arcing use of the
The root class docstring should describe the over-arching use of the
class. It should usually not describe the exact call sequence nor list
important methods, this tends to be hard to keep updated as the API
develops. Don't use section markers (`#`, `##` etc).
@ -105,7 +107,7 @@ def funcname(a, b, c, d=False, **kwargs):
test (list): A test keyword.
Returns:
e (str): The result of the function.
str: The result of the function.
Raises:
RuntimeException: If there is a critical error,
@ -129,20 +131,18 @@ indents to be 4 spaces wide (no tabs!).
Here are all the supported block headers:
```
Args/Arg/Kwargs/Kwarg:
argname (freeform type): text
or
freeform text
"""
Args
argname (freeform type): Description endind with period.
Keyword Args:
argname (freeform type): Description.
Returns/Yields:
kwargname (freeform type): text
or
freeform text
type: Description.
Raises:
Exceptiontype: text
or
freeform text
Exceptiontype: Description.
Notes/Note/Examples/Example:
freeform text
Freeform text.
"""
```
Parts marked with "freeform" means that you can in principle put any
@ -153,20 +153,24 @@ freeform counterpart (this will produce nicer output) but in some
cases the freeform may produce a more compact and readable result
(such as when describing an `*args` or `**kwargs` statement in general
terms). The first `self` argument of class methods should never be
documented.
documented.
Note that
```
"""
Args:
argname (type, optional): text
argname (type, optional): Description.
"""
```
and
```
"""
Keyword Args:
argname (type): text
sargname (type): Description.
"""
```
mean the same thing! Which one is used depends on the function or
@ -176,25 +180,92 @@ good idea, for a small number of arguments though, just using `Args:`
and marking keywords as `optional` will shorten the docstring and make
it easier to read.
### Default Commands
## Default Command Docstrings
These represent a special case since Commands in Evennia are using their
class docstrings to represent the in-game help entry for that command.
So for the default look of Command class docstrings see instead
[the default command documentation policy][command-docstrings].
These represent a special case since Commands in Evennia use their class
docstrings to represent the in-game help entry for that command.
### Automatic docstring templating
All the commands in the _default command_ sets should have their doc-strings
formatted on a similar form. For contribs, this is loosened, but if there is
no particular reason to use a different form, one should aim to use the same
style for contrib-command docstrings as well.
The Python IDE [Pycharm][pycharm] will generate Evennia-friendly
docstring stubs automatically for you, but the default format is
reStructuredText. To change it to Evennia's Google-style, follow
[this guide][pycharm-guide].
```python
"""
Short header
## Ask Questions!
Usage:
key[/switches, if any] <mandatory args> [optional] choice1||choice2||choice3
Switches:
switch1 - description
switch2 - description
Examples:
Usage example and output
Longer documentation detailing the command.
"""
```
- Two spaces are used for *indentation* in all default commands.
- Square brackets `[ ]` surround *optional, skippable arguments*.
- Angled brackets `< >` surround a _description_ of what to write rather than the exact syntax.
- Explicit choices are separated by `|`. To avoid this being parsed as a color code, use `||` (this
will come out as a single `|`) or put spaces around the character ("` | `") if there's plenty of room.
- The `Switches` and `Examples` blocks are optional and based on the Command.
Here is the `nick` command as an example:
```python
"""
Define a personal alias/nick
Usage:
nick[/switches] <nickname> = [<string>]
alias ''
Switches:
object - alias an object
account - alias an account
clearall - clear all your aliases
list - show all defined aliases (also "nicks" works)
Examples:
nick hi = say Hello, I'm Sarah!
nick/object tom = the tall man
A 'nick' is a personal shortcut you create for your own use [...]
"""
```
For commands that *require arguments*, the policy is for it to return a `Usage:`
string if the command is entered without any arguments. So for such commands,
the Command body should contain something to the effect of
```python
if not self.args:
self.caller.msg("Usage: nick[/switches] <nickname> = [<string>]")
return
```
## Tools for auto-linting
### black
Automatic pep8 compliant formatting and linting can be performed using the
`black` formatter:
black --line-length 100
### PyCharm
The Python IDE [Pycharm][pycharm] can auto-generate empty doc-string stubs. The
default is to use `reStructuredText` form, however. To change to Evennia's
Google-style docstrings, follow [this guide][pycharm-guide].
If any of the rules outlined in PEP 8 or in the sections above doesn't
make sense, please don't hesitate to ask on the Evennia mailing list
or in the chat.
[pep8]: http://www.python.org/dev/peps/pep-0008

View file

@ -1,7 +1,17 @@
# Contributing to Evennia
Evennia utilizes GitHub for issue tracking and contributions:
There are many ways you can contribute to Evennia development:
- Reporting Issues issues/bugs and making feature requests can be done [in the issue tracker](https://github.com/evennia/evennia/issues).
- Evennia's documentation is a [wiki](https://github.com/evennia/evennia/wiki) that everyone can contribute to. Further
instructions and details about contributing is found [here](https://github.com/evennia/evennia/wiki/Contributing).
- You can help a lot by being active in the community. You can spread
the word - by writing, talking, blogging etc about Evennia. Let
others know text-based gaming is still a thing!
- You can help by reporting any issues/bugs you find, and tell us of your
feature requests [as an issue on github][issues]. This is also where you
report typos or errors in the [the Evennia documentation][docs].
- To help fixing (or expand) our docs, check out [how to contribute to docs][contribute-docs].
- To contribute to Evennia itself, check out how to [help with code][helping-code].
[issues]: https://github.com/evennia/evennia/issues/new/choose
[docs]: https://www.evennia.com/docs/1.0-dev/index.html
[contribute-docs]: https://www.evennia.com/docs/1.0-dev/Contributing-Docs.html
[helping-code]: https://www.evennia.com/docs/1.0-dev/Contributing.html#helping-with-code

View file

@ -2,7 +2,7 @@
# Base docker image for running Evennia-based games in a container.
#
# Install:
# install `docker` (http://docker.com)
# install `docker` (https://docker.com)
#
# Usage:
# cd to a folder where you want your game data to be (or where it already is).
@ -26,25 +26,27 @@
#
# 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 .
# info, see https://evennia.com/docs/latest/Setup/Installation-Docker
#
FROM python:3.7-alpine
FROM python:3.11-alpine
LABEL maintainer="www.evennia.com"
LABEL maintainer="https://www.evennia.com"
# install compilation environment
RUN apk update && apk add bash gcc jpeg-dev musl-dev procps \
libffi-dev openssl-dev zlib-dev gettext
libffi-dev openssl-dev zlib-dev gettext \
g++ gfortran python3-dev cmake openblas-dev
# add the files required for pip installation
COPY ./pyproject.toml /usr/src/evennia/
COPY ./setup.py /usr/src/evennia/
COPY ./requirements.txt /usr/src/evennia/
COPY ./evennia/VERSION.txt /usr/src/evennia/evennia/
COPY ./bin /usr/src/evennia/bin/
# install dependencies
RUN pip install --upgrade pip && pip install -e /usr/src/evennia --trusted-host pypi.python.org
RUN pip install cryptography pyasn1 service_identity
RUN pip install --upgrade pip && \
pip install cryptography pyasn1 service_identity && \
pip install -e /usr/src/evennia --trusted-host pypi.python.org && \
pip install evennia[extra]
# add the project source; this should always be done after all
# expensive operations have completed to avoid prematurely
@ -53,7 +55,7 @@ COPY . /usr/src/evennia
# add the game source when rebuilding a new docker image from inside
# a game dir
ONBUILD COPY . /usr/src/game
ONBUILD COPY --chown=evennia . /usr/src/game
# make the game source hierarchy persistent with a named volume.
# mount on-disk game location here when using the container
@ -63,15 +65,16 @@ VOLUME /usr/src/game
# set the working directory
WORKDIR /usr/src/game
# set bash prompt
ENV PS1 "evennia|docker \w $ "
# set bash prompt and pythonpath to evennia lib
ENV PS1 "evennia|docker \w $ "
ENV PYTHONPATH /usr/src/evennia
# create and switch to a non-root user for runtime security
# -D - do not set a password
# -H - do not create a home directory
# -s /bin/false - set login shell to /bin/false
RUN adduser -D -H -s /bin/false evennia
USER evennia
# RUN adduser -D -H -s /bin/false evennia
# USER evennia
# startup a shell when we start the container
ENTRYPOINT ["/usr/src/evennia/bin/unix/evennia-docker-start.sh"]

View file

@ -1,5 +1,6 @@
# Evennia installation
You can find the latest updated installation instructions and
requirements [here](https://github.com/evennia/evennia/wiki/Getting-Started).
You can find the latest updated installation instructions and
requirements
[here](https://www.evennia.com/docs/1.0-dev/Setup/Installation.html)

View file

@ -1,35 +1,19 @@
BSD license
===========
BSD 3-Clause License
Evennia MU* creation system
Copyright (c) 2012-, Griatch (griatch <AT> gmail <DOT> com), Gregory Taylor
All rights reserved.
Copyright 2012- Griatch (griatch <AT> gmail <DOT> com), Gregory Taylor
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of the Copyright Holders nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,24 +1,31 @@
# This is used with `make <option>` and is used for running various
# administration operations on the code.
BLACK_FORMAT_CONFIGS = --target-version py37 --line-length 100 --exclude=/docs
TEST_GAME_DIR = .test_game_dir
TESTS ?= evennia
default:
@echo " Usage: "
@echo " make install - install evennia (recommended to activate virtualenv first)"
@echo " make installextra - install evennia with extra-requirements (activate virtualenv first)"
@echo " make fmt/format - run the black autoformatter on the source code"
@echo " make lint - run black in --check mode"
@echo " make test - run evennia test suite with all default values."
@echo " make tests=evennia.path test - run only specific test or tests."
@echo " make testp - run test suite using multiple cores."
@echo " make release - publish evennia to pypi (requires pypi credentials)
install:
pip install -e .
installextra:
pip install -e .
pip install -e .[extra]
# black is configured from pyproject.toml
format:
black $(BLACK_FORMAT_CONFIGS) evennia
black evennia
isort --profile black .
fmt: format
@ -36,3 +43,9 @@ testp:
cd $(TEST_GAME_DIR);\
evennia migrate;\
evennia test --keepdb --parallel 4 $(TESTS);\
version:
echo $(VERSION)
release:
./.release.sh

View file

@ -1,81 +1,62 @@
# Evennia MUD/MU\* Creation System ![evennia logo][logo]
[![Build Status][unittestciimg]][unittestcilink] [![Coverage Status][coverimg]][coverlink]
# Evennia MUD/MU\* Creation System ![][logo]
[![unittestciimg]][unittestcilink] [![Coverage Status][coverimg]][coverlink] [![Pypi Version][pypibadge]][pypilink]
*Evennia* is a modern library for creating [online multiplayer text
[Evennia][homepage] is a modern library for creating [online multiplayer text
games][wikimudpage] (MUD, MUSH, MUX, MUCK, MOO etc) in pure Python. It
allows game creators to design and flesh out their ideas with great
freedom. Evennia is made available under the very friendly [BSD
license][license].
freedom.
http://www.evennia.com is the main hub tracking all things Evennia.
Evennia does not impose a particular style, genre or game mechanic. Instead it
solves the boring networking and basic stuff all online games need. It provides
a framework and tools for you to build the game you want. Coding in Evennia is
done using normal Python modules imported into the server at runtime.
Evennia has [extensive documentation][docs]. It also has a very active community
with [discussion forums][group] and a [discord server][chat] to help and support you!
## Features and Philosophy
## Installation
The Evennia library aims for you to have a fully functioning, if
empty, online game up and running in minutes. Rather than imposing a
particular style, genre or game mechanic we offer a framework on which
you build the game of your dreams. The idea is to allow you to
concentrate on designing the bits that make your game unique.
pip install evennia
(windows users once: py -m evennia)
evennia --init mygame
cd mygame
evennia migrate
evennia start / stop / reload
Coding in Evennia is done using normal Python modules imported into
the server at runtime. All library code is heavily documented and
Evennia comes with extensive manuals and tutorials. You use Python
classes to represent your objects, scripts, players, in-game channels
and so on. The database layer is abstracted away. This makes it
possible to create the game using modern code practices using the full
flexibility and power of Python.
See [the full installation instructions][installation] for more help.
Next, browse to `http://localhost:4001` or use your third-party mud client to
connect to to `localhost`, port `4000` to see your working (if empty) game!
![screenshot][screenshot]
_A game website is created automatically. Connect to your Evennia game from your web browser as well as using traditional third-party clients_.
Evennia offers extensive connectivity options, including traditional
telnet connections. Evennia is also its own web server: A default
website as well as a browser-based mud client (html5 websockets, with
fallback to AJAX) runs by default. Due to our Django and Twisted
foundations, web integration is easy since the same code powering the
game is also used to run its web presence.
## Where to go next
Whereas Evennia is intentionally empty of game content from the onset,
we *do* offer some defaults you can build from. The code base comes
with basic classes for objects, exits, rooms and characters. There are
systems for handling puppeting, scripting, timers, dynamic games
states etc. A default command set (completely replaceable with your
own syntax and functionality) handles administration, building, chat
channels, poses and so on. The default setup is enough to run a
'Talker' or some other social-style game out of the box. We also have
a contributions folder with optional plugins and examples of more
game-specific systems.
If this piqued your interest, there is a [lengthier introduction][introduction] to read. You
can also read our [Evennia in pictures][evenniapictures] overview.
## Current Status
After that, why not check out the [Evennia Beginner tutorial][beginnertutorial].
The codebase is currently in **Beta**. While development continues,
Evennia is already stable enough to be suitable for prototyping and
development of your own games.
## Where to go from here
If this piqued your interest, there is a [lengthier
introduction][introduction] to read.
To learn how to get your hands on the code base, the [Getting
started][gettingstarted] page is the way to go. Otherwise you could
browse the [Documentation][docs] or why not come join the [Evennia
Community forum][group] or join us in our [development chat][chat].
Welcome!
[homepage]: https://www.evennia.com
[gettingstarted]: https://www.evennia.com/docs/latest/Getting-Started.html
[docs]: https://www.evennia.com/docs/latest
[screenshot]: https://user-images.githubusercontent.com/294267/30773728-ea45afb6-a076-11e7-8820-49be2168a6b8.png
[screenshot]: https://user-images.githubusercontent.com/294267/205434941-14cc4f59-7109-49f7-9d71-0ad3371b007c.jpg
[logo]: https://github.com/evennia/evennia/blob/master/evennia/web/website/static/website/images/evennia_logo.png
[unittestciimg]: https://github.com/evennia/evennia/workflows/test-suite/badge.svg
[unittestcilink]: https://github.com/evennia/evennia/actions?query=workflow%3Atest-suite
[coverimg]: https://coveralls.io/repos/github/evennia/evennia/badge.svg?branch=master
[coverlink]: https://coveralls.io/github/evennia/evennia?branch=master
[coverimg]: https://coveralls.io/repos/github/evennia/evennia/badge.svg?branch=main
[coverlink]: https://coveralls.io/github/evennia/evennia?branch=main
[pypibadge]: https://img.shields.io/pypi/v/evennia?color=blue
[pypilink]: https://pypi.org/project/evennia/
[introduction]: https://www.evennia.com/docs/latest/Evennia-Introduction.html
[license]: https://www.evennia.com/docs/latest/Licensing.html
[group]: https://github.com/evennia/evennia/discussions
[chat]: https://discord.gg/AJJpcRUhtF
[wikimudpage]: http://en.wikipedia.org/wiki/MUD
[evenniapictures]: https://www.evennia.com/docs/latest/Evennia-In-Pictures.html
[beginnertutorial]: https://www.evennia.com/docs/latest/Howtos/Howtos-Overview.html#beginner-tutorial
[installation]: https://www.evennia.com/docs/latest/Setup/Setup-Overview.html#installation-and-running

View file

@ -8,10 +8,10 @@ Griatch 2017, released under the BSD license.
"""
import fnmatch
import os
import re
import sys
import os
import fnmatch
ANSI_HILITE = "\033[1m"
ANSI_RED = "\033[31m"

View file

@ -6,5 +6,6 @@ This is copied directly into the python bin directory and makes the
'evennia' program available on $PATH.
"""
from evennia.server.evennia_launcher import main
from evennia.server.evennia_launcher import main
main()

View file

@ -9,7 +9,7 @@ PS1="evennia|docker \w $ "
cmd="$@"
output="Docker starting with argument '$cmd' ..."
if test -z $cmd; then
if test -z "$cmd"; then
cmd="bash"
output="No argument given, starting shell ..."
fi

29
docs/LICENSE.txt Normal file
View file

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2019-, Evennia
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -3,7 +3,7 @@
SOURCEDIR = source
BUILDDIR = build
AUTODOCDIR = $(SOURCEDIR)/api
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
@ -34,7 +34,7 @@ help:
@echo " $(cblue)quick$(cnorm) to build local docs but skip the autodocs (for quick testing)"
@echo " $(cblue)quickstrict$(cnorm) to build like 'quick' but abort immediately on any error"
@echo " $(cblue)local$(cnorm) to build local html docs of the current branch (no multiversion)."
@echo " $(cblue)localupdate$(cnorm) to build local html docs (only update changes)
@echo " $(cblue)localupdate$(cnorm) to build local html docs (only update changes)"
@echo " $(cblue)mv-local$(cnorm) to build multiversion html docs, without deploying (req: local git commit first)"
@echo " $(cblue)deploy$(cnorm) to deploy previously built multiversion docs online (req: commit and github push access)"
@echo " $(cblue)release$(cnorm) to build + deploy multiversion docs online (req: commit and github push access)"
@ -60,6 +60,9 @@ _multiversion-check-env:
_clean_api_index:
rm source/api/*
_clean_api_rsts:
rm source/api/*.rst
# remove superfluos 'module' and 'package' text from api headers
_reformat_apidoc_headers:
for f in source/api/*.rst; do\
@ -89,7 +92,8 @@ _multiversion-build:
@EVDIR=$(EVDIR) EVGAMEDIR=$(EVGAMEDIR) $(SPHINXMULTIVERSION) $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS)
_multiversion-deploy:
@bash -e deploy.sh
@python deploy.py
# @bash -e deploy.sh
_latex-build:
@NOAUTODOC=1 EVDIR=$(EVDIR) EVGAMEDIR=$(EVGAMEDIR) $(SPHINXBUILD) -M latexpdf "$(SOURCEDIR)" "$(BUILDDIR)/latex" $(QUICKFILES)
@ -110,24 +114,23 @@ pdf:
@echo "To see result, open evennia/docs/build/latex/evennia.pdf in a PDF reader."
quick:
make _quick_autodoc-index
make _quick-html-build $(FILES)
make _check-env
make _html-build
@echo ""
@echo "Documentation built (single version, no autodocs)."
@echo "To see result, open evennia/docs/build/html/index.html in a browser."
# abort on warnings too
# abort on warnings too (not working atm)
quickstrict:
SPHINXOPTS=-W make quick
# we build index directly for the current branch
local:
make _check-env
make clean
make _autodoc-index
make _html-build
@echo ""
@echo "Documentation built (single version)."
@echo "Documentation built (single version + autodocs)."
@echo "To see result, open evennia/docs/build/html/index.html in a browser."
# build only that which updated since last run (no clean or index-creation)
@ -135,7 +138,7 @@ localupdate:
make _check-env
make _html-build
@echo ""
@echo "Documentation built (single version, only updates, no auto-index)."
@echo "Documentation built (single version, only updates, no autodocs)."
@echo "To see result, open evennia/docs/build/html/index.html in a browser."
# note that this should be done for each relevant multiversion branch.
@ -145,12 +148,13 @@ mv-index:
mv-local:
make _multiversion-check-env
make clean
make _multiversion-build
@echo ""
@echo "Documentation built (multiversion + autodocs)."
@echo "To see result, open evennia/docs/build/html/<version>/index.html in a browser."
# note - don't run deploy/release manually, the result will clash with the
# result of the github actions!
deploy:
make _multiversion-deploy
@echo "Documentation deployed."

View file

@ -27,10 +27,10 @@ own work-branch, make your changes to files in `evennia/docs/source/` and make a
The sources in `evennia/docs/source/` are built into a pretty documentation using
the [Sphinx][sphinx] static generator system. To do so locally you need to either
use a system with `make` (Linux/Unix/Mac or [Windows-WSL][Windows-WSL]). Lacking that, you could
in principle also run the sphinx build-commands manually - read the `evennia/docs/Makefile` to see
in principle also run the sphinx build-commands manually - read the `evennia/docs/Makefile` to see
which commands are run by `make`.
You don't necessarily _have_ to build the docs locally to contribute. But
You don't necessarily _have_ to build the docs locally to contribute, but
building them allows you to check for yourself that syntax is correct and that
your change comes out looking as you expected.
@ -38,7 +38,7 @@ your change comes out looking as you expected.
If you only want to build the main documentation pages (not the API autodocs),
you don't need to install Evennia itself, only the documentation resources.
All is done in your terminal/console.
This action is done in your terminal/console.
- (Optional, but recommended): Activate a virtualenv with Python 3.7.
- `cd` to into the `evennia/docs` folder (where this README is).
@ -64,7 +64,7 @@ All is done in your terminal/console.
## Building the main documentation and API docs
The full documentation includes both the doc pages and the API documentation
generated from the Evennia source. For this you must install Evennia and
generated from the Evennia source. To build the full documentation you must install Evennia and
initialize a new game with a default database (you don't need to have it
running).
@ -116,8 +116,8 @@ repo with
### Building with another gamedir
If you for some reason want to use another location of your `gamedir/`, or want it
named something else (maybe you already use the name 'gamedir' for your development ...),
If for some reason you want to use another location of your `gamedir/` or want it
named something else (maybe you already use the name 'gamedir' for your development ...)
you can do so by setting the `EVGAMEDIR` environment variable to the absolute path
of your alternative game dir. For example:
@ -130,7 +130,7 @@ of your alternative game dir. For example:
The full Evennia documentation also tracks documentation from older Evennia
versions. This is done by pulling documentation from Evennia's old release
branches and building them all so readers can choose which one to view. Only
specific official Evennia branches will be built, so you can't use this to
specific official Evennia branches will be built so you can't use this to
build your own testing branch.
- All local changes must have been committed to git first, since the versioned
@ -164,26 +164,26 @@ available at `https://evennia.github.io/evennia/`.
The format is [Markdown][commonmark-help] (Commonmark). While markdown supports a few alternative
forms for some of these, we try to stick to the below forms for consistency.
## Italic/Bold
## Italic/Bold
We generally use underscores for italics and double-asterisks for bold:
- `_Italic text_`
- `**Bold Text**`
## Headings
## Headings
We use `#` to indicate sections/headings. The more `#` the more of a sub-heading it is (will get smaller
and smaller font).
We use `#` to indicate sections/headings. The more `#` the more of a sub-heading it is (the font will be smaller
and smaller).
- `# Heading`
- `## SubHeading`
- `## SubSubHeading`
- `## SubSubHeading`
> Don't reuse the same heading/subheading name over and over in the same document. While Markdown does not prevent
it, it makes it impossible to link to those duplicates properly (see next section).
it, it makes it impossible to link to those duplicates properly (see next section).
## Lists
## Lists
One can create both bullet-point lists and numbered lists:
@ -198,34 +198,34 @@ One can create both bullet-point lists and numbered lists:
3. Numbered point three
```
## Notes
## Notes
A note can be used to enphasise important things. It's added by starting one or more lines with `>`.
```
> Note: This is an important
> thing to remember.
> Note: This is an important
> thing to remember.
```
## Links
## Links
- `[linktext](url_or_ref)` - gives a clickable link `linktext`.
- `[linktext](url_or_ref)` - gives a clickable link `linktext`.
The `url_or_ref` can either be a full `http://...` url or an internal _reference_. For example, use
`[my document](My-Document)` to link to the document `evennia/docs/source/My-Document.md`. Avoid using
`[my document](My-Document)` to link to the document `evennia/docs/source/My-Document.md`. Avoid using
full `http://` linking unless really referring to an external resource.
- `[linktext](ref#heading-name)`
You can point to sub-sections (headings) in a document by using a single `#` and the name of the
You can point to sub-sections (headings) in a document by using a single `#` and the name of the
heading, replacing spaces with dashes. So to refer to a heading `## Cool Stuff` inside `My-Document`
would be a link `[cool stuff](My-Document#Cool-Stuff)`.
- `[linktext][linkref]` - refer to a reference defined later in the document.
Urls can get long and if you are using the same url in many places it can get a little cluttered. So you can also put
Urls can get long and if you are using the same url in many places it can get a little cluttered. So you can also put
the url as a 'footnote' at the end of your document
and refer to it by putting your reference within square brackets `[ ]`. Here's an example:
and refer to it by putting your reference within square brackets `[ ]`. Here's an example:
```
This is a [clickable link][mylink]. This is [another link][1].
@ -238,7 +238,7 @@ This is a [clickable link][mylink]. This is [another link][1].
```
### Special references
### Special references
The Evennia documentation supports some special reference shortcuts in links:
@ -246,11 +246,11 @@ The Evennia documentation supports some special reference shortcuts in links:
- `github:` - a shortcut for the full path to the Evennia repository on github. This will refer to
the `master` branch by default:
[link to objects.py](github:evennia/objects/objects.py)
This will remap to https://github.com/evennia/evennia/blob/master/evennia/objects/objects.py.
- To refer to the `develop` branch, start the url with `develop/`:
- To refer to the `develop` branch, start the url with `develop/`:
[link to objects.py](github:develop/evennia/objects/objects.py)
@ -260,37 +260,37 @@ The Evennia documentation supports some special reference shortcuts in links:
[link to api for objects.py](api:evennia.objects)
This will create a link to the auto-generated `evennia/source/api/evennia.objects.rst` document.
This will create a link to the auto-generated `evennia/source/api/evennia.objects.rst` document.
Since api-docs are generated alongside the documentation, this will always be the api docs for the
current version/branch of the docs.
current version/branch of the docs.
#### Bug reports/feature request
- `issue`, `bug-report`, `feature-request` - links to the same github issue select page.
- `issue`, `bug-report`, `feature-request` - links to the same github issue select page.
If you find a problem, make a [bug report](issue)!
This will generate a link to https://github.com/evennia/evennia/issues/new/choose.
> For some reason these particular shortcuts give a warning during documentation compilation. This warning
> can be ignored.
> can be ignored.
## Verbatim text
It's common to want to mark something to be displayed verbatim - just as written - without any
Markdown parsing. In running text, this is done using backticks (\`), like \`verbatim text\` becomes `verbatim text`.
It's common to want to mark something to be displayed verbatim - just as written - without any
Markdown parsing. In running text, this is done using backticks (\`), like \`verbatim text\` becomes `verbatim text`.
If you want to put the verbatim text on its own line, you can do so easily by simply indenting
it 4 spaces (add empty lines on each side for readability too):
it 4 spaces (add empty lines on each side for readability too):
```
This is normal text
This is normal text
This is verbatim text
This is verbatim text
This is normal text
This is normal text
```
Another way is to use triple-backticks:
@ -304,40 +304,32 @@ Everything within these backticks will be verbatim.
## Code blocks
Code examples are a special case - we want them to get code-highlighting for readability. This is done by using
the triple-backticks and specify which language we use:
Code examples are a special case - we want them to get code-highlighting for readability. This is done by using
the triple-backticks and specifying the language we use:
````
```python
def a_python_func(x):
return x * x
```
return x * x
```
````
## ReST blocks
Markdown is easy to read and use, but it isn't as expressive as it needs to be for some things. For this we
need to fall back to the [ReST][ReST] markup language which the documentation system uses under the hood. This is
done by specifying `eval_rst` as the name of the `language` of a literal block:
Markdown is easy to read and use, but it isn't as expressive as it needs to be for some things. For this we
need to fall back to the [ReST][ReST] markup language which the documentation system uses under the hood. This is
done by specifying `eval_rst` as the name of the `language` of a literal block:
````
```eval_rst
```{eval_rst}
This will be evaluated as ReST.
This will be evaluated as ReST.
```
````
There is also a short-hand form for starting a [ReST directive][ReST-directives] without need for `eval_rst`:
````
```directive:: possible-option
Content that *must* be indented for it to be included in the directive.
New lines are ignored except if separated by an empty line.
```
````
See below for examples of this.
@ -347,131 +339,120 @@ See below for examples of this.
This will display a one-line note that will pop even more than a normal `> note`.
````
```{important}
This is important because it is!
```{important}
This is important because it is!
```
````
#### Warning
A warning block is used to draw attention to particularly dangerous things, or features easy to
A warning block is used to draw attention to particularly dangerous things or features that are easy to
mess up.
````
```{warning}
Be careful about this ...
Be careful about this ...
````
#### Version changes and deprecations
These will show up as one-line warnings that suggest an added, changed or deprecated
feature beginning with particular version.
These will show up as one-line warnings that suggest an added, changed or deprecated
feature beginning with the particular version.
````
```versionadded:: 1.0
```{versionadded} 1.0
```
````
````
```versionchanged:: 1.0
```{versionchanged} 1.0
How the feature changed with this version.
```
````
````
```deprecated:: 1.0
```{deprecated} 1.0
```
````
#### Sidebar
#### Sidebar
This will display an informative sidebar that floats to the side of regular content. This is useful
for example to remind the reader of some concept relevant to the text.
to remind the reader of some concept relevant to the text.
````
```sidebar:: Things to remember
```{sidebar} Things to remember
- There can be bullet lists
- in here.
- There can be bullet lists
- in here.
Headers with indented blocks:
like this
Will end up as full sub-headings:
in the sidebar.
Headers with indented blocks:
like this
Will end up as full sub-headings:
in the sidebar.
```
````
> Remember that for ReST-directives, the content within the triple-backticks _must_ be indented to
>some degree or the content will just appear outside of the directive as regular text.
> Remember that for ReST-directives, the content within the triple-backticks _must_ be indented to
>some degree or the content will just appear outside of the directive as regular text.
#### Tables
#### Tables
A table is specified using [ReST table syntax][ReST-tables]:
Tables are done using Markdown syntax
````
```eval_rst
===== ===== =======
A B A and B
===== ===== =======
False False False
True False False
False True False
True True True
===== ===== =======
```
````
or the more flexible but verbose
````
```eval_rst
+------------------------+------------+----------+----------+
| Header row, column 3 | Header 2 | Header 3 | Header 4 |
| (header rows optional) | | | |
+========================+============+==========+==========+
| body row 1, column 1 | column 2 | column 3 | column 4 |
+------------------------+------------+----------+----------+
| body row 2 | ... | ... | |
+------------------------+------------+----------+----------+
| A | B | A and B |
| --- | --- | --- |
| False | False | False |
| True | False | False |
| False | True | False |
| True | True | True |
```
````
#### A more flexible code block
| A | B | A and B |
| --- | --- | --- |
| False | False | False |
| True | False | False |
| False | True | False |
| True | True | True |
The regular Markdown codeblock is usually enough but for more direct control over the style, one
#### A more flexible code block
The regular Markdown codeblock is usually enough but for more direct control over the style, one
can also specify the code block explicitly in `ReST`.
for more flexibility. It also provides a link to the code block, identified by its name.
````
```code-block:: python
:linenos:
:emphasize-lines: 6-7,12
:caption: An example code block
:name: A full code block example
```{code-block} python
:linenos:
:emphasize-lines: 6-7,12
:caption: An example code block
:name: A full code block example
from evennia import Command
class CmdEcho(Command):
"""
Usage: echo <arg>
"""
key = "echo"
def func(self):
self.caller.msg(self.args.strip())
from evennia import Command
class CmdEcho(Command):
"""
Usage: echo <arg>
"""
key = "echo"
def func(self):
self.caller.msg(self.args.strip())
```
````
Here, `:linenos:` turns on line-numbers and `:emphasize-lines:` allows for emphasizing certain lines
in a different color. The `:caption:` shows an instructive text and `:name:` is used to reference this
block through the link that will appear (so it should be unique for a give document).
block through the link that will appear (so it should be unique for a give document).
> The default markdown syntax will actually generate a code-block ReST instruction like this
> The default markdown syntax will actually generate a code-block ReST instruction like this
> automatically for us behind the scenes. The automatic generation can't know things like emphasize-lines
> or caption since that's not a part of the Markdown specification.
# Technical
Evennia leverages [Sphinx][sphinx] with the [recommonmark][recommonmark] extension, which allows us to write our
Evennia leverages [Sphinx][sphinx] with the [MyST][MyST] extension, which allows us to write our
docs in light-weight Markdown (more specifically [CommonMark][commonmark], like on github) rather than ReST.
The recommonmark extension however also allows us to use ReST selectively in the places were it is more
expressive than the simpler (but much easier) Markdown.
@ -482,11 +463,11 @@ to understand our friendly Google-style docstrings used in classes and functions
[sphinx]: https://www.sphinx-doc.org/en/master/
[recommonmark]: https://recommonmark.readthedocs.io/en/latest/index.html
[MyST]: https://myst-parser.readthedocs.io/en/latest/syntax/reference.html
[commonmark]: https://spec.commonmark.org/current/
[commonmark-help]: https://commonmark.org/help/
[sphinx-autodoc]: http://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#module-sphinx.ext.autodoc
[sphinx-napoleon]: http://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html
[sphinx-autodoc]: https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#module-sphinx.ext.autodoc
[sphinx-napoleon]: https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html
[getting-started]: https://github.com/evennia/evennia/wiki/Getting-Started
[contributing]: https://github.com/evennia/evennia/wiki/Contributing
[ReST]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html

89
docs/deploy.py Normal file
View file

@ -0,0 +1,89 @@
"""
Deploy to github, from github Action. This is run after the docs have finished building. All new
documentation branches will be available in build/html/* at this point. We need to copy those
contents to the root of the repo.
This can be tested with `make release` or `make deploy` and require git push rights to
the evennia repo. Use DISABLE_GIT_PUSH for local testing - git-pushing from local can cause
clashes upstream.
We will look in source/conf.py for the `.latest_version` string and `.legacy_versions` list,
this allows us to skip deleting legacy docs (which may be ever harder to build) while correctly
symlinking to the current 'latest' documentation.
This is assumed to be executed from inside the docs/ folder.
"""
import glob
import importlib
import os
import subprocess
import sys
# set for local testing
DISABLE_GIT_PUSH = False
def deploy():
"""Perform the deploy of the built Evennia documentation to the gh-pages branch."""
conf_file = importlib.machinery.SourceFileLoader("conf", "source/conf.py").load_module()
latest_version = conf_file.latest_version
legacy_versions = conf_file.legacy_versions
if subprocess.call(["git", "status", "--untracked=no", "--porcelain"]):
print(
"There are uncommitted or untracked changes. Make sure "
"to commit everything in your current branch first."
)
sys.exit(1)
# get the deployment branch
os.system("git fetch")
os.system("git checkout gh-pages")
os.system("pwd")
os.system("ls")
names_to_skip = legacy_versions + ["build"]
for file_path in glob.glob("*"):
# run from inside the docs/ dir
# delete old but active doc branches
if file_path in names_to_skip:
# skip deleting the legacy brancehs
continue
else:
# we want to delete both active branches and old symlinks
os.system(f"rm -Rf {file_path}")
print(f"removed file_path: {file_path}")
# copy built branches to current dir
os.system("ls")
os.system("cp -Rf build/html/* .")
os.system("ls")
# symlink to latest and link its index to the root
os.system(f"ln -s {latest_version} latest")
os.system(f"ln -s {latest_version}/index.html .")
os.system("ls")
if not DISABLE_GIT_PUSH:
print("committing and pushing docs ...")
os.system("git add .") # docs/build is in .gitignore so will be skipped
os.system('git commit -a -m "Updated HTML docs."')
os.system("git push origin gh-pages")
else:
print("Skipped git push.")
print("Deployed to https:// evennia.github.io/evennia/")
if __name__ == "__main__":
deploy()

View file

@ -1,37 +0,0 @@
#
# deploy to github
#
# This copies the recently built files from build/html into the github-gh branch. Note that
# it's important that build/ must not be committed to git!
#
if [ -n "$(git status --untracked-files=no --porcelain)" ]; then
echo "There are uncommitted changes. Make sure to commit everything in your current branch first."
exit 1
fi
# get the deployment branch
git checkout gh-pages
# at this point we should be inside the docs/ folder of the gh-pages branch,
# with the build/ directory available since this is not in git
# remove all but the build dir
ls -Q | grep -v build | xargs rm -Rf
cp -Rf build/html/* .
# TODO automate this?
ln -s 0.9.5 latest
ln -s 0.9.5/index.html .
# docs/build is in .gitignore so will not be included
git add .
git commit -a -m "Updated HTML docs"
echo "Skipping deployment"
git push origin gh-pages
# get back to previous branch
git checkout -
echo "Deployed to https://evennia.github.io/evennia-docs."

View file

@ -5,21 +5,22 @@ Remap autodoc API rst files to md files and wrap their contents.
"""
from glob import glob
from os.path import abspath, join as pathjoin, dirname
from os import rename
from os.path import abspath, dirname
from os.path import join as pathjoin
def _rst2md(filename_rst):
with open(filename_rst, 'r') as fil:
with open(filename_rst, "r") as fil:
# read rst file, reformat and save
txt = fil.read()
with open(filename_rst, 'w') as fil:
with open(filename_rst, "w") as fil:
txt = "```{eval-rst}\n" + txt + "\n```"
fil.write(txt)
# rename .rst file to .md file
filename, _ = filename_rst.rsplit('.', 1)
filename, _ = filename_rst.rsplit(".", 1)
filename_md = filename + ".md"
rename(filename_rst, filename_md)

View file

@ -7,9 +7,12 @@ directive somewhere.
import re
from collections import defaultdict
from sphinx.errors import DocumentError
from os.path import abspath, dirname
from os.path import join as pathjoin
from os.path import relpath
from pathlib import Path
from os.path import abspath, dirname, join as pathjoin, relpath
from sphinx.errors import DocumentError
_IGNORE_FILES = []
_SOURCEDIR_NAME = "source"
@ -32,41 +35,37 @@ _STRIP_PREFIX = [
"api/",
"api:",
]
TXT_REMAPS = {}
# "Developer Central": "Evennia Components overview",
# "Getting Started": "Setup Quickstart",
# }
URL_REMAPS = {
"Default-Command-Help": "Default-Commands",
"./Default-Command-Help.md": "Default-Commands.md"
TXT_REMAPS = {
"Developer Central": "Evennia Components overview",
"Getting Started": "Setup Quickstart",
}
URL_REMAPS = {
"Developer-Central": "Components/Components-Overview",
"Tutorials": "Howtos/Howtos-Overview",
"../Howtos/Beginner-Tutorial/Directory-Overview": "Gamedir-Overview",
"Howtos/Beginner-Tutorial/Directory-Overview": "Gamedir-Overview",
"Beginner-Tutorial/Directory-Overview": "Gamedir-Overview",
"Directory-Overview": "Gamedir-Overview",
"../Setup/Getting-Started": "Setup-Quickstart",
"Setup/Getting-Started": "Setup-Quickstart",
"Getting-Started": "Setup-Quickstart",
"First-Steps-Coding": "Beginner-Tutorial-Part1",
"../Howtos/Beginner-Tutorial/Adding-Command-Tutorial": "Adding-Commands",
"Howtos/Beginner-Tutorial/Adding-Command-Tutorial": "Adding-Commands",
"Beginner-Tutorial/Adding-Command-Tutorial": "Adding-Commands",
"Adding-Command-Tutorial": "Adding-Commands",
"CmdSet": "Command-Sets",
"Spawner": "Prototypes",
"issue": "github:issue",
"issues": "github:issue",
"bug": "github:issue",
"bug-report": "github:issue",
"./Default-Command-Help": "api:evennia.commands.default#modules",
"../Components/Default-Command-Help": "api:evennia.commands.default#modules",
"../../../Components/Default-Command-Help": "api:evennia.commands.default#modules",
"./Locks.md#permissions": "Permissions",
"modules": "Default-Commands.md",
}
# "Developer-Central": "Components/Components-Overview",
# "Tutorials": "Howto/Howto-Overview",
# "../Howto/Starting/Directory-Overview": "Gamedir-Overview",
# "Howto/Starting/Directory-Overview": "Gamedir-Overview",
# "Starting/Directory-Overview": "Gamedir-Overview",
# "Directory-Overview": "Gamedir-Overview",
# "../Setup/Getting-Started": "Setup-Quickstart",
# "Setup/Getting-Started": "Setup-Quickstart",
# "Setup-Quickstart": "Setup-Quickstart",
# "Setup-Quickstart": "Getting-Started", # back again
# "First-Steps-Coding": "Starting-Part1",
# "../Howto/Starting/Adding-Command-Tutorial": "Adding-Commands",
# "Howto/Starting/Adding-Command-Tutorial": "Adding-Commands",
# "Starting/Adding-Command-Tutorial": "Adding-Commands",
# "Adding-Command-Tutorial": "Adding-Commands",
# "CmdSet": "Command-Sets",
# "Spawner": "Prototypes",
# "issue": "github:issue",
# "issues": "github:issue",
# "bug": "github:issue",
# "bug-report": "github:issue",
# "./Default-Command-Help": "api:evennia.commands.default#modules",
# "../Components/Default-Command-Help": "api:evennia.commands.default#modules",
# "../../../Components/Default-Command-Help": "api:evennia.commands.default#modules",
# "./Locks.md#permissions": "Permissions",
# "Permissions": "./Locks.md#permissions", # back again
# }
_USED_REFS = {}
@ -153,7 +152,7 @@ def auto_link_remapper(no_autodoc=False):
for strip_prefix in _STRIP_PREFIX:
if url.startswith(strip_prefix):
url = url[len(strip_prefix):]
url = url[len(strip_prefix) :]
if any(url.startswith(noremap) for noremap in _NO_REMAP_STARTSWITH):
# skip regular http/s urls etc
@ -161,10 +160,10 @@ def auto_link_remapper(no_autodoc=False):
if url.startswith("evennia."):
# api link - we want to remove legacy #reference and remove .md
if '#' in url:
_, url = url.rsplit('#', 1)
if "#" in url:
_, url = url.rsplit("#", 1)
if url.endswith(".md"):
url, _ = url.rsplit('.', 1)
url, _ = url.rsplit(".", 1)
return f"[{txt}]({url})"
fname, *part = url.rsplit("/", 1)
@ -178,7 +177,9 @@ def auto_link_remapper(no_autodoc=False):
if _CURRFILE in docref_map and fname in docref_map[_CURRFILE]:
cfilename = _CURRFILE.rsplit("/", 1)[-1]
urlout = docref_map[_CURRFILE][fname] + ".md" + ("#" + anchor[0].lower() if anchor else "")
urlout = (
docref_map[_CURRFILE][fname] + ".md" + ("#" + anchor[0].lower() if anchor else "")
)
if urlout != url:
print(f" {cfilename}: [{txt}]({url}) -> [{txt}]({urlout})")
else:
@ -197,19 +198,17 @@ def auto_link_remapper(no_autodoc=False):
for strip_prefix in _STRIP_PREFIX:
if url.startswith(strip_prefix):
url = url[len(strip_prefix):]
url = url[len(strip_prefix) :]
if any(url.startswith(noremap) for noremap in _NO_REMAP_STARTSWITH):
return f"[{txt}]: {url}"
urlout = url
if "http" in url and "://" in url:
urlout = url
elif url.startswith("evennia."):
# api link - we want to remove legacy #reference
if '#' in url:
_, urlout = url.rsplit('#', 1)
if "#" in url:
_, urlout = url.rsplit("#", 1)
else:
fname, *part = url.rsplit("/", 1)
fname = part[0] if part else fname
@ -254,25 +253,26 @@ def auto_link_remapper(no_autodoc=False):
print(f" ORPHANED DOC: no refs found to {src_url}.md")
# write tocfile
with open(_TOC_FILE, "w") as fil:
fil.write("```{toctree}\n")
# with open(_TOC_FILE, "w") as fil:
# fil.write("```{toctree}\n")
if not no_autodoc:
fil.write("- [API root](api/evennia-api.rst)")
# if not no_autodoc:
# fil.write("- [API root](api/evennia-api.rst)")
for ref in sorted(toc_map.values()):
# for ref in sorted(toc_map.values()):
if ref == "toc":
continue
# if ref == "toc":
# continue
# if not "/" in ref:
# ref = "./" + ref
# # if not "/" in ref:
# # ref = "./" + ref
# linkname = ref.replace("-", " ")
fil.write(f"\n{ref}") # - [{linkname}]({ref})")
# # linkname = ref.replace("-", " ")
# fil.write(f"\n{ref}") # - [{linkname}]({ref})")
# we add a self-reference so the toc itself is also a part of a toctree
fil.write("\n```\n\n```{toctree}\n :hidden:\n\ntoc\n```")
# # we add a self-reference so the toc itself is also a part of a toctree
# fil.write("\n```\n\n```{toctree}\n :hidden:\n\ntoc\n```")
# print(" -- File toc.md updated.")
print(" -- Auto-Remapper finished.")

View file

@ -5,11 +5,14 @@
Builds a lunr static search index for optimized search
"""
import os
import json
import glob
import json
import os
from argparse import ArgumentParser
from os.path import sep, abspath, dirname, join as joinpath
from os.path import abspath, dirname
from os.path import join as joinpath
from os.path import sep
from lunr import lunr
_DOCS_PATH = dirname(dirname(abspath(__file__)))

View file

@ -0,0 +1,250 @@
"""
Convert contribs' README files to proper documentation pages along with
an index.
"""
from collections import defaultdict
from glob import glob
from os.path import abspath, dirname
from os.path import join as pathjoin
from os.path import sep
_EVENNIA_PATH = pathjoin(dirname(dirname(dirname(abspath(__file__)))))
_DOCS_PATH = pathjoin(_EVENNIA_PATH, "docs")
_SOURCE_DIR = pathjoin(_EVENNIA_PATH, "evennia", "contrib")
_OUT_DIR = pathjoin(_DOCS_PATH, "source", "Contribs")
_OUT_INDEX_FILE = pathjoin(_OUT_DIR, "Contribs-Overview.md")
_FILENAME_MAP = {"rpsystem": "RPSystem", "xyzgrid": "XYZGrid", "awsstorage": "AWSStorage"}
# ---------------------------------------------------------------------------------------------
_FILE_STRUCTURE = """{header}
{categories}
{footer}"""
_CATEGORY_DESCS = {
"base_systems": """
Systems that are not necessarily tied to a specific
in-game mechanic but which are useful for the game as a whole. Examples include
login systems, new command syntaxes, and build helpers.
""",
"full_systems": """
'Complete' game engines that can be used directly to start creating content
without no further additions (unless you want to).
""",
"game_systems": """
In-game gameplay systems like crafting, mail, combat and more.
Each system is meant to be adopted piecemeal and adopted for your game.
This does not include roleplaying-specific systems, those are found in
the `rpg` category.
""",
"grid": """
Systems related to the game world's topology and structure. Contribs related
to rooms, exits and map building.
""",
"rpg": """
Systems specifically related to roleplaying
and rule implementation like character traits, dice rolling and emoting.
""",
"tutorials": """
Helper resources specifically meant to teach a development concept or
to exemplify an Evennia system. Any extra resources tied to documentation
tutorials are found here. Also the home of the Tutorial-World and Evadventure
demo codes.
""",
"utils": """
Miscellaneous, tools for manipulating text, security auditing, and more.
""",
}
HEADER = """# Contribs
```{{sidebar}} More contributions
Additional Evennia code snippets and contributions can be found
in the [Community Contribs & Snippets][forum] forum.
```
_Contribs_ are optional code snippets and systems contributed by
the Evennia community. They vary in size and complexity and
may be more specific about game types and styles than 'core' Evennia.
This page is auto-generated and summarizes all **{ncontribs}** contribs currently included
with the Evennia distribution.
All contrib categories are imported from `evennia.contrib`, such as
from evennia.contrib.base_systems import building_menu
Each contrib contains installation instructions for how to integrate it
with your other code. If you want to tweak the code of a contrib, just
copy its entire folder to your game directory and modify/use it from there.
If you want to add a contrib, see [the contrib guidelines](Contribs-Guidelines)!
[forum]: https://github.com/evennia/evennia/discussions/categories/community-contribs-snippets
## Index
{category_index}
{index}
"""
TOCTREE = """
```{{toctree}}
:hidden:
Contribs-Guidelines.md
```
```{{toctree}}
:maxdepth: 1
{listing}
```"""
CATEGORY = """
## {category}
_{category_desc}_
{toctree}
{blurbs}
"""
BLURB = """
### `{name}`
_{credits}_
{blurb}
[Read the documentation](./{filename}) - [Browse the Code](api:{code_location})
"""
FOOTER = """
----
<small>This document page is generated from `{path}`. Changes to this
file will be overwritten, so edit that file rather than this one.</small>
"""
INDEX_FOOTER = """
----
<small>This document page is auto-generated. Manual changes
will be overwritten.</small>
"""
def build_table(datalist, ncols):
"""Build a Markdown table-grid for compact display"""
nlen = len(datalist)
table_heading = "| " * (ncols) + "|"
table_sep = "|---" * (ncols) + "|"
table = ""
for ir in range(0, nlen, ncols):
table += "| " + " | ".join(datalist[ir : ir + ncols]) + " |\n"
return f"{table_heading}\n{table_sep}\n{table}"
def readmes2docs(directory=_SOURCE_DIR):
"""
Parse directory for README files and convert them to doc pages.
"""
ncount = 0
index = []
category_index = []
categories = defaultdict(list)
glob_path = f"{directory}{sep}*{sep}*{sep}README.md"
for file_path in glob(glob_path):
# paths are e.g. evennia/contrib/utils/auditing/README.md
_, category, name, _ = file_path.rsplit(sep, 3)
index.append(f"[{name}](#{name.lower()})")
category_index.append(f"[{category}](#{category.lower()})")
pypath = f"evennia.contrib.{category}.{name}"
filename = (
"Contrib-"
+ "-".join(
_FILENAME_MAP.get(part, part.capitalize() if part[0].islower() else part)
for part in name.split("_")
)
+ ".md"
)
outfile = pathjoin(_OUT_DIR, filename)
with open(file_path) as fil:
data = fil.read()
clean_file_path = f"evennia{sep}contrib{file_path[len(directory):]}"
data += FOOTER.format(path=clean_file_path)
try:
credits = data.split("\n\n", 3)[1]
blurb = data.split("\n\n", 3)[2]
except IndexError:
blurb = name
with open(outfile, "w") as fil:
fil.write(data)
categories[category].append((name, credits, blurb, filename, pypath))
ncount += 1
# build the list of categories with blurbs
category_sections = []
for category in sorted(categories):
filenames = []
contrib_tups = categories[category]
catlines = []
for tup in sorted(contrib_tups, key=lambda tup: tup[0].lower()):
catlines.append(
BLURB.format(
name=tup[0], credits=tup[1], blurb=tup[2], filename=tup[3], code_location=tup[4]
)
)
filenames.append(f"{tup[3]}")
toctree = TOCTREE.format(listing="\n".join(filenames))
category_sections.append(
CATEGORY.format(
category=category,
category_desc=_CATEGORY_DESCS[category].strip(),
blurbs="\n".join(catlines),
toctree=toctree,
)
)
# build the header, with two tables and a count
header = HEADER.format(
ncontribs=len(index),
category_index=build_table(sorted(set(category_index)), 7),
index=build_table(sorted(index), 5),
)
# build the final file
text = _FILE_STRUCTURE.format(
header=header, categories="\n".join(category_sections), footer=INDEX_FOOTER
)
with open(_OUT_INDEX_FILE, "w") as fil:
fil.write(text)
print(f" -- Converted Contrib READMEs to {ncount} doc pages + index.")
if __name__ == "__main__":
readmes2docs(_SOURCE_DIR)

View file

@ -13,9 +13,9 @@ We also need to build the toc-tree and should do so automatically for now.
"""
import datetime
import glob
import re
import datetime
_RE_MD_LINK = re.compile(r"\[(?P<txt>[\w -\[\]]+?)\]\((?P<url>.+?)\)", re.I + re.S + re.U)
_RE_REF_LINK = re.compile(r"\[[\w -\[\]]*?\]\(.+?\)", re.I + re.S + re.U)

View file

@ -8,9 +8,9 @@ Usage:
python fmtwidth.py --width 79 ../source/**.md
"""
import argparse
import glob
import textwrap
import argparse
_DEFAULT_WIDTH = 100
@ -26,7 +26,11 @@ if __name__ == "__main__":
filepaths = glob.glob(args.files, recursive=True)
width = args.width
wrapper = textwrap.TextWrapper(width=width, break_long_words=False, expand_tabs=True,)
wrapper = textwrap.TextWrapper(
width=width,
break_long_words=False,
expand_tabs=True,
)
count = 0
for filepath in filepaths:

View file

@ -1,37 +1,40 @@
#
# This creates a Google wiki page for all default commands with __doc__ strings.
#
# Import this from a Django-aware shell, then call run_update.
#
#
"""
from os.path import dirname, abspath, join as pathjoin
from evennia.utils.utils import (
mod_import, variable_from_module, callables_from_module
)
Generates Components/Default-Commands.md from sources.
__all__ = ("run_update")
To test - import this from a Django-aware shell, then call run_update.
"""
from os.path import abspath, dirname
from os.path import join as pathjoin
from evennia.utils.utils import callables_from_module, mod_import, variable_from_module
__all__ = "run_update"
PAGE = """
# Default Commands
The full set of default Evennia commands currently contains {ncommands} commands in {nfiles} source
files. Our policy for adding default commands is outlined [here](Using-MUX-as-a-Standard). The
[Commands](Commands) documentation explains how Commands work as well as make new or customize
existing ones. Note that this page is auto-generated. Report problems to the [issue
tracker](github:issues).
files. Our policy for adding default commands is outlined [here](Default-Command-Syntax). The
[Commands](Commands) documentation explains how Commands work as well as how to make new or customize
existing ones.
> Note that this page is auto-generated. Report problems to the [issue tracker](github:issues).
```{{note}}
Some game-states adds their own Commands which are not listed here. Examples include editing a text
Some game-states add their own Commands which are not listed here. Examples include editing a text
with [EvEditor](EvEditor), flipping pages in [EvMore](EvMore) or using the
[Batch-Processor](Batch-Processors)'s interactive mode.
```
{alphabetical}
"""
""".strip()
def run_update(no_autodoc=False):
@ -71,7 +74,8 @@ def run_update(no_autodoc=False):
for modname in cmd_modules:
module = mod_import(modname)
cmds_per_module[module] = [
cmd for cmd in callables_from_module(module).values() if cmd.__name__.startswith("Cmd")]
cmd for cmd in callables_from_module(module).values() if cmd.__name__.startswith("Cmd")
]
for cmd in cmds_per_module[module]:
cmd_to_module_map[cmd] = module
cmds_alphabetically.append(cmd)
@ -79,14 +83,17 @@ def run_update(no_autodoc=False):
cmd_infos = []
for cmd in cmds_alphabetically:
aliases = f" [{', '.join(cmd.aliases)}]" if cmd.aliases else ""
cmdlink = f"[**{cmd.key}**{aliases}]({cmd.__module__}.{cmd.__name__})"
aliases = [
alias[1:] if alias and alias[0] == "@" else alias for alias in sorted(cmd.aliases)
]
aliases = f" [{', '.join(sorted(cmd.aliases))}]" if aliases else ""
cmdlink = f"[**{cmd.key}**{aliases}](api:{cmd.__module__}#{cmd.__name__})"
category = f"help-category: _{cmd.help_category.capitalize()}_"
cmdset = cmd_to_cmdset_map.get(f"{cmd.__module__}.{cmd.__name__}", None)
if cmdset:
cmodule = cmdset.__module__
cname = cmdset.__class__.__name__
cmdsetlink = f"cmdset: [{cname}]({cmodule}.{cname}), "
cmdsetlink = f"cmdset: [{cname}](api:{cmodule}#{cname}), "
else:
# we skip commands not in the default cmdsets
continue
@ -96,12 +103,13 @@ def run_update(no_autodoc=False):
txt = PAGE.format(
ncommands=len(cmd_to_cmdset_map),
nfiles=len(cmds_per_module),
alphabetical="\n".join(f"- {info}" for info in cmd_infos))
alphabetical="\n".join(f"- {info}" for info in cmd_infos),
)
outdir = pathjoin(dirname(dirname(abspath(__file__))), "source")
outdir = pathjoin(dirname(dirname(abspath(__file__))), "source", "Components")
fname = pathjoin(outdir, "Default-Commands.md")
with open(fname, 'w') as fil:
with open(fname, "w") as fil:
fil.write(txt)
print(" -- Updated Default Command index.")

View file

@ -0,0 +1,101 @@
"""
Update dynamically generated doc pages based on github sources.
"""
from os.path import abspath, dirname
from os.path import join as pathjoin
ROOTDIR = dirname(dirname(dirname(abspath(__file__))))
DOCDIR = pathjoin(ROOTDIR, "docs")
DOCSRCDIR = pathjoin(DOCDIR, "source")
EVENNIADIR = pathjoin(ROOTDIR, "evennia")
def update_code_style():
"""
Plain CODING_STYLE.md copy
"""
sourcefile = pathjoin(ROOTDIR, "CODING_STYLE.md")
targetfile = pathjoin(DOCSRCDIR, "Coding", "Evennia-Code-Style.md")
with open(sourcefile) as fil:
txt = fil.read()
with open(targetfile, "w") as fil:
fil.write(txt)
print(" -- Updated Evennia-Code-Style.md")
def update_changelog():
"""
Plain CHANGELOG.md copy
"""
sourcefile = pathjoin(ROOTDIR, "CHANGELOG.md")
targetfile = pathjoin(DOCSRCDIR, "Coding", "Changelog.md")
with open(sourcefile) as fil:
txt = fil.read()
with open(targetfile, "w") as fil:
fil.write(txt)
print(" -- Updated Changelog.md")
def update_default_settings():
"""
Make a copy of the default settings file for easy reference in docs
"""
sourcefile = pathjoin(EVENNIADIR, "settings_default.py")
targetfile = pathjoin(DOCSRCDIR, "Setup", "Settings-Default.md")
with open(sourcefile) as fil:
txt = fil.read()
txt = f"""
# Evennia Default settings file
Master file is located at `evennia/evennia/settings_default.py`. Read
its comments to see what each setting does and copy only what you want
to change into `mygame/server/conf/settings.py`.
Example of accessing settings:
```
from django.conf import settings
if settings.SERVERNAME == "Evennia":
print("Yay!")
```
----
```python
{txt}
```
"""
with open(targetfile, "w") as fil:
fil.write(txt)
print(" -- Updated Settings-Default.md")
def update_dynamic_pages():
"""
Run the various updaters
"""
update_changelog()
update_default_settings()
update_code_style()
if __name__ == "__main__":
update_dynamic_pages()

View file

@ -3,6 +3,7 @@
sphinx==3.2.1
myst-parser==0.15.2
myst-parser[linkify]==0.15.2
Jinja2 < 3.1
# sphinx-multiversion with evennia fixes
git+https://github.com/evennia/sphinx-multiversion.git@evennia-mods#egg=sphinx-multiversion

8
docs/source/.vale.ini Normal file
View file

@ -0,0 +1,8 @@
StylesPath = .vale
Vocab = docs
Packages = write-good
MinAlertLevel = error
[*.md]
BasedOnStyles = Vale, write-good

View file

@ -0,0 +1,19 @@
Evennia
Pastebin
[Cc]ontrib(s)*
Patreon
[Rr]epo(s)*
(?i)readme
[Ss]ubfolder(s)*
[Dd]ev(s)*
Github
[Dd]ocstring(s)*
[Mm]ygame(s)*
[Gg]amedir(s)*
[Vv]irtualenv(s)*
Python
API
[Tt]ypeclass(es)*?
[Bb]ullet point(s)*
CommonMark
[Pp]reparser(s)*

View file

@ -0,0 +1,702 @@
extends: existence
message: "Try to avoid using clichés like '%s'."
ignorecase: true
level: warning
tokens:
- a chip off the old block
- a clean slate
- a dark and stormy night
- a far cry
- a fine kettle of fish
- a loose cannon
- a penny saved is a penny earned
- a tough row to hoe
- a word to the wise
- ace in the hole
- acid test
- add insult to injury
- against all odds
- air your dirty laundry
- all fun and games
- all in a day's work
- all talk, no action
- all thumbs
- all your eggs in one basket
- all's fair in love and war
- all's well that ends well
- almighty dollar
- American as apple pie
- an axe to grind
- another day, another dollar
- armed to the teeth
- as luck would have it
- as old as time
- as the crow flies
- at loose ends
- at my wits end
- avoid like the plague
- babe in the woods
- back against the wall
- back in the saddle
- back to square one
- back to the drawing board
- bad to the bone
- badge of honor
- bald faced liar
- ballpark figure
- banging your head against a brick wall
- baptism by fire
- barking up the wrong tree
- bat out of hell
- be all and end all
- beat a dead horse
- beat around the bush
- been there, done that
- beggars can't be choosers
- behind the eight ball
- bend over backwards
- benefit of the doubt
- bent out of shape
- best thing since sliced bread
- bet your bottom dollar
- better half
- better late than never
- better mousetrap
- better safe than sorry
- between a rock and a hard place
- beyond the pale
- bide your time
- big as life
- big cheese
- big fish in a small pond
- big man on campus
- bigger they are the harder they fall
- bird in the hand
- bird's eye view
- birds and the bees
- birds of a feather flock together
- bit the hand that feeds you
- bite the bullet
- bite the dust
- bitten off more than he can chew
- black as coal
- black as pitch
- black as the ace of spades
- blast from the past
- bleeding heart
- blessing in disguise
- blind ambition
- blind as a bat
- blind leading the blind
- blood is thicker than water
- blood sweat and tears
- blow off steam
- blow your own horn
- blushing bride
- boils down to
- bolt from the blue
- bone to pick
- bored stiff
- bored to tears
- bottomless pit
- boys will be boys
- bright and early
- brings home the bacon
- broad across the beam
- broken record
- brought back to reality
- bull by the horns
- bull in a china shop
- burn the midnight oil
- burning question
- burning the candle at both ends
- burst your bubble
- bury the hatchet
- busy as a bee
- by hook or by crook
- call a spade a spade
- called onto the carpet
- calm before the storm
- can of worms
- can't cut the mustard
- can't hold a candle to
- case of mistaken identity
- cat got your tongue
- cat's meow
- caught in the crossfire
- caught red-handed
- checkered past
- chomping at the bit
- cleanliness is next to godliness
- clear as a bell
- clear as mud
- close to the vest
- cock and bull story
- cold shoulder
- come hell or high water
- cool as a cucumber
- cool, calm, and collected
- cost a king's ransom
- count your blessings
- crack of dawn
- crash course
- creature comforts
- cross that bridge when you come to it
- crushing blow
- cry like a baby
- cry me a river
- cry over spilt milk
- crystal clear
- curiosity killed the cat
- cut and dried
- cut through the red tape
- cut to the chase
- cute as a bugs ear
- cute as a button
- cute as a puppy
- cuts to the quick
- dark before the dawn
- day in, day out
- dead as a doornail
- devil is in the details
- dime a dozen
- divide and conquer
- dog and pony show
- dog days
- dog eat dog
- dog tired
- don't burn your bridges
- don't count your chickens
- don't look a gift horse in the mouth
- don't rock the boat
- don't step on anyone's toes
- don't take any wooden nickels
- down and out
- down at the heels
- down in the dumps
- down the hatch
- down to earth
- draw the line
- dressed to kill
- dressed to the nines
- drives me up the wall
- dull as dishwater
- dyed in the wool
- eagle eye
- ear to the ground
- early bird catches the worm
- easier said than done
- easy as pie
- eat your heart out
- eat your words
- eleventh hour
- even the playing field
- every dog has its day
- every fiber of my being
- everything but the kitchen sink
- eye for an eye
- face the music
- facts of life
- fair weather friend
- fall by the wayside
- fan the flames
- feast or famine
- feather your nest
- feathered friends
- few and far between
- fifteen minutes of fame
- filthy vermin
- fine kettle of fish
- fish out of water
- fishing for a compliment
- fit as a fiddle
- fit the bill
- fit to be tied
- flash in the pan
- flat as a pancake
- flip your lid
- flog a dead horse
- fly by night
- fly the coop
- follow your heart
- for all intents and purposes
- for the birds
- for what it's worth
- force of nature
- force to be reckoned with
- forgive and forget
- fox in the henhouse
- free and easy
- free as a bird
- fresh as a daisy
- full steam ahead
- fun in the sun
- garbage in, garbage out
- gentle as a lamb
- get a kick out of
- get a leg up
- get down and dirty
- get the lead out
- get to the bottom of
- get your feet wet
- gets my goat
- gilding the lily
- give and take
- go against the grain
- go at it tooth and nail
- go for broke
- go him one better
- go the extra mile
- go with the flow
- goes without saying
- good as gold
- good deed for the day
- good things come to those who wait
- good time was had by all
- good times were had by all
- greased lightning
- greek to me
- green thumb
- green-eyed monster
- grist for the mill
- growing like a weed
- hair of the dog
- hand to mouth
- happy as a clam
- happy as a lark
- hasn't a clue
- have a nice day
- have high hopes
- have the last laugh
- haven't got a row to hoe
- head honcho
- head over heels
- hear a pin drop
- heard it through the grapevine
- heart's content
- heavy as lead
- hem and haw
- high and dry
- high and mighty
- high as a kite
- hit paydirt
- hold your head up high
- hold your horses
- hold your own
- hold your tongue
- honest as the day is long
- horns of a dilemma
- horse of a different color
- hot under the collar
- hour of need
- I beg to differ
- icing on the cake
- if the shoe fits
- if the shoe were on the other foot
- in a jam
- in a jiffy
- in a nutshell
- in a pig's eye
- in a pinch
- in a word
- in hot water
- in the gutter
- in the nick of time
- in the thick of it
- in your dreams
- it ain't over till the fat lady sings
- it goes without saying
- it takes all kinds
- it takes one to know one
- it's a small world
- it's only a matter of time
- ivory tower
- Jack of all trades
- jockey for position
- jog your memory
- joined at the hip
- judge a book by its cover
- jump down your throat
- jump in with both feet
- jump on the bandwagon
- jump the gun
- jump to conclusions
- just a hop, skip, and a jump
- just the ticket
- justice is blind
- keep a stiff upper lip
- keep an eye on
- keep it simple, stupid
- keep the home fires burning
- keep up with the Joneses
- keep your chin up
- keep your fingers crossed
- kick the bucket
- kick up your heels
- kick your feet up
- kid in a candy store
- kill two birds with one stone
- kiss of death
- knock it out of the park
- knock on wood
- knock your socks off
- know him from Adam
- know the ropes
- know the score
- knuckle down
- knuckle sandwich
- knuckle under
- labor of love
- ladder of success
- land on your feet
- lap of luxury
- last but not least
- last hurrah
- last-ditch effort
- law of the jungle
- law of the land
- lay down the law
- leaps and bounds
- let sleeping dogs lie
- let the cat out of the bag
- let the good times roll
- let your hair down
- let's talk turkey
- letter perfect
- lick your wounds
- lies like a rug
- life's a bitch
- life's a grind
- light at the end of the tunnel
- lighter than a feather
- lighter than air
- like clockwork
- like father like son
- like taking candy from a baby
- like there's no tomorrow
- lion's share
- live and learn
- live and let live
- long and short of it
- long lost love
- look before you leap
- look down your nose
- look what the cat dragged in
- looking a gift horse in the mouth
- looks like death warmed over
- loose cannon
- lose your head
- lose your temper
- loud as a horn
- lounge lizard
- loved and lost
- low man on the totem pole
- luck of the draw
- luck of the Irish
- make hay while the sun shines
- make money hand over fist
- make my day
- make the best of a bad situation
- make the best of it
- make your blood boil
- man of few words
- man's best friend
- mark my words
- meaningful dialogue
- missed the boat on that one
- moment in the sun
- moment of glory
- moment of truth
- money to burn
- more power to you
- more than one way to skin a cat
- movers and shakers
- moving experience
- naked as a jaybird
- naked truth
- neat as a pin
- needle in a haystack
- needless to say
- neither here nor there
- never look back
- never say never
- nip and tuck
- nip it in the bud
- no guts, no glory
- no love lost
- no pain, no gain
- no skin off my back
- no stone unturned
- no time like the present
- no use crying over spilled milk
- nose to the grindstone
- not a hope in hell
- not a minute's peace
- not in my backyard
- not playing with a full deck
- not the end of the world
- not written in stone
- nothing to sneeze at
- nothing ventured nothing gained
- now we're cooking
- off the top of my head
- off the wagon
- off the wall
- old hat
- older and wiser
- older than dirt
- older than Methuselah
- on a roll
- on cloud nine
- on pins and needles
- on the bandwagon
- on the money
- on the nose
- on the rocks
- on the spot
- on the tip of my tongue
- on the wagon
- on thin ice
- once bitten, twice shy
- one bad apple doesn't spoil the bushel
- one born every minute
- one brick short
- one foot in the grave
- one in a million
- one red cent
- only game in town
- open a can of worms
- open and shut case
- open the flood gates
- opportunity doesn't knock twice
- out of pocket
- out of sight, out of mind
- out of the frying pan into the fire
- out of the woods
- out on a limb
- over a barrel
- over the hump
- pain and suffering
- pain in the
- panic button
- par for the course
- part and parcel
- party pooper
- pass the buck
- patience is a virtue
- pay through the nose
- penny pincher
- perfect storm
- pig in a poke
- pile it on
- pillar of the community
- pin your hopes on
- pitter patter of little feet
- plain as day
- plain as the nose on your face
- play by the rules
- play your cards right
- playing the field
- playing with fire
- pleased as punch
- plenty of fish in the sea
- point with pride
- poor as a church mouse
- pot calling the kettle black
- pretty as a picture
- pull a fast one
- pull your punches
- pulling your leg
- pure as the driven snow
- put it in a nutshell
- put one over on you
- put the cart before the horse
- put the pedal to the metal
- put your best foot forward
- put your foot down
- quick as a bunny
- quick as a lick
- quick as a wink
- quick as lightning
- quiet as a dormouse
- rags to riches
- raining buckets
- raining cats and dogs
- rank and file
- rat race
- reap what you sow
- red as a beet
- red herring
- reinvent the wheel
- rich and famous
- rings a bell
- ripe old age
- ripped me off
- rise and shine
- road to hell is paved with good intentions
- rob Peter to pay Paul
- roll over in the grave
- rub the wrong way
- ruled the roost
- running in circles
- sad but true
- sadder but wiser
- salt of the earth
- scared stiff
- scared to death
- sealed with a kiss
- second to none
- see eye to eye
- seen the light
- seize the day
- set the record straight
- set the world on fire
- set your teeth on edge
- sharp as a tack
- shoot for the moon
- shoot the breeze
- shot in the dark
- shoulder to the wheel
- sick as a dog
- sigh of relief
- signed, sealed, and delivered
- sink or swim
- six of one, half a dozen of another
- skating on thin ice
- slept like a log
- slinging mud
- slippery as an eel
- slow as molasses
- smart as a whip
- smooth as a baby's bottom
- sneaking suspicion
- snug as a bug in a rug
- sow wild oats
- spare the rod, spoil the child
- speak of the devil
- spilled the beans
- spinning your wheels
- spitting image of
- spoke with relish
- spread like wildfire
- spring to life
- squeaky wheel gets the grease
- stands out like a sore thumb
- start from scratch
- stick in the mud
- still waters run deep
- stitch in time
- stop and smell the roses
- straight as an arrow
- straw that broke the camel's back
- strong as an ox
- stubborn as a mule
- stuff that dreams are made of
- stuffed shirt
- sweating blood
- sweating bullets
- take a load off
- take one for the team
- take the bait
- take the bull by the horns
- take the plunge
- takes one to know one
- takes two to tango
- the more the merrier
- the real deal
- the real McCoy
- the red carpet treatment
- the same old story
- there is no accounting for taste
- thick as a brick
- thick as thieves
- thin as a rail
- think outside of the box
- third time's the charm
- this day and age
- this hurts me worse than it hurts you
- this point in time
- three sheets to the wind
- through thick and thin
- throw in the towel
- tie one on
- tighter than a drum
- time and time again
- time is of the essence
- tip of the iceberg
- tired but happy
- to coin a phrase
- to each his own
- to make a long story short
- to the best of my knowledge
- toe the line
- tongue in cheek
- too good to be true
- too hot to handle
- too numerous to mention
- touch with a ten foot pole
- tough as nails
- trial and error
- trials and tribulations
- tried and true
- trip down memory lane
- twist of fate
- two cents worth
- two peas in a pod
- ugly as sin
- under the counter
- under the gun
- under the same roof
- under the weather
- until the cows come home
- unvarnished truth
- up the creek
- uphill battle
- upper crust
- upset the applecart
- vain attempt
- vain effort
- vanquish the enemy
- vested interest
- waiting for the other shoe to drop
- wakeup call
- warm welcome
- watch your p's and q's
- watch your tongue
- watching the clock
- water under the bridge
- weather the storm
- weed them out
- week of Sundays
- went belly up
- wet behind the ears
- what goes around comes around
- what you see is what you get
- when it rains, it pours
- when push comes to shove
- when the cat's away
- when the going gets tough, the tough get going
- white as a sheet
- whole ball of wax
- whole hog
- whole nine yards
- wild goose chase
- will wonders never cease?
- wisdom of the ages
- wise as an owl
- wolf at the door
- words fail me
- work like a dog
- world weary
- worst nightmare
- worth its weight in gold
- wrong side of the bed
- yanking your chain
- yappy as a dog
- years young
- you are what you eat
- you can run but you can't hide
- you only live once
- you're the boss
- young and foolish
- young and vibrant

View file

@ -0,0 +1,32 @@
extends: existence
message: "Try to avoid using '%s'."
ignorecase: true
level: suggestion
tokens:
- am
- are
- aren't
- be
- been
- being
- he's
- here's
- here's
- how's
- i'm
- is
- isn't
- it's
- she's
- that's
- there's
- they're
- was
- wasn't
- we're
- were
- weren't
- what's
- where's
- who's
- you're

View file

@ -0,0 +1,11 @@
extends: repetition
message: "'%s' is repeated!"
level: warning
alpha: true
action:
name: edit
params:
- truncate
- " "
tokens:
- '[^\s]+'

View file

@ -0,0 +1,183 @@
extends: existence
message: "'%s' may be passive voice. Use active voice if you can."
ignorecase: true
level: warning
raw:
- \b(am|are|were|being|is|been|was|be)\b\s*
tokens:
- '[\w]+ed'
- awoken
- beat
- become
- been
- begun
- bent
- beset
- bet
- bid
- bidden
- bitten
- bled
- blown
- born
- bought
- bound
- bred
- broadcast
- broken
- brought
- built
- burnt
- burst
- cast
- caught
- chosen
- clung
- come
- cost
- crept
- cut
- dealt
- dived
- done
- drawn
- dreamt
- driven
- drunk
- dug
- eaten
- fallen
- fed
- felt
- fit
- fled
- flown
- flung
- forbidden
- foregone
- forgiven
- forgotten
- forsaken
- fought
- found
- frozen
- given
- gone
- gotten
- ground
- grown
- heard
- held
- hidden
- hit
- hung
- hurt
- kept
- knelt
- knit
- known
- laid
- lain
- leapt
- learnt
- led
- left
- lent
- let
- lighted
- lost
- made
- meant
- met
- misspelt
- mistaken
- mown
- overcome
- overdone
- overtaken
- overthrown
- paid
- pled
- proven
- put
- quit
- read
- rid
- ridden
- risen
- run
- rung
- said
- sat
- sawn
- seen
- sent
- set
- sewn
- shaken
- shaven
- shed
- shod
- shone
- shorn
- shot
- shown
- shrunk
- shut
- slain
- slept
- slid
- slit
- slung
- smitten
- sold
- sought
- sown
- sped
- spent
- spilt
- spit
- split
- spoken
- spread
- sprung
- spun
- stolen
- stood
- stridden
- striven
- struck
- strung
- stuck
- stung
- stunk
- sung
- sunk
- swept
- swollen
- sworn
- swum
- swung
- taken
- taught
- thought
- thrived
- thrown
- thrust
- told
- torn
- trodden
- understood
- upheld
- upset
- wed
- wept
- withheld
- withstood
- woken
- won
- worn
- wound
- woven
- written
- wrung

View file

@ -0,0 +1,5 @@
extends: existence
message: "Don't start a sentence with '%s'."
level: error
raw:
- '(?:[;-]\s)so[\s,]|\bSo[\s,]'

View file

@ -0,0 +1,6 @@
extends: existence
message: "Don't start a sentence with '%s'."
ignorecase: false
level: error
raw:
- '(?:[;-]\s)There\s(is|are)|\bThere\s(is|are)\b'

View file

@ -0,0 +1,221 @@
extends: existence
message: "'%s' is too wordy."
ignorecase: true
level: warning
tokens:
- a number of
- abundance
- accede to
- accelerate
- accentuate
- accompany
- accomplish
- accorded
- accrue
- acquiesce
- acquire
- additional
- adjacent to
- adjustment
- admissible
- advantageous
- adversely impact
- advise
- aforementioned
- aggregate
- aircraft
- all of
- all things considered
- alleviate
- allocate
- along the lines of
- already existing
- alternatively
- amazing
- ameliorate
- anticipate
- apparent
- appreciable
- as a matter of fact
- as a means of
- as far as I'm concerned
- as of yet
- as to
- as yet
- ascertain
- assistance
- at the present time
- at this time
- attain
- attributable to
- authorize
- because of the fact that
- belated
- benefit from
- bestow
- by means of
- by virtue of
- by virtue of the fact that
- cease
- close proximity
- commence
- comply with
- concerning
- consequently
- consolidate
- constitutes
- demonstrate
- depart
- designate
- discontinue
- due to the fact that
- each and every
- economical
- eliminate
- elucidate
- employ
- endeavor
- enumerate
- equitable
- equivalent
- evaluate
- evidenced
- exclusively
- expedite
- expend
- expiration
- facilitate
- factual evidence
- feasible
- finalize
- first and foremost
- for all intents and purposes
- for the most part
- for the purpose of
- forfeit
- formulate
- have a tendency to
- honest truth
- however
- if and when
- impacted
- implement
- in a manner of speaking
- in a timely manner
- in a very real sense
- in accordance with
- in addition
- in all likelihood
- in an effort to
- in between
- in excess of
- in lieu of
- in light of the fact that
- in many cases
- in my opinion
- in order to
- in regard to
- in some instances
- in terms of
- in the case of
- in the event that
- in the final analysis
- in the nature of
- in the near future
- in the process of
- inception
- incumbent upon
- indicate
- indication
- initiate
- irregardless
- is applicable to
- is authorized to
- is responsible for
- it is
- it is essential
- it seems that
- it was
- magnitude
- maximum
- methodology
- minimize
- minimum
- modify
- monitor
- multiple
- necessitate
- nevertheless
- not certain
- not many
- not often
- not unless
- not unlike
- notwithstanding
- null and void
- numerous
- objective
- obligate
- obtain
- on the contrary
- on the other hand
- one particular
- optimum
- overall
- owing to the fact that
- participate
- particulars
- pass away
- pertaining to
- point in time
- portion
- possess
- preclude
- previously
- prior to
- prioritize
- procure
- proficiency
- provided that
- purchase
- put simply
- readily apparent
- refer back
- regarding
- relocate
- remainder
- remuneration
- requirement
- reside
- residence
- retain
- satisfy
- shall
- should you wish
- similar to
- solicit
- span across
- strategize
- subsequent
- substantial
- successfully complete
- sufficient
- terminate
- the month of
- the point I am trying to make
- therefore
- time period
- took advantage of
- transmit
- transpire
- type of
- until such time as
- utilization
- utilize
- validate
- various different
- what I mean to say is
- whether or not
- with respect to
- with the exception of
- witnessed

View file

@ -0,0 +1,207 @@
extends: existence
message: "'%s' is a weasel word!"
ignorecase: true
level: warning
tokens:
- absolutely
- accidentally
- additionally
- allegedly
- alternatively
- angrily
- anxiously
- approximately
- awkwardly
- badly
- barely
- beautifully
- blindly
- boldly
- bravely
- brightly
- briskly
- bristly
- bubbly
- busily
- calmly
- carefully
- carelessly
- cautiously
- cheerfully
- clearly
- closely
- coldly
- completely
- consequently
- correctly
- courageously
- crinkly
- cruelly
- crumbly
- cuddly
- currently
- daily
- daringly
- deadly
- definitely
- deliberately
- doubtfully
- dumbly
- eagerly
- early
- easily
- elegantly
- enormously
- enthusiastically
- equally
- especially
- eventually
- exactly
- exceedingly
- exclusively
- extremely
- fairly
- faithfully
- fatally
- fiercely
- finally
- fondly
- few
- foolishly
- fortunately
- frankly
- frantically
- generously
- gently
- giggly
- gladly
- gracefully
- greedily
- happily
- hardly
- hastily
- healthily
- heartily
- helpfully
- honestly
- hourly
- hungrily
- hurriedly
- immediately
- impatiently
- inadequately
- ingeniously
- innocently
- inquisitively
- interestingly
- irritably
- jiggly
- joyously
- justly
- kindly
- largely
- lately
- lazily
- likely
- literally
- lonely
- loosely
- loudly
- loudly
- luckily
- madly
- many
- mentally
- mildly
- monthly
- mortally
- mostly
- mysteriously
- neatly
- nervously
- nightly
- noisily
- normally
- obediently
- occasionally
- only
- openly
- painfully
- particularly
- patiently
- perfectly
- politely
- poorly
- powerfully
- presumably
- previously
- promptly
- punctually
- quarterly
- quickly
- quietly
- rapidly
- rarely
- really
- recently
- recklessly
- regularly
- remarkably
- relatively
- reluctantly
- repeatedly
- rightfully
- roughly
- rudely
- sadly
- safely
- selfishly
- sensibly
- seriously
- sharply
- shortly
- shyly
- significantly
- silently
- simply
- sleepily
- slowly
- smartly
- smelly
- smoothly
- softly
- solemnly
- sparkly
- speedily
- stealthily
- sternly
- stupidly
- substantially
- successfully
- suddenly
- surprisingly
- suspiciously
- swiftly
- tenderly
- tensely
- thoughtfully
- tightly
- timely
- truthfully
- unexpectedly
- unfortunately
- usually
- very
- victoriously
- violently
- vivaciously
- warmly
- waverly
- weakly
- wearily
- weekly
- wildly
- wisely
- worldly
- wrinkly
- yearly

View file

@ -0,0 +1,4 @@
{
"feed": "https://github.com/errata-ai/write-good/releases.atom",
"vale_version": ">=1.0.0"
}

View file

@ -1,46 +0,0 @@
# API refactoring
Building up to Evennia 1.0 and beyond, it's time to comb through the Evennia API for old cruft. This
whitepage is for anyone interested to contribute with their views on what part of the API needs
refactoring, cleanup or clarification (or extension!)
Note that this is not a forum. To keep things clean, each opinion text should ideally present a
clear argument or lay out a suggestion. Asking for clarification and any side-discussions should be
held in chat or forum.
---
### Griatch (Aug 13, 2019)
This is how to enter an opinion. Use any markdown needed but stay within your section. Also remember
to copy your text to the clipboard before saving since if someone else edited the wiki in the
meantime you'll have to start over.
### Griatch (Sept 2, 2019)
I don't agree with removing explicit keywords as suggested by [Johnny on Aug 29 below](API-
refactoring#reduce-usage-of-optionalpositional-arguments-aug-29-2019). Overriding such a method can
still be done by `get(self, **kwargs)` if so desired, making the kwargs explicit helps IMO
readability of the API. If just giving a generic `**kwargs`, one must read the docstring or even the
code to see which keywords are valid.
On the other hand, I think it makes sense to as a standard offer an extra `**kwargs` at the end of
arg-lists for common methods that are expected to be over-ridden. This make the API more flexible by
hinting to the dev that they could expand their own over-ridden implementation with their own
keyword arguments if so desired.
---
### Johnny
#### Reduce usage of optional/positional arguments (Aug 29, 2019)
```
# AttributeHandler
def get(self, key=None, default=None, category=None, return_obj=False,
strattr=False, raise_exception=False, accessing_obj=None,
default_access=True, return_list=False):
```
Many classes have methods requiring lengthy positional argument lists, which are tedious and error-
prone to extend and override especially in cases where not all arguments are even required. It would
be useful if arguments were reserved for required inputs and anything else relegated to kwargs for
easier passthrough on extension.

View file

@ -1,108 +0,0 @@
# Accounts
All *users* (real people) that starts a game [Session](./Sessions.md) on Evennia are doing so through an
object called *Account*. The Account object has no in-game representation, it represents a unique
game account. In order to actually get on the game the Account must *puppet* an [Object](./Objects.md)
(normally a [Character](./Objects.md#characters)).
Exactly how many Sessions can interact with an Account and its Puppets at once is determined by
Evennia's [MULTISESSION_MODE](./Sessions.md#multisession-mode) setting.
Apart from storing login information and other account-specific data, the Account object is what is
chatting on [Channels](./Communications.md). It is also a good place to store [Permissions](./Locks.md) to be
consistent between different in-game characters as well as configuration options. The Account
object also has its own [CmdSet](./Command-Sets.md), the `AccountCmdSet`.
Logged into default evennia, you can use the `ooc` command to leave your current
[character](./Objects.md) and go into OOC mode. You are quite limited in this mode, basically it works
like a simple chat program. It acts as a staging area for switching between Characters (if your
game supports that) or as a safety mode if your Character gets deleted. Use `ic` to attempt to
(re)puppet a Character.
Note that the Account object can have, and often does have, a different set of
[Permissions](./Locks.md#permissions) from the Character they control. Normally you should put your
permissions on the Account level - this will overrule permissions set on the Character level. For
the permissions of the Character to come into play the default `quell` command can be used. This
allows for exploring the game using a different permission set (but you can't escalate your
permissions this way - for hierarchical permissions like `Builder`, `Admin` etc, the *lower* of the
permissions on the Character/Account will always be used).
## How to create your own Account types
You will usually not want more than one Account typeclass for all new accounts (but you could in
principle create a system that changes an account's typeclass dynamically).
An Evennia Account is, per definition, a Python class that includes `evennia.DefaultAccount` among
its parents. In `mygame/typeclasses/accounts.py` there is an empty class ready for you to modify.
Evennia defaults to using this (it inherits directly from `DefaultAccount`).
Here's an example of modifying the default Account class in code:
```python
# in mygame/typeclasses/accounts.py
from evennia import DefaultAccount
class Account(DefaultAccount): # [...]
at_account_creation(self): "this is called only once, when account is first created"
self.db.real_name = None # this is set later self.db.real_address = None #
"
self.db.config_1 = True # default config self.db.config_2 = False # "
self.db.config_3 = 1 # "
# ... whatever else our game needs to know ``` Reload the server with `reload`.
```
... However, if you use `examine *self` (the asterisk makes you examine your Account object rather
than your Character), you won't see your new Attributes yet. This is because `at_account_creation`
is only called the very *first* time the Account is called and your Account object already exists
(any new Accounts that connect will see them though). To update yourself you need to make sure to
re-fire the hook on all the Accounts you have already created. Here is an example of how to do this
using `py`:
``` py [account.at_account_creation() for account in evennia.managers.accounts.all()] ```
You should now see the Attributes on yourself.
> If you wanted Evennia to default to a completely *different* Account class located elsewhere, you
> must point Evennia to it. Add `BASE_ACCOUNT_TYPECLASS` to your settings file, and give the python
> path to your custom class as its value. By default this points to `typeclasses.accounts.Account`,
> the empty template we used above.
## Properties on Accounts
Beyond those properties assigned to all typeclassed objects (see [Typeclasses](./Typeclasses.md)), the
Account also has the following custom properties:
- `user` - a unique link to a `User` Django object, representing the logged-in user.
- `obj` - an alias for `character`.
- `name` - an alias for `user.username`
- `sessions` - an instance of
[ObjectSessionHandler](github:evennia.objects.objects#objectsessionhandler)
managing all connected Sessions (physical connections) this object listens to (Note: In older
versions of Evennia, this was a list). The so-called `session-id` (used in many places) is found
as
a property `sessid` on each Session instance.
- `is_superuser` (bool: True/False) - if this account is a superuser.
Special handlers:
- `cmdset` - This holds all the current [Commands](./Commands.md) of this Account. By default these are
the commands found in the cmdset defined by `settings.CMDSET_ACCOUNT`.
- `nicks` - This stores and handles [Nicks](./Nicks.md), in the same way as nicks it works on Objects.
For Accounts, nicks are primarily used to store custom aliases for
[Channels](./Communications.md#channels).
Selection of special methods (see `evennia.DefaultAccount` for details):
- `get_puppet` - get a currently puppeted object connected to the Account and a given session id, if
any.
- `puppet_object` - connect a session to a puppetable Object.
- `unpuppet_object` - disconnect a session from a puppetable Object.
- `msg` - send text to the Account
- `execute_cmd` - runs a command as if this Account did it.
- `search` - search for Accounts.

View file

@ -1,232 +0,0 @@
# Add a wiki on your website
**Before doing this tutorial you will probably want to read the intro in
[Basic Web tutorial](./Web-Tutorial.md).** Reading the three first parts of the
[Django tutorial](https://docs.djangoproject.com/en/1.9/intro/tutorial01/) might help as well.
This tutorial will provide a step-by-step process to installing a wiki on your website.
Fortunately, you don't have to create the features manually, since it has been done by others, and
we can integrate their work quite easily with Django. I have decided to focus on
the [Django-wiki](http://django-wiki.readthedocs.io/).
> Note: this article has been updated for Evennia 0.9. If you're not yet using this version, be
careful, as the django wiki doesn't support Python 2 anymore. (Remove this note when enough time
has passed.)
The [Django-wiki](http://django-wiki.readthedocs.io/) offers a lot of features associated with
wikis, is
actively maintained (at this time, anyway), and isn't too difficult to install in Evennia. You can
see a [demonstration of Django-wiki here](https://demo.django.wiki).
## Basic installation
You should begin by shutting down the Evennia server if it is running. We will run migrations and
alter the virtual environment just a bit. Open a terminal and activate your Python environment, the
one you use to run the `evennia` command.
* On Linux:
```
source evenv/bin/activate
```
* Or Windows:
```
evenv\bin\activate
```
### Installing with pip
Install the wiki using pip:
pip install wiki
> Note: this will install the last version of Django wiki. Version >0.4 doesn't support Python 2, so
install wiki 0.3 if you haven't updated to Python 3 yet.
It might take some time, the Django-wiki having some dependencies.
### Adding the wiki in the settings
You will need to add a few settings to have the wiki app on your website. Open your
`server/conf/settings.py` file and add the following at the bottom (but before importing
`secret_settings`). Here's what you'll find in my own setting file (add the whole Django-wiki
section):
```python
r"""
Evennia settings file.
...
"""
# Use the defaults from Evennia unless explicitly overridden
from evennia.settings_default import *
######################################################################
# Evennia base server config
######################################################################
# This is the name of your game. Make it catchy!
SERVERNAME = "demowiki"
######################################################################
# Django-wiki settings
######################################################################
INSTALLED_APPS += (
'django.contrib.humanize.apps.HumanizeConfig',
'django_nyt.apps.DjangoNytConfig',
'mptt',
'sorl.thumbnail',
'wiki.apps.WikiConfig',
'wiki.plugins.attachments.apps.AttachmentsConfig',
'wiki.plugins.notifications.apps.NotificationsConfig',
'wiki.plugins.images.apps.ImagesConfig',
'wiki.plugins.macros.apps.MacrosConfig',
)
# Disable wiki handling of login/signup
WIKI_ACCOUNT_HANDLING = False
WIKI_ACCOUNT_SIGNUP_ALLOWED = False
######################################################################
# Settings given in secret_settings.py override those in this file.
######################################################################
try:
from server.conf.secret_settings import *
except ImportError:
print("secret_settings.py file not found or failed to import.")
```
### Adding the new URLs
Next we need to add two URLs in our `web/urls.py` file. Open it and compare the following output:
you will need to add two URLs in `custom_patterns` and add one import line:
```python
from django.conf.urls import url, include
from django.urls import path # NEW!
# default evenni a patterns
from evennia.web.urls import urlpatterns
# eventual custom patterns
custom_patterns = [
# url(r'/desired/url/', view, name='example'),
url('notifications/', include('django_nyt.urls')), # NEW!
url('wiki/', include('wiki.urls')), # NEW!
]
# this is required by Django.
urlpatterns = custom_patterns + urlpatterns
```
You will probably need to copy line 2, 10, and 11. Be sure to place them correctly, as shown in
the example above.
### Running migrations
It's time to run the new migrations. The wiki app adds a few tables in our database. We'll need to
run:
evennia migrate
And that's it, you can start the server. If you go to http://localhost:4001/wiki , you should see
the wiki. Use your account's username and password to connect to it. That's how simple it is.
## Customizing privileges
A wiki can be a great collaborative tool, but who can see it? Who can modify it? Django-wiki comes
with a privilege system centered around four values per wiki page. The owner of an article can
always read and write in it (which is somewhat logical). The group of the article defines who can
read and who can write, if the user seeing the page belongs to this group. The topic of groups in
wiki pages will not be discussed here. A last setting determines which other user (that is, these
who aren't in the groups, and aren't the article's owner) can read and write. Each article has
these four settings (group read, group write, other read, other write). Depending on your purpose,
it might not be a good default choice, particularly if you have to remind every builder to keep the
pages private. Fortunately, Django-wiki gives us additional settings to customize who can read, and
who can write, a specific article.
These settings must be placed, as usual, in your `server/conf/settings.py` file. They take a
function as argument, said function (or callback) will be called with the article and the user.
Remember, a Django user, for us, is an account. So we could check lockstrings on them if needed.
Here is a default setting to restrict the wiki: only builders can write in it, but anyone (including
non-logged in users) can read it. The superuser has some additional privileges.
```python
# In server/conf/settings.py
# ...
def is_superuser(article, user):
"""Return True if user is a superuser, False otherwise."""
return not user.is_anonymous() and user.is_superuser
def is_builder(article, user):
"""Return True if user is a builder, False otherwise."""
return not user.is_anonymous() and user.locks.check_lockstring(user, "perm(Builders)")
def is_anyone(article, user):
"""Return True even if the user is anonymous."""
return True
# Who can create new groups and users from the wiki?
WIKI_CAN_ADMIN = is_superuser
# Who can change owner and group membership?
WIKI_CAN_ASSIGN = is_superuser
# Who can change group membership?
WIKI_CAN_ASSIGN_OWNER = is_superuser
# Who can change read/write access to groups or others?
WIKI_CAN_CHANGE_PERMISSIONS = is_superuser
# Who can soft-delete an article?
WIKI_CAN_DELETE = is_builder
# Who can lock an article and permanently delete it?
WIKI_CAN_MODERATE = is_superuser
# Who can edit articles?
WIKI_CAN_WRITE = is_builder
# Who can read articles?
WIKI_CAN_READ = is_anyone
```
Here, we have created three functions: one to return `True` if the user is the superuser, one to
return `True` if the user is a builder, one to return `True` no matter what (this includes if the
user is anonymous, E.G. if it's not logged-in). We then change settings to allow either the
superuser or
each builder to moderate, read, write, delete, and more. You can, of course, add more functions,
adapting them to your need. This is just a demonstration.
Providing the `WIKI_CAN*...` settings will bypass the original permission system. The superuser
could change permissions of an article, but still, only builders would be able to write it. If you
need something more custom, you will have to expand on the functions you use.
### Managing wiki pages from Evennia
Unfortunately, Django wiki doesn't provide a clear and clean entry point to read and write articles
from Evennia and it doesn't seem to be a very high priority. If you really need to keep Django wiki
and to create and manage wiki pages from your code, you can do so, but this article won't elaborate,
as this is somewhat more technical.
However, it is a good opportunity to present a small project that has been created more recently:
[evennia-wiki](https://github.com/vincent-lg/evennia-wiki) has been created to provide a simple
wiki, more tailored to Evennia and easier to connect. It doesn't, as yet, provide as many options
as does Django wiki, but it's perfectly usable:
- Pages have an inherent and much-easier to understand hierarchy based on URLs.
- Article permissions are connected to Evennia groups and are much easier to accommodate specific
requirements.
- Articles can easily be created, read or updated from the Evennia code itself.
- Markdown is fully-supported with a default integration to Bootstrap to look good on an Evennia
website. Tables and table of contents are supported as well as wiki links.
- The process to override wiki templates makes full use of the `template_overrides` directory.
However evennia-wiki doesn't yet support:
- Images in markdown and the uploading schema. If images are important to you, please consider
contributing to this new project.
- Modifying permissions on a per page/setting basis.
- Moving pages to new locations.
- Viewing page history.
Considering the list of features in Django wiki, obviously other things could be added to the list.
However, these features may be the most important and useful. Additional ones might not be that
necessary. If you're interested in supporting this little project, you are more than welcome to
[contribute to it](https://github.com/vincent-lg/evennia-wiki). Thanks!

View file

@ -1,171 +0,0 @@
# Adding Command Tutorial
This is a quick first-time tutorial expanding on the [Commands](./Commands.md) documentation.
Let's assume you have just downloaded Evennia, installed it and created your game folder (let's call
it just `mygame` here). Now you want to try to add a new command. This is the fastest way to do it.
## Step 1: Creating a custom command
1. Open `mygame/commands/command.py` in a text editor. This is just one place commands could be
placed but you get it setup from the onset as an easy place to start. It also already contains some
example code.
1. Create a new class in `command.py` inheriting from `default_cmds.MuxCommand`. Let's call it
`CmdEcho` in this example.
1. Set the class variable `key` to a good command name, like `echo`.
1. Give your class a useful _docstring_. A docstring is the string at the very top of a class or
function/method. The docstring at the top of the command class is read by Evennia to become the help
entry for the Command (see
[Command Auto-help](./Help-System.md#command-auto-help-system)).
1. Define a class method `func(self)` that echoes your input back to you.
Below is an example how this all could look for the echo command:
```python
# file mygame/commands/command.py
#[...]
from evennia import default_cmds
class CmdEcho(default_cmds.MuxCommand):
"""
Simple command example
Usage:
echo [text]
This command simply echoes text back to the caller.
"""
key = "echo"
def func(self):
"This actually does things"
if not self.args:
self.caller.msg("You didn't enter anything!")
else:
self.caller.msg("You gave the string: '%s'" % self.args)
```
## Step 2: Adding the Command to a default Cmdset
The command is not available to use until it is part of a [Command Set](./Command-Sets.md). In this
example we will go the easiest route and add it to the default Character commandset that already
exists.
1. Edit `mygame/commands/default_cmdsets.py`
1. Import your new command with `from commands.command import CmdEcho`.
1. Add a line `self.add(CmdEcho())` to `CharacterCmdSet`, in the `at_cmdset_creation` method (the
template tells you where).
This is approximately how it should look at this point:
```python
# file mygame/commands/default_cmdsets.py
#[...]
from commands.command import CmdEcho
#[...]
class CharacterCmdSet(default_cmds.CharacterCmdSet):
key = "DefaultCharacter"
def at_cmdset_creation(self):
# this first adds all default commands
super().at_cmdset_creation()
# all commands added after this point will extend or
# overwrite the default commands.
self.add(CmdEcho())
```
Next, run the `@reload` command. You should now be able to use your new `echo` command from inside
the game. Use `help echo` to see the documentation for the command.
If you have trouble, make sure to check the log for error messages (probably due to syntax errors in
your command definition).
> Note: Typing `echotest` will also work. It will be handled as the command `echo` directly followed
by
its argument `test` (which will end up in `self.args). To change this behavior, you can add the
`arg_regex` property alongside `key`, `help_category` etc. [See the arg_regex
documentation](./Commands.md#on-arg_regex) for more info.
If you want to overload existing default commands (such as `look` or `get`), just add your new
command with the same key as the old one - it will then replace it. Just remember that you must use
`@reload` to see any changes.
See [Commands](./Commands.md) for many more details and possibilities when defining Commands and using
Cmdsets in various ways.
## Adding the command to specific object types
Adding your Command to the `CharacterCmdSet` is just one easy exapmple. The cmdset system is very
generic. You can create your own cmdsets (let's say in a module `mycmdsets.py`) and add them to
objects as you please (how to control their merging is described in detail in the [Command Set
documentation](./Command-Sets.md)).
```python
# file mygame/commands/mycmdsets.py
#[...]
from commands.command import CmdEcho
from evennia import CmdSet
#[...]
class MyCmdSet(CmdSet):
key = "MyCmdSet"
def at_cmdset_creation(self):
self.add(CmdEcho())
```
Now you just need to add this to an object. To test things (as superuser) you can do
@py self.cmdset.add("mycmdsets.MyCmdSet")
This will add this cmdset (along with its echo command) to yourself so you can test it. Note that
you cannot add a single Command to an object on its own, it must be part of a CommandSet in order to
do so.
The Command you added is not there permanently at this point. If you do a `@reload` the merger will
be gone. You *could* add the `permanent=True` keyword to the `cmdset.add` call. This will however
only make the new merged cmdset permanent on that *single* object. Often you want *all* objects of
this particular class to have this cmdset.
To make sure all new created objects get your new merged set, put the `cmdset.add` call in your
custom [Typeclasses](./Typeclasses.md)' `at_object_creation` method:
```python
# e.g. in mygame/typeclasses/objects.py
from evennia import DefaultObject
class MyObject(DefaultObject):
def at_object_creation(self):
"called when the object is first created"
self.cmdset.add("mycmdset.MyCmdSet", permanent=True)
```
All new objects of this typeclass will now start with this cmdset and it will survive a `@reload`.
*Note:* An important caveat with this is that `at_object_creation` is only called *once*, when the
object is first created. This means that if you already have existing objects in your databases
using that typeclass, they will not have been initiated the same way. There are many ways to update
them; since it's a one-time update you can usually just simply loop through them. As superuser, try
the following:
@py from typeclasses.objects import MyObject; [o.cmdset.add("mycmdset.MyCmdSet") for o in
MyObject.objects.all()]
This goes through all objects in your database having the right typeclass, adding the new cmdset to
each. The good news is that you only have to do this if you want to post-add *cmdsets*. If you just
want to add a new *command*, you can simply add that command to the cmdset's `at_cmdset_creation`
and `@reload` to make the Command immediately available.
## Change where Evennia looks for command sets
Evennia uses settings variables to know where to look for its default command sets. These are
normally not changed unless you want to re-organize your game folder in some way. For example, the
default character cmdset defaults to being defined as
CMDSET_CHARACTER="commands.default_cmdset.CharacterCmdSet"
See `evennia/settings_default.py` for the other settings.

View file

@ -1,109 +0,0 @@
# Adding Object Typeclass Tutorial
Evennia comes with a few very basic classes of in-game entities:
DefaultObject
|
DefaultCharacter
DefaultRoom
DefaultExit
DefaultChannel
When you create a new Evennia game (with for example `evennia --init mygame`) Evennia will
automatically create empty child classes `Object`, `Character`, `Room` and `Exit` respectively. They
are found `mygame/typeclasses/objects.py`, `mygame/typeclasses/rooms.py` etc.
> Technically these are all [Typeclassed](./Typeclasses.md), which can be ignored for now. In
> `mygame/typeclasses` are also base typeclasses for out-of-character things, notably
> [Channels](./Communications.md), [Accounts](./Accounts.md) and [Scripts](./Scripts.md). We don't cover those in
> this tutorial.
For your own game you will most likely want to expand on these very simple beginnings. It's normal
to want your Characters to have various attributes, for example. Maybe Rooms should hold extra
information or even *all* Objects in your game should have properties not included in basic Evennia.
## Change Default Rooms, Exits, Character Typeclass
This is the simplest case.
The default build commands of a new Evennia game is set up to use the `Room`, `Exit` and `Character`
classes found in the same-named modules under `mygame/typeclasses/`. By default these are empty and
just implements the default parents from the Evennia library (`DefaultRoom`etc). Just add the
changes you want to these classes and run `@reload` to add your new functionality.
## Create a new type of object
Say you want to create a new "Heavy" object-type that characters should not have the ability to pick
up.
1. Edit `mygame/typeclasses/objects.py` (you could also create a new module there, named something
like `heavy.py`, that's up to how you want to organize things).
1. Create a new class inheriting at any distance from `DefaultObject`. It could look something like
this:
```python
# end of file mygame/typeclasses/objects.py
from evennia import DefaultObject
class Heavy(DefaultObject):
"Heavy object"
def at_object_creation(self):
"Called whenever a new object is created"
# lock the object down by default
self.locks.add("get:false()")
# the default "get" command looks for this Attribute in order
# to return a customized error message (we just happen to know
# this, you'd have to look at the code of the 'get' command to
# find out).
self.db.get_err_msg = "This is too heavy to pick up."
```
1. Once you are done, log into the game with a build-capable account and do `@create/drop
rock:objects.Heavy` to drop a new heavy "rock" object in your location. Next try to pick it up
(`@quell` yourself first if you are a superuser). If you get errors, look at your log files where
you will find the traceback. The most common error is that you have some sort of syntax error in
your class.
Note that the [Locks](./Locks.md) and [Attribute](./Attributes.md) which are set in the typeclass could just
as well have been set using commands in-game, so this is a *very* simple example.
## Storing data on initialization
The `at_object_creation` is only called once, when the object is first created. This makes it ideal
for database-bound things like [Attributes](./Attributes.md). But sometimes you want to create temporary
properties (things that are not to be stored in the database but still always exist every time the
object is created). Such properties can be initialized in the `at_init` method on the object.
`at_init` is called every time the object is loaded into memory.
> Note: It's usually pointless and wasteful to assign database data in `at_init`, since this will
> hit the database with the same value over and over. Put those in `at_object_creation` instead.
You are wise to use `ndb` (non-database Attributes) to store these non-persistent properties, since
ndb-properties are protected against being cached out in various ways and also allows you to list
them using various in-game tools:
```python
def at_init(self):
self.ndb.counter = 0
self.ndb.mylist = []
```
> Note: As mentioned in the [Typeclasses](./Typeclasses.md) documentation, `at_init` replaces the use of
> the standard `__init__` method of typeclasses due to how the latter may be called in situations
> other than you'd expect. So use `at_init` where you would normally use `__init__`.
## Updating existing objects
If you already have some `Heavy` objects created and you add a new `Attribute` in
`at_object_creation`, you will find that those existing objects will not have this Attribute. This
is not so strange, since `at_object_creation` is only called once, it will not be called again just
because you update it. You need to update existing objects manually.
If the number of objects is limited, you can use `@typeclass/force/reload objectname` to force a
re-load of the `at_object_creation` method (only) on the object. This case is common enough that
there is an alias `@update objectname` you can use to get the same effect. If there are multiple
objects you can use `@py` to loop over the objects you need:
```
@py from typeclasses.objects import Heavy; [obj.at_object_creation() for obj in Heavy.objects.all()]
```

View file

@ -1,76 +0,0 @@
# Administrative Docs
The following pages are aimed at game administrators -- the higher-ups that possess shell access and
are responsible for managing the game.
### Installation and Early Life
- [Choosing (and installing) an SQL Server](./Choosing-An-SQL-Server.md)
- [Getting Started - Installing Evennia](./Getting-Started.md)
- [Running Evennia in Docker Containers](./Running-Evennia-in-Docker.md)
- [Starting, stopping, reloading and resetting Evennia](./Start-Stop-Reload.md)
- [Keeping your game up to date](./Updating-Your-Game.md)
- [Resetting your database](./Updating-Your-Game.md#resetting-your-database)
- [Making your game available online](./Online-Setup.md)
- [Hosting options](./Online-Setup.md#hosting-options)
- [Securing your server with SSL/Let's Encrypt](./Online-Setup.md#ssl)
- [Listing your game](./Evennia-Game-Index.md) at the online [Evennia game
index](http://games.evennia.com)
### Customizing the server
- [Changing the Settings](./Server-Conf.md#settings-file)
- [Available Master
Settings](https://github.com/evennia/evennia/blob/master/evennia/settings_default.py)
- [Change Evennia's language](./Internationalization.md) (internationalization)
- [Apache webserver configuration](./Apache-Config.md) (optional)
- [Changing text encodings used by the server](./Text-Encodings.md)
- [The Connection Screen](./Connection-Screen.md)
- [Guest Logins](./Guest-Logins.md)
- [How to connect Evennia to IRC channels](./IRC.md)
- [How to connect Evennia to RSS feeds](./RSS.md)
- [How to connect Evennia to Grapevine](./Grapevine.md)
- [How to connect Evennia to Twitter](./How-to-connect-Evennia-to-Twitter.md)
### Administrating the running game
- [Supported clients](./Client-Support-Grid.md) (grid of known client issues)
- [Changing Permissions](./Building-Permissions.md) of users
- [Banning](./Banning.md) and deleting users
- [Summary of abuse-handling tools](./Banning.md#summary-of-abuse-handling-tools) in the default cmdset
### Working with Evennia
- [Setting up your work environment with version control](./Version-Control.md)
- [First steps coding with Evennia](./First-Steps-Coding.md)
- [Setting up a continuous integration build environment](./Continuous-Integration.md)
```{toctree}
:hidden:
Choosing-An-SQL-Server
Getting-Started
Running-Evennia-in-Docker
Start-Stop-Reload
Updating-Your-Game
Online-Setup
Evennia-Game-Index
Server-Conf
Internationalization
Apache-Config
Text-Encodings
Connection-Screen
Guest-Logins
IRC
RSS
Grapevine
How-to-connect-Evennia-to-Twitter
Client-Support-Grid
Building-Permissions
Banning
Version-Control
First-Steps-Coding
Continuous-Integration
```

View file

@ -1,272 +0,0 @@
# Arxcode installing help
## Introduction
[Arx - After the Reckoning](http://play.arxmush.org/) is a big and very popular
[Evennia](http://www.evennia.com)-based game. Arx is heavily roleplaying-centric, relying on game
masters to drive the story. Technically it's maybe best described as "a MUSH, but with more coded
systems". In August of 2018, the game's developer, Tehom, generously released the [source code of
Arx on github](https://github.com/Arx-Game/arxcode). This is a treasure-trove for developers wanting
to pick ideas or even get a starting game to build on. These instructions are based on the Arx-code
released as of *Aug 12, 2018*.
If you are not familiar with what Evennia is, you can read
[an introduction here](./Evennia-Introduction.md).
It's not too hard to run Arx from the sources (of course you'll start with an empty database) but
since part of Arx has grown organically, it doesn't follow standard Evennia paradigms everywhere.
This page covers one take on installing and setting things up while making your new Arx-based game
better match with the vanilla Evennia install.
## Installing Evennia
Firstly, set aside a folder/directory on your drive for everything to follow.
You need to start by installing [Evennia](http://www.evennia.com) by following most of the [Getting
Started
Instructions](./Getting-Started.md) for your OS. The difference is that you need to `git clone
https://github.com/TehomCD/evennia.git` instead of Evennia's repo because Arx uses TehomCD's older
Evennia 0.8 [fork](https://github.com/TehomCD/evennia), notably still using Python2. This detail is
important if referring to newer Evennia documentation.
If you are new to Evennia it's *highly* recommended that you run through the
instructions in full - including initializing and starting a new empty game and connecting to it.
That way you can be sure Evennia works correctly as a base line. If you have trouble, make sure to
read the [Troubleshooting instructions](./Getting-Started.md#troubleshooting) for your
operating system. You can also drop into our
[forums](https://groups.google.com/forum/#%21forum/evennia), join `#evennia` on `irc.freenode.net`
or chat from the linked [Discord Server](https://discord.gg/NecFePw).
After installing you should have a `virtualenv` running and you should have the following file
structure in your set-aside folder:
```
vienv/
evennia/
mygame/
```
Here `mygame` is the empty game you created during the Evennia install, with `evennia --init`. Go to
that and run `evennia stop` to make sure your empty game is not running. We'll instead let Evenna
run Arx, so in principle you could erase `mygame` - but it could also be good to have a clean game
to compare to.
## Installing Arxcode
### Clone the arxcode repo
Cd to the root of your directory and clone the released source code from github:
git clone https://github.com/Arx-Game/arxcode.git myarx
A new folder `myarx` should appear next to the ones you already had. You could rename this to
something else if you want.
Cd into `myarx`. If you wonder about the structure of the game dir, you can [read more about it
here](./Directory-Overview.md).
### Clean up settings
Arx has split evennia's normal settings into `base_settings.py` and `production_settings.py`. It
also has its own solution for managing 'secret' parts of the settings file. We'll keep most of Arx
way but remove the secret-handling and replace it with the normal Evennia method.
Cd into `myarx/server/conf/` and open the file `settings.py` in a text editor. The top part (within
`"""..."""`) is just help text. Wipe everything underneath that and make it look like this instead
(don't forget to save):
```
from base_settings import *
TELNET_PORTS = [4000]
SERVERNAME = "MyArx"
GAME_SLOGAN = "The cool game"
try:
from server.conf.secret_settings import *
except ImportError:
print("secret_settings.py file not found or failed to import.")
```
> Note: Indents and capitalization matter in Python. Make indents 4 spaces (not tabs) for your own
> sanity. If you want a starter on Python in Evennia, [you can look here](Python-basic-
introduction).
This will import Arx' base settings and override them with the Evennia-default telnet port and give
the game a name. The slogan changes the sub-text shown under the name of your game in the website
header. You can tweak these to your own liking later.
Next, create a new, empty file `secret_settings.py` in the same location as the `settings.py` file.
This can just contain the following:
```python
SECRET_KEY = "sefsefiwwj3 jnwidufhjw4545_oifej whewiu hwejfpoiwjrpw09&4er43233fwefwfw"
```
Replace the long random string with random ASCII characters of your own. The secret key should not
be shared.
Next, open `myarx/server/conf/base_settings.py` in your text editor. We want to remove/comment out
all mentions of the `decouple` package, which Evennia doesn't use (we use `private_settings.py` to
hide away settings that should not be shared).
Comment out `from decouple import config` by adding a `#` to the start of the line: `# from decouple
import config`. Then search for `config(` in the file and comment out all lines where this is used.
Many of these are specific to the server environment where the original Arx runs, so is not that
relevant to us.
### Install Arx dependencies
Arx has some further dependencies beyond vanilla Evennia. Start by `cd`:ing to the root of your
`myarx` folder.
> If you run *Linux* or *Mac*: Edit `myarx/requirements.txt` and comment out the line
> `pypiwin32==219` - it's only needed on Windows and will give an error on other platforms.
Make sure your `virtualenv` is active, then run
pip install -r requirements.txt
The needed Python packages will be installed for you.
### Adding logs/ folder
The Arx repo does not contain the `myarx/server/logs/` folder Evennia expects for storing server
logs. This is simple to add:
# linux/mac
mkdir server/logs
# windows
mkdir server\logs
### Setting up the database and starting
From the `myarx` folder, run
evennia migrate
This creates the database and will step through all database migrations needed.
evennia start
If all goes well Evennia will now start up, running Arx! You can connect to it on `localhost` (or
`127.0.0.1` if your platform doesn't alias `localhost`), port `4000` using a Telnet client.
Alternatively, you can use your web browser to browse to `http://localhost:4001` to see the game's
website and get to the web client.
When you log in you'll get the standard Evennia greeting (since the database is empty), but you can
try `help` to see that it's indeed Arx that is running.
### Additional Setup Steps
The first time you start Evennia after creating the database with the `evennia migrate` step above,
it should create a few starting objects for you - your superuser account, which it will prompt you
to enter, a starting room (Limbo), and a character object for you. If for some reason this does not
occur, you may have to follow the steps below. For the first time Superuser login you may have to
run steps 7-8 and 10 to create and connect to your in-came Character.
1. Login to the game website with your Superuser account.
2. Press the `Admin` button to get into the (Django-) Admin Interface.
3. Navigate to the `Accounts` section.
4. Add a new Account named for the new staffer. Use a place holder password and dummy e-mail
address.
5. Flag account as `Staff` and apply the `Admin` permission group (This assumes you have already set
up an Admin Group in Django).
6. Add Tags named `player` and `developer`.
7. Log into the game using the web client (or a third-party telnet client) using your superuser
account. Move to where you want the new staffer character to appear.
8. In the game client, run `@create/drop <staffername>:typeclasses.characters.Character`, where
`<staffername>` is usually the same name you used for the Staffer account you created in the
Admin earlier (if you are creating a Character for your superuser, use your superuser account
name).
This creates a new in-game Character and places it in your current location.
9. Have the new Admin player log into the game.
10. Have the new Admin puppet the character with `@ic StafferName`.
11. Have the new Admin change their password - `@password <old password> = <new password>`.
Now that you have a Character and an Account object, there's a few additional things you may need to
do in order for some commands to function properly. You can either execute these as in-game commands
while `@ic` (controlling your character object).
1. `@py from web.character.models import RosterEntry;RosterEntry.objects.create(player=self.player,
character=self)`
2. `@py from world.dominion.models import PlayerOrNpc, AssetOwner;dompc =
PlayerOrNpc.objects.create(player = self.player);AssetOwner.objects.create(player=dompc)`
Those steps will give you a 'RosterEntry', 'PlayerOrNpc', and 'AssetOwner' objects. RosterEntry
explicitly connects a character and account object together, even while offline, and contains
additional information about a character's current presence in game (such as which 'roster' they're
in, if you choose to use an active roster of characters). PlayerOrNpc are more character extensions,
as well as support for npcs with no in-game presence and just represented by a name which can be
offscreen members of a character's family. It also allows for membership in Organizations.
AssetOwner holds information about a character or organization's money and resources.
## Alternate guide by Pax for installing on Windows
If for some reason you cannot use the Windows Subsystem for Linux (which would use instructions
identical to the ones above), it's possible to get Evennia running under Anaconda for Windows. The
process is a little bit trickier.
Make sure you have:
* Git for Windows https://git-scm.com/download/win
* Anaconda for Windows https://www.anaconda.com/distribution/
* VC++ Compiler for Python 2.7 http://aka.ms/vcpython27
conda update conda
conda create -n arx python=2.7
source activate arx
Set up a convenient repository place for things.
cd ~
mkdir Source
cd Source
mkdir Arx
cd Arx
Replace the SSH git clone links below with your own github forks.
If you don't plan to change Evennia at all, you can use the
evennia/evennia.git repo instead of a forked one.
git clone git@github.com:<youruser>/evennia.git
git clone git@github.com:<youruser>/arxcode.git
Evennia is a package itself, so we want to install it and all of its
prerequisites, after switching to the appropriately-tagged branch for
Arxcode.
cd evennia
git checkout tags/v0.7 -b arx-master
pip install -e .
Arx has some dependencies of its own, so now we'll go install them
As it is not a package, we'll use the normal requirements file.
cd ../arxcode
pip install -r requirements.txt
The git repo doesn't include the empty log directory and Evennia is unhappy if you
don't have it, so while still in the arxcode directory...
mkdir server/logs
Now hit https://github.com/evennia/evennia/wiki/Arxcode-installing-help and
change the setup stuff as in the 'Clean up settings' section.
Then we will create our default database...
../evennia/bin/windows/evennia.bat migrate
...and do the first run. You need winpty because Windows does not have a TTY/PTY
by default, and so the Python console input commands (used for prompts on first
run) will fail and you will end up in an unhappy place. Future runs, you should
not need winpty.
winpty ../evennia/bin/windows/evennia.bat start
Once this is done, you should have your Evennia server running Arxcode up
on localhost at port 4000, and the webserver at http://localhost:4001/
And you are done! Huzzah!

View file

@ -1,233 +0,0 @@
# Async Process
*This is considered an advanced topic.*
## Synchronous versus Asynchronous
Most program code operates *synchronously*. This means that each statement in your code gets
processed and finishes before the next can begin. This makes for easy-to-understand code. It is also
a *requirement* in many cases - a subsequent piece of code often depend on something calculated or
defined in a previous statement.
Consider this piece of code in a traditional Python program:
```python
print("before call ...")
long_running_function()
print("after call ...")
```
When run, this will print `"before call ..."`, after which the `long_running_function` gets to work
for however long time. Only once that is done, the system prints `"after call ..."`. Easy and
logical to follow. Most of Evennia work in this way and often it's important that commands get
executed in the same strict order they were coded.
Evennia, via Twisted, is a single-process multi-user server. In simple terms this means that it
swiftly switches between dealing with player input so quickly that each player feels like they do
things at the same time. This is a clever illusion however: If one user, say, runs a command
containing that `long_running_function`, *all* other players are effectively forced to wait until it
finishes.
Now, it should be said that on a modern computer system this is rarely an issue. Very few commands
run so long that other users notice it. And as mentioned, most of the time you *want* to enforce
all commands to occur in strict sequence.
When delays do become noticeable and you don't care in which order the command actually completes,
you can run it *asynchronously*. This makes use of the `run_async()` function in
`src/utils/utils.py`:
```python
run_async(function, *args, **kwargs)
```
Where `function` will be called asynchronously with `*args` and `**kwargs`. Example:
```python
from evennia import utils
print("before call ...")
utils.run_async(long_running_function)
print("after call ...")
```
Now, when running this you will find that the program will not wait around for
`long_running_function` to finish. In fact you will see `"before call ..."` and `"after call ..."`
printed out right away. The long-running function will run in the background and you (and other
users) can go on as normal.
## Customizing asynchronous operation
A complication with using asynchronous calls is what to do with the result from that call. What if
`long_running_function` returns a value that you need? It makes no real sense to put any lines of
code after the call to try to deal with the result from `long_running_function` above - as we saw
the `"after call ..."` got printed long before `long_running_function` was finished, making that
line quite pointless for processing any data from the function. Instead one has to use *callbacks*.
`utils.run_async` takes reserved kwargs that won't be passed into the long-running function:
- `at_return(r)` (the *callback*) is called when the asynchronous function (`long_running_function`
above) finishes successfully. The argument `r` will then be the return value of that function (or
`None`).
```python
def at_return(r):
print(r)
```
- `at_return_kwargs` - an optional dictionary that will be fed as keyword arguments to the
`at_return` callback.
- `at_err(e)` (the *errback*) is called if the asynchronous function fails and raises an exception.
This exception is passed to the errback wrapped in a *Failure* object `e`. If you do not supply an
errback of your own, Evennia will automatically add one that silently writes errors to the evennia
log. An example of an errback is found below:
```python
def at_err(e):
print("There was an error:", str(e))
```
- `at_err_kwargs` - an optional dictionary that will be fed as keyword arguments to the `at_err`
errback.
An example of making an asynchronous call from inside a [Command](./Commands.md) definition:
```python
from evennia import utils, Command
class CmdAsync(Command):
key = "asynccommand"
def func(self):
def long_running_function():
#[... lots of time-consuming code ...]
return final_value
def at_return_function(r):
self.caller.msg("The final value is %s" % r)
def at_err_function(e):
self.caller.msg("There was an error: %s" % e)
# do the async call, setting all callbacks
utils.run_async(long_running_function, at_return=at_return_function,
at_err=at_err_function)
```
That's it - from here on we can forget about `long_running_function` and go on with what else need
to be done. *Whenever* it finishes, the `at_return_function` function will be called and the final
value will
pop up for us to see. If not we will see an error message.
## delay
The `delay` function is a much simpler sibling to `run_async`. It is in fact just a way to delay the
execution of a command until a future time. This is equivalent to something like `time.sleep()`
except delay is asynchronous while `sleep` would lock the entire server for the duration of the
sleep.
```python
from evennia.utils import delay
# [...]
# e.g. inside a Command, where `self.caller` is available
def callback(obj):
obj.msg("Returning!")
delay(10, callback, self.caller)
```
This will delay the execution of the callback for 10 seconds. This function is explored much more in
the [Command Duration Tutorial](./Command-Duration.md).
You can also try the following snippet just see how it works:
@py from evennia.utils import delay; delay(10, lambda who: who.msg("Test!"), self)
Wait 10 seconds and 'Test!' should be echoed back to you.
## The @interactive decorator
As of Evennia 0.9, the `@interactive` [decorator](https://realpython.com/primer-on-python-decorators/)
is available. This makes any function or method possible to 'pause' and/or await player input
in an interactive way.
```python
from evennia.utils import interactive
@interactive
def myfunc(caller):
while True:
caller.msg("Getting ready to wait ...")
yield(5)
caller.msg("Now 5 seconds have passed.")
response = yield("Do you want to wait another 5 secs?")
if response.lower() not in ("yes", "y"):
break
```
The `@interactive` decorator gives the function the ability to pause. The use
of `yield(seconds)` will do just that - it will asynchronously pause for the
number of seconds given before continuing. This is technically equivalent to
using `call_async` with a callback that continues after 5 secs. But the code
with `@interactive` is a little easier to follow.
Within the `@interactive` function, the `response = yield("question")` question
allows you to ask the user for input. You can then process the input, just like
you would if you used the Python `input` function. There is one caveat to this
functionality though - _it will only work if the function/method has an
argument named exactly `caller`_. This is because internally Evennia will look
for the `caller` argument and treat that as the source of input.
All of this makes the `@interactive` decorator very useful. But it comes with a
few caveats. Notably, decorating a function/method with `@interactive` turns it
into a Python [generator](https://wiki.python.org/moin/Generators). The most
common issue is that you cannot use `return <value>` from a generator (just an
empty `return` works). To return a value from a function/method you have decorated
with `@interactive`, you must instead use a special Twisted function
`twisted.internet.defer.returnValue`. Evennia also makes this function
conveniently available from `evennia.utils`:
```python
from evennia.utils import interactive, returnValue
@interactive
def myfunc():
# ...
result = 10
# this must be used instead of `return result`
returnValue(result)
```
## Assorted notes
Overall, be careful with choosing when to use asynchronous calls. It is mainly useful for large
administration operations that have no direct influence on the game world (imports and backup
operations come to mind). Since there is no telling exactly when an asynchronous call actually ends,
using them for in-game commands is to potentially invite confusion and inconsistencies (and very
hard-to-reproduce bugs).
The very first synchronous example above is not *really* correct in the case of Twisted, which is
inherently an asynchronous server. Notably you might find that you will *not* see the first `before
call ...` text being printed out right away. Instead all texts could end up being delayed until
after the long-running process finishes. So all commands will retain their relative order as
expected, but they may appear with delays or in groups.
## Further reading
Technically, `run_async` is just a very thin and simplified wrapper around a
[Twisted Deferred](http://twistedmatrix.com/documents/9.0.0/core/howto/defer.html) object; the
wrapper sets
up a default errback also if none is supplied. If you know what you are doing there is nothing
stopping you from bypassing the utility function, building a more sophisticated callback chain after
your own liking.

View file

@ -1,393 +0,0 @@
# Attributes
When performing actions in Evennia it is often important that you store data for later. If you write
a menu system, you have to keep track of the current location in the menu tree so that the player
can give correct subsequent commands. If you are writing a combat system, you might have a
combattant's next roll get easier dependent on if their opponent failed. Your characters will
probably need to store roleplaying-attributes like strength and agility. And so on.
[Typeclassed](./Typeclasses.md) game entities ([Accounts](./Accounts.md), [Objects](./Objects.md),
[Scripts](./Scripts.md) and [Channels](./Communications.md)) always have *Attributes* associated with them.
Attributes are used to store any type of data 'on' such entities. This is different from storing
data in properties already defined on entities (such as `key` or `location`) - these have very
specific names and require very specific types of data (for example you couldn't assign a python
*list* to the `key` property no matter how hard you tried). `Attributes` come into play when you
want to assign arbitrary data to arbitrary names.
**Attributes are _not_ secure by default and any player may be able to change them unless you
[prevent this behavior](./Attributes.md#locking-and-checking-attributes).**
## The .db and .ndb shortcuts
To save persistent data on a Typeclassed object you normally use the `db` (DataBase) operator. Let's
try to save some data to a *Rose* (an [Object](./Objects.md)):
```python
# saving
rose.db.has_thorns = True
# getting it back
is_ouch = rose.db.has_thorns
```
This looks like any normal Python assignment, but that `db` makes sure that an *Attribute* is
created behind the scenes and is stored in the database. Your rose will continue to have thorns
throughout the life of the server now, until you deliberately remove them.
To be sure to save **non-persistently**, i.e. to make sure NOT to create a database entry, you use
`ndb` (NonDataBase). It works in the same way:
```python
# saving
rose.ndb.has_thorns = True
# getting it back
is_ouch = rose.ndb.has_thorns
```
Technically, `ndb` has nothing to do with `Attributes`, despite how similar they look. No
`Attribute` object is created behind the scenes when using `ndb`. In fact the database is not
invoked at all since we are not interested in persistence. There is however an important reason to
use `ndb` to store data rather than to just store variables direct on entities - `ndb`-stored data
is tracked by the server and will not be purged in various cache-cleanup operations Evennia may do
while it runs. Data stored on `ndb` (as well as `db`) will also be easily listed by example the
`@examine` command.
You can also `del` properties on `db` and `ndb` as normal. This will for example delete an
`Attribute`:
```python
del rose.db.has_thorns
```
Both `db` and `ndb` defaults to offering an `all` property on themselves. This returns all
associated attributes or non-persistent properties.
```python
list_of_all_rose_attributes = rose.db.all
list_of_all_rose_ndb_attrs = rose.ndb.all
```
If you use `all` as the name of an attribute, this will be used instead. Later deleting your custom
`all` will return the default behaviour.
## The AttributeHandler
The `.db` and `.ndb` properties are very convenient but if you don't know the name of the Attribute
beforehand they cannot be used. Behind the scenes `.db` actually accesses the `AttributeHandler`
which sits on typeclassed entities as the `.attributes` property. `.ndb` does the same for the
`.nattributes` property.
The handlers have normal access methods that allow you to manage and retrieve `Attributes` and
`NAttributes`:
- `has('attrname')` - this checks if the object has an Attribute with this key. This is equivalent
to doing `obj.db.attrname`.
- `get(...)` - this retrieves the given Attribute. Normally the `value` property of the Attribute is
returned, but the method takes keywords for returning the Attribute object itself. By supplying an
`accessing_object` to the call one can also make sure to check permissions before modifying
anything.
- `add(...)` - this adds a new Attribute to the object. An optional [lockstring](./Locks.md) can be
supplied here to restrict future access and also the call itself may be checked against locks.
- `remove(...)` - Remove the given Attribute. This can optionally be made to check for permission
before performing the deletion. - `clear(...)` - removes all Attributes from object.
- `all(...)` - returns all Attributes (of the given category) attached to this object.
See [this section](./Attributes.md#locking-and-checking-attributes) for more about locking down Attribute
access and editing. The `Nattribute` offers no concept of access control.
Some examples:
```python
import evennia
obj = evennia.search_object("MyObject")
obj.attributes.add("test", "testvalue")
print(obj.db.test) # prints "testvalue"
print(obj.attributes.get("test")) # "
print(obj.attributes.all()) # prints [<AttributeObject>]
obj.attributes.remove("test")
```
## Properties of Attributes
An Attribute object is stored in the database. It has the following properties:
- `key` - the name of the Attribute. When doing e.g. `obj.db.attrname = value`, this property is set
to `attrname`.
- `value` - this is the value of the Attribute. This value can be anything which can be pickled -
objects, lists, numbers or what have you (see
[this section](./Attributes.md#what-types-of-data-can-i-save-in-an-attribute) for more info). In the
example
`obj.db.attrname = value`, the `value` is stored here.
- `category` - this is an optional property that is set to None for most Attributes. Setting this
allows to use Attributes for different functionality. This is usually not needed unless you want
to use Attributes for very different functionality ([Nicks](./Nicks.md) is an example of using
Attributes
in this way). To modify this property you need to use the [Attribute
Handler](#the-attributehandler).
- `strvalue` - this is a separate value field that only accepts strings. This severely limits the
data possible to store, but allows for easier database lookups. This property is usually not used
except when re-using Attributes for some other purpose ([Nicks](./Nicks.md) use it). It is only
accessible via the [Attribute Handler](./Attributes.md#the-attributehandler).
There are also two special properties:
- `attrtype` - this is used internally by Evennia to separate [Nicks](./Nicks.md), from Attributes (Nicks
use Attributes behind the scenes).
- `model` - this is a *natural-key* describing the model this Attribute is attached to. This is on
the form *appname.modelclass*, like `objects.objectdb`. It is used by the Attribute and
NickHandler to quickly sort matches in the database. Neither this nor `attrtype` should normally
need to be modified.
Non-database attributes have no equivalence to `category` nor `strvalue`, `attrtype` or `model`.
## Persistent vs non-persistent
So *persistent* data means that your data will survive a server reboot, whereas with
*non-persistent* data it will not ...
... So why would you ever want to use non-persistent data? The answer is, you don't have to. Most of
the time you really want to save as much as you possibly can. Non-persistent data is potentially
useful in a few situations though.
- You are worried about database performance. Since Evennia caches Attributes very aggressively,
this is not an issue unless you are reading *and* writing to your Attribute very often (like many
times per second). Reading from an already cached Attribute is as fast as reading any Python
property. But even then this is not likely something to worry about: Apart from Evennia's own
caching, modern database systems themselves also cache data very efficiently for speed. Our
default
database even runs completely in RAM if possible, alleviating much of the need to write to disk
during heavy loads.
- A more valid reason for using non-persistent data is if you *want* to lose your state when logging
off. Maybe you are storing throw-away data that are re-initialized at server startup. Maybe you
are implementing some caching of your own. Or maybe you are testing a buggy [Script](./Scripts.md) that
does potentially harmful stuff to your character object. With non-persistent storage you can be
sure
that whatever is messed up, it's nothing a server reboot can't clear up.
- NAttributes have no restrictions at all on what they can store (see next section), since they
don't need to worry about being saved to the database - they work very well for temporary storage.
- You want to implement a fully or partly *non-persistent world*. Who are we to argue with your
grand vision!
## What types of data can I save in an Attribute?
> None of the following affects NAttributes, which does not invoke the database at all. There are no
> restrictions to what can be stored in a NAttribute.
The database doesn't know anything about Python objects, so Evennia must *serialize* Attribute
values into a string representation in order to store it to the database. This is done using the
`pickle` module of Python (the only exception is if you use the `strattr` keyword of the
AttributeHandler to save to the `strvalue` field of the Attribute. In that case you can only save
*strings* which will not be pickled).
It's important to note that when you access the data in an Attribute you are *always* de-serializing
it from the database representation every time. This is because we allow for storing
database-entities in Attributes too. If we cached it as its Python form, we might end up with
situations where the database entity was deleted since we last accessed the Attribute.
De-serializing data with a database-entity in it means querying the database for that object and
making sure it still exists (otherwise it will be set to `None`). Performance-wise this is usually
not a big deal. But if you are accessing the Attribute as part of some big loop or doing a large
amount of reads/writes you should first extract it to a temporary variable, operate on *that* and
then save the result back to the Attribute. If you are storing a more complex structure like a
`dict` or a `list` you should make sure to "disconnect" it from the database before looping over it,
as mentioned in the [Retrieving Mutable Objects](./Attributes.md#retrieving-mutable-objects) section
below.
### Storing single objects
With a single object, we mean anything that is *not iterable*, like numbers, strings or custom class
instances without the `__iter__` method.
* You can generally store any non-iterable Python entity that can be
[pickled](http://docs.python.org/library/pickle.html).
* Single database objects/typeclasses can be stored as any other in the Attribute. These can
normally *not* be pickled, but Evennia will behind the scenes convert them to an internal
representation using their classname, database-id and creation-date with a microsecond precision,
guaranteeing you get the same object back when you access the Attribute later.
* If you *hide* a database object inside a non-iterable custom class (like stored as a variable
inside it), Evennia will not know it's there and won't convert it safely. Storing classes with
such hidden database objects is *not* supported and will lead to errors!
```python
# Examples of valid single-value attribute data:
obj.db.test1 = 23
obj.db.test1 = False
# a database object (will be stored as an internal representation)
obj.db.test2 = myobj
# example of an invalid, "hidden" dbobject
class Invalid(object):
def __init__(self, dbobj):
# no way for Evennia to know this is a dbobj
self.dbobj = dbobj
invalid = Invalid(myobj)
obj.db.invalid = invalid # will cause error!
```
### Storing multiple objects
This means storing objects in a collection of some kind and are examples of *iterables*, pickle-able
entities you can loop over in a for-loop. Attribute-saving supports the following iterables:
* [Tuples](https://docs.python.org/2/library/functions.html#tuple), like `(1,2,"test", <dbobj>)`.
* [Lists](https://docs.python.org/2/tutorial/datastructures.html#more-on-lists), like `[1,2,"test",
<dbobj>]`.
* [Dicts](https://docs.python.org/2/tutorial/datastructures.html#dictionaries), like `{1:2,
"test":<dbobj>]`.
* [Sets](https://docs.python.org/2/tutorial/datastructures.html#sets), like `{1,2,"test",<dbobj>}`.
* [collections.OrderedDict](https://docs.python.org/2/library/collections.html#collections.OrderedDict), like `OrderedDict((1,2), ("test", <dbobj>))`.
* [collections.Deque](https://docs.python.org/2/library/collections.html#collections.deque), like
`deque((1,2,"test",<dbobj>))`.
* *Nestings* of any combinations of the above, like lists in dicts or an OrderedDict of tuples, each
containing dicts, etc.
* All other iterables (i.e. entities with the `__iter__` method) will be converted to a *list*.
Since you can use any combination of the above iterables, this is generally not much of a
limitation.
Any entity listed in the [Single object](./Attributes.md#storing-single-objects) section above can be
stored in the iterable.
> As mentioned in the previous section, database entities (aka typeclasses) are not possible to
> pickle. So when storing an iterable, Evennia must recursively traverse the iterable *and all its
> nested sub-iterables* in order to find eventual database objects to convert. This is a very fast
> process but for efficiency you may want to avoid too deeply nested structures if you can.
```python
# examples of valid iterables to store
obj.db.test3 = [obj1, 45, obj2, 67]
# a dictionary
obj.db.test4 = {'str':34, 'dex':56, 'agi':22, 'int':77}
# a mixed dictionary/list
obj.db.test5 = {'members': [obj1,obj2,obj3], 'enemies':[obj4,obj5]}
# a tuple with a list in it
obj.db.test6 = (1,3,4,8, ["test", "test2"], 9)
# a set
obj.db.test7 = set([1,2,3,4,5])
# in-situ manipulation
obj.db.test8 = [1,2,{"test":1}]
obj.db.test8[0] = 4
obj.db.test8[2]["test"] = 5
# test8 is now [4,2,{"test":5}]
```
### Retrieving Mutable objects
A side effect of the way Evennia stores Attributes is that *mutable* iterables (iterables that can
be modified in-place after they were created, which is everything except tuples) are handled by
custom objects called `_SaverList`, `_SaverDict` etc. These `_Saver...` classes behave just like the
normal variant except that they are aware of the database and saves to it whenever new data gets
assigned to them. This is what allows you to do things like `self.db.mylist[7] = val` and be sure
that the new version of list is saved. Without this you would have to load the list into a temporary
variable, change it and then re-assign it to the Attribute in order for it to save.
There is however an important thing to remember. If you retrieve your mutable iterable into another
variable, e.g. `mylist2 = obj.db.mylist`, your new variable (`mylist2`) will *still* be a
`_SaverList`. This means it will continue to save itself to the database whenever it is updated!
```python
obj.db.mylist = [1,2,3,4]
mylist = obj.db.mylist
mylist[3] = 5 # this will also update database
print(mylist) # this is now [1,2,3,5]
print(obj.db.mylist) # this is also [1,2,3,5]
```
To "disconnect" your extracted mutable variable from the database you simply need to convert the
`_Saver...` iterable to a normal Python structure. So to convert a `_SaverList`, you use the
`list()` function, for a `_SaverDict` you use `dict()` and so on.
```python
obj.db.mylist = [1,2,3,4]
mylist = list(obj.db.mylist) # convert to normal list
mylist[3] = 5
print(mylist) # this is now [1,2,3,5]
print(obj.db.mylist) # this is still [1,2,3,4]
```
A further problem comes with *nested mutables*, like a dict containing lists of dicts or something
like that. Each of these nested mutables would be `_Saver*` structures connected to the database and
disconnecting the outermost one of them would not disconnect those nested within. To make really
sure you disonnect a nested structure entirely from the database, Evennia provides a special
function `evennia.utils.dbserialize.deserialize`:
```
from evennia.utils.dbserialize import deserialize
decoupled_mutables = deserialize(nested_mutables)
```
The result of this operation will be a structure only consisting of normal Python mutables (`list`
instead of `_SaverList` and so on).
Remember, this is only valid for *mutable* iterables.
[Immutable](http://en.wikipedia.org/wiki/Immutable) objects (strings, numbers, tuples etc) are
already disconnected from the database from the onset.
```python
obj.db.mytup = (1,2,[3,4])
obj.db.mytup[0] = 5 # this fails since tuples are immutable
# this works but will NOT update database since outermost is a tuple
obj.db.mytup[2][1] = 5
print(obj.db.mytup[2][1]) # this still returns 4, not 5
mytup1 = obj.db.mytup # mytup1 is already disconnected from database since outermost
# iterable is a tuple, so we can edit the internal list as we want
# without affecting the database.
```
> Attributes will fetch data fresh from the database whenever you read them, so
> if you are performing big operations on a mutable Attribute property (such as looping over a list
> or dict) you should make sure to "disconnect" the Attribute's value first and operate on this
> rather than on the Attribute. You can gain dramatic speed improvements to big loops this
> way.
## Locking and checking Attributes
Attributes are normally not locked down by default, but you can easily change that for individual
Attributes (like those that may be game-sensitive in games with user-level building).
First you need to set a *lock string* on your Attribute. Lock strings are specified [Locks](./Locks.md).
The relevant lock types are
- `attrread` - limits who may read the value of the Attribute
- `attredit` - limits who may set/change this Attribute
You cannot use the `db` handler to modify Attribute object (such as setting a lock on them) - The
`db` handler will return the Attribute's *value*, not the Attribute object itself. Instead you use
the AttributeHandler and set it to return the object instead of the value:
```python
lockstring = "attread:all();attredit:perm(Admins)"
obj.attributes.get("myattr", return_obj=True).locks.add(lockstring)
```
Note the `return_obj` keyword which makes sure to return the `Attribute` object so its LockHandler
could be accessed.
A lock is no good if nothing checks it -- and by default Evennia does not check locks on Attributes.
You have to add a check to your commands/code wherever it fits (such as before setting an
Attribute).
```python
# in some command code where we want to limit
# setting of a given attribute name on an object
attr = obj.attributes.get(attrname,
return_obj=True,
accessing_obj=caller,
default=None,
default_access=False)
if not attr:
caller.msg("You cannot edit that Attribute!")
return
# edit the Attribute here
```
The same keywords are available to use with `obj.attributes.set()` and `obj.attributes.remove()`,
those will check for the `attredit` lock type.

View file

@ -1,229 +0,0 @@
# Batch Code Processor
For an introduction and motivation to using batch processors, see [here](./Batch-Processors.md). This
page describes the Batch-*code* processor. The Batch-*command* one is covered [here](Batch-Command-
Processor).
## Basic Usage
The batch-code processor is a superuser-only function, invoked by
> @batchcode path.to.batchcodefile
Where `path.to.batchcodefile` is the path to a *batch-code file*. Such a file should have a name
ending in "`.py`" (but you shouldn't include that in the path). The path is given like a python path
relative to a folder you define to hold your batch files, set by `BATCH_IMPORT_PATH` in your
settings. Default folder is (assuming your game is called "mygame") `mygame/world/`. So if you want
to run the example batch file in `mygame/world/batch_code.py`, you could simply use
> @batchcode batch_code
This will try to run through the entire batch file in one go. For more gradual, *interactive*
control you can use the `/interactive` switch. The switch `/debug` will put the processor in
*debug* mode. Read below for more info.
## The batch file
A batch-code file is a normal Python file. The difference is that since the batch processor loads
and executes the file rather than importing it, you can reliably update the file, then call it
again, over and over and see your changes without needing to `@reload` the server. This makes for
easy testing. In the batch-code file you have also access to the following global variables:
- `caller` - This is a reference to the object running the batchprocessor.
- `DEBUG` - This is a boolean that lets you determine if this file is currently being run in debug-
mode or not. See below how this can be useful.
Running a plain Python file through the processor will just execute the file from beginning to end.
If you want to get more control over the execution you can use the processor's *interactive* mode.
This runs certain code blocks on their own, rerunning only that part until you are happy with it. In
order to do this you need to add special markers to your file to divide it up into smaller chunks.
These take the form of comments, so the file remains valid Python.
Here are the rules of syntax of the batch-code `*.py` file.
- `#CODE` as the first on a line marks the start of a *code* block. It will last until the beginning
of another marker or the end of the file. Code blocks contain functional python code. Each `#CODE`
block will be run in complete isolation from other parts of the file, so make sure it's self-
contained.
- `#HEADER` as the first on a line marks the start of a *header* block. It lasts until the next
marker or the end of the file. This is intended to hold imports and variables you will need for all
other blocks .All python code defined in a header block will always be inserted at the top of every
`#CODE` blocks in the file. You may have more than one `#HEADER` block, but that is equivalent to
having one big one. Note that you can't exchange data between code blocks, so editing a header-
variable in one code block won't affect that variable in any other code block!
- `#INSERT path.to.file` will insert another batchcode (Python) file at that position.
- A `#` that is not starting a `#HEADER`, `#CODE` or `#INSERT` instruction is considered a comment.
- Inside a block, normal Python syntax rules apply. For the sake of indentation, each block acts as
a separate python module.
Below is a version of the example file found in `evennia/contrib/tutorial_examples/`.
```python
#
# This is an example batch-code build file for Evennia.
#
#HEADER
# This will be included in all other #CODE blocks
from evennia import create_object, search_object
from evennia.contrib.tutorial_examples import red_button
from typeclasses.objects import Object
limbo = search_object('Limbo')[0]
#CODE
red_button = create_object(red_button.RedButton, key="Red button",
location=limbo, aliases=["button"])
# caller points to the one running the script
caller.msg("A red button was created.")
# importing more code from another batch-code file
#INSERT batch_code_insert
#CODE
table = create_object(Object, key="Blue Table", location=limbo)
chair = create_object(Object, key="Blue Chair", location=limbo)
string = "A %s and %s were created."
if DEBUG:
table.delete()
chair.delete()
string += " Since debug was active, " \
"they were deleted again."
caller.msg(string % (table, chair))
```
This uses Evennia's Python API to create three objects in sequence.
## Debug mode
Try to run the example script with
> @batchcode/debug tutorial_examples.example_batch_code
The batch script will run to the end and tell you it completed. You will also get messages that the
button and the two pieces of furniture were created. Look around and you should see the button
there. But you won't see any chair nor a table! This is because we ran this with the `/debug`
switch, which is directly visible as `DEBUG==True` inside the script. In the above example we
handled this state by deleting the chair and table again.
The debug mode is intended to be used when you test out a batchscript. Maybe you are looking for
bugs in your code or try to see if things behave as they should. Running the script over and over
would then create an ever-growing stack of chairs and tables, all with the same name. You would have
to go back and painstakingly delete them later.
## Interactive mode
Interactive mode works very similar to the [batch-command processor counterpart](Batch-Command-
Processor). It allows you more step-wise control over how the batch file is executed. This is useful
for debugging or for picking and choosing only particular blocks to run. Use `@batchcode` with the
`/interactive` flag to enter interactive mode.
> @batchcode/interactive tutorial_examples.example_batch_code
You should see the following:
01/02: red_button = create_object(red_button.RedButton, [...] (hh for help)
This shows that you are on the first `#CODE` block, the first of only two commands in this batch
file. Observe that the block has *not* actually been executed at this point!
To take a look at the full code snippet you are about to run, use `ll` (a batch-processor version of
`look`).
```python
from evennia.utils import create, search
from evennia.contrib.tutorial_examples import red_button
from typeclasses.objects import Object
limbo = search.objects(caller, 'Limbo', global_search=True)[0]
red_button = create.create_object(red_button.RedButton, key="Red button",
location=limbo, aliases=["button"])
# caller points to the one running the script
caller.msg("A red button was created.")
```
Compare with the example code given earlier. Notice how the content of `#HEADER` has been pasted at
the top of the `#CODE` block. Use `pp` to actually execute this block (this will create the button
and give you a message). Use `nn` (next) to go to the next command. Use `hh` for a list of commands.
If there are tracebacks, fix them in the batch file, then use `rr` to reload the file. You will
still be at the same code block and can rerun it easily with `pp` as needed. This makes for a simple
debug cycle. It also allows you to rerun individual troublesome blocks - as mentioned, in a large
batch file this can be very useful (don't forget the `/debug` mode either).
Use `nn` and `bb` (next and back) to step through the file; e.g. `nn 12` will jump 12 steps forward
(without processing any blocks in between). All normal commands of Evennia should work too while
working in interactive mode.
## Limitations and Caveats
The batch-code processor is by far the most flexible way to build a world in Evennia. There are
however some caveats you need to keep in mind.
### Safety
Or rather the lack of it. There is a reason only *superusers* are allowed to run the batch-code
processor by default. The code-processor runs **without any Evennia security checks** and allows
full access to Python. If an untrusted party could run the code-processor they could execute
arbitrary python code on your machine, which is potentially a very dangerous thing. If you want to
allow other users to access the batch-code processor you should make sure to run Evennia as a
separate and very limited-access user on your machine (i.e. in a 'jail'). By comparison, the batch-
command processor is much safer since the user running it is still 'inside' the game and can't
really do anything outside what the game commands allow them to.
### No communication between code blocks
Global variables won't work in code batch files, each block is executed as stand-alone environments.
`#HEADER` blocks are literally pasted on top of each `#CODE` block so updating some header-variable
in your block will not make that change available in another block. Whereas a python execution
limitation, allowing this would also lead to very hard-to-debug code when using the interactive mode
- this would be a classical example of "spaghetti code".
The main practical issue with this is when building e.g. a room in one code block and later want to
connect that room with a room you built in the current block. There are two ways to do this:
- Perform a database search for the name of the room you created (since you cannot know in advance
which dbref it got assigned). The problem is that a name may not be unique (you may have a lot of "A
dark forest" rooms). There is an easy way to handle this though - use [Tags](./Tags.md) or *Aliases*. You
can assign any number of tags and/or aliases to any object. Make sure that one of those tags or
aliases is unique to the room (like "room56") and you will henceforth be able to always uniquely
search and find it later.
- Use the `caller` global property as an inter-block storage. For example, you could have a
dictionary of room references in an `ndb`:
```python
#HEADER
if caller.ndb.all_rooms is None:
caller.ndb.all_rooms = {}
#CODE
# create and store the castle
castle = create_object("rooms.Room", key="Castle")
caller.ndb.all_rooms["castle"] = castle
#CODE
# in another node we want to access the castle
castle = caller.ndb.all_rooms.get("castle")
```
Note how we check in `#HEADER` if `caller.ndb.all_rooms` doesn't already exist before creating the
dict. Remember that `#HEADER` is copied in front of every `#CODE` block. Without that `if` statement
we'd be wiping the dict every block!
### Don't treat a batchcode file like any Python file
Despite being a valid Python file, a batchcode file should *only* be run by the batchcode processor.
You should not do things like define Typeclasses or Commands in them, or import them into other
code. Importing a module in Python will execute base level of the module, which in the case of your
average batchcode file could mean creating a lot of new objects every time.
### Don't let code rely on the batch-file's real file path
When you import things into your batchcode file, don't use relative imports but always import with
paths starting from the root of your game directory or evennia library. Code that relies on the
batch file's "actual" location *will fail*. Batch code files are read as text and the strings
executed. When the code runs it has no knowledge of what file those strings where once a part of.

View file

@ -1,182 +0,0 @@
# Batch Command Processor
For an introduction and motivation to using batch processors, see [here](./Batch-Processors.md). This
page describes the Batch-*command* processor. The Batch-*code* one is covered [here](Batch-Code-
Processor).
## Basic Usage
The batch-command processor is a superuser-only function, invoked by
> @batchcommand path.to.batchcmdfile
Where `path.to.batchcmdfile` is the path to a *batch-command file* with the "`.ev`" file ending.
This path is given like a python path relative to a folder you define to hold your batch files, set
with `BATCH_IMPORT_PATH` in your settings. Default folder is (assuming your game is in the `mygame`
folder) `mygame/world`. So if you want to run the example batch file in
`mygame/world/batch_cmds.ev`, you could use
> @batchcommand batch_cmds
A batch-command file contains a list of Evennia in-game commands separated by comments. The
processor will run the batch file from beginning to end. Note that *it will not stop if commands in
it fail* (there is no universal way for the processor to know what a failure looks like for all
different commands). So keep a close watch on the output, or use *Interactive mode* (see below) to
run the file in a more controlled, gradual manner.
## The batch file
The batch file is a simple plain-text file containing Evennia commands. Just like you would write
them in-game, except you have more freedom with line breaks.
Here are the rules of syntax of an `*.ev` file. You'll find it's really, really simple:
- All lines having the `#` (hash)-symbol *as the first one on the line* are considered *comments*.
All non-comment lines are treated as a command and/or their arguments.
- Comment lines have an actual function -- they mark the *end of the previous command definition*.
So never put two commands directly after one another in the file - separate them with a comment, or
the second of the two will be considered an argument to the first one. Besides, using plenty of
comments is good practice anyway.
- A line that starts with the word `#INSERT` is a comment line but also signifies a special
instruction. The syntax is `#INSERT <path.batchfile>` and tries to import a given batch-cmd file
into this one. The inserted batch file (file ending `.ev`) will run normally from the point of the
`#INSERT` instruction.
- Extra whitespace in a command definition is *ignored*. - A completely empty line translates in to
a line break in texts. Two empty lines thus means a new paragraph (this is obviously only relevant
for commands accepting such formatting, such as the `@desc` command).
- The very last command in the file is not required to end with a comment.
- You *cannot* nest another `@batchcommand` statement into your batch file. If you want to link many
batch-files together, use the `#INSERT` batch instruction instead. You also cannot launch the
`@batchcode` command from your batch file, the two batch processors are not compatible.
Below is a version of the example file found in `evennia/contrib/tutorial_examples/batch_cmds.ev`.
```bash
#
# This is an example batch build file for Evennia.
#
# This creates a red button
@create button:tutorial_examples.red_button.RedButton
# (This comment ends input for @create)
# Next command. Let's create something.
@set button/desc =
This is a large red button. Now and then
it flashes in an evil, yet strangely tantalizing way.
A big sign sits next to it. It says:
-----------
Press me!
-----------
... It really begs to be pressed! You
know you want to!
# This inserts the commands from another batch-cmd file named
# batch_insert_file.ev.
#INSERT examples.batch_insert_file
# (This ends the @set command). Note that single line breaks
# and extra whitespace in the argument are ignored. Empty lines
# translate into line breaks in the output.
# Now let's place the button where it belongs (let's say limbo #2 is
# the evil lair in our example)
@teleport #2
# (This comments ends the @teleport command.)
# Now we drop it so others can see it.
# The very last command in the file needs not be ended with #.
drop button
```
To test this, run `@batchcommand` on the file:
> @batchcommand contrib.tutorial_examples.batch_cmds
A button will be created, described and dropped in Limbo. All commands will be executed by the user
calling the command.
> Note that if you interact with the button, you might find that its description changes, loosing
your custom-set description above. This is just the way this particular object works.
## Interactive mode
Interactive mode allows you to more step-wise control over how the batch file is executed. This is
useful for debugging and also if you have a large batch file and is only updating a small part of it
-- running the entire file again would be a waste of time (and in the case of `@create`-ing objects
you would to end up with multiple copies of same-named objects, for example). Use `@batchcommand`
with the `/interactive` flag to enter interactive mode.
> @batchcommand/interactive tutorial_examples.batch_cmds
You will see this:
01/04: @create button:tutorial_examples.red_button.RedButton (hh for help)
This shows that you are on the `@create` command, the first out of only four commands in this batch
file. Observe that the command `@create` has *not* been actually processed at this point!
To take a look at the full command you are about to run, use `ll` (a batch-processor version of
`look`). Use `pp` to actually process the current command (this will actually `@create` the button)
-- and make sure it worked as planned. Use `nn` (next) to go to the next command. Use `hh` for a
list of commands.
If there are errors, fix them in the batch file, then use `rr` to reload the file. You will still be
at the same command and can rerun it easily with `pp` as needed. This makes for a simple debug
cycle. It also allows you to rerun individual troublesome commands - as mentioned, in a large batch
file this can be very useful. Do note that in many cases, commands depend on the previous ones (e.g.
if `@create` in the example above had failed, the following commands would have had nothing to
operate on).
Use `nn` and `bb` (next and back) to step through the file; e.g. `nn 12` will jump 12 steps forward
(without processing any command in between). All normal commands of Evennia should work too while
working in interactive mode.
## Limitations and Caveats
The batch-command processor is great for automating smaller builds or for testing new commands and
objects repeatedly without having to write so much. There are several caveats you have to be aware
of when using the batch-command processor for building larger, complex worlds though.
The main issue is that when you run a batch-command script you (*you*, as in your superuser
character) are actually moving around in the game creating and building rooms in sequence, just as
if you had been entering those commands manually, one by one. You have to take this into account
when creating the file, so that you can 'walk' (or teleport) to the right places in order.
This also means there are several pitfalls when designing and adding certain types of objects. Here
are some examples:
- *Rooms that change your [Command Set](./Command-Sets.md)*: Imagine that you build a 'dark' room, which
severely limits the cmdsets of those entering it (maybe you have to find the light switch to
proceed). In your batch script you would create this room, then teleport to it - and promptly be
shifted into the dark state where none of your normal build commands work ...
- *Auto-teleportation*: Rooms that automatically teleport those that enter them to another place
(like a trap room, for example). You would be teleported away too.
- *Mobiles*: If you add aggressive mobs, they might attack you, drawing you into combat. If they
have AI they might even follow you around when building - or they might move away from you before
you've had time to finish describing and equipping them!
The solution to all these is to plan ahead. Make sure that superusers are never affected by whatever
effects are in play. Add an on/off switch to objects and make sure it's always set to *off* upon
creation. It's all doable, one just needs to keep it in mind.
## Assorted notes
The fact that you build as 'yourself' can also be considered an advantage however, should you ever
decide to change the default command to allow others than superusers to call the processor. Since
normal access-checks are still performed, a malevolent builder with access to the processor should
not be able to do all that much damage (this is the main drawback of the [Batch Code
Processor](./Batch-Code-Processor.md))
- [GNU Emacs](https://www.gnu.org/software/emacs/) users might find it interesting to use emacs'
*evennia mode*. This is an Emacs major mode found in `evennia/utils/evennia-mode.el`. It offers
correct syntax highlighting and indentation with `<tab>` when editing `.ev` files in Emacs. See the
header of that file for installation instructions.
- [VIM](http://www.vim.org/) users can use amfl's [vim-evennia](https://github.com/amfl/vim-evennia)
mode instead, see its readme for install instructions.

View file

@ -1,82 +0,0 @@
# Batch Processors
Building a game world is a lot of work, especially when starting out. Rooms should be created,
descriptions have to be written, objects must be detailed and placed in their proper places. In many
traditional MUD setups you had to do all this online, line by line, over a telnet session.
Evennia already moves away from much of this by shifting the main coding work to external Python
modules. But also building would be helped if one could do some or all of it externally. Enter
Evennia's *batch processors* (there are two of them). The processors allows you, as a game admin, to
build your game completely offline in normal text files (*batch files*) that the processors
understands. Then, when you are ready, you use the processors to read it all into Evennia (and into
the database) in one go.
You can of course still build completely online should you want to - this is certainly the easiest
way to go when learning and for small build projects. But for major building work, the advantages of
using the batch-processors are many:
- It's hard to compete with the comfort of a modern desktop text editor; Compared to a traditional
MUD line input, you can get much better overview and many more features. Also, accidentally pressing
Return won't immediately commit things to the database.
- You might run external spell checkers on your batch files. In the case of one of the batch-
processors (the one that deals with Python code), you could also run external debuggers and code
analyzers on your file to catch problems before feeding it to Evennia.
- The batch files (as long as you keep them) are records of your work. They make a natural starting
point for quickly re-building your world should you ever decide to start over.
- If you are an Evennia developer, using a batch file is a fast way to setup a test-game after
having reset the database.
- The batch files might come in useful should you ever decide to distribute all or part of your
world to others.
There are two batch processors, the Batch-*command* processor and the Batch-*code* processor. The
first one is the simpler of the two. It doesn't require any programming knowledge - you basically
just list in-game commands in a text file. The code-processor on the other hand is much more
powerful but also more complex - it lets you use Evennia's API to code your world in full-fledged
Python code.
- The [Batch Command Processor](./Batch-Command-Processor.md)
- The [Batch Code Processor](./Batch-Code-Processor.md)
If you plan to use international characters in your batchfiles you are wise to read about *file
encodings* below.
## A note on File Encodings
As mentioned, both the processors take text files as input and then proceed to process them. As long
as you stick to the standard [ASCII](http://en.wikipedia.org/wiki/Ascii) character set (which means
the normal English characters, basically) you should not have to worry much about this section.
Many languages however use characters outside the simple `ASCII` table. Common examples are various
apostrophes and umlauts but also completely different symbols like those of the greek or cyrillic
alphabets.
First, we should make it clear that Evennia itself handles international characters just fine. It
(and Django) uses [unicode](http://en.wikipedia.org/wiki/Unicode) strings internally.
The problem is that when reading a text file like the batchfile, we need to know how to decode the
byte-data stored therein to universal unicode. That means we need an *encoding* (a mapping) for how
the file stores its data. There are many, many byte-encodings used around the world, with opaque
names such as `Latin-1`, `ISO-8859-3` or `ARMSCII-8` to pick just a few examples. Problem is that
it's practially impossible to determine which encoding was used to save a file just by looking at it
(it's just a bunch of bytes!). You have to *know*.
With this little introduction it should be clear that Evennia can't guess but has to *assume* an
encoding when trying to load a batchfile. The text editor and Evennia must speak the same "language"
so to speak. Evennia will by default first try the international `UTF-8` encoding, but you can have
Evennia try any sequence of different encodings by customizing the `ENCODINGS` list in your settings
file. Evennia will use the first encoding in the list that do not raise any errors. Only if none
work will the server give up and return an error message.
You can often change the text editor encoding (this depends on your editor though), otherwise you
need to add the editor's encoding to Evennia's `ENCODINGS` list. If you are unsure, write a test
file with lots of non-ASCII letters in the editor of your choice, then import to make sure it works
as it should.
More help with encodings can be found in the entry [Text Encodings](./Text-Encodings.md) and also in the
Wikipedia article [here](http://en.wikipedia.org/wiki/Text_encodings).
**A footnote for the batch-code processor**: Just because *Evennia* can parse your file and your
fancy special characters, doesn't mean that *Python* allows their use. Python syntax only allows
international characters inside *strings*. In all other source code only `ASCII` set characters are
allowed.

View file

@ -1,100 +0,0 @@
# Bootstrap & Evennia
# What is Bootstrap?
Evennia's new default web page uses a framework called [Bootstrap](https://getbootstrap.com/). This
framework is in use across the internet - you'll probably start to recognize its influence once you
learn some of the common design patterns. This switch is great for web developers, perhaps like
yourself, because instead of wondering about setting up different grid systems or what custom class
another designer used, we have a base, a bootstrap, to work from. Bootstrap is responsive by
default, and comes with some default styles that Evennia has lightly overrode to keep some of the
same colors and styles you're used to from the previous design.
For your reading pleasure, a brief overview of Bootstrap follows. For more in-depth info, please
read [the documentation](https://getbootstrap.com/docs/4.0/getting-started/introduction/).
***
## The Layout System
Other than the basic styling Bootstrap includes, it also includes [a built in layout and grid
system](https://getbootstrap.com/docs/4.0/layout/overview/).
The first part of this system is [the
container](https://getbootstrap.com/docs/4.0/layout/overview/#containers).
The container is meant to hold all your page content. Bootstrap provides two types: fixed-width and
full-width.
Fixed-width containers take up a certain max-width of the page - they're useful for limiting the
width on Desktop or Tablet platforms, instead of making the content span the width of the page.
```
<div class="container">
<!--- Your content here -->
</div>
```
Full width containers take up the maximum width available to them - they'll span across a wide-
screen desktop or a smaller screen phone, edge-to-edge.
```
<div class="container-fluid">
<!--- This content will span the whole page -->
</div>
```
The second part of the layout system is [the grid](https://getbootstrap.com/docs/4.0/layout/grid/).
This is the bread-and-butter of the layout of Bootstrap - it allows you to change the size of
elements depending on the size of the screen, without writing any media queries. We'll briefly go
over it - to learn more, please read the docs or look at the source code for Evennia's home page in
your browser.
> Important! Grid elements should be in a .container or .container-fluid. This will center the
contents of your site.
Bootstrap's grid system allows you to create rows and columns by applying classes based on
breakpoints. The default breakpoints are extra small, small, medium, large, and extra-large. If
you'd like to know more about these breakpoints, please [take a look at the documentation for
them.](https://getbootstrap.com/docs/4.0/layout/overview/#responsive-breakpoints)
To use the grid system, first create a container for your content, then add your rows and columns
like so:
```
<div class="container">
<div class="row">
<div class="col">
1 of 3
</div>
<div class="col">
2 of 3
</div>
<div class="col">
3 of 3
</div>
</div>
</div>
```
This layout would create three equal-width columns.
To specify your sizes - for instance, Evennia's default site has three columns on desktop and
tablet, but reflows to single-column on smaller screens. Try it out!
```
<div class="container">
<div class="row">
<div class="col col-md-6 col-lg-3">
1 of 4
</div>
<div class="col col-md-6 col-lg-3">
2 of 4
</div>
<div class="col col-md-6 col-lg-3">
3 of 4
</div>
<div class="col col-md-6 col-lg-3">
4 of 4
</div>
</div>
</div>
```
This layout would be 4 columns on large screens, 2 columns on medium screens, and 1 column on
anything smaller.
To learn more about Bootstrap's grid, please [take a look at the
docs](https://getbootstrap.com/docs/4.0/layout/grid/)
***
## More Bootstrap
Bootstrap also provides a huge amount of utilities, as well as styling and content elements. To
learn more about them, please [read the Bootstrap docs](https://getbootstrap.com/docs/4.0/getting-started/introduction/) or read one of our other web tutorials.

View file

@ -1,82 +0,0 @@
# Bootstrap Components and Utilities
Bootstrap provides many utilities and components you can use when customizing Evennia's web
presence. We'll go over a few examples here that you might find useful.
> Please take a look at either [the basic web tutorial](./Add-a-simple-new-web-page.md) or [the web
character view tutorial](./Web-Character-View-Tutorial.md)
> to get a feel for how to add pages to Evennia's website to test these examples.
## General Styling
Bootstrap provides base styles for your site. These can be customized through CSS, but the default
styles are intended to provide a consistent, clean look for sites.
### Color
Most elements can be styled with default colors. [Take a look at the
documentation](https://getbootstrap.com/docs/4.0/utilities/colors/) to learn more about these colors
- suffice to say, adding a class of text-* or bg-*, for instance, text-primary, sets the text color
or background color.
### Borders
Simply adding a class of 'border' to an element adds a border to the element. For more in-depth
info, please [read the documentation on
borders.](https://getbootstrap.com/docs/4.0/utilities/borders/).
```
<span class="border border-dark"></span>
```
You can also easily round corners just by adding a class.
```
<img src="..." class="rounded" />
```
### Spacing
Bootstrap provides classes to easily add responsive margin and padding. Most of the time, you might
like to add margins or padding through CSS itself - however these classes are used in the default
Evennia site. [Take a look at the docs](https://getbootstrap.com/docs/4.0/utilities/spacing/) to
learn more.
***
## Components
### Buttons
[Buttons](https://getbootstrap.com/docs/4.0/components/buttons/) in Bootstrap are very easy to use -
button styling can be added to `<button>`, `<a>`, and `<input>` elements.
```
<a class="btn btn-primary" href="#" role="button">I'm a Button</a>
<button class="btn btn-primary" type="submit">Me too!</button>
<input class="btn btn-primary" type="button" value="Button">
<input class="btn btn-primary" type="submit" value="Also a Button">
<input class="btn btn-primary" type="reset" value="Button as Well">
```
### Cards
[Cards](https://getbootstrap.com/docs/4.0/components/card/) provide a container for other elements
that stands out from the rest of the page. The "Accounts", "Recently Connected", and "Database
Stats" on the default webpage are all in cards. Cards provide quite a bit of formatting options -
the following is a simple example, but read the documentation or look at the site's source for more.
```
<div class="card">
<div class="card-body">
<h4 class="card-title">Card title</h4>
<h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
<p class="card-text">Fancy, isn't it?</p>
<a href="#" class="card-link">Card link</a>
</div>
</div>
```
### Jumbotron
[Jumbotrons](https://getbootstrap.com/docs/4.0/components/jumbotron/) are useful for featuring an
image or tagline for your game. They can flow with the rest of your content or take up the full
width of the page - Evennia's base site uses the former.
```
<div class="jumbotron jumbotron-fluid">
<div class="container">
<h1 class="display-3">Full Width Jumbotron</h1>
<p class="lead">Look at the source of the default Evennia page for a regular Jumbotron</p>
</div>
</div>
```
### Forms
[Forms](https://getbootstrap.com/docs/4.0/components/forms/) are highly customizable with Bootstrap.
For a more in-depth look at how to use forms and their styles in your own Evennia site, please read
over [the web character gen tutorial.](./Web-Character-Generation.md)

View file

@ -1,43 +0,0 @@
# Builder Docs
This section contains information useful to world builders.
### Building basics
- [Default in-game commands](evennia.commands.default)
- [Building Quick-start](./Building-Quickstart.md)
- [Giving build permissions to others](./Building-Permissions.md)
- [Adding text tags](./TextTags.md)
- [Colored text](./TextTags.md#coloured-text)
- [Clickable links](./TextTags.md#clickable-links)
- [Inline functions](./TextTags.md#inline-functions)
- [Customizing the connection screen](./Connection-Screen.md)
### Advanced building and World building
- [Overview of batch processors](./Batch-Processors.md)
- [Batch-command processor](./Batch-Command-Processor.md)
- [Batch-code processor](./Batch-Code-Processor.md)
- [Using the Spawner for individualizing objects](./Spawner-and-Prototypes.md)
- [Adding Zones](./Zones.md)
### The Tutorial world
- [Introduction and setup](./Tutorial-World-Introduction.md)
```{toctree}
:hidden:
Building-Quickstart
Building-Permissions
TextTags
Connection-Screen
Batch-Processors
Batch-Command-Processor
Batch-Code-Processor
Spawner-and-Prototypes
Zones
Tutorial-World-Introduction
```

View file

@ -1,72 +0,0 @@
# Building Permissions
*OBS: This gives only a brief introduction to the access system. Locks and permissions are fully
detailed* [here](./Locks.md).
## The super user
There are strictly speaking two types of users in Evennia, the *super user* and everyone else. The
superuser is the first user you create, object `#1`. This is the all-powerful server-owner account.
Technically the superuser not only has access to everything, it *bypasses* the permission checks
entirely. This makes the superuser impossible to lock out, but makes it unsuitable to actually play-
test the game's locks and restrictions with (see `@quell` below). Usually there is no need to have
but one superuser.
## Assigning permissions
Whereas permissions can be used for anything, those put in `settings.PERMISSION_HIERARCHY` will have
a ranking relative each other as well. We refer to these types of permissions as *hierarchical
permissions*. When building locks to check these permissions, the `perm()` [lock function](./Locks.md) is
used. By default Evennia creates the following hierarchy (spelled exactly like this):
1. **Developers** basically have the same access as superusers except that they do *not* sidestep
the Permission system. Assign only to really trusted server-admin staff since this level gives
access both to server reload/shutdown functionality as well as (and this may be more critical) gives
access to the all-powerful `@py` command that allows the execution of arbitrary Python code on the
command line.
1. **Admins** can do everything *except* affecting the server functions themselves. So an Admin
couldn't reload or shutdown the server for example. They also cannot execute arbitrary Python code
on the console or import files from the hard drive.
1. **Builders** - have all the build commands, but cannot affect other accounts or mess with the
server.
1. **Helpers** are almost like a normal *Player*, but they can also add help files to the database.
1. **Players** is the default group that new players end up in. A new player have permission to use
tells and to use and create new channels.
A user having a certain level of permission automatically have access to locks specifying access of
a lower level.
To assign a new permission from inside the game, you need to be able to use the `@perm` command.
This is an *Developer*-level command, but it could in principle be made lower-access since it only
allows assignments equal or lower to your current level (so you cannot use it to escalate your own
permission level). So, assuming you yourself have *Developer* access (or is superuser), you assign
a new account "Tommy" to your core staff with the command
@perm/account Tommy = Developer
or
@perm *Tommy = Developer
We use a switch or the `*name` format to make sure to put the permission on the *Account* and not on
any eventual *Character* that may also be named "Tommy". This is usually what you want since the
Account will then remain an Developer regardless of which Character they are currently controlling.
To limit permission to a per-Character level you should instead use *quelling* (see below). Normally
permissions can be any string, but for these special hierarchical permissions you can also use
plural ("Developer" and "Developers" both grant the same powers).
## Quelling your permissions
When developing it can be useful to check just how things would look had your permission-level been
lower. For this you can use *quelling*. Normally, when you puppet a Character you are using your
Account-level permission. So even if your Character only has *Accounts* level permissions, your
*Developer*-level Account will take precedence. With the `@quell` command you can change so that the
Character's permission takes precedence instead:
@quell
This will allow you to test out the game using the current Character's permission level. A developer
or builder can thus in principle maintain several test characters, all using different permission
levels. Note that you cannot escalate your permissions this way; If the Character happens to have a
*higher* permission level than the Account, the *Account's* (lower) permission will still be used.

View file

@ -1,236 +0,0 @@
# Building a mech tutorial
> This page was adapted from the article "Building a Giant Mech in Evennia" by Griatch, published in
Imaginary Realities Volume 6, issue 1, 2014. The original article is no longer available online,
this is a version adopted to be compatible with the latest Evennia.
## Creating the Mech
Let us create a functioning giant mech using the Python MUD-creation system Evennia. Everyone likes
a giant mech, right? Start in-game as a character with build privileges (or the superuser).
@create/drop Giant Mech ; mech
Boom. We created a Giant Mech Object and dropped it in the room. We also gave it an alias *mech*.
Lets describe it.
@desc mech = This is a huge mech. It has missiles and stuff.
Next we define who can “puppet” the mech object.
@lock mech = puppet:all()
This makes it so that everyone can control the mech. More mechs to the people! (Note that whereas
Evennias default commands may look vaguely MUX-like, you can change the syntax to look like
whatever interface style you prefer.)
Before we continue, lets make a brief detour. Evennia is very flexible about its objects and even
more flexible about using and adding commands to those objects. Here are some ground rules well
worth remembering for the remainder of this article:
- The [Account](./Accounts.md) represents the real person logging in and has no game-world existence.
- Any [Object](./Objects.md) can be puppeted by an Account (with proper permissions).
- [Characters](./Objects.md#characters), [Rooms](./Objects.md#rooms), and [Exits](./Objects.md#exits) are just
children of normal Objects.
- Any Object can be inside another (except if it creates a loop).
- Any Object can store custom sets of commands on it. Those commands can:
- be made available to the puppeteer (Account),
- be made available to anyone in the same location as the Object, and
- be made available to anyone “inside” the Object
- Also Accounts can store commands on themselves. Account commands are always available unless
commands on a puppeted Object explicitly override them.
In Evennia, using the `@ic` command will allow you to puppet a given Object (assuming you have
puppet-access to do so). As mentioned above, the bog-standard Character class is in fact like any
Object: it is auto-puppeted when logging in and just has a command set on it containing the normal
in-game commands, like look, inventory, get and so on.
@ic mech
You just jumped out of your Character and *are* now the mech! If people look at you in-game, they
will look at a mech. The problem at this point is that the mech Object has no commands of its own.
The usual things like look, inventory and get sat on the Character object, remember? So at the
moment the mech is not quite as cool as it could be.
@ic <Your old Character>
You just jumped back to puppeting your normal, mundane Character again. All is well.
> (But, you ask, where did that `@ic` command come from, if the mech had no commands on it? The
answer is that it came from the Account's command set. This is important. Without the Account being
the one with the `@ic` command, we would not have been able to get back out of our mech again.)
### Arming the Mech
Let us make the mech a little more interesting. In our favorite text editor, we will create some new
mech-suitable commands. In Evennia, commands are defined as Python classes.
```python
# in a new file mygame/commands/mechcommands.py
from evennia import Command
class CmdShoot(Command):
"""
Firing the mechs gun
Usage:
shoot [target]
This will fire your mechs main gun. If no
target is given, you will shoot in the air.
"""
key = "shoot"
aliases = ["fire", "fire!"]
def func(self):
"This actually does the shooting"
caller = self.caller
location = caller.location
if not self.args:
# no argument given to command - shoot in the air
message = "BOOM! The mech fires its gun in the air!"
location.msg_contents(message)
return
# we have an argument, search for target
target = caller.search(self.args.strip())
if target:
message = "BOOM! The mech fires its gun at %s" % target.key
location.msg_contents(message)
class CmdLaunch(Command):
# make your own 'launch'-command here as an exercise!
# (it's very similar to the 'shoot' command above).
```
This is saved as a normal Python module (lets call it `mechcommands.py`), in a place Evennia looks
for such modules (`mygame/commands/`). This command will trigger when the player gives the command
“shoot”, “fire,” or even “fire!” with an exclamation mark. The mech can shoot in the air or at a
target if you give one. In a real game the gun would probably be given a chance to hit and give
damage to the target, but this is enough for now.
We also make a second command for launching missiles (`CmdLaunch`). To save
space we wont describe it here; it looks the same except it returns a text
about the missiles being fired and has different `key` and `aliases`. We leave
that up to you to create as an exercise. You could have it print "WOOSH! The
mech launches missiles against <target>!", for example.
Now we shove our commands into a command set. A [Command Set](./Command-Sets.md) (CmdSet) is a container
holding any number of commands. The command set is what we will store on the mech.
```python
# in the same file mygame/commands/mechcommands.py
from evennia import CmdSet
from evennia import default_cmds
class MechCmdSet(CmdSet):
"""
This allows mechs to do do mech stuff.
"""
key = "mechcmdset"
def at_cmdset_creation(self):
"Called once, when cmdset is first created"
self.add(CmdShoot())
self.add(CmdLaunch())
```
This simply groups all the commands we want. We add our new shoot/launch commands. Lets head back
into the game. For testing we will manually attach our new CmdSet to the mech.
@py self.search("mech").cmdset.add("commands.mechcommands.MechCmdSet")
This is a little Python snippet (run from the command line as an admin) that searches for the mech
in our current location and attaches our new MechCmdSet to it. What we add is actually the Python
path to our cmdset class. Evennia will import and initialize it behind the scenes.
@ic mech
We are back as the mech! Lets do some shooting!
fire!
BOOM! The mech fires its gun in the air!
There we go, one functioning mech. Try your own `launch` command and see that it works too. We can
not only walk around as the mech — since the CharacterCmdSet is included in our MechCmdSet, the mech
can also do everything a Character could do, like look around, pick up stuff, and have an inventory.
We could now shoot the gun at a target or try the missile launch command. Once you have your own
mech, what else do you need?
> Note: You'll find that the mech's commands are available to you by just standing in the same
location (not just by puppeting it). We'll solve this with a *lock* in the next section.
## Making a Mech production line
What weve done so far is just to make a normal Object, describe it and put some commands on it.
This is great for testing. The way we added it, the MechCmdSet will even go away if we reload the
server. Now we want to make the mech an actual object “type” so we can create mechs without those
extra steps. For this we need to create a new Typeclass.
A [Typeclass](./Typeclasses.md) is a near-normal Python class that stores its existence to the database
behind the scenes. A Typeclass is created in a normal Python source file:
```python
# in the new file mygame/typeclasses/mech.py
from typeclasses.objects import Object
from commands.mechcommands import MechCmdSet
from evennia import default_cmds
class Mech(Object):
"""
This typeclass describes an armed Mech.
"""
def at_object_creation(self):
"This is called only when object is first created"
self.cmdset.add_default(default_cmds.CharacterCmdSet)
self.cmdset.add(MechCmdSet, permanent=True)
self.locks.add("puppet:all();call:false()")
self.db.desc = "This is a huge mech. It has missiles and stuff."
```
For convenience we include the full contents of the default `CharacterCmdSet` in there. This will
make a Characters normal commands available to the mech. We also add the mech-commands from before,
making sure they are stored persistently in the database. The locks specify that anyone can puppet
the meck and no-one can "call" the mech's Commands from 'outside' it - you have to puppet it to be
able to shoot.
Thats it. When Objects of this type are created, they will always start out with the mechs command
set and the correct lock. We set a default description, but you would probably change this with
`@desc` to individualize your mechs as you build them.
Back in the game, just exit the old mech (`@ic` back to your old character) then do
@create/drop The Bigger Mech ; bigmech : mech.Mech
We create a new, bigger mech with an alias bigmech. Note how we give the python-path to our
Typeclass at the end — this tells Evennia to create the new object based on that class (we don't
have to give the full path in our game dir `typeclasses.mech.Mech` because Evennia knows to look in
the `typeclasses` folder already). A shining new mech will appear in the room! Just use
@ic bigmech
to take it on a test drive.
## Future Mechs
To expand on this you could add more commands to the mech and remove others. Maybe the mech
shouldnt work just like a Character after all. Maybe it makes loud noises every time it passes from
room to room. Maybe it cannot pick up things without crushing them. Maybe it needs fuel, ammo and
repairs. Maybe youll lock it down so it can only be puppeted by emo teenagers.
Having you puppet the mech-object directly is also just one way to implement a giant mech in
Evennia.
For example, you could instead picture a mech as a “vehicle” that you “enter” as your normal
Character (since any Object can move inside another). In that case the “insides” of the mech Object
could be the “cockpit”. The cockpit would have the `MechCommandSet` stored on itself and all the
shooting goodness would be made available to you only when you enter it.
And of course you could put more guns on it. And make it fly.

View file

@ -1,249 +0,0 @@
# Choosing An SQL Server
This page gives an overview of the supported SQL databases as well as instructions on install:
- SQLite3 (default)
- PostgreSQL
- MySQL / MariaDB
Since Evennia uses [Django](http://djangoproject.com), most of our notes are based off of what we
know from the community and their documentation. While the information below may be useful, you can
always find the most up-to-date and "correct" information at Django's [Notes about supported
Databases](http://docs.djangoproject.com/en/dev/ref/databases/#ref-databases) page.
## SQLite3
[SQLite3](https://sqlite.org/) is a light weight single-file database. It is our default database
and Evennia will set this up for you automatically if you give no other options. SQLite stores the
database in a single file (`mygame/server/evennia.db3`). This means it's very easy to reset this
database - just delete (or move) that `evennia.db3` file and run `evennia migrate` again! No server
process is needed and the administrative overhead and resource consumption is tiny. It is also very
fast since it's run in-memory. For the vast majority of Evennia installs it will probably be all
that's ever needed.
SQLite will generally be much faster than MySQL/PostgreSQL but its performance comes with two
drawbacks:
* SQLite [ignores length constraints by design](https://www.sqlite.org/faq.html#q9); it is possible
to store very large strings and numbers in fields that technically should not accept them. This is
not something you will notice; your game will read and write them and function normally, but this
*can* create some data migration problems requiring careful thought if you do need to change
databases later.
* SQLite can scale well to storage of millions of objects, but if you end up with a thundering herd
of users trying to access your MUD and web site at the same time, or you find yourself writing long-
running functions to update large numbers of objects on a live game, either will yield errors and
interference. SQLite does not work reliably with multiple concurrent threads or processes accessing
its records. This has to do with file-locking clashes of the database file. So for a production
server making heavy use of process- or thread pools (or when using a third-party webserver like
Apache), a proper database is a more appropriate choice.
### Install of SQlite3
This is installed and configured as part of Evennia. The database file is created as
`mygame/server/evennia.db3` when you run
evennia migrate
without changing any database options. An optional requirement is the `sqlite3` client program -
this is required if you want to inspect the database data manually. A shortcut for using it with the
evennia database is `evennia dbshell`. Linux users should look for the `sqlite3` package for their
distro while Mac/Windows should get the [sqlite-tools package from this
page](https://sqlite.org/download.html).
To inspect the default Evennia database (once it's been created), go to your game dir and do
```bash
sqlite3 server/evennia.db3
# or
evennia dbshell
```
This will bring you into the sqlite command line. Use `.help` for instructions and `.quit` to exit.
See [here](https://gist.github.com/vincent178/10889334) for a cheat-sheet of commands.
## PostgreSQL
[PostgreSQL](https://www.postgresql.org/) is an open-source database engine, recommended by Django.
While not as fast as SQLite for normal usage, it will scale better than SQLite, especially if your
game has an very large database and/or extensive web presence through a separate server process.
### Install and initial setup of PostgreSQL
First, install the posgresql server. Version `9.6` is tested with Evennia. Packages are readily
available for all distributions. You need to also get the `psql` client (this is called `postgresql-
client` on debian-derived systems). Windows/Mac users can [find what they need on the postgresql
download page](https://www.postgresql.org/download/). You should be setting up a password for your
database-superuser (always called `postgres`) when you install.
For interaction with Evennia you need to also install `psycopg2` to your Evennia install (`pip
install psycopg2-binary` in your virtualenv). This acts as the python bridge to the database server.
Next, start the postgres client:
```bash
psql -U postgres --password
```
> :warning: **Warning:** With the `--password` argument, Postgres should prompt you for a password.
If it won't, replace that with `-p yourpassword` instead. Do not use the `-p` argument unless you
have to since the resulting command, and your password, will be logged in the shell history.
This will open a console to the postgres service using the psql client.
On the psql command line:
```sql
CREATE USER evennia WITH PASSWORD 'somepassword';
CREATE DATABASE evennia;
-- Postgres-specific optimizations
-- https://docs.djangoproject.com/en/dev/ref/databases/#optimizing-postgresql-s-configuration
ALTER ROLE evennia SET client_encoding TO 'utf8';
ALTER ROLE evennia SET default_transaction_isolation TO 'read committed';
ALTER ROLE evennia SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE evennia TO evennia;
-- Other useful commands:
-- \l (list all databases and permissions)
-- \q (exit)
```
[Here](https://gist.github.com/Kartones/dd3ff5ec5ea238d4c546) is a cheat-sheet for psql commands.
We create a database user 'evennia' and a new database named `evennia` (you can call them whatever
you want though). We then grant the 'evennia' user full privileges to the new database so it can
read/write etc to it.
If you in the future wanted to completely wipe the database, an easy way to do is to log in as the
`postgres` superuser again, then do `DROP DATABASE evennia;`, then `CREATE` and `GRANT` steps above
again to recreate the database and grant privileges.
### Evennia PostgreSQL configuration
Edit `mygame/server/conf/secret_settings.py and add the following section:
```python
#
# PostgreSQL Database Configuration
#
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'evennia',
'USER': 'evennia',
'PASSWORD': 'somepassword',
'HOST': 'localhost',
'PORT': '' # use default
}}
```
If you used some other name for the database and user, enter those instead. Run
evennia migrate
to populate your database. Should you ever want to inspect the database directly you can from now on
also use
evennia dbshell
as a shortcut to get into the postgres command line for the right database and user.
With the database setup you should now be able to start start Evennia normally with your new
database.
## MySQL / MariaDB
[MySQL](https://www.mysql.com/) is a commonly used proprietary database system, on par with
PostgreSQL. There is an open-source alternative called [MariaDB](https://mariadb.org/) that mimics
all functionality and command syntax of the former. So this section covers both.
### Installing and initial setup of MySQL/MariaDB
First, install and setup MariaDB or MySQL for your specific server. Linux users should look for the
`mysql-server` or `mariadb-server` packages for their respective distributions. Windows/Mac users
will find what they need from the [MySQL downloads](https://www.mysql.com/downloads/) or [MariaDB
downloads](https://mariadb.org/download/) pages. You also need the respective database clients
(`mysql`, `mariadb-client`), so you can setup the database itself. When you install the server you
should usually be asked to set up the database root user and password.
You will finally also need a Python interface to allow Evennia to talk to the database. Django
recommends the `mysqlclient` one. Install this into the evennia virtualenv with `pip install
mysqlclient`.
Start the database client (this is named the same for both mysql and mariadb):
```bash
mysql -u root -p
```
You should get to enter your database root password (set this up when you installed the database
server).
Inside the database client interface:
```sql
CREATE USER 'evennia'@'localhost' IDENTIFIED BY 'somepassword';
CREATE DATABASE evennia;
ALTER DATABASE `evennia` CHARACTER SET utf8; -- note that it's `evennia` with back-ticks, not
quotes!
GRANT ALL PRIVILEGES ON evennia.* TO 'evennia'@'localhost';
FLUSH PRIVILEGES;
-- use 'exit' to quit client
```
[Here](https://gist.github.com/hofmannsven/9164408) is a mysql command cheat sheet.
Above we created a new local user and database (we called both 'evennia' here, you can name them
what you prefer). We set the character set to `utf8` to avoid an issue with prefix character length
that can pop up on some installs otherwise. Next we grant the 'evennia' user all privileges on the
`evennia` database and make sure the privileges are applied. Exiting the client brings us back to
the normal terminal/console.
> Note: If you are not using MySQL for anything else you might consider granting the 'evennia' user
full privileges with `GRANT ALL PRIVILEGES ON *.* TO 'evennia'@'localhost';`. If you do, it means
you can use `evennia dbshell` later to connect to mysql, drop your database and re-create it as a
way of easy reset. Without this extra privilege you will be able to drop the database but not re-
create it without first switching to the database-root user.
## Add MySQL configuration to Evennia
To tell Evennia to use your new database you need to edit `mygame/server/conf/settings.py` (or
`secret_settings.py` if you don't want your db info passed around on git repositories).
> Note: The Django documentation suggests using an external `db.cnf` or other external conf-
formatted file. Evennia users have however found that this leads to problems (see e.g. [issue
#1184](https://git.io/vQdiN)). To avoid trouble we recommend you simply put the configuration in
your settings as below.
```python
#
# MySQL Database Configuration
#
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'evennia',
'USER': 'evennia',
'PASSWORD': 'somepassword',
'HOST': 'localhost', # or an IP Address that your DB is hosted on
'PORT': '', # use default port
}
}
```
Change this to fit your database setup. Next, run:
evennia migrate
to populate your database. Should you ever want to inspect the database directly you can from now on
also use
evennia dbshell
as a shortcut to get into the postgres command line for the right database and user.
With the database setup you should now be able to start start Evennia normally with your new
database.
## Others
No testing has been performed with Oracle, but it is also supported through Django. There are
community maintained drivers for [MS SQL](http://code.google.com/p/django-mssql/) and possibly a few
others. If you try other databases out, consider expanding this page with instructions.

View file

@ -1,389 +0,0 @@
# Coding FAQ
*This FAQ page is for users to share their solutions to coding problems. Keep it brief and link to
the docs if you can rather than too lengthy explanations. Don't forget to check if an answer already
exists before answering - maybe you can clarify that answer rather than to make a new Q&A section.*
## Table of Contents
- [Will I run out of dbrefs?](./Coding-FAQ.md#will-i-run-out-of-dbrefs)
- [Removing default commands](./Coding-FAQ.md#removing-default-commands)
- [Preventing character from moving based on a condition](./Coding-FAQ.md#preventing-character-from-
moving-based-on-a-condition)
- [Reference initiating object in an EvMenu command](./Coding-FAQ.md#reference-initiating-object-in-an-
evmenu-command)
- [Adding color to default Evennia Channels](./Coding-FAQ.md#adding-color-to-default-evennia-channels)
- [Selectively turn off commands in a room](./Coding-FAQ.md#selectively-turn-off-commands-in-a-room)
- [Select Command based on a condition](./Coding-FAQ.md#select-command-based-on-a-condition)
- [Automatically updating code when reloading](./Coding-FAQ.md#automatically-updating-code-when-
reloading)
- [Changing all exit messages](./Coding-FAQ.md#changing-all-exit-messages)
- [Add parsing with the "to" delimiter](./Coding-FAQ.md#add-parsing-with-the-to-delimiter)
- [Store last used session IP address](./Coding-FAQ.md#store-last-used-session-ip-address)
- [Use wide characters with EvTable](./Coding-FAQ.md#non-latin-characters-in-evtable)
## Will I run out of dbrefs?
**Q:** The `#dbref` of a database object is ever-increasing. Evennia doesn't allow you to change or
reuse them. Will not a big/old game run out of dbref integers eventually?
**A:** No. For example, the default sqlite3 database's max dbref is `2**64`. If you created `10 000`
objects every second every minute and every day of the year it would take ~60 million years for you
to run out of dbref numbers. That's a database of 140 TeraBytes, if every row was empty. If you are
still using Evennia at that point and has this concern, get back to us and we can discuss adding
dbref reuse then.
## Removing default commands
**Q:** How does one *remove* (not replace) e.g. the default `get` [Command](./Commands.md) from the
Character [Command Set](./Command-Sets.md)?
**A:** Go to `mygame/commands/default_cmdsets.py`. Find the `CharacterCmdSet` class. It has one
method named `at_cmdset_creation`. At the end of that method, add the following line:
`self.remove(default_cmds.CmdGet())`. See the [Adding Commands Tutorial](./Adding-Command-Tutorial.md)
for more info.
## Preventing character from moving based on a condition
**Q:** How does one keep a character from using any exit, if they meet a certain condition? (I.E. in
combat, immobilized, etc.)
**A:** The `at_before_move` hook is called by Evennia just before performing any move. If it returns
`False`, the move is aborted. Let's say we want to check for an [Attribute](./Attributes.md) `cantmove`.
Add the following code to the `Character` class:
```python
def at_before_move(self, destination):
"Called just before trying to move"
if self.db.cantmove: # replace with condition you want to test
self.msg("Something is preventing you from moving!")
return False
return True
```
## Reference initiating object in an EvMenu command.
**Q:** An object has a Command on it starts up an EvMenu instance. How do I capture a reference to
that object for use in the menu?
**A:** When an [EvMenu](./EvMenu.md) is started, the menu object is stored as `caller.ndb._menutree`.
This is a good place to store menu-specific things since it will clean itself up when the menu
closes. When initiating the menu, any additional keywords you give will be available for you as
properties on this menu object:
```python
class MyObjectCommand(Command):
# A Command stored on an object (the object is always accessible from
# the Command as self.obj)
def func(self):
# add the object as the stored_obj menu property
EvMenu(caller, ..., stored_obj=self.obj)
```
Inside the menu you can now access the object through `caller.ndb._menutree.stored_obj`.
## Adding color to default Evennia Channels
**Q:** How do I add colors to the names of Evennia channels?
**A:** The Channel typeclass' `channel_prefix` method decides what is shown at the beginning of a
channel send. Edit `mygame/typeclasses/channels.py` (and then `@reload`):
```python
# define our custom color names
CHANNEL_COLORS = {'public': '|015Public|n',
'newbie': '|550N|n|551e|n|552w|n|553b|n|554i|n|555e|n',
'staff': '|010S|n|020t|n|030a|n|040f|n|050f|n'}
# Add to the Channel class
# ...
def channel_prefix(self, msg, emit=False):
prefix_string = ""
if self.key in COLORS:
prefix_string = "[%s] " % CHANNEL_COLORS.get(self.key.lower())
else:
prefix_string = "[%s] " % self.key.capitalize()
return prefix_string
```
Additional hint: To make colors easier to change from one place you could instead put the
`CHANNEL_COLORS` dict in your settings file and import it as `from django.conf.settings import
CHANNEL_COLORS`.
## Selectively turn off commands in a room
**Q:** I want certain commands to turn off in a given room. They should still work normally for
staff.
**A:** This is done using a custom cmdset on a room [locked with the 'call' lock type](./Locks.md). Only
if this lock is passed will the commands on the room be made available to an object inside it. Here
is an example of a room where certain commands are disabled for non-staff:
```python
# in mygame/typeclasses/rooms.py
from evennia import default_commands, CmdSet
class CmdBlocking(default_commands.MuxCommand):
# block commands give, get, inventory and drop
key = "give"
aliases = ["get", "inventory", "drop"]
def func(self):
self.caller.msg("You cannot do that in this room.")
class BlockingCmdSet(CmdSet):
key = "blocking_cmdset"
# default commands have prio 0
priority = 1
def at_cmdset_creation(self):
self.add(CmdBlocking())
class BlockingRoom(Room):
def at_object_creation(self):
self.cmdset.add(BlockingCmdSet, permanent=True)
# only share commands with players in the room that
# are NOT Builders or higher
self.locks.add("call:not perm(Builders)")
```
After `@reload`, make some `BlockingRooms` (or switch a room to it with `@typeclass`). Entering one
will now replace the given commands for anyone that does not have the `Builders` or higher
permission. Note that the 'call' lock is special in that even the superuser will be affected by it
(otherwise superusers would always see other player's cmdsets and a game would be unplayable for
superusers).
## Select Command based on a condition
**Q:** I want a command to be available only based on a condition. For example I want the "werewolf"
command to only be available on a full moon, from midnight to three in-game time.
**A:** This is easiest accomplished by putting the "werewolf" command on the Character as normal,
but to [lock](./Locks.md) it with the "cmd" type lock. Only if the "cmd" lock type is passed will the
command be available.
```python
# in mygame/commands/command.py
from evennia import Command
class CmdWerewolf(Command):
key = "werewolf"
# lock full moon, between 00:00 (midnight) and 03:00.
locks = "cmd:is_full_moon(0, 3)"
def func(self):
# ...
```
Add this to the [default cmdset as usual](./Adding-Command-Tutorial.md). The `is_full_moon` [lock
function](./Locks.md#lock-functions) does not yet exist. We must create that:
```python
# in mygame/server/conf/lockfuncs.py
def is_full_moon(accessing_obj, accessed_obj,
starthour, endhour, *args, **kwargs):
# calculate if the moon is full here and
# if current game time is between starthour and endhour
# return True or False
```
After a `@reload`, the `werewolf` command will be available only at the right time, that is when the
`is_full_moon` lock function returns True.
## Automatically updating code when reloading
**Q:** I have a development server running Evennia. Can I have the server update its code-base when
I reload?
**A:** Having a development server that pulls updated code whenever you reload it can be really
useful if you have limited shell access to your server, or want to have it done automatically. If
you have your project in a configured Git environment, it's a matter of automatically calling `git
pull` when you reload. And that's pretty straightforward:
In `/server/conf/at_server_startstop.py`:
```python
import subprocess
# ... other hooks ...
def at_server_reload_stop():
"""
This is called only time the server stops before a reload.
"""
print("Pulling from the game repository...")
process = subprocess.call(["git", "pull"], shell=False)
```
That's all. We call `subprocess` to execute a shell command (that code works on Windows and Linux,
assuming the current directory is your game directory, which is probably the case when you run
Evennia). `call` waits for the process to complete, because otherwise, Evennia would reload on
partially-modified code, which would be problematic.
Now, when you enter `@reload` on your development server, the game repository is updated from the
configured remote repository (Github, for instance). Your development cycle could resemble
something like:
1. Coding on the local machine.
2. Testing modifications.
3. Committing once, twice or more (being sure the code is still working, unittests are pretty useful
here).
4. When the time comes, login to the development server and run `@reload`.
The reloading might take one or two additional seconds, since Evennia will pull from your remote Git
repository. But it will reload on it and you will have your modifications ready, without needing
connecting to your server using SSH or something similar.
## Changing all exit messages
**Q:** How can I change the default exit messages to something like "XXX leaves east" or "XXX
arrives from the west"?
**A:** the default exit messages are stored in two hooks, namely `announce_move_from` and
`announce_move_to`, on the `Character` typeclass (if what you want to change is the message other
characters will see when a character exits).
These two hooks provide some useful features to easily update the message to be displayed. They
take both the default message and mapping as argument. You can easily call the parent hook with
these information:
* The message represents the string of characters sent to characters in the room when a character
leaves.
* The mapping is a dictionary containing additional mappings (you will probably not need it for
simple customization).
It is advisable to look in the [code of both
hooks](https://github.com/evennia/evennia/tree/master/evennia/objects/objects.py), and read the
hooks' documentation. The explanations on how to quickly update the message are shown below:
```python
# In typeclasses/characters.py
"""
Characters
"""
from evennia import DefaultCharacter
class Character(DefaultCharacter):
"""
The default character class.
...
"""
def announce_move_from(self, destination, msg=None, mapping=None):
"""
Called if the move is to be announced. This is
called while we are still standing in the old
location.
Args:
destination (Object): The place we are going to.
msg (str, optional): a replacement message.
mapping (dict, optional): additional mapping objects.
You can override this method and call its parent with a
message to simply change the default message. In the string,
you can use the following as mappings (between braces):
object: the object which is moving.
exit: the exit from which the object is moving (if found).
origin: the location of the object before the move.
destination: the location of the object after moving.
"""
super().announce_move_from(destination, msg="{object} leaves {exit}.")
def announce_move_to(self, source_location, msg=None, mapping=None):
"""
Called after the move if the move was not quiet. At this point
we are standing in the new location.
Args:
source_location (Object): The place we came from
msg (str, optional): the replacement message if location.
mapping (dict, optional): additional mapping objects.
You can override this method and call its parent with a
message to simply change the default message. In the string,
you can use the following as mappings (between braces):
object: the object which is moving.
exit: the exit from which the object is moving (if found).
origin: the location of the object before the move.
destination: the location of the object after moving.
"""
super().announce_move_to(source_location, msg="{object} arrives from the {exit}.")
```
We override both hooks, but call the parent hook to display a different message. If you read the
provided docstrings, you will better understand why and how we use mappings (information between
braces). You can provide additional mappings as well, if you want to set a verb to move, for
instance, or other, extra information.
## Add parsing with the "to" delimiter
**Q:** How do I change commands to undestand say `give obj to target` as well as the default `give
obj = target`?
**A:** You can make change the default `MuxCommand` parent with your own class making a small change
in its `parse` method:
```python
# in mygame/commands/command.py
from evennia import default_cmds
class MuxCommand(default_cmds.MuxCommand):
def parse(self):
"""Implement an additional parsing of 'to'"""
super().parse()
if " to " in self.args:
self.lhs, self.rhs = self.args.split(" to ", 1)
```
Next you change the parent of the default commands in settings:
```python
COMMAND_DEFAULT_CLASS = "commands.command.MuxCommand"
```
Do a `@reload` and all default commands will now use your new tweaked parent class. A copy of the
MuxCommand class is also found commented-out in the `mygame/commands/command.py` file.
## Store last used session IP address
**Q:** If a user has already logged out of an Evennia account, their IP is no longer visible to
staff that wants to ban-by-ip (instead of the user) with `@ban/ip`?
**A:** One approach is to write the IP from the last session onto the "account" account object.
`typeclasses/accounts.py`
```python
def at_post_login(self, session=None, **kwargs):
super().at_post_login(session=session, **kwargs)
self.db.lastsite = self.sessions.all()[-1].address
```
Adding timestamp for login time and appending to a list to keep the last N login IP addresses and
timestamps is possible, also. Additionally, if you don't want the list to grow beyond a
`do_not_exceed` length, conditionally pop a value after you've added it, if the length has grown too
long.
**NOTE:** You'll need to add `import time` to generate the login timestamp.
```python
def at_post_login(self, session=None, **kwargs):
super().at_post_login(session=session, **kwargs)
do_not_exceed = 24 # Keep the last two dozen entries
session = self.sessions.all()[-1] # Most recent session
if not self.db.lastsite:
self.db.lastsite = []
self.db.lastsite.insert(0, (session.address, int(time.time())))
if len(self.db.lastsite) > do_not_exceed:
self.db.lastsite.pop()
```
This only stores the data. You may want to interface the `@ban` command or make a menu-driven viewer
for staff to browse the list and display how long ago the login occurred.
## Non-latin characters in EvTable
**Q:** When using e.g. Chinese characters in EvTable, some lines appear to be too wide, for example
```
+------+------+
| | |
| 测试 | 测试 |
| | |
+~~~~~~+~~~~~~+
```
**A:** The reason for this is because certain non-latin characters are *visually* much wider than
their len() suggests. There is little Evennia can (reliably) do about this. If you are using such
characters, you need to make sure to use a suitable mono-spaced font where are width are equal. You
can set this in your web client and need to recommend it for telnet-client users. See [this
discussion](https://github.com/evennia/evennia/issues/1522) where some suitable fonts are suggested.

View file

@ -1,99 +0,0 @@
# Coding Introduction
Evennia allows for a lot of freedom when designing your game - but to code efficiently you still
need to adopt some best practices as well as find a good place to start to learn.
Here are some pointers to get you going.
### Python
Evennia is developed using Python. Even if you are more of a designer than a coder, it is wise to
learn how to read and understand basic Python code. If you are new to Python, or need a refresher,
take a look at our two-part [Python introduction](./Python-basic-introduction.md).
### Explore Evennia interactively
When new to Evennia it can be hard to find things or figure out what is available. Evennia offers a
special interactive python shell that allows you to experiment and try out things. It's recommended
to use [ipython](http://ipython.org/) for this since the vanilla python prompt is very limited. Here
are some simple commands to get started:
# [open a new console/terminal]
# [activate your evennia virtualenv in this console/terminal]
pip install ipython # [only needed the first time]
cd mygame
evennia shell
This will open an Evennia-aware python shell (using ipython). From within this shell, try
import evennia
evennia.<TAB>
That is, enter `evennia.` and press the `<TAB>` key. This will show you all the resources made
available at the top level of Evennia's "flat API". See the [flat API](./Evennia-API.md) page for more
info on how to explore it efficiently.
You can complement your exploration by peeking at the sections of the much more detailed
[Developer Central](./Developer-Central.md). The [Tutorials](./Tutorials.md) section also contains a growing collection
of system- or implementation-specific help.
### Use a python syntax checker
Evennia works by importing your own modules and running them as part of the server. Whereas Evennia
should just gracefully tell you what errors it finds, it can nevertheless be a good idea for you to
check your code for simple syntax errors *before* you load it into the running server. There are
many python syntax checkers out there. A fast and easy one is
[pyflakes](https://pypi.python.org/pypi/pyflakes), a more verbose one is
[pylint](http://www.pylint.org/). You can also check so that your code looks up to snuff using
[pep8](https://pypi.python.org/pypi/pep8). Even with a syntax checker you will not be able to catch
every possible problem - some bugs or problems will only appear when you actually run the code. But
using such a checker can be a good start to weed out the simple problems.
### Plan before you code
Before you start coding away at your dream game, take a look at our [Game Planning](./Game-Planning.md)
page. It might hopefully help you avoid some common pitfalls and time sinks.
### Code in your game folder, not in the evennia/ repository
As part of the Evennia setup you will create a game folder to host your game code. This is your
home. You should *never* need to modify anything in the `evennia` library (anything you download
from us, really). You import useful functionality from here and if you see code you like, copy&paste
it out into your game folder and edit it there.
If you find that Evennia doesn't support some functionality you need, make a
[Feature Request](github:issue) about it. Same goes for [bugs](github:issue). If you add features or fix bugs
yourself, please consider [Contributing](./Contributing.md) your changes upstream!
### Learn to read tracebacks
Python is very good at reporting when and where things go wrong. A *traceback* shows everything you
need to know about crashing code. The text can be pretty long, but you usually are only interested
in the last bit, where it says what the error is and at which module and line number it happened -
armed with this info you can resolve most problems.
Evennia will usually not show the full traceback in-game though. Instead the server outputs errors
to the terminal/console from which you started Evennia in the first place. If you want more to show
in-game you can add `IN_GAME_ERRORS = True` to your settings file. This will echo most (but not all)
tracebacks both in-game as well as to the terminal/console. This is a potential security problem
though, so don't keep this active when your game goes into production.
> A common confusing error is finding that objects in-game are suddenly of the type `DefaultObject`
rather than your custom typeclass. This happens when you introduce a critical Syntax error to the
module holding your custom class. Since such a module is not valid Python, Evennia can't load it at
all. Instead of crashing, Evennia will then print the full traceback to the terminal/console and
temporarily fall back to the safe `DefaultObject` until you fix the problem and reload.
### Docs are here to help you
Some people find reading documentation extremely dull and shun it out of principle. That's your
call, but reading docs really *does* help you, promise! Evennia's documentation is pretty thorough
and knowing what is possible can often give you a lot of new cool game ideas. That said, if you
can't find the answer in the docs, don't be shy to ask questions! The
[discussion group](https://sites.google.com/site/evenniaserver/discussions) and the
[irc chat](http://webchat.freenode.net/?channels=evennia) are also there for you.
### The most important point
And finally, of course, have fun!

View file

@ -0,0 +1,780 @@
# Changelog
### Evennia 1.0
2019-2022
_Changed to using `main` branch to follow github standard. Old `master` branch remains
for now but will not be used anymore, so as to not break installs during transition._
Increase requirements: Django 4.1+, Twisted 22.10+ Python 3.10, 3.11. PostgreSQL 11+.
- New `drop:holds()` lock default to limit dropping nonsensical things. Access check
defaults to True for backwards-compatibility in 0.9, will be False in 1.0
- REST API allows you external access to db objects through HTTP requests (Tehom)
- `Object.normalize_name` and `.validate_name` added to (by default) enforce latinify
on character name and avoid potential exploits using clever Unicode chars (trhr)
- New `utils.format_grid` for easily displaying long lists of items in a block.
- Using `lunr` search indexing for better `help` matching and suggestions. Also improve
the main help command's default listing output.
- Added `content_types` indexing to DefaultObject's ContentsHandler. (volund)
- Made most of the networking classes such as Protocols and the SessionHandlers
replaceable via `settings.py` for modding enthusiasts. (volund)
- The `initial_setup.py` file can now be substituted in `settings.py` to customize
initial game database state. (volund)
- Added new Traits contrib, converted and expanded from Ainneve project.
- Added new `requirements_extra.txt` file for easily getting all optional dependencies.
- Change default multi-match syntax from 1-obj, 2-obj to obj-1, obj-2.
- Make `object.search` support 'stacks=0' keyword - if ``>0``, the method will return
N identical matches instead of triggering a multi-match error.
- Add `tags.has()` method for checking if an object has a tag or tags (PR by ChrisLR)
- Make IP throttle use Django-based cache system for optional persistence (PR by strikaco)
- Renamed Tutorial classes "Weapon" and "WeaponRack" to "TutorialWeapon" and
"TutorialWeaponRack" to prevent collisions with classes in mygame
- New `crafting` contrib, adding a full crafting subsystem (Griatch 2020)
- The `rplanguage` contrib now auto-capitalizes sentences and retains ellipsis (...). This
change means that proper nouns at the start of sentences will not be treated as nouns.
- Make MuxCommand `lhs/rhslist` always be lists, also if empty (used to be the empty string)
- Fix typo in UnixCommand contrib, where `help` was given as `--hel`.
- Latin (la) i18n translation (jamalainm)
- Made the `evennia` dir possible to use without gamedir for purpose of doc generation.
- Make Scripts' timer component independent from script object deletion; can now start/stop
timer without deleting Script. The `.persistent` flag now only controls if timer survives
reload - Script has to be removed with `.delete()` like other typeclassed entities.
- Add `utils.repeat` and `utils.unrepeat` as shortcuts to TickerHandler add/remove, similar
to how `utils.delay` is a shortcut for TaskHandler add.
- Refactor the classic `red_button` example to use `utils.delay/repeat` and modern recommended
code style and paradigms instead of relying on `Scripts` for everything.
- Expand `CommandTest` with ability to check multiple message-receivers; inspired by PR by
user davewiththenicehat. Also add new doc string.
- Add central `FuncParser` as a much more powerful replacement for the old `parse_inlinefunc`
function.
- Attribute/NAttribute got a homogenous representation, using intefaces, both
`AttributeHandler` and `NAttributeHandler` has same api now.
- Add `evennia/utils/verb_conjugation` for automatic verb conjugation (English only). This
is useful for implementing actor-stance emoting for sending a string to different targets.
- New version of Italian translation (rpolve)
- `utils.evmenu.ask_yes_no` is a helper function that makes it easy to ask a yes/no question
to the user and respond to their input. This complements the existing `get_input` helper.
- Allow sending messages with `page/tell` without a `=` if target name contains no spaces.
- New FileHelpStorage system allows adding help entries via external files.
- `sethelp` command now warns if shadowing other help-types when creating a new
entry.
- Help command now uses `view` lock to determine if cmd/entry shows in index and
`read` lock to determine if it can be read. It used to be `view` in the role
of the latter. Migration swaps these around.
- In modules given by `settings.PROTOTYPE_MODULES`, spawner will now first look for a global
list `PROTOTYPE_LIST` of dicts before loading all dicts in the module as prototypes.
- New Channel-System using the `channel` command and nicks. Removed the `ChannelHandler` and the
concept of a dynamically created `ChannelCmdSet`.
- Add `Msg.db_receiver_external` field to allowe external, string-id message-receivers.
- Renamed `app.css` to `website.css` for consistency. Removed old prosimii-css files.
- Remove `mygame/web/static_overrides` and -`template_overrides`, reorganize website/admin/client/api
into a more consistent structure for overriding. Expanded webpage documentation considerably.
- REST API list-view was shortened (#2401). New CSS/HTML. Add ReDoc for API autodoc page.
- Update and fix dummyrunner with cleaner code and setup.
- Made `iter_to_str` format prettier strings, using Oxford comma.
- Added an MXP anchor tag to also support clickable web links.
- New `tasks` command for managing tasks started with `utils.delay` (PR by davewiththenicehat)
- Make `help` index output clickable for webclient/clients with MXP (PR by davewiththenicehat)
- Custom `evennia` launcher commands (e.g. `evennia mycmd foo bar`). Add new commands as callables
accepting `*args`, as `settings.EXTRA_LAUNCHER_COMMANDS = {'mycmd': 'path.to.callable', ...}`.
- New `XYZGrid` contrib, adding x,y,z grid coordinates with in-game map and
pathfinding. Controlled outside of the game via custom evennia launcher command.
- `Script.delete` has new kwarg `stop_task=True`, that can be used to avoid
infinite recursion when wanting to set up Script to delete-on-stop.
- Command executions now done on copies to make sure `yield` don't cause crossovers. Add
`Command.retain_instance` flag for reusing the same command instance.
- The `typeclass` command will now correctly search the correct database-table for the target
obj (avoids mistakenly assigning an AccountDB-typeclass to a Character etc).
- Merged `script` and `scripts` commands into one, for both managing global- and
on-object Scripts. Moved `CmdScripts` and `CmdObjects` to `commands/default/building.py`.
- Keep GMCP function case if outputfunc starts with capital letter (so `cmd_name` -> `Cmd.Name`
but `Cmd_nAmE` -> `Cmd.nAmE`). This helps e.g Mudlet's legacy `Client_GUI` implementation)
- Prototypes now allow setting `prototype_parent` directly to a prototype-dict.
This makes it easier when dynamically building in-module prototypes.
- `RPSystem contrib` was expanded to support case, so /tall becomes 'tall man'
while /Tall becomes 'Tall man'. One can turn this off if wanting the old style.
- Change `EvTable` fixed-height rebalance algorithm to fill with empty lines at end of
column instead of inserting rows based on cell-size (could be mistaken for a bug).
- Split `return_appearance` hook with helper methods and have it use a template
string in order to make it easier to override.
- Add validation question to default account creation.
- Add `LOCALECHO` client option to add server-side echo for clients that does
not support this (useful for getting a complete log).
- Make `@lazy_property` decorator create read/delete-protected properties. This is
because it's used for handlers, and e.g. self.locks=[] is a common beginner mistake.
- Add `$pron()` inlinefunc for pronoun parsing in actor-stance strings using
`msg_contents`.
- Update defauklt website to show Telnet/SSL/SSH connect info. Added new
`SERVER_HOSTNAME` setting for use in the server:port stanza.
- Changed all `at_before/after_*` hooks to `at_pre/post_*` for consistency
across Evennia (the old names still work but are deprecated)
- Change `settings.COMMAND_DEFAULT_ARG_REGEX` default from `None` to a regex meaning that
a space or `/` must separate the cmdname and args. This better fits common expectations.
- Add confirmation question to `ban`/`unban` commands.
- Check new `teleport` and `teleport_here` lock-types in `teleport` command to optionally
allow to limit teleportation of an object or to a specific destination.
- Add `settings.MXP_ENABLED=True` and `settings.MXP_OUTGOING_ONLY=True` as sane defaults,
to avoid known security issues with players entering MXP links.
- Add browser name to webclient `CLIENT_NAME` in `session.protocol_flags`, e.g.
`"Evennia webclient (websocket:firefox)"` or `"evennia webclient (ajax:chrome)"`.
- `TagHandler.add/has(tag=...)` kwarg changed to `add/has(key=...)` for consistency
with other handlers.
- Make `DefaultScript.delete`, `DefaultChannel.delete` and `DefaultAccount.delete` return
bool True/False if deletion was successful (like `DefaultObject.delete` before them)
- `contrib.custom_gametime` days/weeks/months now always starts from 1 (to match
the standard calendar form ... there is no month 0 every year after all).
- `AttributeProperty`/`NAttributeProperty` to allow managing Attributes/NAttributes
on typeclasses in the same way as Django fields.
- Give build/system commands a `@name` to fall back to if the non-@ name is used
by another command (like `open` and `@open`. If no duplicate, @ is optional.
- Move legacy channel-management commands (`ccreate`, `addcom` etc) to a contrib
since their work is now fully handled by the single `channel` command.
- Expand `examine` command's code to much more extensible and modular. Show
attribute categories and value types (when not strings).
- `AttributeHandler.remove(key, return_exception=False, category=None, ...)` changed
to `.remove(key, category=None, return_exception=False, ...)` for consistency.
- New `command cooldown` contrib for making it easier to manage commands using
dynamic cooldowns between uses (owllex)
- Restructured `contrib/` folder, placing all contribs as separate packages under
subfolders. All imports will need to be updated.
- Made `MonitorHandler.add/remove` support `category` for monitoring Attributes
with a category (before only key was used, ignoring category entirely).
- Move `create_*` functions into db managers, leaving `utils.create` only being
wrapper functions (consistent with `utils.search`). No change of api otherwise.
- Add support for `$dbref()` and `$search` when assigning an Attribute value
with the `set` command. This allows assigning real objects from in-game.
- Add ability to examine `/script` and `/channel` entities with `examine` command.
- Homogenize manager search methods to return querysets and not lists.
- Restructure unit tests to always honor default settings; make new parents in
on location for easy use in game dir.
- The `Lunr` search engine used by help excludes common words; the settings-list
`LUNR_STOP_WORD_FILTER_EXCEPTIONS` can be extended to make sure common names are included.
- Add `.deserialize()` method to `_Saver*` structures to help completely
decouple structures from database without needing separate import.
- Add `run_in_main_thread` as a helper for those wanting to code server code
from a web view.
- Update `evennia.utils.logger` to use Twisted's new logging API. No change in Evennia API
except more standard aliases logger.error/info/exception/debug etc can now be used.
- Have `type/force` default to `update`-mode rather than `reset`mode and add more verbose
warning when using reset mode.
- Attribute storage support defaultdics (Hendher)
- Add ObjectParent mixin to default game folder template as an easy, ready-made
way to override features on all ObjectDB-inheriting objects easily.
source location, mimicking behavior of `at_pre_move` hook - returning False will abort move.
- Add `TagProperty`, `AliasProperty` and `PermissionProperty` to assign these
data in a similar way to django fields.
- New `at_pre_object_receive(obj, source_location)` method on Objects. Called on
destination, mimicking behavior of `at_pre_move` hook - returning False will abort move.
- New `at_pre_object_leave(obj, destination)` method on Objects. Called on
- The db pickle-serializer now checks for methods `__serialize_dbobjs__` and `__deserialize_dbobjs__`
to allow custom packing/unpacking of nested dbobjs, to allow storing in Attribute.
- Optimizations to rpsystem contrib performance. Breaking change: `.get_sdesc()` will
now return `None` instead of `.db.desc` if no sdesc is set; fallback in hook (inspectorCaracal)
- Reworked text2html parser to avoid problems with stateful color tags (inspectorCaracal)
- Simplified `EvMenu.options_formatter` hook to use `EvColumn` and f-strings (inspectorcaracal)
- Allow `# CODE`, `# HEADER` etc as well as `#CODE`/`#HEADER` in batchcode
files - this works better with black linting.
- Added `move_type` str kwarg to `move_to()` calls, optionally identifying the type of
move being done ('teleport', 'disembark', 'give' etc). (volund)
- Made RPSystem contrib msg calls pass `pose` or `say` as msg-`type` for use in
e.g. webclient pane filtering where desired. (volund)
- Added `Account.uses_screenreader(session=None)` as a quick shortcut for
finding if a user uses a screenreader (and adjust display accordingly).
- Fixed bug in `cmdset.remove()` where a command could not be deleted by `key`,
even though doc suggested one could (ChrisLR)
- New contrib `name_generator` for building random real-world based or fantasy-names
based on phonetic rules.
- Enable proper serialization of dict subclasses in Attributes (aogier)
- `object.search` fuzzy-matching now uses `icontains` instead of `istartswith`
to better match how search works elsewhere (volund)
- The `.at_traverse` hook now receives a `exit_obj` kwarg, linking back to the
exit triggering the hook (volund)
- Contrib `buffs` for managing temporary and permanent RPG status buffs effects (tegiminis)
- New `at_server_init()` hook called before all other startup hooks for all
startup modes. Used for more generic overriding (volund)
- New `search` lock type used to completely hide an object from being found by
the `DefaultObject.search` (`caller.search`) method. (CloudKeeper)
- Change setting `MULTISESSION_MODE` to now only control sessions, not how many
characters can be puppeted simultaneously. New settings now control that.
- Add new setting `AUTO_CREATE_CHARACTER_WITH_ACCOUNT`, a boolean deciding if
the new account should also get a matching character (legacy MUD style).
- Add new setting `AUTO_PUPPET_ON_LOGIN`, boolean deciding if one should
automatically puppet the last/available character on connection (legacy MUD style)
- Add new setting `MAX_NR_SIMULTANEUS_PUPPETS` - how many puppets the account
can run at the same time. Used to limit multi-playing.
- Make setting `MAX_NR_CHARACTERS` interact better with the new settings above.
- Allow `$search` funcparser func to search tags and to accept kwargs for more
powerful searches passed into the regular search functions.
- `spawner.spawn` and linked methods now has a kwarg `protfunc_raise_errors`
(default True) to disable strict errors on malformed/not-found protfuncs
- Improve search performance when having many DB-based prototypes via caching.
- Remove the `return_parents` kwarg of `evennia.prototypes.spawner.spawn` since it
was inefficient and unused.
- Made all id fields BigAutoField for all databases. (owllex)
- `EvForm` refactored. New `literals` mapping, for literal mappings into the
main template (e.g. for single-character replacements).
- `EvForm` `cells` kwarg now accepts `EvCells` with custom formatting options
(mainly for custom align/valign). `EvCells` now makes use of `utils.justify`.
- `utils.justify` now supports `align="a"` (absolute alignments. This keeps
the given left indent but crops/fills to the width. Used in EvCells.
- `EvTable` now supports passing `EvColumn`s as a list directly, (`EvTable(table=[colA,colB])`)
- Add `tags=` search criterion to `DefaultObject.search`.
- Add `AT_EXIT_TRAVERSE` signal, firing when an exit is traversed.
- Add integration between Evennia and Discord channels (PR by Inspector Cararacal)
- Support for using a Godot-powered client with Evennia (PR by ChrisLR)
- Added German translation (patch by Zhuraj)
## Evennia 0.9.5
> 2019-2020
> Released 2020-11-14.
> Transitional release, including new doc system.
Backported from develop: Python 3.8, 3.9 support. Django 3.2+ support, Twisted 21+ support.
- `is_typeclass(obj (Object), exact (bool))` now defaults to exact=False
- `py` command now reroutes stdout to output results in-game client. `py`
without arguments starts a full interactive Python console.
- Webclient default to a single input pane instead of two. Now defaults to no help-popup.
- Webclient fix of prompt display
- Webclient multimedia support for relaying images, video and sounds via
`.msg(image=URL)`, `.msg(video=URL)`
and `.msg(audio=URL)`
- Add Spanish translation (fermuch)
- Expand `GLOBAL_SCRIPTS` container to always start scripts and to include all
global scripts regardless of how they were created.
- Change settings to always use lists instead of tuples, to make mutable
settings easier to add to. (#1912)
- Make new `CHANNEL_MUDINFO` setting for specifying the mudinfo channel
- Make `CHANNEL_CONNECTINFO` take full channel definition
- Make `DEFAULT_CHANNELS` list auto-create channels missing at reload
- Webclient `ANSI->HTML` parser updated. Webclient line width changed from 1.6em to 1.1em
to better make ANSI graphics look the same as for third-party clients
- `AttributeHandler.get(return_list=True)` will return `[]` if there are no
Attributes instead of `[None]`.
- Remove `pillow` requirement (install especially if using imagefield)
- Add Simplified Korean translation (aceamro)
- Show warning on `start -l` if settings contains values unsafe for production.
- Make code auto-formatted with Black.
- Make default `set` command able to edit nested structures (PR by Aaron McMillan)
- Allow running Evennia test suite from core repo with `make test`.
- Return `store_key` from `TickerHandler.add` and add `store_key` as a kwarg to
the `TickerHandler.remove` method. This makes it easier to manage tickers.
- EvMore auto-justify now defaults to False since this works better with all types
of texts (such as tables). New `justify` bool. Old `justify_kwargs` remains
but is now only used to pass extra kwargs into the justify function.
- EvMore `text` argument can now also be a list or a queryset. Querysets will be
sliced to only return the required data per page.
- Improve performance of `find` and `objects` commands on large data sets (strikaco)
- New `CHANNEL_HANDLER_CLASS` setting allows for replacing the ChannelHandler entirely.
- Made `py` interactive mode support regular quit() and more verbose.
- Made `Account.options.get` accept `default=None` kwarg to mimic other uses of get. Set
the new `raise_exception` boolean if ranting to raise KeyError on a missing key.
- Moved behavior of unmodified `Command` and `MuxCommand` `.func()` to new
`.get_command_info()` method for easier overloading and access. (Volund)
- Removed unused `CYCLE_LOGFILES` setting. Added `SERVER_LOG_DAY_ROTATION`
and `SERVER_LOG_MAX_SIZE` (and equivalent for PORTAL) to control log rotation.
- Addded `inside_rec` lockfunc - if room is locked, the normal `inside()` lockfunc will
fail e.g. for your inventory objs (since their loc is you), whereas this will pass.
- RPSystem contrib's CmdRecog will now list all recogs if no arg is given. Also multiple
bugfixes.
- Remove `dummy@example.com` as a default account email when unset, a string is no longer
required by Django.
- Fixes to `spawn`, make updating an existing prototype/object work better. Add `/raw` switch
to `spawn` command to extract the raw prototype dict for manual editing.
- `list_to_string` is now `iter_to_string` (but old name still works as legacy alias). It will
now accept any input, including generators and single values.
- EvTable should now correctly handle columns with wider asian-characters in them.
- Update Twisted requirement to >=2.3.0 to close security vulnerability
- Add `$random` inlinefunc, supports minval,maxval arguments that can be ints and floats.
- Add `evennia.utils.inlinefuncs.raw(<str>)` as a helper to escape inlinefuncs in a string.
- Make CmdGet/Drop/Give give proper error if `obj.move_to` returns `False`.
- Make `Object/Room/Exit.create`'s `account` argument optional. If not given, will set perms
to that of the object itself (along with normal Admin/Dev permission).
- Make `INLINEFUNC_STACK_MAXSIZE` default visible in `settings_default.py`.
- Change how `ic` finds puppets; non-priveleged users will use `_playable_characters` list as
candidates, Builders+ will use list, local search and only global search if no match found.
- Make `cmd.at_post_cmd()` always run after `cmd.func()`, even when the latter uses delays
with yield.
- `EvMore` support for db queries and django paginators as well as easier to override for custom
pagination (e.g. to create EvTables for every page instead of splittine one table)
- Using `EvMore pagination`, dramatically improves performance of `spawn/list` and `scripts` listings
(100x speed increase for displaying 1000+ prototypes/scripts).
- `EvMenu` now uses the more logically named `.ndb._evmenu` instead of `.ndb._menutree` to store itself.
Both still work for backward compatibility, but `_menutree` is deprecated.
- `EvMenu.msg(txt)` added as a central place to send text to the user, makes it easier to override.
Default `EvMenu.msg` sends with OOB type="menu" for use with OOB and webclient pane-redirects.
- New EvMenu templating system for quickly building simpler EvMenus without as much code.
- Add `Command.client_height()` method to match existing `.client_width` (stricako)
- Include more Web-client info in `session.protocol_flags`.
- Fixes in multi-match situations - don't allow finding/listing multimatches for 3-box when
only two boxes in location.
- Fix for TaskHandler with proper deferred returns/ability to cancel etc (PR by davewiththenicehat)
- Add `PermissionHandler.check` method for straight string perm-checks without needing lockstrings.
- Add `evennia.utils.utils.strip_unsafe_input` for removing html/newlines/tags from user input. The
`INPUT_CLEANUP_BYPASS_PERMISSIONS` is a list of perms that bypass this safety stripping.
- Make default `set` and `examine` commands aware of Attribute categories.
## Evennia 0.9
> 2018-2019
> Released Oct 2019
### Distribution
- New requirement: Python 3.7 (py2.7 support removed)
- Django 2.1
- Twisted 19.2.1
- Autobahn websockets (removed old tmwx)
- Docker image updated
### Commands
- Remove `@`-prefix from all default commands (prefixes still work, optional)
- Removed default `@delaccount` command, incorporating as `@account/delete` instead. Added confirmation
question.
- Add new `@force` command to have another object perform a command.
- Add the Portal uptime to the `@time` command.
- Make the `@link` command first make a local search before a global search.
- Have the default Unloggedin-look command look for optional `connection_screen()` callable in
`mygame/server/conf/connection_screen.py`. This allows for more flexible welcome screens
that are calculated on the fly.
- `@py` command now defaults to escaping html tags in its output when viewing in the webclient.
Use new `/clientraw` switch to get old behavior (issue #1369).
- Shorter and more informative, dynamic, listing of on-command vars if not
setting func() in child command class.
- New Command helper methods
- `.client_width()` returns client width of the session running the command.
- `.styled_table(*args, **kwargs)` returns a formatted evtable styled by user's options
- `.style_header(*args, **kwargs)` creates styled header entry
- `.style_separator(*args, **kwargs)` " separator
- `.style_footer(*args, **kwargs)` " footer
### Web
- Change webclient from old txws version to use more supported/feature-rich Autobahn websocket library
#### Evennia game index
- Made Evennia game index client a part of core - now configured from settings file (old configs
need to be moved)
- The `evennia connections` command starts a wizard that helps you connect your game to the game index.
- The game index now accepts games with no public telnet/webclient info (for early prototypes).
#### New golden-layout based Webclient UI (@friarzen)
- Features
- Much slicker behavior and more professional look
- Allows tabbing as well as click and drag of panes in any grid position
- Renaming tabs, assignments of data tags and output types are simple per-pane menus now
- Any number of input panes, with separate histories
- Button UI (disabled in JS by default)
#### Web/Django standard initiative (@strikaco)
- Features
- Adds a series of web-based forms and generic class-based views
- Accounts
- Register - Enhances registration; allows optional collection of email address
- Form - Adds a generic Django form for creating Accounts from the web
- Characters
- Create - Authenticated users can create new characters from the website (requires associated form)
- Detail - Authenticated and authorized users can view select details about characters
- List - Authenticated and authorized users can browse a list of all characters
- Manage - Authenticated users can edit or delete owned characters from the web
- Form - Adds a generic Django form for creating characters from the web
- Channels
- Detail - Authorized users can view channel logs from the web
- List - Authorized users can browse a list of all channels
- Help Entries
- Detail - Authorized users can view help entries from the web
- List - Authorized users can browse a list of all help entries from the web
- Navbar changes
- Characters - Link to character list
- Channels - Link to channel list
- Help - Link to help entry list
- Puppeting
- Users can puppet their own characters within the context of the website
- Dropdown
- Link to create characters
- Link to manage characters
- Link to quick-select puppets
- Link to password change workflow
- Functions
- Updates Bootstrap to v4 stable
- Enables use of Django Messages framework to communicate with users in browser
- Implements webclient/website `_shared_login` functionality as Django middleware
- 'account' and 'puppet' are added to all request contexts for authenticated users
- Adds unit tests for all web views
- Cosmetic
- Prettifies Django 'forgot password' workflow (requires SMTP to actually function)
- Prettifies Django 'change password' workflow
- Bugfixes
- Fixes bug on login page where error messages were not being displayed
- Remove strvalue field from admin; it made no sense to have here, being an optimization field
for internal use.
### Prototypes
- `evennia.prototypes.save_prototype` now takes the prototype as a normal
argument (`prototype`) instead of having to give it as `**prototype`.
- `evennia.prototypes.search_prototype` has a new kwarg `require_single=False` that
raises a KeyError exception if query gave 0 or >1 results.
- `evennia.prototypes.spawner` can now spawn by passing a `prototype_key`
### Typeclasses
- Add new methods on all typeclasses, useful specifically for object handling from the website/admin:
+ `web_get_admin_url()`: Returns the path to the object detail page in the Admin backend.
+ `web_get_create_url()`: Returns the path to the typeclass' creation page on the website, if implemented.
+ `web_get_absolute_url()`: Returns the path to the object's detail page on the website, if implemented.
+ `web_get_update_url()`: Returns the path to the object's update page on the website, if implemented.
+ `web_get_delete_url()`: Returns the path to the object's delete page on the website, if implemented.
- All typeclasses have new helper class method `create`, which encompasses useful functionality
that used to be embedded for example in the respective `@create` or `@connect` commands.
- DefaultAccount now has new class methods implementing many things that used to be in unloggedin
commands (these can now be customized on the class instead):
+ `is_banned()`: Checks if a given username or IP is banned.
+ `get_username_validators`: Return list of validators for username validation (see
`settings.AUTH_USERNAME_VALIDATORS`)
+ `authenticate`: Method to check given username/password.
+ `normalize_username`: Normalizes names so (for Unicode environments) users cannot mimic existing usernames by replacing select characters with visually-similar Unicode chars.
+ `validate_username`: Mechanism for validating a username based on predefined Django validators.
+ `validate_password`: Mechanism for validating a password based on predefined Django validators.
+ `set_password`: Apply password to account, using validation checks.
- `AttributeHandler.remove` and `TagHandler.remove` can now be used to delete by-category. If neither
key nor category is given, they now work the same as .clear().
### Protocols
- Support for `Grapevine` MUD-chat network ("channels" supported)
### Server
- Convert ServerConf model to store its values as a Picklefield (same as
Attributes) instead of using a custom solution.
- OOB: Add support for MSDP LIST, REPORT, UNREPORT commands (re-mapped to `msdp_list`,
`msdp_report`, `msdp_unreport`, inlinefuncs)
- Added `evennia.ANSIString` to flat API.
- Server/Portal log files now cycle to names on the form `server_.log_19_03_08_` instead of `server.log___19.3.8`, retaining
unix file sorting order.
- Django signals fire for important events: Puppet/Unpuppet, Object create/rename, Login,
Logout, Login fail Disconnect, Account create/rename
### Settings
- `GLOBAL_SCRIPTS` - dict defining typeclasses of global scripts to store on the new
`evennia.GLOBAL_SCRIPTS` container. These will auto-start when Evennia start and will always
exist.
- `OPTIONS_ACCOUNTS_DEFAULT` - option dict with option defaults and Option classes
- `OPTION_CLASS_MODULES` - classes representing an on-Account Option, on special form
- `VALIDATOR_FUNC_MODULES` - (general) text validator functions, for verifying an input
is on a specific form.
### Utils
- `evennia` launcher now fully handles all django-admin commands, like running tests in parallel.
- `evennia.utils.create.account` now also takes `tags` and `attrs` keywords.
- `evennia.utils.interactive` decorator can now allow you to use yield(secs) to pause operation
in any function, not just in Command.func. Likewise, response = yield(question) will work
if the decorated function has an argument or kwarg `caller`.
- Added many more unit tests.
- Swap argument order of `evennia.set_trace` to `set_trace(term_size=(140, 40), debugger='auto')`
since the size is more likely to be changed on the command line.
- `utils.to_str(text, session=None)` now acts as the old `utils.to_unicode` (which was removed).
This converts to the str() type (not to a byte-string as in Evennia 0.8), trying different
encodings. This function will also force-convert any object passed to it into a string (so
`force_string` flag was removed and assumed always set).
- `utils.to_bytes(text, session=None)` replaces the old `utils.to_str()` functionality and converts
str to bytes.
- `evennia.MONITOR_HANDLER.all` now takes keyword argument `obj` to only retrieve monitors from that specific
Object (rather than all monitors in the entire handler).
- Support adding `\f` in command doc strings to force where EvMore puts page breaks.
- Validation Functions now added with standard API to homogenize user input validation.
- Option Classes added to make storing user-options easier and smoother.
- `evennia.VALIDATOR_CONTAINER` and `evennia.OPTION_CONTAINER` added to load these.
### Contribs
- Evscaperoom - a full puzzle engine for making multiplayer escape rooms in Evennia. Used to make
the entry for the MUD-Coder's Guild's 2019 Game Jam with the theme "One Room", where it ranked #1.
- Evennia game-index client no longer a contrib - moved into server core and configured with new
setting `GAME_INDEX_ENABLED`.
- The `extended_room` contrib saw some backwards-incompatible refactoring:
+ All commands now begin with `CmdExtendedRoom`. So before it was `CmdExtendedLook`, now
it's `CmdExtendedRoomLook` etc.
+ The `detail` command was broken out of the `desc` command and is now a new, stand-alone command
`CmdExtendedRoomDetail`. This was done to make things easier to extend and to mimic how the detail
command works in the tutorial-world.
+ The `detail` command now also supports deleting details (like the tutorial-world version).
+ The new `ExtendedRoomCmdSet` includes all the extended-room commands and is now the recommended way
to install the extended-room contrib.
- Reworked `menu_login` contrib to use latest EvMenu standards. Now also supports guest logins.
- Mail contrib was refactored to have optional Command classes `CmdMail` for OOC+IC mail (added
to the CharacterCmdSet and `CmdMailCharacter` for IC-only mailing between chars (added to CharacterCmdSet)
### Translations
- Simplified chinese, courtesy of user MaxAlex.
## Evennia 0.8
> 2017-2018
> Released Nov 2018
### Requirements
- Up requirements to Django 1.11.x, Twisted 18 and pillow 5.2.0
- Add `inflect` dependency for automatic pluralization of object names.
### Server/Portal
- Removed `evennia_runner`, completely refactor `evennia_launcher.py` (the 'evennia' program)
with different functionality).
- Both Portal/Server are now stand-alone processes (easy to run as daemon)
- Made Portal the AMP Server for starting/restarting the Server (the AMP client)
- Dynamic logging now happens using `evennia -l` rather than by interactive mode.
- Made AMP secure against erroneous HTTP requests on the wrong port (return error messages).
- The `evennia istart` option will start/switch the Server in foreground (interactive) mode, where it logs
to terminal and can be stopped with Ctrl-C. Using `evennia reload`, or reloading in-game, will
return Server to normal daemon operation.
- For validating passwords, use safe Django password-validation backend instead of custom Evennia one.
- Alias `evennia restart` to mean the same as `evennia reload`.
### Prototype changes
- New OLC started from `olc` command for loading/saving/manipulating prototypes in a menu.
- Moved evennia/utils/spawner.py into the new evennia/prototypes/ along with all new
functionality around prototypes.
- A new form of prototype - database-stored prototypes, editable from in-game, was added. The old,
module-created prototypes remain as read-only prototypes.
- All prototypes must have a key `prototype_key` identifying the prototype in listings. This is
checked to be server-unique. Prototypes created in a module will use the global variable name they
are assigned to if no `prototype_key` is given.
- Prototype field `prototype` was renamed to `prototype_parent` to avoid mixing terms.
- All prototypes must either have `typeclass` or `prototype_parent` defined. If using
`prototype_parent`, `typeclass` must be defined somewhere in the inheritance chain. This is a
change from Evennia 0.7 which allowed 'mixin' prototypes without `typeclass`/`prototype_key`. To
make a mixin now, give it a default typeclass, like `evennia.objects.objects.DefaultObject` and just
override in the child as needed.
- Spawning an object using a prototype will automatically assign a new tag to it, named the same as
the `prototype_key` and with the category `from_prototype`.
- The spawn command was extended to accept a full prototype on one line.
- The spawn command got the /save switch to save the defined prototype and its key
- The command spawn/menu will now start an OLC (OnLine Creation) menu to load/save/edit/spawn prototypes.
### EvMenu
- Added `EvMenu.helptext_formatter(helptext)` to allow custom formatting of per-node help.
- Added `evennia.utils.evmenu.list_node` decorator for turning an EvMenu node into a multi-page listing.
- A `goto` option callable returning None (rather than the name of the next node) will now rerun the
current node instead of failing.
- Better error handling of in-node syntax errors.
- Improve dedent of default text/helptext formatter. Right-strip whitespace.
- Add `debug` option when creating menu - this turns off persistence and makes the `menudebug`
command available for examining the current menu state.
### Webclient
- Webclient now uses a plugin system to inject new components from the html file.
- Split-windows - divide input field into any number of horizontal/vertical panes and
assign different types of server messages to them.
- Lots of cleanup and bug fixes.
- Hot buttons plugin (friarzen) (disabled by default).
### Locks
- New function `evennia.locks.lockhandler.check_lockstring`. This allows for checking an object
against an arbitrary lockstring without needing the lock to be stored on an object first.
- New function `evennia.locks.lockhandler.validate_lockstring` allows for stand-alone validation
of a lockstring.
- New function `evennia.locks.lockhandler.get_all_lockfuncs` gives a dict {"name": lockfunc} for
all available lock funcs. This is useful for dynamic listings.
### Utils
- Added new `columnize` function for easily splitting text into multiple columns. At this point it
is not working too well with ansi-colored text however.
- Extend the `dedent` function with a new `baseline_index` kwarg. This allows to force all lines to
the indentation given by the given line regardless of if other lines were already a 0 indentation.
This removes a problem with the original `textwrap.dedent` which will only dedent to the least
indented part of a text.
- Added `exit_cmd` to EvMore pager, to allow for calling a command (e.g. 'look') when leaving the pager.
- `get_all_typeclasses` will return dict `{"path": typeclass, ...}` for all typeclasses available
in the system. This is used by the new `@typeclass/list` subcommand (useful for builders etc).
- `evennia.utils.dbserialize.deserialize(obj)` is a new helper function to *completely* disconnect
a mutable recovered from an Attribute from the database. This will convert all nested `_Saver*`
classes to their plain-Python counterparts.
### General
- Start structuring the `CHANGELOG` to list features in more detail.
- Docker image `evennia/evennia:develop` is now auto-built, tracking the develop branch.
- Inflection and grouping of multiple objects in default room (an box, three boxes)
- `evennia.set_trace()` is now a shortcut for launching pdb/pudb on a line in the Evennia event loop.
- Removed the enforcing of `MAX_NR_CHARACTERS=1` for `MULTISESSION_MODE` `0` and `1` by default.
- Add `evennia.utils.logger.log_sec` for logging security-related messages (marked SS in log).
### Contribs
- `Auditing` (Johnny): Log and filter server input/output for security purposes
- `Build Menu` (vincent-lg): New @edit command to edit object properties in a menu.
- `Field Fill` (Tim Ashley Jenkins): Wraps EvMenu for creating submittable forms.
- `Health Bar` (Tim Ashley Jenkins): Easily create colorful bars/meters.
- `Tree select` (Fluttersprite): Wrap EvMenu to create a common type of menu from a string.
- `Turnbattle suite` (Tim Ashley Jenkins)- the old `turnbattle.py` was moved into its own
`turnbattle/` package and reworked with many different flavors of combat systems:
- `tb_basic` - The basic turnbattle system, with initiative/turn order attack/defense/damage.
- `tb_equip` - Adds weapon and armor, wielding, accuracy modifiers.
- `tb_items` - Extends `tb_equip` with item use with conditions/status effects.
- `tb_magic` - Extends `tb_equip` with spellcasting.
- `tb_range` - Adds system for abstract positioning and movement.
- The `extended_room` contrib saw some backwards-incompatible refactoring:
- All commands now begin with `CmdExtendedRoom`. So before it was `CmdExtendedLook`, now
it's `CmdExtendedRoomLook` etc.
- The `detail` command was broken out of the `desc` command and is now a new, stand-alone command
`CmdExtendedRoomDetail`. This was done to make things easier to extend and to mimic how the detail
command works in the tutorial-world.
- The `detail` command now also supports deleting details (like the tutorial-world version).
- The new `ExtendedRoomCmdSet` includes all the extended-room commands and is now the recommended way
to install the extended-room contrib.
- Updates and some cleanup of existing contribs.
### Internationalization
- Polish translation by user ogotai
# Overview-Changelogs
> These are changelogs from a time before we used formal version numbers.
## 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:
New devel branch created, to lead up to Evennia 0.7.
## Dec 2016:
Lots of bugfixes and considerable uptick in contributors. Unittest coverage
and PEP8 adoption and refactoring.
## May 2016:
Evennia 0.6 with completely reworked Out-of-band system, making
the message path completely flexible and built around input/outputfuncs.
A completely new webclient, split into the evennia.js library and a
gui library, making it easier to customize.
## Feb 2016:
Added the new EvMenu and EvMore utilities, updated EvEdit and cleaned up
a lot of the batchcommand functionality. Started work on new Devel branch.
## Sept 2015:
Evennia 0.5. Merged devel branch, full library format implemented.
## Feb 2015:
Development currently in devel/ branch. Moved typeclasses to use
django's proxy functionality. Changed the Evennia folder layout to a
library format with a stand-alone launcher, in preparation for making
an 'evennia' pypy package and using versioning. The version we will
merge with will likely be 0.5. There is also work with an expanded
testing structure and the use of threading for saves. We also now
use Travis for automatic build checking.
## Sept 2014:
Updated to Django 1.7+ which means South dependency was dropped and
minimum Python version upped to 2.7. MULTISESSION_MODE=3 was added
and the web customization system was overhauled using the latest
functionality of django. Otherwise, mostly bug-fixes and
implementation of various smaller feature requests as we got used
to github. Many new users have appeared.
## Jan 2014:
Moved Evennia project from Google Code to github.com/evennia/evennia.
## Nov 2013:
Moved the internal webserver into the Server and added support for
out-of-band protocols (MSDP initially). This large development push
also meant fixes and cleanups of the way attributes were handled.
Tags were added, along with proper handlers for permissions, nicks
and aliases.
## May 2013:
Made players able to control more than one Character at the same
time, through the MULTISESSION_MODE=2 addition. This lead to a lot
of internal changes for the server.
## Oct 2012:
Changed Evennia from the Modified Artistic 1.0 license to the more
standard and permissive BSD license. Lots of updates and bug fixes as
more people start to use it in new ways. Lots of new caching and
speed-ups.
## March 2012:
Evennia's API has changed and simplified slightly in that the
base-modules where removed from game/gamesrc. Instead admins are
encouraged to explicitly create new modules under game/gamesrc/ when
they want to implement their game - gamesrc/ is empty by default
except for the example folders that contain template files to use for
this purpose. We also added the ev.py file, implementing a new, flat
API. Work is ongoing to add support for mud-specific telnet
extensions, notably the MSDP and GMCP out-of-band extensions. On the
community side, evennia's dev blog was started and linked on planet
Mud-dev aggregator.
## Nov 2011:
After creating several different proof-of-concept game systems (in
contrib and privately) as well testing lots of things to make sure the
implementation is basically sound, we are declaring Evennia out of
Alpha. This can mean as much or as little as you want, admittedly -
development is still heavy but the issue list is at an all-time low
and the server is slowly stabilizing as people try different things
with it. So Beta it is!
## Aug 2011:
Split Evennia into two processes: Portal and Server. After a lot of
work trying to get in-memory code-reloading to work, it's clear this
is not Python's forte - it's impossible to catch all exceptions,
especially in asynchronous code like this. Trying to do so results in
hackish, flakey and unstable code. With the Portal-Server split, the
Server can simply be rebooted while players connected to the Portal
remain connected. The two communicates over twisted's AMP protocol.
## May 2011:
The new version of Evennia, originally hitting trunk in Aug2010, is
maturing. All commands from the pre-Aug version, including IRC/IMC2
support works again. An ajax web-client was added earlier in the year,
including moving Evennia to be its own webserver (no more need for
Apache or django-testserver). Contrib-folder added.
## Aug 2010:
Evennia-griatch-branch is ready for merging with trunk. This marks a
rather big change in the inner workings of the server, such as the
introduction of TypeClasses and Scripts (as compared to the old
ScriptParents and Events) but should hopefully bring everything
together into one consistent package as code development continues.
## May 2010:
Evennia is currently being heavily revised and cleaned from
the years of gradual piecemeal development. It is thus in a very
'Alpha' stage at the moment. This means that old code snippets
will not be backwards compatabile. Changes touch almost all
parts of Evennia's innards, from the way Objects are handled
to Events, Commands and Permissions.
## April 2010:
Griatch takes over Maintainership of the Evennia project from
the original creator Greg Taylor.
# Older
Earlier revisions, with previous maintainer, used SVN on Google Code
and have no changelogs.
First commit (Evennia's birthday) was November 20, 2006.

View file

@ -0,0 +1,33 @@
# Coding and development help
This documentation aims to help you set up a sane development environment to
make your game, also if you never coded before.
See also the [Beginner Tutorial](../Howtos/Beginner-Tutorial/Beginner-Tutorial-Overview.md).
```{toctree}
:maxdepth: 2
Evennia-Code-Style.md
Default-Command-Syntax.md
Version-Control.md
Debugging.md
Unit-Testing.md
Profiling.md
Continuous-Integration.md
Setting-up-PyCharm.md
```
## Evennia Changelog
```{toctree}
:maxdepth: 2
Changelog.md
```
```{toctree}
:hidden:
Release-Notes-1.0
```

View file

@ -0,0 +1,198 @@
# Continuous Integration - TeamCity (linux)
This sets up a TeamCity build integration environment on Linux.
## Prerequisites
- Follow [TeamCity](https://www.jetbrains.com/teamcity/) 's in-depth
[Setup Guide](https://confluence.jetbrains.com/display/TCD8/Installing+and+Configuring+the+TeamCity+Server).
- You need to use [Version Control](./Version-Control.md).
After meeting the preparation steps for your specific environment, log on to your teamcity interface
at `http://<your server>:8111/`.
Create a new project named "Evennia" and in it construct a new template called `continuous-integration`.
## A Quick Overview
_Templates_ are fancy objects in TeamCity that allow an administrator to define build steps that are
shared between one or more build projects. Assigning a VCS Root (Source Control) is unnecessary at
this stage, primarily you'll be worrying about the build steps and your default parameters (both
visible on the tabs to the left.)
## Template Setup
In this template, you'll be outlining the steps necessary to build your specific game. (A number of
sample scripts are provided under this section below!) Click Build Steps and prepare your general
flow. For this example, we will be doing a few basic example steps:
* Transforming the Settings.py file - We do this to update ports or other information that make your production
environment unique from your development environment.
* Making migrations and migrating the game database.
* Publishing the game files.
* Reloading the server.
For each step we'll being use the "Command Line Runner" (a fancy name for a shell script executor).
Create a build step with the name: "Transform Configuration" and add the script:
```bash
#!/bin/bash
# Replaces the game configuration with one
# appropriate for this deployment.
CONFIG="%system.teamcity.build.checkoutDir%/server/conf/settings.py"
MYCONF="%system.teamcity.build.checkoutDir%/server/conf/my.cnf"
sed -e 's/TELNET_PORTS = [4000]/TELNET_PORTS = [%game.ports%]/g' "$CONFIG" > "$CONFIG".tmp && mv
"$CONFIG".tmp "$CONFIG"
sed -e 's/WEBSERVER_PORTS = [(4001, 4002)]/WEBSERVER_PORTS = [%game.webports%]/g' "$CONFIG" >
"$CONFIG".tmp && mv "$CONFIG".tmp "$CONFIG"
``````
```bash
# settings.py MySQL DB configuration
echo Configuring Game Database...
echo "" >> "$CONFIG"
echo "######################################################################" >> "$CONFIG"
echo "# MySQL Database Configuration" >> "$CONFIG"
echo "######################################################################" >> "$CONFIG"
echo "DATABASES = {" >> "$CONFIG"
echo " 'default': {" >> "$CONFIG"
echo " 'ENGINE': 'django.db.backends.mysql'," >> "$CONFIG"
echo " 'OPTIONS': {" >> "$CONFIG"
echo " 'read_default_file': 'server/conf/my.cnf'," >> "$CONFIG"
echo " }," >> "$CONFIG"
echo " }" >> "$CONFIG"
echo "}" >> "$CONFIG"
# Create the My.CNF file.
echo "[client]" >> "$MYCONF"
echo "database = %mysql.db%" >> "$MYCONF"
echo "user = %mysql.user%" >> "$MYCONF"
echo "password = %mysql.pass%" >> "$MYCONF"
echo "default-character-set = utf8" >> "$MYCONF"
```
If you look at the parameters side of the page after saving this script, you'll notice that some new
parameters have been populated for you. This is because we've included new teamcity configuration
parameters that are populated when the build itself is ran. When creating projects that inherit this
template, we'll be able to fill in or override those parameters for project-specific configuration.
Go ahead and create another build step called "Make Database Migration"
If you're using Sqlite3 for your game (default database), it's prudent to change working directory on this
step to your game dir.
```bash
#!/bin/bash
# Update the DB migration
LOGDIR="server/logs"
. %evenv.dir%/bin/activate
# Check that the logs directory exists.
if [ ! -d "$LOGDIR" ]; then
# Control will enter here if $LOGDIR doesn't exist.
mkdir "$LOGDIR"
fi
evennia makemigrations
```
Create yet another build step, this time named: "Execute Database Migration":
If you're using Sqlite3 for your game (default database), it's prudent to change working directory on this
step to your game dir.
```bash
#!/bin/bash
# Apply the database migration.
LOGDIR="server/logs"
. %evenv.dir%/bin/activate
# Check that the logs directory exists.
if [ ! -d "$LOGDIR" ]; then
# Control will enter here if $LOGDIR doesn't exist.
mkdir "$LOGDIR"
fi
evennia migrate
```
Our next build step is where we actually publish our build. Up until now, all work on game has been
done in a 'work' directory on TeamCity's build agent. From that directory we will now copy our files
to where our game actually exists on the local server.
Create a new build step called "Publish Build". If you're using SQlite3 on your game, be sure to order this step ABOVE
the Database Migration steps. The build order will matter!
```bash
#!/bin/bash
# Publishes the build to the proper build directory.
DIRECTORY="<game_dir>"
if [ ! -d "$DIRECTORY" ]; then
# Control will enter here if $DIRECTORY doesn't exist.
mkdir "$DIRECTORY"
fi
# Copy all the files.
cp -ruv %teamcity.build.checkoutDir%/* "$DIRECTORY"
chmod -R 775 "$DIRECTORY"
```
Finally the last script will reload our game for us.
Create a new script called "Reload Game":
The working directory on this build step will be: `%game.dir%`
```bash
#!/bin/bash
# Apply the database migration.
LOGDIR="server/logs"
PIDDIR="server/server.pid"
. %evenv.dir%/bin/activate
# Check that the logs directory exists.
if [ ! -d "$LOGDIR" ]; then
# Control will enter here if $LOGDIR doesn't exist.
mkdir "$LOGDIR"
fi
# Check that the server is running.
if [ -d "$PIDDIR" ]; then
# Control will enter here if the game is running.
evennia reload
fi
```
Now the template is ready for use! It would be useful this time to revisit the parameters page and
set the evenv parameter to the directory where your virtualenv exists: IE "/srv/mush/evenv".
### Creating the Project
Now it's time for the last few steps to set up a CI environment.
* Return to the Evennia Project overview/administration page.
* Create a new Sub-Project called "Production". This will be the category that holds our actual game.
* Create a new Build Configuration in Production with the name of your MUSH. Base this configuration off of the
continuous-integration template we made earlier.
* In the build configuration, enter VCS roots and create a new VCS root that points to the
branch/version control that you are using.
* Go to the parameters page and fill in the undefined parameters for your specific configuration.
* If you wish for the CI to run every time a commit is made, go to the VCS triggers and add one for
"On Every Commit".
And you're done! At this point, you can return to the project overview page and queue a new build
for your game. If everything was set up correctly, the build will complete successfully. Additional
build steps could be added or removed at this point, adding some features like Unit Testing or more!

View file

@ -0,0 +1,39 @@
# Continuous integration with Travis
[Travis CI](https://travis-ci.org/) is an online service for checking, validating and potentially
deploying code automatically. It can check that every commit is building successfully after every
commit to its Github repository.
If your game is open source on Github you may use Travis for free.
See [the Travis docs](https://docs.travis-ci.com/user/getting- started/) for how to get started.
After logging in you will get to point Travis to your repository on github. One further thing you
need to set up yourself is a Travis config file named `.travis.yml` (note the initial period `.`).
This should be created in the root of your game directory. The idea with this file is that it
describes what Travis needs to import and build in order to create an instance of Evennia from
scratch and then run validation tests on it. Here is an example:
``` yaml
language: python
python:
- "3.10"
install:
- git clone https://github.com/evennia/evennia.git
- cd evennia
- pip install -e .
- cd $TRAVIS_BUILD_DIR
script:
- evennia migrate
- evennia test --settings settings.py .
```
This will tell travis how to download Evennia, install it, set up a database and then run
your own test suite (inside the game dir). Use `evennia test evennia` if you also want to
run the Evennia full test suite.
You need to add this file to git (`git add .travis.yml`) and then commit your changes before Travis
will be able to see it.
For properly testing your game you of course also need to write unittests.
The [Unit testing](./Unit-Testing.md) doc page gives some ideas on how to set those up for Evennia.
You should be able to refer to that for making tests fitting your game.

View file

@ -0,0 +1,28 @@
# Continuous Integration (CI)
[Continuous Integration (CI)](https://www.thoughtworks.com/continuous-integration) is a development practice that requires developers to integrate code into a shared repository. Each check-in is then verified by an automated build, allowing teams to detect problems early. This can be set up to safely deploy data to a production server only after tests have passed, for example.
For Evennia, continuous integration allows an automated build process to:
* Pull down a latest build from Source Control.
* Run migrations on the backing SQL database.
* Automate additional unique tasks for that project.
* Run unit tests.
* Publish those files to the server directory
* Reload the game.
## Continuous-Integration guides
There are a lot of tools and services providing CI functionality. Here are a few that people have used with Evennia:
```{toctree}
:maxdepth: 1
Continuous-Integration-Travis.md
Continuous-Integration-TeamCity.md
```
- Evennia is itself making heavy use of [github actions]()
[This is an overview of other tools](https://www.atlassian.com/continuous-delivery/continuous-integration/tools) (external link).

View file

@ -1,12 +1,8 @@
# Debugging
Sometimes, an error is not trivial to resolve. A few simple `print` statements is not enough to find the cause of the issue. The traceback is not informative or even non-existing.
Sometimes, an error is not trivial to resolve. A few simple `print` statements is not enough to find
the cause of the issue. Running a *debugger* can then be very helpful and save a lot of time.
Debugging
means running Evennia under control of a special *debugger* program. This allows you to stop the
action at a given point, view the current state and step forward through the program to see how its
logic works.
Running a *debugger* can then be very helpful and save a lot of time. Debugging means running Evennia under control of a special *debugger* program. This allows you to stop the action at a given point, view the current state and step forward through the program to see how its logic works.
Evennia natively supports these debuggers:
@ -24,11 +20,8 @@ To run Evennia with the debugger, follow these steps:
```python
from evennia import set_trace;set_trace()
```
2. (Re-)start Evennia in interactive (foreground) mode with `evennia istart`. This is important -
without this step the debugger will not start correctly - it will start in this interactive
terminal.
3. Perform the steps that will trigger the line where you added the `set_trace()` call. The debugger
will start in the terminal from which Evennia was interactively started.
2. (Re-)start Evennia in interactive (foreground) mode with `evennia istart`. This is important - without this step the debugger will not start correctly - it will start in this interactive terminal.
3. Perform the steps that will trigger the line where you added the `set_trace()` call. The debugger will start in the terminal from which Evennia was interactively started.
The `evennia.set_trace` function takes the following arguments:
@ -37,8 +30,7 @@ The `evennia.set_trace` function takes the following arguments:
evennia.set_trace(debugger='auto', term_size=(140, 40))
```
Here, `debugger` is one of `pdb`, `pudb` or `auto`. If `auto`, use `pudb` if available, otherwise
use `pdb`. The `term_size` tuple sets the viewport size for `pudb` only (it's ignored by `pdb`).
Here, `debugger` is one of `pdb`, `pudb` or `auto`. If `auto`, use `pudb` if available, otherwise use `pdb`. The `term_size` tuple sets the viewport size for `pudb` only (it's ignored by `pdb`).
## A simple example using pdb
@ -71,9 +63,7 @@ class CmdTest(Command):
```
If you type `test` in your game, everything will freeze. You won't get any feedback from the game,
and you won't be able to enter any command (nor anyone else). It's because the debugger has started
in your console, and you will find it here. Below is an example with `pdb`.
If you type `test` in your game, everything will freeze. You won't get any feedback from the game, and you won't be able to enter any command (nor anyone else). It's because the debugger has started in your console, and you will find it here. Below is an example with `pdb`.
```
...
@ -83,13 +73,11 @@ in your console, and you will find it here. Below is an example with `pdb`.
```
`pdb` notes where it has stopped execution and, what line is about to be executed (in our case, `obj
= self.search(self.args)`), and ask what you would like to do.
`pdb` notes where it has stopped execution and, what line is about to be executed (in our case, `obj = self.search(self.args)`), and ask what you would like to do.
### Listing surrounding lines of code
When you have the `pdb` prompt `(Pdb)`, you can type in different commands to explore the code. The
first one you should know is `list` (you can type `l` for short):
When you have the `pdb` prompt `(Pdb)`, you can type in different commands to explore the code. The first one you should know is `list` (you can type `l` for short):
```
(Pdb) l
@ -107,18 +95,13 @@ first one you should know is `list` (you can type `l` for short):
(Pdb)
```
Okay, this didn't do anything spectacular, but when you become more confident with `pdb` and find
yourself in lots of different files, you sometimes need to see what's around in code. Notice that
there is a little arrow (`->`) before the line that is about to be executed.
Okay, this didn't do anything spectacular, but when you become more confident with `pdb` and find yourself in lots of different files, you sometimes need to see what's around in code. Notice that there is a little arrow (`->`) before the line that is about to be executed.
This is important: **about to be**, not **has just been**. You need to tell `pdb` to go on (we'll
soon see how).
This is important: **about to be**, not **has just been**. You need to tell `pdb` to go on (we'll soon see how).
### Examining variables
`pdb` allows you to examine variables (or really, to run any Python instruction). It is very useful
to know the values of variables at a specific line. To see a variable, just type its name (as if
you were in the Python interpreter:
`pdb` allows you to examine variables (or really, to run any Python instruction). It is very useful to know the values of variables at a specific line. To see a variable, just type its name (as if you were in the Python interpreter:
```
(Pdb) self
@ -158,9 +141,7 @@ AttributeError: "'CmdTest' object has no attribute 'search'"
(Pdb)
```
`Pdb` is complaining that you try to call the `search` method on a command... whereas there's no
`search` method on commands. The character executing the command is in `self.caller`, so we might
change our line:
`Pdb` is complaining that you try to call the `search` method on a command... whereas there's no `search` method on commands. The character executing the command is in `self.caller`, so we might change our line:
```python
obj = self.caller.search(self.args)
@ -168,19 +149,14 @@ obj = self.caller.search(self.args)
### Letting the program run
`pdb` is waiting to execute the same instruction... it provoked an error but it's ready to try
again, just in case. We have fixed it in theory, but we need to reload, so we need to enter a
command. To tell `pdb` to terminate and keep on running the program, use the `continue` (or `c`)
command:
`pdb` is waiting to execute the same instruction... it provoked an error but it's ready to try again, just in case. We have fixed it in theory, but we need to reload, so we need to enter a command. To tell `pdb` to terminate and keep on running the program, use the `continue` (or `c`) command:
```
(Pdb) c
...
```
You see an error being caught, that's the error we have fixed... or hope to have. Let's reload the
game and try again. You need to run `evennia istart` again and then run `test` to get into the
command again.
You see an error being caught, that's the error we have fixed... or hope to have. Let's reload the game and try again. You need to run `evennia istart` again and then run `test` to get into the command again.
```
> .../mygame/commands/command.py(79)func()
@ -218,12 +194,11 @@ fix that bug too, it would be better):
...
```
Notice that you'll have an error in the game this time. Let's try with a valid parameter. I have
another character, `barkeep`, in this room:
Notice that you'll have an error in the game this time. Let's try with a valid parameter. I have another character, `barkeep`, in this room:
```test barkeep```
And again, the command freezes, and we have the debugger opened in the console.
And again, the command freezes, and we have the debugger opened in the console.
Let's execute this line right away:
@ -248,32 +223,16 @@ TypeError: 'get_display_name() takes exactly 2 arguments (1 given)'
(Pdb)
```
As an exercise, fix this error, reload and run the debugger again. Nothing better than some
experimenting!
As an exercise, fix this error, reload and run the debugger again. Nothing better than some experimenting!
Your debugging will often follow the same strategy:
1. Receive an error you don't understand.
2. Put a breaking point **BEFORE** the error occurs.
3. Run the code again and see the debugger open.
4. Run the program line by line,examining variables, checking the logic of instructions.
5. Continue and try again, each step a bit further toward the truth and the working feature.
### Stepping through a function
`n` is useful, but it will avoid stepping inside of functions if it can. But most of the time, when
we have an error we don't understand, it's because we use functions or methods in a way that wasn't
intended by the developer of the API. Perhaps using wrong arguments, or calling the function in a
situation that would cause a bug. When we have a line in the debugger that calls a function or
method, we can "step" to examine it further. For instance, in the previous example, when `pdb` was
about to execute `obj = self.caller.search(self.args)`, we may want to see what happens inside of
the `search` method.
To do so, use the `step` (or `s`) command. This command will show you the definition of the
function/method and you can then use `n` as before to see it line-by-line. In our little example,
stepping through a function or method isn't that useful, but when you have an impressive set of
commands, functions and so on, it might really be handy to examine some feature and make sure they
operate as planned.
3. Run `evennia istart`
4. Run the code again and see the debugger open.
5. Run the program line by line, examining variables, checking the logic of instructions.
6. Continue and try again, each step a bit further toward the truth and the working feature.
## Cheat-sheet of pdb/pudb commands
@ -292,5 +251,4 @@ this directly). |
| `<RETURN>` | Repeat the last command (don't type `n` repeatedly, just type it once and then press
`<RETURN>` to repeat it). |
If you want to learn more about debugging with Pdb, you will find an [interesting tutorial on that
topic here](https://pymotw.com/3/pdb/).
If you want to learn more about debugging with Pdb, you will find an [interesting tutorial on that topic here](https://pymotw.com/3/pdb/).

View file

@ -0,0 +1,24 @@
# Default Command Syntax
Evennia allows for any command syntax.
If you like the way DikuMUDs, LPMuds or MOOs handle things, you could emulate that with Evennia. If you are ambitious you could even design a whole new style, perfectly fitting your own dreams of the ideal game. See the [Command](../Components/Commands.md) documentation for how to do this.
We do offer a default however. The default Evennia setup tends to *resemble* [MUX2](https://www.tinymux.org/), and its cousins [PennMUSH](https://www.pennmush.org), [TinyMUSH](https://github.com/TinyMUSH/TinyMUSH/wiki), and [RhostMUSH](http://www.rhostmush.com/):
```
command[/switches] object [= options]
```
While the reason for this similarity is partly historical, these codebases offer very mature feature sets for administration and building.
Evennia is *not* a MUX system though. It works very differently in many ways. For example, Evennia
deliberately lacks an online softcode language (a policy explained on our [softcode policy page](./Soft-Code.md)). Evennia also does not shy from using its own syntax when deemed appropriate: the
MUX syntax has grown organically over a long time and is, frankly, rather arcane in places. All in
all the default command syntax should at most be referred to as "MUX-like" or "MUX-inspired".
```{toctree}
:hidden:
Soft-Code
```

View file

@ -0,0 +1,278 @@
# Evennia Code Style
All code submitted or committed to the Evennia project should aim to follow the
guidelines outlined in [Python PEP 8][pep8]. Keeping the code style uniform
makes it much easier for people to collaborate and read the code.
A good way to check if your code follows PEP8 is to use the [PEP8 tool][pep8tool]
on your sources.
## Main code style specification
* 4-space indentation, NO TABS!
* Unix line endings.
* 100 character line widths
* CamelCase is only used for classes, nothing else.
* All non-global variable names and all function names are to be
lowercase, words separated by underscores. Variable names should
always be more than two letters long.
* Module-level global variables (only) are to be in CAPITAL letters.
* Imports should be done in this order:
- Python modules (builtins and standard library)
- Twisted modules
- Django modules
- Evennia library modules (`evennia`)
- Evennia contrib modules (`evennia.contrib`)
* All modules, classes, functions and methods should have doc strings formatted
as outlined below.
* All default commands should have a consistent docstring formatted as
outlined below.
## Code Docstrings
All modules, classes, functions and methods should have docstrings
formatted with [Google style][googlestyle] -inspired indents, using
[Markdown][githubmarkdown] formatting where needed. Evennia's `api2md`
parser will use this to create pretty API documentation.
### Module docstrings
Modules should all start with at least a few lines of docstring at
their top describing the contents and purpose of the module.
Example of module docstring (top of file):
```python
"""
This module handles the creation of `Objects` that
are useful in the game ...
"""
```
Sectioning (`# title`, `## subtile` etc) should not be used in
freeform docstrings - this will confuse the sectioning of the auto
documentation page and the auto-api will create this automatically.
Write just the section name bolded on its own line to mark a section.
Beyond sections markdown should be used as needed to format
the text.
Code examples should use [multi-line syntax highlighting][markdown-hilight]
to mark multi-line code blocks, using the "python" identifier. Just
indenting code blocks (common in markdown) will not produce the
desired look.
When using any code tags (inline or blocks) it's recommended that you
don't let the code extend wider than about 70 characters or it will
need to be scrolled horizontally in the wiki (this does not affect any
other text, only code).
### Class docstrings
The root class docstring should describe the over-arching use of the
class. It should usually not describe the exact call sequence nor list
important methods, this tends to be hard to keep updated as the API
develops. Don't use section markers (`#`, `##` etc).
Example of class docstring:
```python
class MyClass(object):
"""
This class describes the creation of `Objects`. It is useful
in many situations, such as ...
"""
```
### Function / method docstrings
Example of function or method docstring:
```python
def funcname(a, b, c, d=False, **kwargs):
"""
This is a brief introduction to the function/class/method
Args:
a (str): This is a string argument that we can talk about
over multiple lines.
b (int or str): Another argument.
c (list): A list argument.
d (bool, optional): An optional keyword argument.
Keyword Args:
test (list): A test keyword.
Returns:
str: The result of the function.
Raises:
RuntimeException: If there is a critical error,
this is raised.
IOError: This is only raised if there is a
problem with the database.
Notes:
This is an example function. If `d=True`, something
amazing will happen.
"""
```
The syntax is very "loose" but the indentation matters. That is, you
should end the block headers (like `Args:`) with a line break followed by
an indent. When you need to break a line you should start the next line
with another indent. For consistency with the code we recommend all
indents to be 4 spaces wide (no tabs!).
Here are all the supported block headers:
```
"""
Args
argname (freeform type): Description endind with period.
Keyword Args:
argname (freeform type): Description.
Returns/Yields:
type: Description.
Raises:
Exceptiontype: Description.
Notes/Note/Examples/Example:
Freeform text.
"""
```
Parts marked with "freeform" means that you can in principle put any
text there using any formatting except for sections markers (`#`, `##`
etc). You must also keep indentation to mark which block you are part
of. You should normally use the specified format rather than the
freeform counterpart (this will produce nicer output) but in some
cases the freeform may produce a more compact and readable result
(such as when describing an `*args` or `**kwargs` statement in general
terms). The first `self` argument of class methods should never be
documented.
Note that
```
"""
Args:
argname (type, optional): Description.
"""
```
and
```
"""
Keyword Args:
sargname (type): Description.
"""
```
mean the same thing! Which one is used depends on the function or
method documented, but there are no hard rules; If there is a large
`**kwargs` block in the function, using the `Keyword Args:` block may be a
good idea, for a small number of arguments though, just using `Args:`
and marking keywords as `optional` will shorten the docstring and make
it easier to read.
## Default Command Docstrings
These represent a special case since Commands in Evennia use their class
docstrings to represent the in-game help entry for that command.
All the commands in the _default command_ sets should have their doc-strings
formatted on a similar form. For contribs, this is loosened, but if there is
no particular reason to use a different form, one should aim to use the same
style for contrib-command docstrings as well.
```python
"""
Short header
Usage:
key[/switches, if any] <mandatory args> [optional] choice1||choice2||choice3
Switches:
switch1 - description
switch2 - description
Examples:
Usage example and output
Longer documentation detailing the command.
"""
```
- Two spaces are used for *indentation* in all default commands.
- Square brackets `[ ]` surround *optional, skippable arguments*.
- Angled brackets `< >` surround a _description_ of what to write rather than the exact syntax.
- Explicit choices are separated by `|`. To avoid this being parsed as a color code, use `||` (this
will come out as a single `|`) or put spaces around the character ("` | `") if there's plenty of room.
- The `Switches` and `Examples` blocks are optional and based on the Command.
Here is the `nick` command as an example:
```python
"""
Define a personal alias/nick
Usage:
nick[/switches] <nickname> = [<string>]
alias ''
Switches:
object - alias an object
account - alias an account
clearall - clear all your aliases
list - show all defined aliases (also "nicks" works)
Examples:
nick hi = say Hello, I'm Sarah!
nick/object tom = the tall man
A 'nick' is a personal shortcut you create for your own use [...]
"""
```
For commands that *require arguments*, the policy is for it to return a `Usage:`
string if the command is entered without any arguments. So for such commands,
the Command body should contain something to the effect of
```python
if not self.args:
self.caller.msg("Usage: nick[/switches] <nickname> = [<string>]")
return
```
## Tools for auto-linting
### black
Automatic pep8 compliant formatting and linting can be performed using the
`black` formatter:
black --line-length 100
### PyCharm
The Python IDE [Pycharm][pycharm] can auto-generate empty doc-string stubs. The
default is to use `reStructuredText` form, however. To change to Evennia's
Google-style docstrings, follow [this guide][pycharm-guide].
[pep8]: http://www.python.org/dev/peps/pep-0008
[pep8tool]: https://pypi.python.org/pypi/pep8
[googlestyle]: https://www.sphinx-doc.org/en/master/usage/extensions/example_google.html
[githubmarkdown]: https://help.github.com/articles/github-flavored-markdown/
[markdown-hilight]: https://help.github.com/articles/github-flavored-markdown/#syntax-highlighting
[command-docstrings]: https://github.com/evennia/evennia/wiki/Using%20MUX%20As%20a%20Standard#documentation-policy
[pycharm]: https://www.jetbrains.com/pycharm/
[pycharm-guide]: https://www.jetbrains.com/help/pycharm/2016.3/python-integrated-tools.html

View file

@ -0,0 +1,197 @@
# Profiling
```{important}
This is considered an advanced topic. It's mainly of interest to server developers.
```
Sometimes it can be useful to try to determine just how efficient a particular piece of code is, or to figure out if one could speed up things more than they are. There are many ways to test the performance of Python and the running server.
Before digging into this section, remember Donald Knuth's [words of wisdom](https://en.wikipedia.org/wiki/Program_optimization#When_to_optimize):
> *[...]about 97% of the time: Premature optimization is the root of all evil*.
That is, don't start to try to optimize your code until you have actually identified a need to do so. This means your code must actually be working before you start to consider optimization. Optimization will also often make your code more complex and harder to read. Consider readability and maintainability and you may find that a small gain in speed is just not worth it.
## Simple timer tests
Python's `timeit` module is very good for testing small things. For example, in
order to test if it is faster to use a `for` loop or a list comprehension you
could use the following code:
```python
import timeit
# Time to do 1000000 for loops
timeit.timeit("for i in range(100):\n a.append(i)", setup="a = []")
<<< 10.70982813835144
# Time to do 1000000 list comprehensions
timeit.timeit("a = [i for i in range(100)]")
<<< 5.358283996582031
```
The `setup` keyword is used to set up things that should not be included in the time measurement, like `a = []` in the first call.
By default the `timeit` function will re-run the given test 1000000 times and returns the *total time* to do so (so *not* the average per test). A hint is to not use this default for testing something that includes database writes - for that you may want to use a lower number of repeats (say 100 or 1000) using the `number=100` keyword.
In the example above, we see that this number of calls, using a list comprehension is about twice as fast as building a list using `.append()`.
## Using cProfile
Python comes with its own profiler, named cProfile (this is for cPython, no tests have been done with `pypy` at this point). Due to the way Evennia's processes are handled, there is no point in using the normal way to start the profiler (`python -m cProfile evennia.py`). Instead you start the profiler through the launcher:
evennia --profiler start
This will start Evennia with the Server component running (in daemon mode) under cProfile. You could instead try `--profile` with the `portal` argument to profile the Portal (you would then need to [start the Server separately](../Setup/Running-Evennia.md)).
Please note that while the profiler is running, your process will use a lot more memory than usual. Memory usage is even likely to climb over time. So don't leave it running perpetually but monitor it carefully (for example using the `top` command on Linux or the Task Manager's memory display on Windows).
Once you have run the server for a while, you need to stop it so the profiler can give its report. Do *not* kill the program from your task manager or by sending it a kill signal - this will most likely also mess with the profiler. Instead either use `evennia.py stop` or (which may be even better), use `@shutdown` from inside the game.
Once the server has fully shut down (this may be a lot slower than usual) you will find that profiler has created a new file `mygame/server/logs/server.prof`.
### Analyzing the profile
The `server.prof` file is a binary file. There are many ways to analyze and display its contents, all of which has only been tested in Linux (If you are a Windows/Mac user, let us know what works).
You can look at the contents of the profile file with Python's in-built `pstats` module in the evennia shell (it's recommended you install `ipython` with `pip install ipython` in your virtualenv first, for prettier output):
evennia shell
Then in the shell
```python
import pstats
from pstats import SortKey
p = pstats.Stats('server/log/server.prof')
p.strip_dirs().sort_stats(-1).print_stats()
```
See the [Python profiling documentation](https://docs.python.org/3/library/profile.html#instant-user-s-manual) for more information.
You can also visualize the data in various ways.
- [Runsnake](https://pypi.org/project/RunSnakeRun/) visualizes the profile to
give a good overview. Install with `pip install runsnakerun`. Note that this
may require a C compiler and be quite slow to install.
- For more detailed listing of usage time, you can use
[KCachegrind](http://kcachegrind.sourceforge.net/html/Home.html). To make
KCachegrind work with Python profiles you also need the wrapper script
[pyprof2calltree](https://pypi.python.org/pypi/pyprof2calltree/). You can get
`pyprof2calltree` via `pip` whereas KCacheGrind is something you need to get
via your package manager or their homepage.
How to analyze and interpret profiling data is not a trivial issue and depends on what you are profiling for. Evennia being an asynchronous server can also confuse profiling. Ask on the mailing list if you need help and be ready to be able to supply your `server.prof` file for comparison, along with the exact conditions under which it was obtained.
## The Dummyrunner
It is difficult to test "actual" game performance without having players in your game. For this reason Evennia comes with the *Dummyrunner* system. The Dummyrunner is a stress-testing system: a separate program that logs into your game with simulated players (aka "bots" or "dummies"). Once connected, these dummies will semi-randomly perform various tasks from a list of possible actions. Use `Ctrl-C` to stop the Dummyrunner.
```{warning}
You should not run the Dummyrunner on a production database. It
will spawn many objects and also needs to run with general permissions.
This is the recommended process for using the dummy runner:
```
1. Stop your server completely with `evennia stop`.
1. At _the end_ of your `mygame/server/conf.settings.py` file, add the line
from evennia.server.profiling.settings_mixin import *
This will override your settings and disable Evennia's rate limiters and DoS-protections, which would otherwise block mass-connecting clients from one IP. Notably, it will also change to a different (faster) password hasher.
1. (recommended): Build a new database. If you use default Sqlite3 and want to
keep your existing database, just rename `mygame/server/evennia.db3` to
`mygame/server/evennia.db3_backup` and run `evennia migrate` and `evennia
start` to create a new superuser as usual.
1. (recommended) Log into the game as your superuser. This is just so you
can manually check response. If you kept an old database, you will _not_
be able to connect with an _existing_ user since the password hasher changed!
1. Start the dummyrunner with 10 dummy users from the terminal with
evennia --dummyrunner 10
Use `Ctrl-C` (or `Cmd-C`) to stop it.
If you want to see what the dummies are actually doing you can run with a single dummy:
evennia --dummyrunner 1
The inputs/outputs from the dummy will then be printed. By default the runner uses the 'looker' profile, which just logs in and sends the 'look' command over and over. To change the settings, copy the file `evennia/server/profiling/dummyrunner_settings.py` to your `mygame/server/conf/` directory, then add this line to your settings file to use it in the new location:
DUMMYRUNNER_SETTINGS_MODULE = "server/conf/dummyrunner_settings.py"
The dummyrunner settings file is a python code module in its own right - it defines the actions available to the dummies. These are just tuples of command strings (like "look here") for the dummy to send to the server along with a probability of them happening. The dummyrunner looks for a global variable `ACTIONS`, a list of tuples, where the first two elements define the commands for logging in/out of the server.
Below is a simplified minimal setup (the default settings file adds a lot more functionality and info):
```python
# minimal dummyrunner setup file
# Time between each dummyrunner "tick", in seconds. Each dummy will be called
# with this frequency.
TIMESTEP = 1
# Chance of a dummy actually performing an action on a given tick. This
# spreads out usage randomly, like it would be in reality.
CHANCE_OF_ACTION = 0.5
# Chance of a currently unlogged-in dummy performing its login action every
# tick. This emulates not all accounts logging in at exactly the same time.
CHANCE_OF_LOGIN = 0.01
# Which telnet port to connect to. If set to None, uses the first default
# telnet port of the running server.
TELNET_PORT = None
# actions
def c_login(client):
name = f"Character-{client.gid}"
pwd = f"23fwsf23sdfw23wef23"
return (
f"create {name} {pwd}"
f"connect {name} {pwd}"
)
def c_logout(client):
return ("quit", )
def c_look(client):
return ("look here", "look me")
# this is read by dummyrunner.
ACTIONS = (
c_login,
c_logout,
(1.0, c_look) # (probability, command-generator)
)
```
At the bottom of the default file are a few default profiles you can test out by just setting the `PROFILE` variable to one of the options.
### Dummyrunner hints
- Don't start with too many dummies. The Dummyrunner taxes the server much more
than 'real' users tend to do. Start with 10-100 to begin with.
- Stress-testing can be fun, but also consider what a 'realistic' number of
users would be for your game.
- Note in the dummyrunner output how many commands/s are being sent to the
server by all dummies. This is usually a lot higher than what you'd
realistically expect to see from the same number of users.
- The default settings sets up a 'lag' measure to measaure the round-about
message time. It updates with an average every 30 seconds. It can be worth to
have this running for a small number of dummies in one terminal before adding
more by starting another dummyrunner in another terminal - the first one will
act as a measure of how lag changes with different loads. Also verify the
lag-times by entering commands manually in-game.
- Check the CPU usage of your server using `top/htop` (linux). In-game, use the
`server` command.
- You can run the server with `--profiler start` to test it with dummies. Note
that the profiler will itself affect server performance, especially memory
consumption.
- Generally, the dummyrunner system makes for a decent test of general
performance; but it is of course hard to actually mimic human user behavior.
For this, actual real-game testing is required.

View file

@ -0,0 +1,151 @@
# Evennia 1.0 Release Notes
This summarizes the changes. See the [Changelog](./Changelog.md) for the full list.
- Main development now on `main` branch. `master` branch remains, but will not be updated anymore.
## Minimum requirements
- Python 3.10 is now required minimum. Ubuntu LTS now installs with 3.10. Evennia 1.0 is also tested with Python 3.11 - this is the recommended version for Linux/Mac. Windows users may want to stay on Python 3.10 unless they are okay with installing a C++ compiler.
- Twisted 22.10+
- Django 4.1+
## Major new features
- Evennia is now on PyPi and is installable as [pip install evennia](../Setup/Installation.md).
- A completely revamped documentation at https://www.evennia.com/docs/latest. The old wiki and readmedocs pages will close.
- Evennia 1.0 now has a REST API which allows you access game objects using CRUD operations GET/POST etc. See [The Web-API docs][Web-API] for more information.
- [Evennia<>Discord Integration](../Setup/Channels-to-Discord.md) between Evennia channels and Discord servers.
- [Script](../Components/Scripts.md) overhaul: Scripts' timer component independent from script object deletion; can now start/stop timer without deleting Script. The `.persistent` flag now only controls if timer survives reload - Script has to be removed with `.delete()` like other typeclassed entities. This makes Scripts even more useful as general storage entities.
- The [FuncParser](../Components/FuncParser.md) centralizes and vastly improves all in-string function calls, such as `say the result is $eval(3 * 7)` and say the result `the result is 21`. The parser completely replaces the old `parse_inlinefunc`. The new parser can handle both arguments and kwargs and are also used for in-prototype parsing as well as director stance messaging, such as using `$You()` to represent yourself in a string and having the result come out differently depending on who see you.
- [Channels](../Components/Channels.md) New Channel-System using the `channel` command and nicks. The old `ChannelHandler` was removed and the customization and operation of channels have been simplified a lot. The old command syntax commands are now available as a contrib.
- [Help System](../Components/Help-System.md) was refactored.
- A new type of `FileHelp` system allows you to add in-game help files as external Python files. This means there are three ways to add help entries in Evennia: 1) Auto-generated from Command's code. 2) Manually added to the database from the `sethelp` command in-game and 3) Created as external Python files that Evennia loads and makes available in-game.
- We now use `lunr` search indexing for better `help` matching and suggestions. Also improve
the main help command's default listing output.
- Help command now uses `view` lock to determine if cmd/entry shows in index and `read` lock to determine if it can be read. It used to be `view` in the role of the latter.
- `sethelp` command now warns if shadowing other help-types when creating a new entry.
- Make `help` index output clickable for webclient/clients with MXP (PR by davewiththenicehat)
- Rework of the [Web](../Components/Website.md) setup, into a much more consistent structure and update to latest Django. The `mygame/web/static_overrides` and `-template_overrides` were removed. The folders are now just `mygame/web/static` and `/templates` and handle the automatic copying of data behind the scenes. `app.css` to `website.css` for consistency. The old `prosimii-css` files were removed.
- [AttributeProperty](../Components/Attributes.md#using-attributeproperty)/[TagProperty](../Components/Tags.md) along with `AliasProperty` and `PermissionProperty` to allow managing Attributes, Tags, Aliases and Permissios on typeclasses in the same way as Django fields. This dramatically reduces the need to assign Attributes/Tags in `at_create_object` hook.
- The old `MULTISESSION_MODE` was divided into smaller settings, for better controlling what happens when a user connects, if a character should be auto-created, and how many characters they can control at the same time. See [Connection-Styles](../Concepts/Connection-Styles.md) for a detailed explanation.
- Evennia now supports custom `evennia` launcher commands (e.g. `evennia mycmd foo bar`). Add new commands as callables accepting `*args`, as `settings.EXTRA_LAUNCHER_COMMANDS = {'mycmd': 'path.to.callable', ...}`.
## Contribs
The `contrib` folder structure was changed from 0.9.5. All contribs are now in sub-folders and organized into categories. All import paths must be updated. See [Contribs overview](../Contribs/Contribs-Overview.md).
- New [Traits contrib](../Contribs/Contrib-Traits.md), converted and expanded from Ainneve project. (whitenoise, Griatch)
- New [Crafting contrib](../Contribs/Contrib-Crafting.md), adding a full crafting subsystem (Griatch)
- New [XYZGrid contrib](../Contribs/Contrib-XYZGrid.md), adding x,y,z grid coordinates with in-game map and pathfinding. Controlled outside of the game via custom evennia launcher command (Griatch)
- New [Command cooldown contrib](../Contribs/Contrib-Cooldowns.md) contrib for making it easier to manage commands using
dynamic cooldowns between uses (owllex)
- New [Godot Protocol contrib](../Contribs/Contrib-Godotwebsocket.md) for connecting to Evennia from a client written in the open-source game engine [Godot](https://godotengine.org/) (ChrisLR).
- New [name_generator contrib](../Contribs/Contrib-Name-Generator.md) for building random real-world based or fantasy-names based on phonetic rules (InspectorCaracal)
- New [Buffs contrib](../Contribs/Contrib-Buffs.md) for managing temporary and permanent RPG status buffs effects (tegiminis)
- The existing [RPSystem contrib](../Contribs/Contrib-RPSystem.md) was refactored and saw a speed boost (InspectorCaracal, other contributors)
## Translations
- New Latin (la) translation (jamalainm)
- New German (de) translation (Zhuraj)
- Updated Italian translation (rpolve)
- Updated Swedish translation
## Utils
- New `utils.format_grid` for easily displaying long lists of items in a block. This is now used for the default help display.
- Add `utils.repeat` and `utils.unrepeat` as shortcuts to TickerHandler add/remove, similar
to how `utils.delay` is a shortcut for TaskHandler add.
- Add `utils/verb_conjugation` for automatic verb conjugation (English only). This
is useful for implementing actor-stance emoting for sending a string to different targets.
- `utils.evmenu.ask_yes_no` is a helper function that makes it easy to ask a yes/no question
to the user and respond to their input. This complements the existing `get_input` helper.
- New `tasks` command for managing tasks started with `utils.delay` (PR by davewiththenicehat)
- Add `.deserialize()` method to `_Saver*` structures to help completely
decouple structures from database without needing separate import.
- Add `run_in_main_thread` as a helper for those wanting to code server code
from a web view.
- Update `evennia.utils.logger` to use Twisted's new logging API. No change in Evennia API
except more standard aliases logger.error/info/exception/debug etc can now be used.
- Made `utils.iter_to_str` format prettier strings, using Oxford comma.
- Move `create_*` functions into db managers, leaving `utils.create` only being
wrapper functions (consistent with `utils.search`). No change of api otherwise.
## Locks
- New `search:` lock type used to completely hide an object from being found by
the `DefaultObject.search` (`caller.search`) method. (CloudKeeper)
- New default for `holds()` lockfunc - changed from default of `True` to default of `False` in order to disallow dropping nonsensical things (such as things you don't hold).
## Hook changes
- Changed all `at_before/after_*` hooks to `at_pre/post_*` for consistency
across Evennia (the old names still work but are deprecated)
- New `at_pre_object_leave(obj, destination)` method on `Objects`.
- New `at_server_init()` hook called before all other startup hooks for all
startup modes. Used for more generic overriding (volund)
- New `at_pre_object_receive(obj, source_location)` method on Objects. Called on
destination, mimicking behavior of `at_pre_move` hook - returning False will abort move.
- `Object.normalize_name` and `.validate_name` added to (by default) enforce latinify
on character name and avoid potential exploits using clever Unicode chars (trhr)
- Make `object.search` support 'stacks=0' keyword - if ``>0``, the method will return
N identical matches instead of triggering a multi-match error.
- Add `tags.has()` method for checking if an object has a tag or tags (PR by ChrisLR)
- Add `Msg.db_receiver_external` field to allowe external, string-id message-receivers.
- Add `$pron()` and `$You()` inlinefuncs for pronoun parsing in actor-stance strings using `msg_contents`.
## Command changes
- Change default multi-match syntax from `1-obj`, `2-obj` to `obj-1`, `obj-2`, which seems to be what most expect.
- Split `return_appearance` hook with helper methods and have it use a template
string in order to make it easier to override.
- Command executions now done on copies to make sure `yield` don't cause crossovers. Add
`Command.retain_instance` flag for reusing the same command instance.
- Allow sending messages with `page/tell` without a `=` if target name contains no spaces.
- The `typeclass` command will now correctly search the correct database-table for the target
obj (avoids mistakenly assigning an AccountDB-typeclass to a Character etc).
- Merged `script` and `scripts` commands into one, for both managing global- and
on-object Scripts. Moved `CmdScripts` and `CmdObjects` to `commands/default/building.py`.
- The `channel` commands replace all old channel-related commands, such as `cset` etc
- Expand `examine` command's code to much more extensible and modular. Show
attribute categories and value types (when not strings).
- Add ability to examine `/script` and `/channel` entities with `examine` command.
- Add support for `$dbref()` and `$search` when assigning an Attribute value
with the `set` command. This allows assigning real objects from in-game.
- Have `type/force` default to `update`-mode rather than `reset`mode and add more verbose
warning when using reset mode.
## Coding improvement highlights
- The db pickle-serializer now checks for methods `__serialize_dbobjs__` and `__deserialize_dbobjs__` to allow custom packing/unpacking of nested dbobjs, to allow storing in Attribute. See [Attributes](../Components/Attributes.md) documentation.
- Add `ObjectParent` mixin to default game folder template as an easy, ready-made
way to override features on all ObjectDB-inheriting objects easily.
source location, mimicking behavior of `at_pre_move` hook - returning False will abort move.
- New Unit test parent classes, for use both in Evenia core and in mygame. Restructured unit tests to always honor default settings.
## Other
- Homogenize manager search methods to always return querysets and not sometimes querysets and sometimes lists.
- Attribute/NAttribute got a homogenous representation, using intefaces, both
`AttributeHandler` and `NAttributeHandler` has same api now.
- Added `content_types` indexing to DefaultObject's ContentsHandler. (volund)
- Made most of the networking classes such as Protocols and the SessionHandlers
replaceable via `settings.py` for modding enthusiasts. (volund)
- The `initial_setup.py` file can now be substituted in `settings.py` to customize
initial game database state. (volund)
- Make IP throttle use Django-based cache system for optional persistence (PR by strikaco)
- In modules given by `settings.PROTOTYPE_MODULES`, spawner will now first look for a global
list `PROTOTYPE_LIST` of dicts before loading all dicts in the module as prototypes.
concept of a dynamically created `ChannelCmdSet`.
- Prototypes now allow setting `prototype_parent` directly to a prototype-dict.
This makes it easier when dynamically building in-module prototypes.
- Make `@lazy_property` decorator create read/delete-protected properties. This is because it's used for handlers, and e.g. self.locks=[] is a common beginner mistake.
- Change `settings.COMMAND_DEFAULT_ARG_REGEX` default from `None` to a regex meaning that
a space or `/` must separate the cmdname and args. This better fits common expectations.
- Add `settings.MXP_ENABLED=True` and `settings.MXP_OUTGOING_ONLY=True` as sane defaults, to avoid known security issues with players entering MXP links.
- Made `MonitorHandler.add/remove` support `category` for monitoring Attributes with a category (before only key was used, ignoring category entirely).

View file

@ -1,28 +1,19 @@
# Setting up PyCharm
# Setting up PyCharm with Evennia
# Directions for setting up PyCharm with Evennia
[PyCharm](https://www.jetbrains.com/pycharm/) is a Python developer's IDE from Jetbrains available for Windows, Mac and Linux. It is a commercial product but offer free trials, a scaled-down community edition and also generous licenses for OSS projects like Evennia.
[PyCharm](https://www.jetbrains.com/pycharm/) is a Python developer's IDE from Jetbrains available
for Windows, Mac and Linux. It is a commercial product but offer free trials, a scaled-down
community edition and also generous licenses for OSS projects like Evennia.
> This page was originally tested on Windows (so use Windows-style path examples), but should work the same for all platforms.
> This page was originally tested on Windows (so use Windows-style path examples), but should work
the same for all platforms.
First, install Evennia on your local machine with [[Getting Started]]. If you're new to PyCharm,
loading your project is as easy as selecting the `Open` option when PyCharm starts, and browsing to
your game folder (the one created with `evennia --init`). We refer to it as `mygame` here.
First, install Evennia on your local machine with [[Getting Started]]. If you're new to PyCharm, loading your project is as easy as selecting the `Open` option when PyCharm starts, and browsing to your game folder (the one created with `evennia --init`). We refer to it as `mygame` here.
If you want to be able to examine evennia's core code or the scripts inside your virtualenv, you'll
need to add them to your project too:
1. Go to `File > Open...`
1. Select the folder (i.e. the `evennia` root)
1. Select "Open in current window" and "Add to currently opened projects"
## Setting up the project interpreter
It's a good idea to do this before attempting anything further. The rest of this page assumes your
project is already configured in PyCharm.
It's a good idea to set up the interpreter this before attempting anything further. The rest of this page assumes your project is already configured in PyCharm.
1. Go to `File > Settings... > Project: \<mygame\> > Project Interpreter`
1. Click the Gear symbol `> Add local`
@ -30,74 +21,51 @@ project is already configured in PyCharm.
Enjoy seeing all your imports checked properly, setting breakpoints, and live variable watching!
## Attaching PyCharm debugger to Evennia
## Debug Evennia from inside PyCharm
1. Launch Evennia in your preferred way (usually from a console/terminal)
1. Open your project in PyCharm
1. In the PyCharm menu, select `Run > Attach to Local Process...`
1. From the list, pick the `twistd` process with the `server.py` parameter (Example: `twistd.exe
--nodaemon --logfile=\<mygame\>\server\logs\server.log --python=\<evennia
repo\>\evennia\server\server.py`)
1. From the list, pick the `twistd` process with the `server.py` parameter (Example: `twistd.exe --nodaemon --logfile=\<mygame\>\server\logs\server.log --python=\<evennia repo\>\evennia\server\server.py`)
Of course you can attach to the `portal` process as well. If you want to debug the Evennia launcher
or runner for some reason (or just learn how they work!), see Run Configuration below.
> NOTE: Whenever you reload Evennia, the old Server process will die and a new one start. So when
you restart you have to detach from the old and then reattach to the new process that was created.
> NOTE: Whenever you reload Evennia, the old Server process will die and a new one start. So when you restart you have to detach from the old and then reattach to the new process that was created.
> To make the process less tedious you can apply a filter in settings to show only the server.py process in the list. To do that navigate to: `Settings/Preferences | Build, Execution, Deployment | Python Debugger` and then in `Attach to process` field put in: `twistd.exe" --nodaemon`. This is an example for windows, I don't have a working mac/linux box.
> To make the process less tedious you can apply a filter in settings to show only the server.py
process in the list. To do that navigate to: `Settings/Preferences | Build, Execution, Deployment |
Python Debugger` and then in `Attach to process` field put in: `twistd.exe" --nodaemon`. This is an
example for windows, I don't have a working mac/linux box.
![Example process filter configuration](https://i.imgur.com/vkSheR8.png)
## Setting up an Evennia run configuration
## Run Evennia from inside PyCharm
This configuration allows you to launch Evennia from inside PyCharm. Besides convenience, it also
allows suspending and debugging the evennia_launcher or evennia_runner at points earlier than you
could by running them externally and attaching. In fact by the time the server and/or portal are
running the launcher will have exited already.
This configuration allows you to launch Evennia from inside PyCharm. Besides convenience, it also allows suspending and debugging the evennia_launcher or evennia_runner at points earlier than you could by running them externally and attaching. In fact by the time the server and/or portal are running the launcher will have exited already.
1. Go to `Run > Edit Configutations...`
1. Click the plus-symbol to add a new configuration and choose Python
1. Add the script: `\<yourrepo\>\evenv\Scripts\evennia_launcher.py` (substitute your virtualenv if
it's not named `evenv`)
1. Add the script: `\<yourrepo\>\evenv\Scripts\evennia_launcher.py` (substitute your virtualenv if it's not named `evenv`)
1. Set script parameters to: `start -l` (-l enables console logging)
1. Ensure the chosen interpreter is from your virtualenv
1. Set Working directory to your `mygame` folder (not evenv nor evennia)
1. You can refer to the PyCharm documentation for general info, but you'll want to set at least a
config name (like "MyMUD start" or similar).
1. You can refer to the PyCharm documentation for general info, but you'll want to set at least a config name (like "MyMUD start" or similar).
Now set up a "stop" configuration by following the same steps as above, but set your Script
parameters to: stop (and name the configuration appropriately).
Now set up a "stop" configuration by following the same steps as above, but set your Script parameters to: stop (and name the configuration appropriately).
A dropdown box holding your new configurations should appear next to your PyCharm run button.
Select MyMUD start and press the debug icon to begin debugging. Depending on how far you let the
program run, you may need to run your "MyMUD stop" config to actually stop the server, before you'll
be able start it again.
A dropdown box holding your new configurations should appear next to your PyCharm run button. Select MyMUD start and press the debug icon to begin debugging. Depending on how far you let the program run, you may need to run your "MyMUD stop" config to actually stop the server, before you'll be able start it again.
## Alternative run configuration - utilizing logfiles as source of data
### Alternative config - utilizing logfiles as source of data
This configuration takes a bit different approach as instead of focusing on getting the data back
through logfiles. Reason for that is this way you can easily separate data streams, for example you
rarely want to follow both server and portal at the same time, and this will allow it. This will
also make sure to stop the evennia before starting it, essentially working as reload command (it
will also include instructions how to disable that part of functionality). We will start by defining
a configuration that will stop evennia. This assumes that `upfire` is your pycharm project name, and
also the game name, hence the `upfire/upfire` path.
This configuration takes a bit different approach as instead of focusing on getting the data back through logfiles. Reason for that is this way you can easily separate data streams, for example you rarely want to follow both server and portal at the same time, and this will allow it. This will also make sure to stop the evennia before starting it, essentially working as reload command (it will also include instructions how to disable that part of functionality). We will start by defining a configuration that will stop evennia. This assumes that `upfire` is your pycharm project name, and also the game name, hence the `upfire/upfire` path.
1. Go to `Run > Edit Configutations...`\
1. Click the plus-symbol to add a new configuration and choose the python interpreter to use (should
be project default)
1. Click the plus-symbol to add a new configuration and choose the python interpreter to use (should be project default)
1. Name the configuration as "stop evennia" and fill rest of the fields accordingly to the image:
![Stop run configuration](https://i.imgur.com/gbkXhlG.png)
1. Press `Apply`
Now we will define the start/reload command that will make sure that evennia is not running already,
and then start the server in one go.
Now we will define the start/reload command that will make sure that evennia is not running already, and then start the server in one go.
1. Go to `Run > Edit Configutations...`\
1. Click the plus-symbol to add a new configuration and choose the python interpreter to use (should
be project default)
1. Click the plus-symbol to add a new configuration and choose the python interpreter to use (should be project default)
1. Name the configuration as "start evennia" and fill rest of the fields accordingly to the image:
![Start run configuration](https://i.imgur.com/5YEjeHq.png)
1. Navigate to the `Logs` tab and add the log files you would like to follow. The picture shows

View file

@ -0,0 +1,67 @@
# Soft Code
Softcode is a very simple programming language that was created for in-game development on TinyMUD derivatives such as MUX, PennMUSH, TinyMUSH, and RhostMUSH. The idea is that by providing a stripped down, minimalistic language for in-game use, you can allow quick and easy building and game development to happen without having to learn C/C++. There is an added benefit of not having to have to hand out shell access to all developers, and permissions can be used to alleviate many security problems.
Writing and installing softcode is done through a MUD client. Thus it is not a formatted language.
Each softcode function is a single line of varying size. Some functions can be a half of a page long
or more which is obviously not very readable nor (easily) maintainable over time.
## Examples of Softcode
Here is a simple 'Hello World!' command:
```bash
@set me=HELLO_WORLD.C:$hello:@pemit %#=Hello World!
```
Pasting this into a MUX/MUSH and typing 'hello' will theoretically yield 'Hello World!', assuming
certain flags are not set on your account object.
Setting attributes is done via `@set`. Softcode also allows the use of the ampersand (`&`) symbol.
This shorter version looks like this:
```bash
&HELLO_WORLD.C me=$hello:@pemit %#=Hello World!
```
Perhaps I want to break the Hello World into an attribute which is retrieved when emitting:
```bash
&HELLO_VALUE.D me=Hello World
&HELLO_WORLD.C me=$hello:@pemit %#=[v(HELLO_VALUE.D)]
```
The `v()` function returns the `HELLO_VALUE.D` attribute on the object that the command resides
(`me`, which is yourself in this case). This should yield the same output as the first example.
If you are still curious about how Softcode works, take a look at some external resources:
- https://wiki.tinymux.org/index.php/Softcode
- https://www.duh.com/discordia/mushman/man2x1
## Problems with Softcode
Softcode is excellent at what it was intended for: *simple things*. It is a great tool for making an interactive object, a room with ambiance, simple global commands, simple economies and coded systems. However, once you start to try to write something like a complex combat system or a higher end economy, you're likely to find yourself buried under a mountain of functions that span multiple objects across your entire code.
Not to mention, softcode is not an inherently fast language. It is not compiled, it is parsed with each calling of a function. While MUX and MUSH parsers have jumped light years ahead of where they once were they can still stutter under the weight of more complex systems if not designed properly.
## Changing Times
Now that starting text-based games is easy and an option for even the most technically inarticulate, new projects are a dime a dozen. People are starting new MUDs every day with varying levels of commitment and ability. Because of this shift from fewer, larger, well-staffed games to a bunch of small, one or two developer games, some of the benefit of softcode fades.
Softcode is great in that it allows a mid to large sized staff all work on the same game without stepping on one another's toes. As mentioned before, shell access is not necessary to develop a MUX or a MUSH. However, now that we are seeing a lot more small, one or two-man shops, the issue of shell access and stepping on each other's toes is a lot less.
## Our Solution
Evennia shuns in-game softcode for on-disk Python modules. Python is a popular, mature and
professional programming language. You code it using the conveniences of modern text editors.
Evennia developers have access to the entire library of Python modules out there in the wild - not
to mention the vast online help resources available. Python code is not bound to one-line functions
on objects but complex systems may be organized neatly into real source code modules, sub-modules, or even broken out into entire Python packages as desired.
So what is *not* included in Evennia is a MUX/MOO-like online player-coding system. Advanced coding in Evennia is primarily intended to be done outside the game, in full-fledged Python modules. Advanced building is best handled by extending Evennia's command system with your own sophisticated building commands. We feel that with a small development team you are better off using a professional source-control system (svn, git, bazaar, mercurial etc) anyway.
## Your Solution
Adding advanced and flexible building commands to your game is easy and will probably be enough to satisfy most creative builders. However, if you really, *really* want to offer online coding, there is of course nothing stopping you from adding that to Evennia, no matter our recommendations. You could even re-implement MUX' softcode in Python should you be very ambitious. The [in-game-python](../Contribs/Contrib-Ingame-Python.md) is an optional pseudo-softcode plugin aimed at developers wanting to script their game from inside it.

View file

@ -0,0 +1,306 @@
# Unit Testing
*Unit testing* means testing components of a program in isolation from each other to make sure every part works on its own before using it with others. Extensive testing helps avoid new updates causing unexpected side effects as well as alleviates general code rot (a more comprehensive wikipedia article on unit testing can be found [here](https://en.wikipedia.org/wiki/Unit_test)).
A typical unit test set calls some function or method with a given input, looks at the result and makes sure that this result looks as expected. Rather than having lots of stand-alone test programs, Evennia makes use of a central *test runner*. This is a program that gathers all available tests all over the Evennia source code (called *test suites*) and runs them all in one go. Errors and tracebacks are reported.
By default Evennia only tests itself. But you can also add your own tests to your game code and have Evennia run those for you.
## Running the Evennia test suite
To run the full Evennia test suite, go to your game folder and issue the command
evennia test evennia
This will run all the evennia tests using the default settings. You could also run only a subset of
all tests by specifying a subpackage of the library:
evennia test evennia.commands.default
A temporary database will be instantiated to manage the tests. If everything works out you will see
how many tests were run and how long it took. If something went wrong you will get error messages.
If you contribute to Evennia, this is a useful sanity check to see you haven't introduced an
unexpected bug.
## Running custom game-dir unit tests
If you have implemented your own tests for your game you can run them from your game dir
with
evennia test --settings settings.py .
The period (`.`) means to run all tests found in the current directory and all subdirectories. You
could also specify, say, `typeclasses` or `world` if you wanted to just run tests in those subdirs.
An important thing to note is that those tests will all be run using the _default Evennia settings_.
To run the tests with your own settings file you must use the `--settings` option:
evennia test --settings settings.py .
The `--settings` option of Evennia takes a file name in the `mygame/server/conf` folder. It is
normally used to swap settings files for testing and development. In combination with `test`, it
forces Evennia to use this settings file over the default one.
You can also test specific things by giving their path
evennia test --settings settings.py .world.tests.YourTest
## Writing new unit tests
Evennia's test suite makes use of Django unit test system, which in turn relies on Python's
*unittest* module.
To make the test runner find the tests, they must be put in a module named `test*.py` (so `test.py`,
`tests.py` etc). Such a test module will be found wherever it is in the package. It can be a good
idea to look at some of Evennia's `tests.py` modules to see how they look.
Inside the module you need to put a class inheriting (at any distance) from `unittest.TestCase`. Each
method on that class that starts with `test_` will be run separately as a unit test. There
are two special, optional methods `setUp` and `tearDown` that will (if you define them) run before
_every_ test. This can be useful for setting up and deleting things.
To actually test things, you use special `assert...` methods on the class. Most common on is
`assertEqual`, which makes sure a result is what you expect it to be.
Here's an example of the principle. Let's assume you put this in `mygame/world/tests.py`
and want to test a function in `mygame/world/myfunctions.py`
```python
# in a module tests.py somewhere i your game dir
import unittest
from evennia import create_object
# the function we want to test
from .myfunctions import myfunc
class TestObj(unittest.TestCase):
"This tests a function myfunc."
def setUp(self):
"""done before every of the test_ * methods below"""
self.obj = create_object("mytestobject")
def tearDown(self):
"""done after every test_* method below """
self.obj.delete()
def test_return_value(self):
"""test method. Makes sure return value is as expected."""
actual_return = myfunc(self.obj)
expected_return = "This is the good object 'mytestobject'."
# test
self.assertEqual(expected_return, actual_return)
def test_alternative_call(self):
"""test method. Calls with a keyword argument."""
actual_return = myfunc(self.obj, bad=True)
expected_return = "This is the baaad object 'mytestobject'."
# test
self.assertEqual(expected_return, actual_return)
```
To test this, run
evennia test --settings settings.py .
to run the entire test module
evennia test --settings setings.py .world.tests
or a specific class:
evennia test --settings settings.py .world.tests.TestObj
You can also run a specific test:
evennia test --settings settings.py .world.tests.TestObj.test_alternative_call
You might also want to read the [Python documentation for the unittest module](https://docs.python.org/library/unittest.html).
### Using the Evennia testing classes
Evennia offers many custom testing classes that helps with testing Evennia features.
They are all found in [evennia.utils.test_resources](evennia.utils.test_resources). Note that
these classes implement the `setUp` and `tearDown` already, so if you want to add stuff in them
yourself you should remember to use e.g. `super().setUp()` in your code.
#### Classes for testing your game dir
These all use whatever setting you pass to them and works well for testing code in your game dir.
- `EvenniaTest` - this sets up a full object environment for your test. All the created entities
can be accesses as properties on the class:
- `.account` - A fake [Account](evennia.accounts.accounts.DefaultAccount) named "TestAccount".
- `.account2` - Another account named "TestAccount2"
- `char1` - A [Character](evennia.objects.objects.DefaultCharacter) linked to `.account`, named `Char`.
This has 'Developer' permissions but is not a superuser.
- `.char2` - Another character linked to `account`, named `Char2`. This has base permissions (player).
- `.obj1` - A regular [Object](evennia.objects.objects.DefaultObject) named "Obj".
- `.obj2` - Another object named "Obj2".
- `.room1` - A [Room](evennia.objects.objects.DefaultRoom) named "Room". Both characters and both
objects are located inside this room. It has a description of "room_desc".
- `.room2` - Another room named "Room2". It is empty and has no set description.
- `.exit` - An exit named "out" that leads from `.room1` to `.room2`.
- `.script` - A [Script](evennia.scripts.scripts.DefaultScript) named "Script". It's an inert script
without a timing component.
- `.session` - A fake [Session](evennia.server.serversession.ServerSession) that mimics a player
connecting to the game. It is used by `.account1` and has a sessid of 1.
- `EvenniaCommandTest` - has the same environment like `EvenniaTest` but also adds a special
[.call()](evennia.utils.test_resources.EvenniaCommandTestMixin.call) method specifically for
testing Evennia [Commands](../Components/Commands.md). It allows you to compare what the command _actually_
returns to the player with what you expect. Read the `call` api doc for more info.
- `EvenniaTestCase` - This is identical to the regular Python `TestCase` class, it's
just there for naming symmetry with `BaseEvenniaTestCase` below.
Here's an example of using `EvenniaTest`
```python
# in a test module
from evennia.utils.test_resources import EvenniaTest
class TestObject(EvenniaTest):
"""Remember that the testing class creates char1 and char2 inside room1 ..."""
def test_object_search_character(self):
"""Check that char1 can search for char2 by name"""
self.assertEqual(self.char1.search(self.char2.key), self.char2)
def test_location_search(self):
"""Check so that char1 can find the current location by name"""
self.assertEqual(self.char1.search(self.char1.location.key), self.char1.location)
# ...
```
This example tests a custom command.
```python
from evennia.commands.default.tests import EvenniaCommandTest
from commands import command as mycommand
class TestSet(EvenniaCommandTest):
"tests the look command by simple call, using Char2 as a target"
def test_mycmd_char(self):
self.call(mycommand.CmdMyLook(), "Char2", "Char2(#7)")
def test_mycmd_room(self):
"tests the look command by simple call, with target as room"
self.call(mycommand.CmdMyLook(), "Room",
"Room(#1)\nroom_desc\nExits: out(#3)\n"
"You see: Obj(#4), Obj2(#5), Char2(#7)")
```
When using `.call`, you don't need to specify the entire string; you can just give the beginning
of it and if it matches, that's enough. Use `\n` to denote line breaks and (this is a special for
the `.call` helper), `||` to indicate multiple uses of `.msg()` in the Command. The `.call` helper
has a lot of arguments for mimicing different ways of calling a Command, so make sure to
[read the API docs for .call()](evennia.utils.test_resources.EvenniaCommandTestMixin.call).
#### Classes for testing Evennia core
These are used for testing Evennia itself. They provide the same resources as the classes
above but enforce Evennias default settings found in `evennia/settings_default.py`, ignoring
any settings changes in your game dir.
- `BaseEvenniaTest` - all the default objects above but with enforced default settings
- `BaseEvenniaCommandTest` - for testing Commands, but with enforced default settings
- `BaseEvenniaTestCase` - no default objects, only enforced default settings
There are also two special 'mixin' classes. These are uses in the classes above, but may also
be useful if you want to mix your own testing classes:
- `EvenniaTestMixin` - A class mixin that creates all test environment objects.
- `EvenniaCommandMixin` - A class mixin that adds the `.call()` Command-tester helper.
If you want to help out writing unittests for Evennia, take a look at Evennia's [coveralls.io
page](https://coveralls.io/github/evennia/evennia). There you see which modules have any form of
test coverage and which does not. All help is appreciated!
### Unit testing contribs with custom models
A special case is if you were to create a contribution to go to the `evennia/contrib` folder that
uses its [own database models](../Concepts/Models.md). The problem with this is that Evennia (and Django) will
only recognize models in `settings.INSTALLED_APPS`. If a user wants to use your contrib, they will
be required to add your models to their settings file. But since contribs are optional you cannot
add the model to Evennia's central `settings_default.py` file - this would always create your
optional models regardless of if the user wants them. But at the same time a contribution is a part
of the Evennia distribution and its unit tests should be run with all other Evennia tests using
`evennia test evennia`.
The way to do this is to only temporarily add your models to the `INSTALLED_APPS` directory when the test runs. here is an example of how to do it.
> Note that this solution, derived from this [stackexchange answer](http://stackoverflow.com/questions/502916/django-how-to-create-a-model-dynamically-just-for-testing#503435) is currently untested! Please report your findings.
```python
# a file contrib/mycontrib/tests.py
from django.conf import settings
import django
from evennia.utils.test_resources import BaseEvenniaTest
OLD_DEFAULT_SETTINGS = settings.INSTALLED_APPS
DEFAULT_SETTINGS = dict(
INSTALLED_APPS=(
'contrib.mycontrib.tests',
),
DATABASES={
"default": {
"ENGINE": "django.db.backends.sqlite3"
}
},
SILENCED_SYSTEM_CHECKS=["1_7.W001"],
)
class TestMyModel(BaseEvenniaTest):
def setUp(self):
if not settings.configured:
settings.configure(**DEFAULT_SETTINGS)
django.setup()
from django.core.management import call_command
from django.db.models import loading
loading.cache.loaded = False
call_command('syncdb', verbosity=0)
def tearDown(self):
settings.configure(**OLD_DEFAULT_SETTINGS)
django.setup()
from django.core.management import call_command
from django.db.models import loading
loading.cache.loaded = False
call_command('syncdb', verbosity=0)
# test cases below ...
def test_case(self):
# test case here
```
### A note on making the test runner faster
If you have custom models with a large number of migrations, creating the test database can take a very long time. If you don't require migrations to run for your tests, you can disable them with the
django-test-without-migrations package. To install it, simply:
```
$ pip install django-test-without-migrations
```
Then add it to your `INSTALLED_APPS` in your `server.conf.settings.py`:
```python
INSTALLED_APPS = (
# ...
'test_without_migrations',
)
```
After doing so, you can then run tests without migrations by adding the `--nomigrations` argument:
```
evennia test --settings settings.py --nomigrations .
```

View file

@ -0,0 +1,344 @@
# Coding using Version Control
[Version control](https://en.wikipedia.org/wiki/Version_control) allows you to track changes to your code. You can save 'snapshots' of your progress which means you can roll back undo things easily. Version control also allows you to easily back up your code to an online _repository_ such as Github. It also allows you to collaborate with others on the same code without clashing or worry about who changed what.
```{sidebar} Do it!
It's _strongly_ recommended that you [put your game folder under version control](#putting-your-game-dir-under-version-control). Using git is is also the way to contribue to Evennia itself.
```
Evennia uses the most commonly used version control system, [Git](https://git-scm.com/) . For additional help on using Git, please refer to the [Official GitHub documentation](https://help.github.com/articles/set-up-git#platform-all).
## Setting up Git
- **Fedora Linux**
yum install git-core
- **Debian Linux** _(Ubuntu, Linux Mint, etc.)_
apt-get install git
- **Windows**: It is recommended to use [Git for Windows](https://gitforwindows.org/).
- **Mac**: Mac platforms offer two methods for installation, one via MacPorts, which you can find out about [here](https://git-scm.com/book/en/Getting-Started-Installing-Git#Installing-on-Mac), or you can use the [Git OSX Installer](https://sourceforge.net/projects/git-osx-installer/).
> You can find expanded instructions for installation [here](https://git-scm.com/book/en/Getting-Started-Installing-Git).
```{sidebar} Git user nickname
If you ever make your code available online (or contribute to Evennia), your name will be visible to those reading the code-commit history. So if you are not comfortable with using your real, full name online, put a nickname (or your github handler) here.
```
To avoid a common issue later, you will need to set a couple of settings; first you will need to tell Git your username, followed by your e-mail address, so that when you commit code later you will be properly credited.
1. Set the default name for git to use when you commit:
git config --global user.name "Your Name Here"
2. Set the default email for git to use when you commit:
git config --global user.email "your_email@example.com"
> To get a running start with Git, here's [a good YouTube talk about it](https://www.youtube.com/watch?v=1ffBJ4sVUb4#t=1m58s). It's a bit long but it will help you understand the underlying ideas behind GIT (which in turn makes it a lot more intuitive to use).
## Common Git commands
```{sidebar} Git repository
This is just a fancy name for the folder you have designated to be under version control. We will make your `mygame` game folder into such a repository. The Evennia code is also in a (separate) git repository.
```
Git can be controlled via a GUI. But it's often easier to use the base terminal/console commands, since it makes it clear if something goes wrong.
All these actions need to be done from inside the _git repository_ .
Git may seem daunting at first. But when working with git, you'll be using the same 2-3 commands 99% of the time. And you can make git _aliases_ to have them be even easier to remember.
### `git init`
This initializes a folder/directory on your drive as a 'git repository'
git init .
The `.` means to apply to the current directory. If you are inside `mygame`, this makes your game dir into a git repository. That's all there is to it, really. You only need to do this once.
### `git add`
git add <file>
This tells Git to start to _track_ the file under version control. You need to do this when you create a new file. You can also add all files in your current directory:
git add .
Or
git add *
All files in the current directory are now tracked by Git. You only need to do this once for every file you want to track.
### `git commit`
git commit -a -m "This is the initial commit"
This _commits_ your changes. It stores a snapshot of all (`-a`) your code at the current time, adding a message `-m` so you know what you did. Later you can _check out_ your code the way it was at a given time. The message is mandatory and you will thank yourself later if write clear and descriptive log messages. If you don't add `-m`, a text editor opens for you to write the message instead.
The `git commit` is something you'll be using all the time, so it can be useful to make a _git alias_ for it:
git config --global alias.cma 'commit -a -m'
After you've run this, you can commit much simpler, like this:
git cma "This is the initial commit"
Much easier to remember!
### `git status`, `git diff` and `git log`
git status -s
This gives a short (`-s`) of the files that changes since your last `git commit`.
git diff --word-diff`
This shows exactly what changed in each file since you last made a `git commit`. The `--word-diff` option means it will mark if a single word changed on a line.
git log
This shows the log of all `commits` done. Each log will show you who made the change, the commit-message and a unique _hash_ (like `ba214f12ab12e123...`) that uniquely describes that commit.
You can make the `log` command more succinct with some more options:
ls=log --pretty=format:%C(green)%h\ %C(yellow)[%ad]%Cred%d\ %Creset%s%Cblue\ [%an] --decorate --date=relative
This adds coloration and another fancy effects (use `git help log` to see what they mean).
Let's add aliases:
git config --global alias.st 'status -s'
git config --global alias.df 'diff --word-diff'
git config --global alias.ls 'log --pretty=format:%C(green)%h\ %C(yellow)[%ad]%Cred%d\ %Creset%s%Cblue\ [%an] --decorate --date=relative'
You can now use the much shorter
git st # short status
git dif # diff with word-marking
git ls # log with pretty formatting
for these useful functions.
### `git branch`, `checkout` and `merge`
Git allows you to work with _branches_. These are separate development paths your code may take, completely separate from each other. You can later _merge_ the code from a branch back into another branch. Evennia's `main` and `develop` branches are examples of this.
git branch -b branchaname
This creates a new branch, exactly identical to the branch you were on. It also moves you to that branch.
git branch -D branchname
Deletes a branch.
git branch
Shows all your branches, marking which one you are currently on.
git checkout branchname
This checks out another branch. As long as you are in a branch all `git commit`s will commit the code to that branch only.
git checkout .
This checks out your _current branch_ and has the effect of throwing away all your changes since your last commit. This is like undoing what you did since the last save point.
git checkout b2342bc21c124
This checks out a particular _commit_, identified by the hash you find with `git log`. This open a 'temporary branch' where the code is as it was when you made this commit. As an example, you can use this to check where a bug was introduced. Check out an existing branch to go back to your normal timeline, or use `git branch -b newbranch` to break this code off into a new branch you can continue working from.
git merge branchname
This _merges_ the code from `branchname` into the branch you are currently in. Doing so may lead to _merge conflicts_ if the same code changed in different ways in the two branches. See [how to resolve merge conflicts in git](https://phoenixnap.com/kb/how-to-resolve-merge-conflicts-in-git) for more help.
### `git glone`, `git push` and `git pull`
All of these other commands have dealt with code only sitting in your local repository-folder. These commands instead allows you to exchange code with a _remote_ repository - usually one that is online (like on github).
> How you actually set up a remote repository is described [in the next section](#pushing-your-code-online).
git clone repository/path
This copies the remote repository to your current location. If you used the [Git installation instructions](../Setup/Installation-Git.md) to install Evennia, this is what you used to get your local copy of the Evennia repository.
git pull
Once you cloned or otherwise set up a remote repository, using `git pull` will re-sync the remote with what you have locally. If what you download clashes with local changes, git will force you to `git commit` your changes before you can continue with `git pull`.
git push
This uploads your local changes _of your current branch_ to the same-named branch on the remote repository. To be able to do this you must have write-permissions to the remote repository.
### Other git commands
There are _many_ other git commands. Read up on them online:
git reflog
Shows hashes of individual git actions. This allows you to go back in the git event history itself.
git reset
Force reset a branch to an earlier commit. This could throw away some history, so be careful.
git grep -n -I -i <query>
Quickly search for a phrase/text in all files tracked by git. Very useful to quickly find where things are. Set up an alias `git gr` with
```
git config --global alias.gr 'grep -n -I -i'
```
## Putting your game dir under version control
This makes use of the git commands listed in the previous section.
```{sidebar} git aliases
If you set up the git aliases for commands suggested in the previous section, you can use them instead!
```
cd mygame
git init .
git add *
git commit -a -m "Initial commit"
Your game-dir is now tracked by git.
You will notice that some files are not covered by your git version control, notably your secret-settings file (`mygame/server/conf/secret_settings.py`) and your sqlite3 database file `mygame/server/evennia.db3`. This is intentional and controlled from the file `mygame/.gitignore`.
```{warning}
You should *never* put your sqlite3 database file into git by removing its entry
in `.gitignore`. GIT is for backing up your code, not your database. That way
lies madness and a good chance you'll confuse yourself. Make one mistake or local change and after a few commits and reverts you will have lost track of what is in your database or not. If you want to backup your SQlite3 database, do so by simply copying the database file to a safe location.
```
### Pushing your code online
So far your code is only located on your private machine. A good idea is to back it up online. The easiest way to do this is to `git push` it to your own remote repository on GitHub. So for this you need a (free) Github account.
If you don't want your code to be publicly visible, Github also allows you set up a _private_ repository, only visible to you.
Create a new, empty repository on Github. [Github explains how here](https://help.github.com/articles/create-a-repo/) . _Don't_ allow it to add a README, license etc, that will just clash with what we upload later.
```{sidebar} Origin
We label the remote repository 'origin'. This is the git default and means we won't need to specify it explicitly later.
```
Make sure you are in your local game dir (previously initialized as a git repo).
git remote add origin <github URL>
This tells Git that there is a remote repository at `<github URL>`. See the github docs as to which URL to use. Verify that the remote works with `git remote -v`
Now we push to the remote (labeled 'origin' which is the default):
git push
Depending on how you set up your authentication with github, you may be asked to enter your github username and password. If you set up SSH authentication, this command will just work.
You use `git push` to upload your local changes so the remote repository is in sync with your local one. If you edited a file online using the Github editor (or a collaborator pushed code), you use `git pull` to sync in the other direction.
## Contributing to Evennia
If you want to help contributing to Evennia you must do so by _forking_ - making your own remote copy of the Evennia repository on Github. So for this, you need a (free) Github account. Doing so is a completely separate process from [putting your game dir under version control](#putting-your-game-dir-under-version-control) (which you should also do!).
At the top right of [the evennia github page](https://github.com/evennia/evennia), click the "Fork" button:
![fork button](../_static/images/fork_button.png)
This will create a new online fork Evennia under your github account.
The fork only exists online as of yet. In a terminal, `cd` to the folder you wish to develop in. This folder should _not_ be your game dir, nor the place you cloned Evennia into if you used the [Git installation](../Setup/Installation-Git.md).
From this directory run the following command:
git clone https://github.com/yourusername/evennia.git evennia
This will download your fork to your computer. It creates a new folder `evennia/` at your current location. If you installed Evennia using the [Git installation](../Setup/Installation-Git.md), this folder will be identical in content to the `evennia` folder you cloned during that installation. The difference is that this repo is connected to your remote fork and not to the 'original' _upstream_ Evennia.
When we cloned our fork, git automatically set up a 'remote repository' labeled `origin` pointing to it. So if we do `git pull` and `git push`, we'll push to our fork.
We now want to add a second remote repository linked to the original Evennia repo. We will label this remote repository `upstream`:
cd evennia
git remote add upstream https://github.com/evennia/evennia.git
If you also want to access Evennia's `develop` branch (the bleeding edge development) do the following:
git fetch upstream develop
git checkout develop
Use
git checkout main
git checkout develop
to switch between the branches.
To pull the latest from upstream Evennia, just checkout the branch you want and do
git pull upstream
```{sidebar} Pushing to upstream
You can't do `git push upstream` unless you have write-access to the upstream Evennia repository. So there is no risk of you accidentally pushing your own code into the main, public repository.
```
### Fixing an Evennia bug or feature
This should be done in your fork of Evennia. You should _always_ do this in a _separate git branch_ based off the Evennia branch you want to improve.
git checkout main (or develop)
git branch -b myfixbranch
Now fix whatever needs fixing. Abide by the [Evennia code style](./Evennia-Code-Style.md). You can `git commit` commit your changes along the way as normal.
Upstream Evennia is not standing still, so you want to make sure that your work is up-to-date with upstream changes. Make sure to first commit your `myfixbranch` changes, then
git checkout main (or develop)
git pull upstream
git checkout myfixbranch
git merge main (or develop)
Up to this point your `myfixbranch` branch only exists on your local computer. No
one else can see it.
git push
This will automatically create a matching `myfixbranch` in your forked version of Evennia and push to it. On github you will be able to see appear it in the `branches` dropdown. You can keep pushing to your remote `myfixbranch` as much as you like.
Once you feel you have something to share, you need to [create a pull request](https://github.com/evennia/evennia/pulls) (PR):
This is a formal request for upstream Evennia to adopt and pull your code into the main repository.
1. Click `New pull request`
2. Choose `compare across forks`
3. Select your fork from dropdown list of `head repository` repos. Pick the right branch to `compare`.
4. On the Evennia side (to the left) make sure to pick the right `base` branch: If you want to contribute a change to the `develop` branch, you must pick `develop` as the `base`.
5. Then click `Create pull request` and fill in as much information as you can in the form.
6. Optional: Once you saved your PR, you can go into your code (on github) and add some per-line comments; this can help reviewers by explaining complex code or decisions you made.
Now you just need to wait for your code to be reviewed. Expect to get feedback and be asked to make changes, add more documentation etc. Getting as PR merged can take a few iterations.
```{sidebar} Not all PRs can merge
While most PRs get merged, Evennia can't **guarantee** that your PR code will be deemed suitable to merge into upstream Evennia. For this reason it's a good idea to check in with the community _before_ you spend a lot of time on a large piece of code (fixing bugs is always a safe bet though!)
```
## Troubleshooting
### Getting 403: Forbidden access
Some users have experienced this on `git push` to their remote repository. They are not asked for username/password (and don't have a ssh key set up).
Some users have reported that the workaround is to create a file `.netrc` under your home directory and add your github credentials there:
```bash
machine github.com
login <my_github_username>
password <my_github_password>
```

View file

@ -1,98 +0,0 @@
# Command Cooldown
Some types of games want to limit how often a command can be run. If a
character casts the spell *Firestorm*, you might not want them to spam that
command over and over. Or in an advanced combat system, a massive swing may
offer a chance of lots of damage at the cost of not being able to re-do it for
a while. Such effects are called *cooldowns*.
This page exemplifies a very resource-efficient way to do cooldowns. A more
'active' way is to use asynchronous delays as in the [command duration
tutorial](./Command-Duration.md#blocking-commands), the two might be useful to
combine if you want to echo some message to the user after the cooldown ends.
## Non-persistent cooldown
This little recipe will limit how often a particular command can be run. Since
Commands are class instances, and those are cached in memory, a command
instance will remember things you store on it. So just store the current time
of execution! Next time the command is run, it just needs to check if it has
that time stored, and compare it with the current time to see if a desired
delay has passed.
```python
import time
from evennia import default_cmds
class CmdSpellFirestorm(default_cmds.MuxCommand):
"""
Spell - Firestorm
Usage:
cast firestorm <target>
This will unleash a storm of flame. You can only release one
firestorm every five minutes (assuming you have the mana).
"""
key = "cast firestorm"
locks = "cmd:isFireMage()"
def func(self):
"Implement the spell"
# check cooldown (5 minute cooldown)
now = time.time()
if hasattr(self, "lastcast") and \
now - self.lastcast < 5 * 60:
message = "You cannot cast this spell again yet."
self.caller.msg(message)
return
#[the spell effect is implemented]
# if the spell was successfully cast, store the casting time
self.lastcast = now
```
We just check the `lastcast` flag, and update it if everything works out.
Simple and very effective since everything is just stored in memory. The
drawback of this simple scheme is that it's non-persistent. If you do
`@reload`, the cache is cleaned and all such ongoing cooldowns will be
forgotten. It is also limited only to this one command, other commands cannot
(easily) check for this value.
## Persistent cooldown
This is essentially the same mechanism as the simple one above, except we use
the database to store the information which means the cooldown will survive a
server reload/reboot. Since commands themselves have no representation in the
database, you need to use the caster for the storage.
```python
# inside the func() of CmdSpellFirestorm as above
# check cooldown (5 minute cooldown)
now = time.time()
lastcast = self.caller.db.firestorm_lastcast
if lastcast and now - lastcast < 5 * 60:
message = "You need to wait before casting this spell again."
self.caller.msg(message)
return
#[the spell effect is implemented]
# if the spell was successfully cast, store the casting time
self.caller.db.firestorm_lastcast = now
```
Since we are storing as an [Attribute](./Attributes.md), we need to identify the
variable as `firestorm_lastcast` so we are sure we get the right one (we'll
likely have other skills with cooldowns after all). But this method of
using cooldowns also has the advantage of working *between* commands - you can
for example let all fire-related spells check the same cooldown to make sure
the casting of *Firestorm* blocks all fire-related spells for a while. Or, in
the case of taking that big swing with the sword, this could now block all
other types of attacks for a while before the warrior can recover.

View file

@ -1,403 +0,0 @@
# Command Duration
Before reading this tutorial, if you haven't done so already, you might want to
read [the documentation on commands](./Commands.md) to get a basic understanding of
how commands work in Evennia.
In some types of games a command should not start and finish immediately.
Loading a crossbow might take a bit of time to do - time you don't have when
the enemy comes rushing at you. Crafting that armour will not be immediate
either. For some types of games the very act of moving or changing pose all
comes with a certain time associated with it.
## The simple way to pause commands with yield
Evennia allows a shortcut in syntax to create simple pauses in commands. This
syntax uses the `yield` keyword. The `yield` keyword is used in Python to
create generators, although you don't need to know what generators are to use
this syntax. A short example will probably make it clear:
```python
class CmdTest(Command):
"""
A test command just to test waiting.
Usage:
test
"""
key = "test"
locks = "cmd:all()"
def func(self):
self.msg("Before ten seconds...")
yield 10
self.msg("Afterwards.")
```
> Important: The `yield` functionality will *only* work in the `func` method of
> Commands. It only works because Evennia has especially
> catered for it in Commands. If you want the same functionality elsewhere you
> must use the [interactive decorator](./Async-Process.md#the-interactive-decorator).
The important line is the `yield 10`. It tells Evennia to "pause" the command
and to wait for 10 seconds to execute the rest. If you add this command and
run it, you'll see the first message, then, after a pause of ten seconds, the
next message. You can use `yield` several times in your command.
This syntax will not "freeze" all commands. While the command is "pausing",
you can execute other commands (or even call the same command again). And
other players aren't frozen either.
> Note: this will not save anything in the database. If you reload the game
> while a command is "paused", it will not resume after the server has
> reloaded.
## The more advanced way with utils.delay
The `yield` syntax is easy to read, easy to understand, easy to use. But it's not that flexible if
you want more advanced options. Learning to use alternatives might be much worth it in the end.
Below is a simple command example for adding a duration for a command to finish.
```python
from evennia import default_cmds, utils
class CmdEcho(default_cmds.MuxCommand):
"""
wait for an echo
Usage:
echo <string>
Calls and waits for an echo
"""
key = "echo"
locks = "cmd:all()"
def func(self):
"""
This is called at the initial shout.
"""
self.caller.msg("You shout '%s' and wait for an echo ..." % self.args)
# this waits non-blocking for 10 seconds, then calls self.echo
utils.delay(10, self.echo) # call echo after 10 seconds
def echo(self):
"Called after 10 seconds."
shout = self.args
string = "You hear an echo: %s ... %s ... %s"
string = string % (shout.upper(), shout.capitalize(), shout.lower())
self.caller.msg(string)
```
Import this new echo command into the default command set and reload the server. You will find that
it will take 10 seconds before you see your shout coming back. You will also find that this is a
*non-blocking* effect; you can issue other commands in the interim and the game will go on as usual.
The echo will come back to you in its own time.
### About utils.delay()
`utils.delay(timedelay, callback, persistent=False, *args, **kwargs)` is a useful function. It will
wait `timedelay` seconds, then call the `callback` function, optionally passing to it the arguments
provided to utils.delay by way of *args and/or **kwargs`.
> Note: The callback argument should be provided with a python path to the desired function, for
instance `my_object.my_function` instead of `my_object.my_function()`. Otherwise my_function would
get called and run immediately upon attempting to pass it to the delay function.
If you want to provide arguments for utils.delay to use, when calling your callback function, you
have to do it separatly, for instance using the utils.delay *args and/or **kwargs, as mentioned
above.
> If you are not familiar with the syntax `*args` and `**kwargs`, [see the Python documentation
here](https://docs.python.org/2/tutorial/controlflow.html#arbitrary-argument-lists).
Looking at it you might think that `utils.delay(10, callback)` in the code above is just an
alternative to some more familiar thing like `time.sleep(10)`. This is *not* the case. If you do
`time.sleep(10)` you will in fact freeze the *entire server* for ten seconds! The `utils.delay()`is
a thin wrapper around a Twisted
[Deferred](http://twistedmatrix.com/documents/11.0.0/core/howto/defer.html) that will delay
execution until 10 seconds have passed, but will do so asynchronously, without bothering anyone else
(not even you - you can continue to do stuff normally while it waits to continue).
The point to remember here is that the `delay()` call will not "pause" at that point when it is
called (the way `yield` does in the previous section). The lines after the `delay()` call will
actually execute *right away*. What you must do is to tell it which function to call *after the time
has passed* (its "callback"). This may sound strange at first, but it is normal practice in
asynchronous systems. You can also link such calls together as seen below:
```python
from evennia import default_cmds, utils
class CmdEcho(default_cmds.MuxCommand):
"""
waits for an echo
Usage:
echo <string>
Calls and waits for an echo
"""
key = "echo"
locks = "cmd:all()"
def func(self):
"This sets off a chain of delayed calls"
self.caller.msg("You shout '%s', waiting for an echo ..." % self.args)
# wait 2 seconds before calling self.echo1
utils.delay(2, self.echo1)
# callback chain, started above
def echo1(self):
"First echo"
self.caller.msg("... %s" % self.args.upper())
# wait 2 seconds for the next one
utils.delay(2, self.echo2)
def echo2(self):
"Second echo"
self.caller.msg("... %s" % self.args.capitalize())
# wait another 2 seconds
utils.delay(2, callback=self.echo3)
def echo3(self):
"Last echo"
self.caller.msg("... %s ..." % self.args.lower())
```
The above version will have the echoes arrive one after another, each separated by a two second
delay.
> echo Hello!
... HELLO!
... Hello!
... hello! ...
## Blocking commands
As mentioned, a great thing about the delay introduced by `yield` or `utils.delay()` is that it does
not block. It just goes on in the background and you are free to play normally in the interim. In
some cases this is not what you want however. Some commands should simply "block" other commands
while they are running. If you are in the process of crafting a helmet you shouldn't be able to also
start crafting a shield at the same time, or if you just did a huge power-swing with your weapon you
should not be able to do it again immediately.
The simplest way of implementing blocking is to use the technique covered in the [Command
Cooldown](./Command-Cooldown.md) tutorial. In that tutorial we implemented cooldowns by having the
Command store the current time. Next time the Command was called, we compared the current time to
the stored time to determine if enough time had passed for a renewed use. This is a *very*
efficient, reliable and passive solution. The drawback is that there is nothing to tell the Player
when enough time has passed unless they keep trying.
Here is an example where we will use `utils.delay` to tell the player when the cooldown has passed:
```python
from evennia import utils, default_cmds
class CmdBigSwing(default_cmds.MuxCommand):
"""
swing your weapon in a big way
Usage:
swing <target>
Makes a mighty swing. Doing so will make you vulnerable
to counter-attacks before you can recover.
"""
key = "bigswing"
locks = "cmd:all()"
def func(self):
"Makes the swing"
if self.caller.ndb.off_balance:
# we are still off-balance.
self.caller.msg("You are off balance and need time to recover!")
return
# [attack/hit code goes here ...]
self.caller.msg("You swing big! You are off balance now.")
# set the off-balance flag
self.caller.ndb.off_balance = True
# wait 8 seconds before we can recover. During this time
# we won't be able to swing again due to the check at the top.
utils.delay(8, self.recover)
def recover(self):
"This will be called after 8 secs"
del self.caller.ndb.off_balance
self.caller.msg("You regain your balance.")
```
Note how, after the cooldown, the user will get a message telling them they are now ready for
another swing.
By storing the `off_balance` flag on the character (rather than on, say, the Command instance
itself) it can be accessed by other Commands too. Other attacks may also not work when you are off
balance. You could also have an enemy Command check your `off_balance` status to gain bonuses, to
take another example.
## Abortable commands
One can imagine that you will want to abort a long-running command before it has a time to finish.
If you are in the middle of crafting your armor you will probably want to stop doing that when a
monster enters your smithy.
You can implement this in the same way as you do the "blocking" command above, just in reverse.
Below is an example of a crafting command that can be aborted by starting a fight:
```python
from evennia import utils, default_cmds
class CmdCraftArmour(default_cmds.MuxCommand):
"""
Craft armour
Usage:
craft <name of armour>
This will craft a suit of armour, assuming you
have all the components and tools. Doing some
other action (such as attacking someone) will
abort the crafting process.
"""
key = "craft"
locks = "cmd:all()"
def func(self):
"starts crafting"
if self.caller.ndb.is_crafting:
self.caller.msg("You are already crafting!")
return
if self._is_fighting():
self.caller.msg("You can't start to craft "
"in the middle of a fight!")
return
# [Crafting code, checking of components, skills etc]
# Start crafting
self.caller.ndb.is_crafting = True
self.caller.msg("You start crafting ...")
utils.delay(60, self.step1)
def _is_fighting(self):
"checks if we are in a fight."
if self.caller.ndb.is_fighting:
del self.caller.ndb.is_crafting
return True
def step1(self):
"first step of armour construction"
if self._is_fighting():
return
self.msg("You create the first part of the armour.")
utils.delay(60, callback=self.step2)
def step2(self):
"second step of armour construction"
if self._is_fighting():
return
self.msg("You create the second part of the armour.")
utils.delay(60, step3)
def step3(self):
"last step of armour construction"
if self._is_fighting():
return
# [code for creating the armour object etc]
del self.caller.ndb.is_crafting
self.msg("You finalize your armour.")
# example of a command that aborts crafting
class CmdAttack(default_cmds.MuxCommand):
"""
attack someone
Usage:
attack <target>
Try to cause harm to someone. This will abort
eventual crafting you may be currently doing.
"""
key = "attack"
aliases = ["hit", "stab"]
locks = "cmd:all()"
def func(self):
"Implements the command"
self.caller.ndb.is_fighting = True
# [...]
```
The above code creates a delayed crafting command that will gradually create the armour. If the
`attack` command is issued during this process it will set a flag that causes the crafting to be
quietly canceled next time it tries to update.
## Persistent delays
In the latter examples above we used `.ndb` storage. This is fast and easy but it will reset all
cooldowns/blocks/crafting etc if you reload the server. If you don't want that you can replace
`.ndb` with `.db`. But even this won't help because the `yield` keyword is not persisent and nor is
the use of `delay` shown above. To resolve this you can use `delay` with the `persistent=True`
keyword. But wait! Making something persistent will add some extra complications, because now you
must make sure Evennia can properly store things to the database.
Here is the original echo-command reworked to function with persistence:
```python
from evennia import default_cmds, utils
# this is now in the outermost scope and takes two args!
def echo(caller, args):
"Called after 10 seconds."
shout = args
string = "You hear an echo: %s ... %s ... %s"
string = string % (shout.upper(), shout.capitalize(), shout.lower())
caller.msg(string)
class CmdEcho(default_cmds.MuxCommand):
"""
wait for an echo
Usage:
echo <string>
Calls and waits for an echo
"""
key = "echo"
locks = "cmd:all()"
def func(self):
"""
This is called at the initial shout.
"""
self.caller.msg("You shout '%s' and wait for an echo ..." % self.args)
# this waits non-blocking for 10 seconds, then calls echo(self.caller, self.args)
utils.delay(10, echo, self.caller, self.args, persistent=True) # changes!
```
Above you notice two changes:
- The callback (`echo`) was moved out of the class and became its own stand-alone function in the
outermost scope of the module. It also now takes `caller` and `args` as arguments (it doesn't have
access to them directly since this is now a stand-alone function).
- `utils.delay` specifies the `echo` function (not `self.echo` - it's no longer a method!) and sends
`self.caller` and `self.args` as arguments for it to use. We also set `persistent=True`.
The reason for this change is because Evennia needs to `pickle` the callback into storage and it
cannot do this correctly when the method sits on the command class. Now this behave the same as the
first version except if you reload (or even shut down) the server mid-delay it will still fire the
callback when the server comes back up (it will resume the countdown and ignore the downtime).

View file

@ -1,9 +0,0 @@
# Command System
- [Commands](./Commands.md)
- [Command Sets](./Command-Sets.md)
- [Command Auto-help](./Help-System.md#command-auto-help-system)
See also:
- [Default Command Help](./Default-Commands.md)
- [Adding Command Tutorial](./Adding-Command-Tutorial.md)

View file

@ -1,663 +0,0 @@
# Commands
Commands are intimately linked to [Command Sets](./Command-Sets.md) and you need to read that page too to
be familiar with how the command system works. The two pages were split for easy reading.
The basic way for users to communicate with the game is through *Commands*. These can be commands
directly related to the game world such as *look*, *get*, *drop* and so on, or administrative
commands such as *examine* or *@dig*.
The [default commands](./Default-Commands.md) coming with Evennia are 'MUX-like' in that they use @
for admin commands, support things like switches, syntax with the '=' symbol etc, but there is
nothing that prevents you from implementing a completely different command scheme for your game. You
can find the default commands in `evennia/commands/default`. You should not edit these directly -
they will be updated by the Evennia team as new features are added. Rather you should look to them
for inspiration and inherit your own designs from them.
There are two components to having a command running - the *Command* class and the [Command
Set](./Command-Sets.md) (command sets were split into a separate wiki page for ease of reading).
1. A *Command* is a python class containing all the functioning code for what a command does - for
example, a *get* command would contain code for picking up objects.
1. A *Command Set* (often referred to as a CmdSet or cmdset) is like a container for one or more
Commands. A given Command can go into any number of different command sets. Only by putting the
command set on a character object you will make all the commands therein available to use by that
character. You can also store command sets on normal objects if you want users to be able to use the
object in various ways. Consider a "Tree" object with a cmdset defining the commands *climb* and
*chop down*. Or a "Clock" with a cmdset containing the single command *check time*.
This page goes into full detail about how to use Commands. To fully use them you must also read the
page detailing [Command Sets](./Command-Sets.md). There is also a step-by-step [Adding Command
Tutorial](./Adding-Command-Tutorial.md) that will get you started quickly without the extra explanations.
## Defining Commands
All commands are implemented as normal Python classes inheriting from the base class `Command`
(`evennia.Command`). You will find that this base class is very "bare". The default commands of
Evennia actually inherit from a child of `Command` called `MuxCommand` - this is the class that
knows all the mux-like syntax like `/switches`, splitting by "=" etc. Below we'll avoid mux-
specifics and use the base `Command` class directly.
```python
# basic Command definition
from evennia import Command
class MyCmd(Command):
"""
This is the help-text for the command
"""
key = "mycommand"
def parse(self):
# parsing the command line here
def func(self):
# executing the command here
```
Here is a minimalistic command with no custom parsing:
```python
from evennia import Command
class CmdEcho(Command):
key = "echo"
def func(self):
# echo the caller's input back to the caller
self.caller.msg("Echo: {}".format(self.args)
```
You define a new command by assigning a few class-global properties on your inherited class and
overloading one or two hook functions. The full gritty mechanic behind how commands work are found
towards the end of this page; for now you only need to know that the command handler creates an
instance of this class and uses that instance whenever you use this command - it also dynamically
assigns the new command instance a few useful properties that you can assume to always be available.
### Who is calling the command?
In Evennia there are three types of objects that may call the command. It is important to be aware
of this since this will also assign appropriate `caller`, `session`, `sessid` and `account`
properties on the command body at runtime. Most often the calling type is `Session`.
* A [Session](./Sessions.md). This is by far the most common case when a user is entering a command in
their client.
* `caller` - this is set to the puppeted [Object](./Objects.md) if such an object exists. If no
puppet is found, `caller` is set equal to `account`. Only if an Account is not found either (such as
before being logged in) will this be set to the Session object itself.
* `session` - a reference to the [Session](./Sessions.md) object itself.
* `sessid` - `sessid.id`, a unique integer identifier of the session.
* `account` - the [Account](./Accounts.md) object connected to this Session. None if not logged in.
* An [Account](./Accounts.md). This only happens if `account.execute_cmd()` was used. No Session
information can be obtained in this case.
* `caller` - this is set to the puppeted Object if such an object can be determined (without
Session info this can only be determined in `MULTISESSION_MODE=0` or `1`). If no puppet is found,
this is equal to `account`.
* `session` - `None*`
* `sessid` - `None*`
* `account` - Set to the Account object.
* An [Object](./Objects.md). This only happens if `object.execute_cmd()` was used (for example by an
NPC).
* `caller` - This is set to the calling Object in question.
* `session` - `None*`
* `sessid` - `None*`
* `account` - `None`
> `*)`: There is a way to make the Session available also inside tests run directly on Accounts and
Objects, and that is to pass it to `execute_cmd` like so: `account.execute_cmd("...",
session=<Session>)`. Doing so *will* make the `.session` and `.sessid` properties available in the
command.
### Properties assigned to the command instance at run-time
Let's say account *Bob* with a character *BigGuy* enters the command *look at sword*. After the
system having successfully identified this as the "look" command and determined that BigGuy really
has access to a command named `look`, it chugs the `look` command class out of storage and either
loads an existing Command instance from cache or creates one. After some more checks it then assigns
it the following properties:
- `caller` - The character BigGuy, in this example. This is a reference to the object executing the
command. The value of this depends on what type of object is calling the command; see the previous
section.
- `session` - the [Session](./Sessions.md) Bob uses to connect to the game and control BigGuy (see also
previous section).
- `sessid` - the unique id of `self.session`, for quick lookup.
- `account` - the [Account](./Accounts.md) Bob (see previous section).
- `cmdstring` - the matched key for the command. This would be *look* in our example.
- `args` - this is the rest of the string, except the command name. So if the string entered was
*look at sword*, `args` would be " *at sword*". Note the space kept - Evennia would correctly
interpret `lookat sword` too. This is useful for things like `/switches` that should not use space.
In the `MuxCommand` class used for default commands, this space is stripped. Also see the
`arg_regex` property if you want to enforce a space to make `lookat sword` give a command-not-found
error.
- `obj` - the game [Object](./Objects.md) on which this command is defined. This need not be the caller,
but since `look` is a common (default) command, this is probably defined directly on *BigGuy* - so
`obj` will point to BigGuy. Otherwise `obj` could be an Account or any interactive object with
commands defined on it, like in the example of the "check time" command defined on a "Clock" object.
- `cmdset` - this is a reference to the merged CmdSet (see below) from which this command was
matched. This variable is rarely used, it's main use is for the [auto-help system](Help-
System#command-auto-help-system) (*Advanced note: the merged cmdset need NOT be the same as
`BigGuy.cmdset`. The merged set can be a combination of the cmdsets from other objects in the room,
for example*).
- `raw_string` - this is the raw input coming from the user, without stripping any surrounding
whitespace. The only thing that is stripped is the ending newline marker.
#### Other useful utility methods:
- `.get_help(caller, cmdset)` - Get the help entry for this command. By default the arguments are
not
used, but they could be used to implement alternate help-display systems.
- `.client_width()` - Shortcut for getting the client's screen-width. Note that not all clients will
truthfully report this value - that case the `settings.DEFAULT_SCREEN_WIDTH` will be returned.
- `.styled_table(*args, **kwargs)` - This returns an [EvTable](module-
evennia.utils.evtable) styled based on the
session calling this command. The args/kwargs are the same as for EvTable, except styling defaults
are set.
- `.styled_header`, `_footer`, `separator` - These will produce styled decorations for
display to the user. They are useful for creating listings and forms with colors adjustable per-
user.
### Defining your own command classes
Beyond the properties Evennia always assigns to the command at run-time (listed above), your job is
to define the following class properties:
- `key` (string) - the identifier for the command, like `look`. This should (ideally) be unique. A
key can consist of more than one word, like "press button" or "pull left lever". Note that *both*
`key` and `aliases` below determine the identity of a command. So two commands are considered if
either matches. This is important for merging cmdsets described below.
- `aliases` (optional list) - a list of alternate names for the command (`["glance", "see", "l"]`).
Same name rules as for `key` applies.
- `locks` (string) - a [lock definition](./Locks.md), usually on the form `cmd:<lockfuncs>`. Locks is a
rather big topic, so until you learn more about locks, stick to giving the lockstring `"cmd:all()"`
to make the command available to everyone (if you don't provide a lock string, this will be assigned
for you).
- `help_category` (optional string) - setting this helps to structure the auto-help into categories.
If none is set, this will be set to *General*.
- `save_for_next` (optional boolean). This defaults to `False`. If `True`, a copy of this command
object (along with any changes you have done to it) will be stored by the system and can be accessed
by the next command by retrieving `self.caller.ndb.last_cmd`. The next run command will either clear
or replace the storage.
- `arg_regex` (optional raw string): Used to force the parser to limit itself and tell it when the
command-name ends and arguments begin (such as requiring this to be a space or a /switch). This is
done with a regular expression. [See the arg_regex section](./Commands.md#on-arg_regex) for the details.
- `auto_help` (optional boolean). Defaults to `True`. This allows for turning off the [auto-help
system](./Help-System.md#command-auto-help-system) on a per-command basis. This could be useful if you
either want to write your help entries manually or hide the existence of a command from `help`'s
generated list.
- `is_exit` (bool) - this marks the command as being used for an in-game exit. This is, by default,
set by all Exit objects and you should not need to set it manually unless you make your own Exit
system. It is used for optimization and allows the cmdhandler to easily disregard this command when
the cmdset has its `no_exits` flag set.
- `is_channel` (bool)- this marks the command as being used for an in-game channel. This is, by
default, set by all Channel objects and you should not need to set it manually unless you make your
own Channel system. is used for optimization and allows the cmdhandler to easily disregard this
command when its cmdset has its `no_channels` flag set.
- `msg_all_sessions` (bool): This affects the behavior of the `Command.msg` method. If unset
(default), calling `self.msg(text)` from the Command will always only send text to the Session that
actually triggered this Command. If set however, `self.msg(text)` will send to all Sessions relevant
to the object this Command sits on. Just which Sessions receives the text depends on the object and
the server's `MULTISESSION_MODE`.
You should also implement at least two methods, `parse()` and `func()` (You could also implement
`perm()`, but that's not needed unless you want to fundamentally change how access checks work).
- `at_pre_cmd()` is called very first on the command. If this function returns anything that
evaluates to `True` the command execution is aborted at this point.
- `parse()` is intended to parse the arguments (`self.args`) of the function. You can do this in any
way you like, then store the result(s) in variable(s) on the command object itself (i.e. on `self`).
To take an example, the default mux-like system uses this method to detect "command switches" and
store them as a list in `self.switches`. Since the parsing is usually quite similar inside a command
scheme you should make `parse()` as generic as possible and then inherit from it rather than re-
implementing it over and over. In this way, the default `MuxCommand` class implements a `parse()`
for all child commands to use.
- `func()` is called right after `parse()` and should make use of the pre-parsed input to actually
do whatever the command is supposed to do. This is the main body of the command. The return value
from this method will be returned from the execution as a Twisted Deferred.
- `at_post_cmd()` is called after `func()` to handle eventual cleanup.
Finally, you should always make an informative [doc
string](http://www.python.org/dev/peps/pep-0257/#what-is-a-docstring) (`__doc__`) at the top of your
class. This string is dynamically read by the [Help System](./Help-System.md) to create the help entry
for this command. You should decide on a way to format your help and stick to that.
Below is how you define a simple alternative "`smile`" command:
```python
from evennia import Command
class CmdSmile(Command):
"""
A smile command
Usage:
smile [at] [<someone>]
grin [at] [<someone>]
Smiles to someone in your vicinity or to the room
in general.
(This initial string (the __doc__ string)
is also used to auto-generate the help
for this command)
"""
key = "smile"
aliases = ["smile at", "grin", "grin at"]
locks = "cmd:all()"
help_category = "General"
def parse(self):
"Very trivial parser"
self.target = self.args.strip()
def func(self):
"This actually does things"
caller = self.caller
if not self.target or self.target == "here":
string = f"{caller.key} smiles"
else:
target = caller.search(self.target)
if not target:
return
string = f"{caller.key} smiles at {target.key}"
caller.location.msg_contents(string)
```
The power of having commands as classes and to separate `parse()` and `func()`
lies in the ability to inherit functionality without having to parse every
command individually. For example, as mentioned the default commands all
inherit from `MuxCommand`. `MuxCommand` implements its own version of `parse()`
that understands all the specifics of MUX-like commands. Almost none of the
default commands thus need to implement `parse()` at all, but can assume the
incoming string is already split up and parsed in suitable ways by its parent.
Before you can actually use the command in your game, you must now store it
within a *command set*. See the [Command Sets](./Command-Sets.md) page.
### On arg_regex
The command parser is very general and does not require a space to end your command name. This means
that the alias `:` to `emote` can be used like `:smiles` without modification. It also means
`getstone` will get you the stone (unless there is a command specifically named `getstone`, then
that will be used). If you want to tell the parser to require a certain separator between the
command name and its arguments (so that `get stone` works but `getstone` gives you a 'command not
found' error) you can do so with the `arg_regex` property.
The `arg_regex` is a [raw regular expression string](http://docs.python.org/library/re.html). The
regex will be compiled by the system at runtime. This allows you to customize how the part
*immediately following* the command name (or alias) must look in order for the parser to match for
this command. Some examples:
- `commandname argument` (`arg_regex = r"\s.+"`): This forces the parser to require the command name
to be followed by one or more spaces. Whatever is entered after the space will be treated as an
argument. However, if you'd forget the space (like a command having no arguments), this would *not*
match `commandname`.
- `commandname` or `commandname argument` (`arg_regex = r"\s.+|$"`): This makes both `look` and
`look me` work but `lookme` will not.
- `commandname/switches arguments` (`arg_regex = r"(?:^(?:\s+|\/).*$)|^$"`. If you are using
Evennia's `MuxCommand` Command parent, you may wish to use this since it will allow `/switche`s to
work as well as having or not having a space.
The `arg_regex` allows you to customize the behavior of your commands. You can put it in the parent
class of your command to customize all children of your Commands. However, you can also change the
base default behavior for all Commands by modifying `settings.COMMAND_DEFAULT_ARG_REGEX`.
## Exiting a command
Normally you just use `return` in one of your Command class' hook methods to exit that method. That
will however still fire the other hook methods of the Command in sequence. That's usually what you
want but sometimes it may be useful to just abort the command, for example if you find some
unacceptable input in your parse method. To exit the command this way you can raise
`evennia.InterruptCommand`:
```python
from evennia import InterruptCommand
class MyCommand(Command):
# ...
def parse(self):
# ...
# if this fires, `func()` and `at_post_cmd` will not
# be called at all
raise InterruptCommand()
```
## Pauses in commands
Sometimes you want to pause the execution of your command for a little while before continuing -
maybe you want to simulate a heavy swing taking some time to finish, maybe you want the echo of your
voice to return to you with an ever-longer delay. Since Evennia is running asynchronously, you
cannot use `time.sleep()` in your commands (or anywhere, really). If you do, the *entire game* will
be frozen for everyone! So don't do that. Fortunately, Evennia offers a really quick syntax for
making pauses in commands.
In your `func()` method, you can use the `yield` keyword. This is a Python keyword that will freeze
the current execution of your command and wait for more before processing.
> Note that you *cannot* just drop `yield` into any code and expect it to pause. Evennia will only
pause for you if you `yield` inside the Command's `func()` method. Don't expect it to work anywhere
else.
Here's an example of a command using a small pause of five seconds between messages:
```python
from evennia import Command
class CmdWait(Command):
"""
A dummy command to show how to wait
Usage:
wait
"""
key = "wait"
locks = "cmd:all()"
help_category = "General"
def func(self):
"""Command execution."""
self.msg("Starting to wait ...")
yield 5
self.msg("... This shows after 5 seconds. Waiting ...")
yield 2
self.msg("... And now another 2 seconds have passed.")
```
The important line is the `yield 5` and `yield 2` lines. It will tell Evennia to pause execution
here and not continue until the number of seconds given has passed.
There are two things to remember when using `yield` in your Command's `func` method:
1. The paused state produced by the `yield` is not saved anywhere. So if the server reloads in the
middle of your command pausing, it will *not* resume when the server comes back up - the remainder
of the command will never fire. So be careful that you are not freezing the character or account in
a way that will not be cleared on reload.
2. If you use `yield` you may not also use `return <values>` in your `func` method. You'll get an
error explaining this. This is due to how Python generators work. You can however use a "naked"
`return` just fine. Usually there is no need for `func` to return a value, but if you ever do need
to mix `yield` with a final return value in the same `func`, look at [twisted.internet.defer.returnV
alue](https://twistedmatrix.com/documents/current/api/twisted.internet.defer.html#returnValue).
## Asking for user input
The `yield` keyword can also be used to ask for user input. Again you can't
use Python's `input` in your command, for it would freeze Evennia for
everyone while waiting for that user to input their text. Inside a Command's
`func` method, the following syntax can also be used:
```python
answer = yield("Your question")
```
Here's a very simple example:
```python
class CmdConfirm(Command):
"""
A dummy command to show confirmation.
Usage:
confirm
"""
key = "confirm"
def func(self):
answer = yield("Are you sure you want to go on?")
if answer.strip().lower() in ("yes", "y"):
self.msg("Yes!")
else:
self.msg("No!")
```
This time, when the user enters the 'confirm' command, she will be asked if she wants to go on.
Entering 'yes' or "y" (regardless of case) will give the first reply, otherwise the second reply
will show.
> Note again that the `yield` keyword does not store state. If the game reloads while waiting for
the user to answer, the user will have to start over. It is not a good idea to use `yield` for
important or complex choices, a persistent [EvMenu](./EvMenu.md) might be more appropriate in this case.
## System commands
*Note: This is an advanced topic. Skip it if this is your first time learning about commands.*
There are several command-situations that are exceptional in the eyes of the server. What happens if
the account enters an empty string? What if the 'command' given is infact the name of a channel the
user wants to send a message to? Or if there are multiple command possibilities?
Such 'special cases' are handled by what's called *system commands*. A system command is defined
in the same way as other commands, except that their name (key) must be set to one reserved by the
engine (the names are defined at the top of `evennia/commands/cmdhandler.py`). You can find (unused)
implementations of the system commands in `evennia/commands/default/system_commands.py`. Since these
are not (by default) included in any `CmdSet` they are not actually used, they are just there for
show. When the special situation occurs, Evennia will look through all valid `CmdSet`s for your
custom system command. Only after that will it resort to its own, hard-coded implementation.
Here are the exceptional situations that triggers system commands. You can find the command keys
they use as properties on `evennia.syscmdkeys`:
- No input (`syscmdkeys.CMD_NOINPUT`) - the account just pressed return without any input. Default
is to do nothing, but it can be useful to do something here for certain implementations such as line
editors that interpret non-commands as text input (an empty line in the editing buffer).
- Command not found (`syscmdkeys.CMD_NOMATCH`) - No matching command was found. Default is to
display the "Huh?" error message.
- Several matching commands where found (`syscmdkeys.CMD_MULTIMATCH`) - Default is to show a list of
matches.
- User is not allowed to execute the command (`syscmdkeys.CMD_NOPERM`) - Default is to display the
"Huh?" error message.
- Channel (`syscmdkeys.CMD_CHANNEL`) - This is a [Channel](./Communications.md) name of a channel you are
subscribing to - Default is to relay the command's argument to that channel. Such commands are
created by the Comm system on the fly depending on your subscriptions.
- New session connection (`syscmdkeys.CMD_LOGINSTART`). This command name should be put in the
`settings.CMDSET_UNLOGGEDIN`. Whenever a new connection is established, this command is always
called on the server (default is to show the login screen).
Below is an example of redefining what happens when the account doesn't provide any input (e.g. just
presses return). Of course the new system command must be added to a cmdset as well before it will
work.
```python
from evennia import syscmdkeys, Command
class MyNoInputCommand(Command):
"Usage: Just press return, I dare you"
key = syscmdkeys.CMD_NOINPUT
def func(self):
self.caller.msg("Don't just press return like that, talk to me!")
```
## Dynamic Commands
*Note: This is an advanced topic.*
Normally Commands are created as fixed classes and used without modification. There are however
situations when the exact key, alias or other properties is not possible (or impractical) to pre-
code ([Exits](./Commands.md#exits) is an example of this).
To create a command with a dynamic call signature, first define the command body normally in a class
(set your `key`, `aliases` to default values), then use the following call (assuming the command
class you created is named `MyCommand`):
```python
cmd = MyCommand(key="newname",
aliases=["test", "test2"],
locks="cmd:all()",
...)
```
*All* keyword arguments you give to the Command constructor will be stored as a property on the
command object. This will overload existing properties defined on the parent class.
Normally you would define your class and only overload things like `key` and `aliases` at run-time.
But you could in principle also send method objects (like `func`) as keyword arguments in order to
make your command completely customized at run-time.
## Exits
*Note: This is an advanced topic.*
Exits are examples of the use of a [Dynamic Command](./Commands.md#dynamic-commands).
The functionality of [Exit](./Objects.md) objects in Evennia is not hard-coded in the engine. Instead
Exits are normal [typeclassed](./Typeclasses.md) objects that auto-create a [CmdSet](./Command-Sets.md) on
themselves when they load. This cmdset has a single dynamically created Command with the same
properties (key, aliases and locks) as the Exit object itself. When entering the name of the exit,
this dynamic exit-command is triggered and (after access checks) moves the Character to the exit's
destination.
Whereas you could customize the Exit object and its command to achieve completely different
behaviour, you will usually be fine just using the appropriate `traverse_*` hooks on the Exit
object. But if you are interested in really changing how things work under the hood, check out
`evennia/objects/objects.py` for how the `Exit` typeclass is set up.
## Command instances are re-used
*Note: This is an advanced topic that can be skipped when first learning about Commands.*
A Command class sitting on an object is instantiated once and then re-used. So if you run a command
from object1 over and over you are in fact running the same command instance over and over (if you
run the same command but sitting on object2 however, it will be a different instance). This is
usually not something you'll notice, since every time the Command-instance is used, all the relevant
properties on it will be overwritten. But armed with this knowledge you can implement some of the
more exotic command mechanism out there, like the command having a 'memory' of what you last entered
so that you can back-reference the previous arguments etc.
> Note: On a server reload, all Commands are rebuilt and memory is flushed.
To show this in practice, consider this command:
```python
class CmdTestID(Command):
key = "testid"
def func(self):
if not hasattr(self, "xval"):
self.xval = 0
self.xval += 1
self.caller.msg("Command memory ID: {} (xval={})".format(id(self), self.xval))
```
Adding this to the default character cmdset gives a result like this in-game:
```
> testid
Command memory ID: 140313967648552 (xval=1)
> testid
Command memory ID: 140313967648552 (xval=2)
> testid
Command memory ID: 140313967648552 (xval=3)
```
Note how the in-memory address of the `testid` command never changes, but `xval` keeps ticking up.
## Dynamically created commands
*This is also an advanced topic.*
Commands can also be created and added to a cmdset on the fly. Creating a class instance with a
keyword argument, will assign that keyword argument as a property on this paricular command:
```
class MyCmdSet(CmdSet):
def at_cmdset_creation(self):
self.add(MyCommand(myvar=1, foo="test")
```
This will start the `MyCommand` with `myvar` and `foo` set as properties (accessable as `self.myvar`
and `self.foo`). How they are used is up to the Command. Remember however the discussion from the
previous section - since the Command instance is re-used, those properties will *remain* on the
command as long as this cmdset and the object it sits is in memory (i.e. until the next reload).
Unless `myvar` and `foo` are somehow reset when the command runs, they can be modified and that
change will be remembered for subsequent uses of the command.
## How commands actually work
*Note: This is an advanced topic mainly of interest to server developers.*
Any time the user sends text to Evennia, the server tries to figure out if the text entered
corresponds to a known command. This is how the command handler sequence looks for a logged-in user:
1. A user enters a string of text and presses enter.
2. The user's Session determines the text is not some protocol-specific control sequence or OOB
command, but sends it on to the command handler.
3. Evennia's *command handler* analyzes the Session and grabs eventual references to Account and
eventual puppeted Characters (these will be stored on the command object later). The *caller*
property is set appropriately.
4. If input is an empty string, resend command as `CMD_NOINPUT`. If no such command is found in
cmdset, ignore.
5. If command.key matches `settings.IDLE_COMMAND`, update timers but don't do anything more.
6. The command handler gathers the CmdSets available to *caller* at this time:
- The caller's own currently active CmdSet.
- CmdSets defined on the current account, if caller is a puppeted object.
- CmdSets defined on the Session itself.
- The active CmdSets of eventual objects in the same location (if any). This includes commands
on [Exits](./Objects.md#exits).
- Sets of dynamically created *System commands* representing available
[Communications](./Communications.md#channels).
7. All CmdSets *of the same priority* are merged together in groups. Grouping avoids order-
dependent issues of merging multiple same-prio sets onto lower ones.
8. All the grouped CmdSets are *merged* in reverse priority into one combined CmdSet according to
each set's merge rules.
9. Evennia's *command parser* takes the merged cmdset and matches each of its commands (using its
key and aliases) against the beginning of the string entered by *caller*. This produces a set of
candidates.
10. The *cmd parser* next rates the matches by how many characters they have and how many percent
matches the respective known command. Only if candidates cannot be separated will it return multiple
matches.
- If multiple matches were returned, resend as `CMD_MULTIMATCH`. If no such command is found in
cmdset, return hard-coded list of matches.
- If no match was found, resend as `CMD_NOMATCH`. If no such command is found in cmdset, give
hard-coded error message.
11. If a single command was found by the parser, the correct command object is plucked out of
storage. This usually doesn't mean a re-initialization.
12. It is checked that the caller actually has access to the command by validating the *lockstring*
of the command. If not, it is not considered as a suitable match and `CMD_NOMATCH` is triggered.
13. If the new command is tagged as a channel-command, resend as `CMD_CHANNEL`. If no such command
is found in cmdset, use hard-coded implementation.
14. Assign several useful variables to the command instance (see previous sections).
15. Call `at_pre_command()` on the command instance.
16. Call `parse()` on the command instance. This is fed the remainder of the string, after the name
of the command. It's intended to pre-parse the string into a form useful for the `func()` method.
17. Call `func()` on the command instance. This is the functional body of the command, actually
doing useful things.
18. Call `at_post_command()` on the command instance.
## Assorted notes
The return value of `Command.func()` is a Twisted
[deferred](http://twistedmatrix.com/documents/current/core/howto/defer.html).
Evennia does not use this return value at all by default. If you do, you must
thus do so asynchronously, using callbacks.
```python
# in command class func()
def callback(ret, caller):
caller.msg("Returned is %s" % ret)
deferred = self.execute_command("longrunning")
deferred.addCallback(callback, self.caller)
```
This is probably not relevant to any but the most advanced/exotic designs (one might use it to
create a "nested" command structure for example).
The `save_for_next` class variable can be used to implement state-persistent commands. For example
it can make a command operate on "it", where it is determined by what the previous command operated
on.

View file

@ -1,113 +0,0 @@
# Communications
Apart from moving around in the game world and talking, players might need other forms of
communication. This is offered by Evennia's `Comm` system. Stock evennia implements a 'MUX-like'
system of channels, but there is nothing stopping you from changing things to better suit your
taste.
Comms rely on two main database objects - `Msg` and `Channel`. There is also the `TempMsg` which
mimics the API of a `Msg` but has no connection to the database.
## Msg
The `Msg` object is the basic unit of communication in Evennia. A message works a little like an
e-mail; it always has a sender (a [Account](./Accounts.md)) and one or more recipients. The recipients
may be either other Accounts, or a *Channel* (see below). You can mix recipients to send the message
to both Channels and Accounts if you like.
Once created, a `Msg` is normally not changed. It is peristently saved in the database. This allows
for comprehensive logging of communications. This could be useful for allowing senders/receivers to
have 'mailboxes' with the messages they want to keep.
### Properties defined on `Msg`
- `senders` - this is a reference to one or many [Account](./Accounts.md) or [Objects](./Objects.md) (normally
*Characters*) sending the message. This could also be an *External Connection* such as a message
coming in over IRC/IMC2 (see below). There is usually only one sender, but the types can also be
mixed in any combination.
- `receivers` - a list of target [Accounts](./Accounts.md), [Objects](./Objects.md) (usually *Characters*) or
*Channels* to send the message to. The types of receivers can be mixed in any combination.
- `header` - this is a text field for storing a title or header for the message.
- `message` - the actual text being sent.
- `date_sent` - when message was sent (auto-created).
- `locks` - a [lock definition](./Locks.md).
- `hide_from` - this can optionally hold a list of objects, accounts or channels to hide this `Msg`
from. This relationship is stored in the database primarily for optimization reasons, allowing for
quickly post-filter out messages not intended for a given target. There is no in-game methods for
setting this, it's intended to be done in code.
You create new messages in code using `evennia.create_message` (or
`evennia.utils.create.create_message.`)
## TempMsg
`evennia.comms.models` also has `TempMsg` which mimics the API of `Msg` but is not connected to the
database. TempMsgs are used by Evennia for channel messages by default. They can be used for any
system expecting a `Msg` but when you don't actually want to save anything.
## Channels
Channels are [Typeclassed](./Typeclasses.md) entities, which mean they can be easily extended and their
functionality modified. To change which channel typeclass Evennia uses, change
settings.BASE_CHANNEL_TYPECLASS.
Channels act as generic distributors of messages. Think of them as "switch boards" redistributing
`Msg` or `TempMsg` objects. Internally they hold a list of "listening" objects and any `Msg` (or
`TempMsg`) sent to the channel will be distributed out to all channel listeners. Channels have
[Locks](./Locks.md) to limit who may listen and/or send messages through them.
The *sending* of text to a channel is handled by a dynamically created [Command](./Commands.md) that
always have the same name as the channel. This is created for each channel by the global
`ChannelHandler`. The Channel command is added to the Account's cmdset and normal command locks are
used to determine which channels are possible to write to. When subscribing to a channel, you can
then just write the channel name and the text to send.
The default ChannelCommand (which can be customized by pointing `settings.CHANNEL_COMMAND_CLASS` to
your own command), implements a few convenient features:
- It only sends `TempMsg` objects. Instead of storing individual entries in the database it instead
dumps channel output a file log in `server/logs/channel_<channelname>.log`. This is mainly for
practical reasons - we find one rarely need to query individual Msg objects at a later date. Just
stupidly dumping the log to a file also means a lot less database overhead.
- It adds a `/history` switch to view the 20 last messages in the channel. These are read from the
end of the log file. One can also supply a line number to start further back in the file (but always
20 entries at a time). It's used like this:
> public/history
> public/history 35
There are two default channels created in stock Evennia - `MudInfo` and `Public`. `MudInfo`
receives server-related messages meant for Admins whereas `Public` is open to everyone to chat on
(all new accounts are automatically joined to it when logging in, it is useful for asking
questions). The default channels are defined by the `DEFAULT_CHANNELS` list (see
`evennia/settings_default.py` for more details).
You create new channels with `evennia.create_channel` (or `evennia.utils.create.create_channel`).
In code, messages are sent to a channel using the `msg` or `tempmsg` methods of channels:
channel.msg(msgobj, header=None, senders=None, persistent=True)
The argument `msgobj` can be either a string, a previously constructed `Msg` or a `TempMsg` - in the
latter cases all the following keywords are ignored since the message objects already contains all
this information. If `msgobj` is a string, the other keywords are used for creating a new `Msg` or
`TempMsg` on the fly, depending on if `persistent` is set or not. By default, a `TempMsg` is emitted
for channel communication (since the default ChannelCommand instead logs to a file).
```python
# assume we have a 'sender' object and a channel named 'mychan'
# manually sending a message to a channel
mychan.msg("Hello!", senders=[sender])
```
### Properties defined on `Channel`
- `key` - main name for channel
- `aliases` - alternative native names for channels
- `desc` - optional description of channel (seen in listings)
- `keep_log` (bool) - if the channel should store messages (default)
- `locks` - A [lock definition](./Locks.md). Channels normally use the access_types `send, control` and
`listen`.

View file

@ -0,0 +1,90 @@
# Accounts
```
┌──────┐ │ ┌───────┐ ┌───────┐ ┌──────┐
│Client├─┼──►│Session├───►│Account├──►│Object│
└──────┘ │ └───────┘ └───────┘ └──────┘
^
```
An _Account_ represents a unique game account - one player playing the game. Whereas a player can potentially connect to the game from several Clients/Sessions, they will normally have only one Account.
The Account object has no in-game representation. In order to actually get on the game the Account must *puppet* an [Object](./Objects.md) (normally a [Character](./Objects.md#characters)).
Exactly how many Sessions can interact with an Account and its Puppets at once is determined by
Evennia's [MULTISESSION_MODE](../Concepts/Connection-Styles.md#multisession-mode-and-multiplaying)
Apart from storing login information and other account-specific data, the Account object is what is chatting on Evennia's default [Channels](./Channels.md). It is also a good place to store [Permissions](./Locks.md) to be consistent between different in-game characters. It can also hold player-level configuration options.
The Account object has its own default [CmdSet](./Command-Sets.md), the `AccountCmdSet`. The commands in this set are available to the player no matter which character they are puppeting. Most notably the default game's `exit`, `who` and chat-channel commands are in the Account cmdset.
> ooc
The default `ooc` command causes you to leave your current puppet and go into OOC mode. In this mode you have no location and have only the Account-cmdset available. It acts a staging area for switching characters (if your game supports that) as well as a safety fallback if your character gets accidentally deleted.
> ic
This re-puppets the latest character.
Note that the Account object can have, and often does have, a different set of [Permissions](./Permissions.md) from the Character they control. Normally you should put your permissions on the Account level - this will overrule permissions set on the Character level. For the permissions of the Character to come into play the default `quell` command can be used. This allows for exploring the game using a different permission set (but you can't escalate your permissions this way - for hierarchical permissions like `Builder`, `Admin` etc, the *lower* of the permissions on the Character/Account will always be used).
## Working with Accounts
You will usually not want more than one Account typeclass for all new accounts.
An Evennia Account is, per definition, a Python class that includes `evennia.DefaultAccount` among its parents. In `mygame/typeclasses/accounts.py` there is an empty class ready for you to modify. Evennia defaults to using this (it inherits directly from `DefaultAccount`).
Here's an example of modifying the default Account class in code:
```python
# in mygame/typeclasses/accounts.py
from evennia import DefaultAccount
class Account(DefaultAccount):
# [...]
def at_account_creation(self):
"this is called only once, when account is first created"
self.db.real_name = None # this is set later
self.db.real_address = None # "
self.db.config_1 = True # default config
self.db.config_2 = False # "
self.db.config_3 = 1 # "
# ... whatever else our game needs to know
```
Reload the server with `reload`.
... However, if you use `examine *self` (the asterisk makes you examine your Account object rather than your Character), you won't see your new Attributes yet. This is because `at_account_creation` is only called the very *first* time the Account is called and your Account object already exists (any new Accounts that connect will see them though). To update yourself you need to make sure to re-fire the hook on all the Accounts you have already created. Here is an example of how to do this using `py`:
``` py [account.at_account_creation() for account in evennia.managers.accounts.all()] ```
You should now see the Attributes on yourself.
> If you wanted Evennia to default to a completely *different* Account class located elsewhere, you > must point Evennia to it. Add `BASE_ACCOUNT_TYPECLASS` to your settings file, and give the python path to your custom class as its value. By default this points to `typeclasses.accounts.Account`, the empty template we used above.
### Properties on Accounts
Beyond those properties assigned to all typeclassed objects (see [Typeclasses](./Typeclasses.md)), the Account also has the following custom properties:
- `user` - a unique link to a `User` Django object, representing the logged-in user.
- `obj` - an alias for `character`.
- `name` - an alias for `user.username`
- `sessions` - an instance of [ObjectSessionHandler](github:evennia.objects.objects#objectsessionhandler) managing all connected Sessions (physical connections) this object listens to (Note: In older versions of Evennia, this was a list). The so-called `session-id` (used in many places) is found as a property `sessid` on each Session instance.
- `is_superuser` (bool: True/False) - if this account is a superuser.
Special handlers:
- `cmdset` - This holds all the current [Commands](./Commands.md) of this Account. By default these are
the commands found in the cmdset defined by `settings.CMDSET_ACCOUNT`.
- `nicks` - This stores and handles [Nicks](./Nicks.md), in the same way as nicks it works on Objects. For Accounts, nicks are primarily used to store custom aliases for [Channels](./Channels.md).
Selection of special methods (see `evennia.DefaultAccount` for details):
- `get_puppet` - get a currently puppeted object connected to the Account and a given session id, if any.
- `puppet_object` - connect a session to a puppetable Object.
- `unpuppet_object` - disconnect a session from a puppetable Object.
- `msg` - send text to the Account
- `execute_cmd` - runs a command as if this Account did it.
- `search` - search for Accounts.

View file

@ -0,0 +1,598 @@
# Attributes
```{code-block}
:caption: In-game
> set obj/myattr = "test"
```
```{code-block} python
:caption: In-code, using the .db wrapper
obj.db.foo = [1, 2, 3, "bar"]
value = obj.db.foo
```
```{code-block} python
:caption: In-code, using the .attributes handler
obj.attributes.add("myattr", 1234, category="bar")
value = attributes.get("myattr", category="bar")
```
```{code-block} python
:caption: In-code, using `AttributeProperty` at class level
from evennia import DefaultObject
from evennia import AttributeProperty
class MyObject(DefaultObject):
foo = AttributeProperty(default=[1, 2, 3, "bar"])
myattr = AttributeProperty(100, category='bar')
```
_Attributes_ allow you to to store arbitrary data on objects and make sure the data survives a server reboot. An Attribute can store pretty much any Python data structure and data type, like numbers, strings, lists, dicts etc. You can also store (references to) database objects like characters and rooms.
## Working with Attributes
Attributes are usually handled in code. All [Typeclassed](./Typeclasses.md) entities ([Accounts](./Accounts.md), [Objects](./Objects.md), [Scripts](./Scripts.md) and [Channels](./Channels.md)) can (and usually do) have Attributes associated with them. There
are three ways to manage Attributes, all of which can be mixed.
### Using .db
The simplest way to get/set Attributes is to use the `.db` shortcut. This allows for setting and getting Attributes that lack a _category_ (having category `None`)
```python
import evennia
obj = evennia.create_object(key="Foo")
obj.db.foo1 = 1234
obj.db.foo2 = [1, 2, 3, 4]
obj.db.weapon = "sword"
obj.db.self_reference = obj # stores a reference to the obj
# (let's assume a rose exists in-game)
rose = evennia.search_object(key="rose")[0] # returns a list, grab 0th element
rose.db.has_thorns = True
# retrieving
val1 = obj.db.foo1
val2 = obj.db.foo2
weap = obj.db.weapon
myself = obj.db.self_reference # retrieve reference from db, get object back
is_ouch = rose.db.has_thorns
# this will return None, not AttributeError!
not_found = obj.db.jiwjpowiwwerw
# returns all Attributes on the object
obj.db.all
# delete an Attribute
del obj.db.foo2
```
Trying to access a non-existing Attribute will never lead to an `AttributeError`. Instead
you will get `None` back. The special `.db.all` will return a list of all Attributes on
the object. You can replace this with your own Attribute `all` if you want, it will replace the
default `all` functionality until you delete it again.
### Using .attributes
If you want to group your Attribute in a category, or don't know the name of the Attribute beforehand, you can make use of
the [AttributeHandler](evennia.typeclasses.attributes.AttributeHandler), available as `.attributes` on all typeclassed entities. With no extra keywords, this is identical to using the `.db` shortcut (`.db` is actually using the `AttributeHandler` internally):
```python
is_ouch = rose.attributes.get("has_thorns")
obj.attributes.add("helmet", "Knight's helmet")
helmet = obj.attributes.get("helmet")
# you can give space-separated Attribute-names (can't do that with .db)
obj.attributes.add("my game log", "long text about ...")
```
By using a category you can separate same-named Attributes on the same object to help organization.
```python
# store (let's say we have gold_necklace and ringmail_armor from before)
obj.attributes.add("neck", gold_necklace, category="clothing")
obj.attributes.add("neck", ringmail_armor, category="armor")
# retrieve later - we'll get back gold_necklace and ringmail_armor
neck_clothing = obj.attributes.get("neck", category="clothing")
neck_armor = obj.attributes.get("neck", category="armor")
```
If you don't specify a category, the Attribute's `category` will be `None` and can thus also be found via `.db`. `None` is considered a category of its own, so you won't find `None`-category Attributes mixed with Attributes having categories.
Here are the methods of the `AttributeHandler`. See the [AttributeHandler API](evennia.typeclasses.attributes.AttributeHandler) for more details.
- `has(...)` - this checks if the object has an Attribute with this key. This is equivalent
to doing `obj.db.attrname` except you can also check for a specific `category.
- `get(...)` - this retrieves the given Attribute. You can also provide a `default` value to return
if the Attribute is not defined (instead of None). By supplying an
`accessing_object` to the call one can also make sure to check permissions before modifying
anything. The `raise_exception` kwarg allows you to raise an `AttributeError` instead of returning
`None` when you access a non-existing `Attribute`. The `strattr` kwarg tells the system to store
the Attribute as a raw string rather than to pickle it. While an optimization this should usually
not be used unless the Attribute is used for some particular, limited purpose.
- `add(...)` - this adds a new Attribute to the object. An optional [lockstring](./Locks.md) can be
supplied here to restrict future access and also the call itself may be checked against locks.
- `remove(...)` - Remove the given Attribute. This can optionally be made to check for permission
before performing the deletion. - `clear(...)` - removes all Attributes from object.
- `all(category=None)` - returns all Attributes (of the given category) attached to this object.
Examples:
```python
try:
# raise error if Attribute foo does not exist
val = obj.attributes.get("foo", raise_exception=True):
except AttributeError:
# ...
# return default value if foo2 doesn't exist
val2 = obj.attributes.get("foo2", default=[1, 2, 3, "bar"])
# delete foo if it exists (will silently fail if unset, unless
# raise_exception is set)
obj.attributes.remove("foo")
# view all clothes on obj
all_clothes = obj.attributes.all(category="clothes")
```
### Using AttributeProperty
The third way to set up an Attribute is to use an `AttributeProperty`. This
is done on the _class level_ of your typeclass and allows you to treat Attributes a bit like Django database Fields. Unlike using `.db` and `.attributes`, an `AttributeProperty` can't be created on the fly, you must assign it in the class code.
```python
# mygame/typeclasses/characters.py
from evennia import DefaultCharacter
from evennia.typeclasses.attributes import AttributeProperty
class Character(DefaultCharacter):
strength = AttributeProperty(10, category='stat')
constitution = AttributeProperty(11, category='stat')
agility = AttributeProperty(12, category='stat')
magic = AttributeProperty(13, category='stat')
sleepy = AttributeProperty(False, autocreate=False)
poisoned = AttributeProperty(False, autocreate=False)
def at_object_creation(self):
# ...
```
When a new instance of the class is created, new `Attributes` will be created with the value and category given.
With `AttributeProperty`'s set up like this, one can access the underlying `Attribute` like a regular property on the created object:
```python
char = create_object(Character)
char.strength # returns 10
char.agility = 15 # assign a new value (category remains 'stat')
char.db.magic # returns None (wrong category)
char.attributes.get("agility", category="stat") # returns 15
char.db.sleepy # returns None because autocreate=False (see below)
```
```{warning}
Be careful to not assign AttributeProperty's to names of properties and methods already existing on the class, like 'key' or 'at_object_creation'. That could lead to very confusing errors.
```
The `autocreate=False` (default is `True`) used for `sleepy` and `poisoned` is worth a closer explanation. When `False`, _no_ Attribute will be auto-created for these AttributProperties unless they are _explicitly_ set.
The advantage of not creating an Attribute is that the default value given to `AttributeProperty` is returned with no database access unless you change it. This also means that if you want to change the default later, all entities previously create will inherit the new default.
The drawback is that without a database precense you can't find the Attribute via `.db` and `.attributes.get` (or by querying for it in other ways in the database):
```python
char.sleepy # returns False, no db access
char.db.sleepy # returns None - no Attribute exists
char.attributes.get("sleepy") # returns None too
char.sleepy = True # now an Attribute is created
char.db.sleepy # now returns True!
char.attributes.get("sleepy") # now returns True
char.sleepy # now returns True, involves db access
```
You can e.g. `del char.strength` to set the value back to the default (the value defined in the `AttributeProperty`).
See the [AttributeProperty API](evennia.typeclasses.attributes.AttributeProperty) for more details on how to create it with special options, like giving access-restrictions.
### Properties of Attributes
An `Attribute` object is stored in the database. It has the following properties:
- `key` - the name of the Attribute. When doing e.g. `obj.db.attrname = value`, this property is set
to `attrname`.
- `value` - this is the value of the Attribute. This value can be anything which can be pickled -
objects, lists, numbers or what have you (see
[this section](./Attributes.md#what-types-of-data-can-i-save-in-an-attribute) for more info). In the
example
`obj.db.attrname = value`, the `value` is stored here.
- `category` - this is an optional property that is set to None for most Attributes. Setting this
allows to use Attributes for different functionality. This is usually not needed unless you want
to use Attributes for very different functionality ([Nicks](./Nicks.md) is an example of using
Attributes in this way). To modify this property you need to use the [Attribute Handler](#attributes)
- `strvalue` - this is a separate value field that only accepts strings. This severely limits the
data possible to store, but allows for easier database lookups. This property is usually not used
except when re-using Attributes for some other purpose ([Nicks](./Nicks.md) use it). It is only
accessible via the [Attribute Handler](#attributes).
There are also two special properties:
- `attrtype` - this is used internally by Evennia to separate [Nicks](./Nicks.md), from Attributes (Nicks
use Attributes behind the scenes).
- `model` - this is a *natural-key* describing the model this Attribute is attached to. This is on
the form *appname.modelclass*, like `objects.objectdb`. It is used by the Attribute and
NickHandler to quickly sort matches in the database. Neither this nor `attrtype` should normally
need to be modified.
Non-database attributes are not stored in the database and have no equivalence
to `category` nor `strvalue`, `attrtype` or `model`.
### Managing Attributes in-game
Attributes are mainly used by code. But one can also allow the builder to use Attributes to
'turn knobs' in-game. For example a builder could want to manually tweak the "level" Attribute of an
enemy NPC to lower its difficuly.
When setting Attributes this way, you are severely limited in what can be stored - this is because
giving players (even builders) the ability to store arbitrary Python would be a severe security
problem.
In game you can set an Attribute like this:
set myobj/foo = "bar"
To view, do
set myobj/foo
or see them together with all object-info with
examine myobj
The first `set`-example will store a new Attribute `foo` on the object `myobj` and give it the
value "bar".
You can store numbers, booleans, strings, tuples, lists and dicts this way. But if
you store a list/tuple/dict they must be proper Python structures and may _only_ contain strings
or numbers. If you try to insert an unsupported structure, the input will be converted to a
string.
set myobj/mybool = True
set myobj/mybool = True
set myobj/mytuple = (1, 2, 3, "foo")
set myobj/mylist = ["foo", "bar", 2]
set myobj/mydict = {"a": 1, "b": 2, 3: 4}
set mypobj/mystring = [1, 2, foo] # foo is invalid Python (no quotes)
For the last line you'll get a warning and the value instead will be saved as a string `"[1, 2, foo]"`.
### Locking and checking Attributes
While the `set` command is limited to builders, individual Attributes are usually not
locked down. You may want to lock certain sensitive Attributes, in particular for games
where you allow player building. You can add such limitations by adding a [lock string](./Locks.md)
to your Attribute. A NAttribute have no locks.
The relevant lock types are
- `attrread` - limits who may read the value of the Attribute
- `attredit` - limits who may set/change this Attribute
You must use the `AttributeHandler` to assign the lockstring to the Attribute:
```python
lockstring = "attread:all();attredit:perm(Admins)"
obj.attributes.add("myattr", "bar", lockstring=lockstring)"
```
If you already have an Attribute and want to add a lock in-place you can do so
by having the `AttributeHandler` return the `Attribute` object itself (rather than
its value) and then assign the lock to it directly:
```python
lockstring = "attread:all();attredit:perm(Admins)"
obj.attributes.get("myattr", return_obj=True).locks.add(lockstring)
```
Note the `return_obj` keyword which makes sure to return the `Attribute` object so its LockHandler
could be accessed.
A lock is no good if nothing checks it -- and by default Evennia does not check locks on Attributes.
To check the `lockstring` you provided, make sure you include `accessing_obj` and set
`default_access=False` as you make a `get` call.
```python
# in some command code where we want to limit
# setting of a given attribute name on an object
attr = obj.attributes.get(attrname,
return_obj=True,
accessing_obj=caller,
default=None,
default_access=False)
if not attr:
caller.msg("You cannot edit that Attribute!")
return
# edit the Attribute here
```
The same keywords are available to use with `obj.attributes.set()` and `obj.attributes.remove()`,
those will check for the `attredit` lock type.
## What types of data can I save in an Attribute?
The database doesn't know anything about Python objects, so Evennia must *serialize* Attribute
values into a string representation before storing it to the database. This is done using the
[pickle](https://docs.python.org/library/pickle.html) module of Python.
> The only exception is if you use the `strattr` keyword of the
`AttributeHandler` to save to the `strvalue` field of the Attribute. In that case you can _only_ save
*strings* and those will not be pickled).
### Storing single objects
With a single object, we mean anything that is *not iterable*, like numbers,
strings or custom class instances without the `__iter__` method.
* You can generally store any non-iterable Python entity that can be _pickled_.
* Single database objects/typeclasses can be stored, despite them normally not
being possible to pickle. Evennia will convert them to an internal
representation using theihr classname, database-id and creation-date with a
microsecond precision. When retrieving, the object instance will be re-fetched
from the database using this information.
* If you 'hide' a db-obj as a property on a custom class, Evennia will not be
able to find it to serialize it. For that you need to help it out (see below).
```{code-block} python
:caption: Valid assignments
# Examples of valid single-value attribute data:
obj.db.test1 = 23
obj.db.test1 = False
# a database object (will be stored as an internal representation)
obj.db.test2 = myobj
```
As mentioned, Evennia will not be able to automatically serialize db-objects
'hidden' in arbitrary properties on an object. This will lead to an error
when saving the Attribute.
```{code-block} python
:caption: Invalid, 'hidden' dbobject
# example of storing an invalid, "hidden" dbobject in Attribute
class Container:
def __init__(self, mydbobj):
# no way for Evennia to know this is a database object!
self.mydbobj = mydbobj
# let's assume myobj is a db-object
container = Container(myobj)
obj.db.mydata = container # will raise error!
```
By adding two methods `__serialize_dbobjs__` and `__deserialize_dbobjs__` to the
object you want to save, you can pre-serialize and post-deserialize all 'hidden'
objects before Evennia's main serializer gets to work. Inside these methods, use Evennia's
[evennia.utils.dbserialize.dbserialize](evennia.utils.dbserialize.dbserialize) and
[dbunserialize](evennia.utils.dbserialize.dbunserialize) functions to safely
serialize the db-objects you want to store.
```{code-block} python
:caption: Fixing an invalid 'hidden' dbobj for storing in Attribute
from evennia.utils import dbserialize # important
class Container:
def __init__(self, mydbobj):
# A 'hidden' db-object
self.mydbobj = mydbobj
def __serialize_dbobjs__(self):
"""This is called before serialization and allows
us to custom-handle those 'hidden' dbobjs"""
self.mydbobj = dbserialize.dbserialize(self.mydbobj
def __deserialize_dbobjs__(self):
"""This is called after deserialization and allows you to
restore the 'hidden' dbobjs you serialized before"""
if isinstance(self.mydbobj, bytes):
# make sure to check if it's bytes before trying dbunserialize
self.mydbobj = dbserialize.dbunserialize(self.mydbobj)
# let's assume myobj is a db-object
container = Container(myobj)
obj.db.mydata = container # will now work fine!
```
> Note the extra check in `__deserialize_dbobjs__` to make sure the thing you
> are deserializing is a `bytes` object. This is needed because the Attribute's
> cache reruns deserializations in some situations when the data was already
> once deserialized. If you see errors in the log saying
> `Could not unpickle data for storage: ...`, the reason is
> likely that you forgot to add this check.
### Storing multiple objects
This means storing objects in a collection of some kind and are examples of *iterables*, pickle-able
entities you can loop over in a for-loop. Attribute-saving supports the following iterables:
* [Tuples](https://docs.python.org/2/library/functions.html#tuple), like `(1,2,"test", <dbobj>)`.
* [Lists](https://docs.python.org/2/tutorial/datastructures.html#more-on-lists), like `[1,2,"test", <dbobj>]`.
* [Dicts](https://docs.python.org/2/tutorial/datastructures.html#dictionaries), like `{1:2, "test":<dbobj>]`.
* [Sets](https://docs.python.org/2/tutorial/datastructures.html#sets), like `{1,2,"test",<dbobj>}`.
* [collections.OrderedDict](https://docs.python.org/2/library/collections.html#collections.OrderedDict),
like `OrderedDict((1,2), ("test", <dbobj>))`.
* [collections.Deque](https://docs.python.org/2/library/collections.html#collections.deque), like `deque((1,2,"test",<dbobj>))`.
* *Nestings* of any combinations of the above, like lists in dicts or an OrderedDict of tuples, each
containing dicts, etc.
* All other iterables (i.e. entities with the `__iter__` method) will be converted to a *list*.
Since you can use any combination of the above iterables, this is generally not much of a
limitation.
Any entity listed in the [Single object](./Attributes.md#storing-single-objects) section above can be
stored in the iterable.
> As mentioned in the previous section, database entities (aka typeclasses) are not possible to
> pickle. So when storing an iterable, Evennia must recursively traverse the iterable *and all its
> nested sub-iterables* in order to find eventual database objects to convert. This is a very fast
> process but for efficiency you may want to avoid too deeply nested structures if you can.
```python
# examples of valid iterables to store
obj.db.test3 = [obj1, 45, obj2, 67]
# a dictionary
obj.db.test4 = {'str':34, 'dex':56, 'agi':22, 'int':77}
# a mixed dictionary/list
obj.db.test5 = {'members': [obj1,obj2,obj3], 'enemies':[obj4,obj5]}
# a tuple with a list in it
obj.db.test6 = (1, 3, 4, 8, ["test", "test2"], 9)
# a set
obj.db.test7 = set([1, 2, 3, 4, 5])
# in-situ manipulation
obj.db.test8 = [1, 2, {"test":1}]
obj.db.test8[0] = 4
obj.db.test8[2]["test"] = 5
# test8 is now [4,2,{"test":5}]
```
Note that if make some advanced iterable object, and store an db-object on it in
a way such that it is _not_ returned by iterating over it, you have created a
'hidden' db-object. See [the previous section](#storing-single-objects) for how
to tell Evennia how to serialize such hidden objects safely.
### Retrieving Mutable objects
A side effect of the way Evennia stores Attributes is that *mutable* iterables (iterables that can
be modified in-place after they were created, which is everything except tuples) are handled by
custom objects called `_SaverList`, `_SaverDict` etc. These `_Saver...` classes behave just like the
normal variant except that they are aware of the database and saves to it whenever new data gets
assigned to them. This is what allows you to do things like `self.db.mylist[7] = val` and be sure
that the new version of list is saved. Without this you would have to load the list into a temporary
variable, change it and then re-assign it to the Attribute in order for it to save.
There is however an important thing to remember. If you retrieve your mutable iterable into another
variable, e.g. `mylist2 = obj.db.mylist`, your new variable (`mylist2`) will *still* be a
`_SaverList`. This means it will continue to save itself to the database whenever it is updated!
```python
obj.db.mylist = [1, 2, 3, 4]
mylist = obj.db.mylist
mylist[3] = 5 # this will also update database
print(mylist) # this is now [1, 2, 3, 5]
print(obj.db.mylist) # now also [1, 2, 3, 5]
```
When you extract your mutable Attribute data into a variable like `mylist`, think of it as getting a _snapshot_
of the variable. If you update the snapshot, it will save to the database, but this change _will not propagate to
any other snapshots you may have done previously_.
```python
obj.db.mylist = [1, 2, 3, 4]
mylist1 = obj.db.mylist
mylist2 = obj.db.mylist
mylist1[3] = 5
print(mylist1) # this is now [1, 2, 3, 5]
print(obj.db.mylist) # also updated to [1, 2, 3, 5]
print(mylist2) # still [1, 2, 3, 4] !
```
```{sidebar}
Remember, the complexities of this section only relate to *mutable* iterables - things you can update
in-place, like lists and dicts. [Immutable](https://en.wikipedia.org/wiki/Immutable) objects (strings,
numbers, tuples etc) are already disconnected from the database from the onset.
```
To avoid confusion with mutable Attributes, only work with one variable (snapshot) at a time and save
back the results as needed.
You can also choose to "disconnect" the Attribute entirely from the
database with the help of the `.deserialize()` method:
```python
obj.db.mylist = [1, 2, 3, 4, {1: 2}]
mylist = obj.db.mylist.deserialize()
```
The result of this operation will be a structure only consisting of normal Python mutables (`list`
instead of `_SaverList`, `dict` instead of `_SaverDict` and so on). If you update it, you need to
explicitly save it back to the Attribute for it to save.
## In-memory Attributes (NAttributes)
_NAttributes_ (short of Non-database Attributes) mimic Attributes in most things except they
are **non-persistent** - they will _not_ survive a server reload.
- Instead of `.db` use `.ndb`.
- Instead of `.attributes` use `.nattributes`
- Instead of `AttributeProperty`, use `NAttributeProperty`.
```python
rose.ndb.has_thorns = True
is_ouch = rose.ndb.has_thorns
rose.nattributes.add("has_thorns", True)
is_ouch = rose.nattributes.get("has_thorns")
```
Differences between `Attributes` and `NAttributes`:
- `NAttribute`s are always wiped on a server reload.
- They only exist in memory and never involve the database at all, making them faster to
access and edit than `Attribute`s.
- `NAttribute`s can store _any_ Python structure (and database object) without limit.
- They can _not_ be set with the standard `set` command (but they are visible with `examine`)
There are some important reasons we recommend using `ndb` to store temporary data rather than
the simple alternative of just storing a variable directly on an object:
- NAttributes are tracked by Evennia and will not be purged in various cache-cleanup operations
the server may do. So using them guarantees that they'll remain available at least as long as
the server lives.
- It's a consistent style - `.db/.attributes` and `.ndb/.nattributes` makes for clean-looking code
where it's clear how long-lived (or not) your data is to be.
### Persistent vs non-persistent
So *persistent* data means that your data will survive a server reboot, whereas with
*non-persistent* data it will not ...
... So why would you ever want to use non-persistent data? The answer is, you don't have to. Most of
the time you really want to save as much as you possibly can. Non-persistent data is potentially
useful in a few situations though.
- You are worried about database performance. Since Evennia caches Attributes very aggressively,
this is not an issue unless you are reading *and* writing to your Attribute very often (like many
times per second). Reading from an already cached Attribute is as fast as reading any Python
property. But even then this is not likely something to worry about: Apart from Evennia's own
caching, modern database systems themselves also cache data very efficiently for speed. Our
default
database even runs completely in RAM if possible, alleviating much of the need to write to disk
during heavy loads.
- A more valid reason for using non-persistent data is if you *want* to lose your state when logging
off. Maybe you are storing throw-away data that are re-initialized at server startup. Maybe you
are implementing some caching of your own. Or maybe you are testing a buggy [Script](./Scripts.md) that
does potentially harmful stuff to your character object. With non-persistent storage you can be
sure that whatever is messed up, it's nothing a server reboot can't clear up.
- `NAttribute`s have no restrictions at all on what they can store, since they
don't need to worry about being saved to the database - they work very well for temporary storage.
- You want to implement a fully or partly *non-persistent world*. Who are we to argue with your
grand vision!

View file

@ -0,0 +1,163 @@
# Batch Code Processor
For an introduction and motivation to using batch processors, see [here](./Batch-Processors.md). This page describes the Batch-*code* processor. The Batch-*command* one is covered [here](Batch-Command- Processor).
The batch-code processor is a superuser-only function, invoked by
> batchcode path.to.batchcodefile
Where `path.to.batchcodefile` is the path to a *batch-code file*. Such a file should have a name ending in "`.py`" (but you shouldn't include that in the path). The path is given like a python path relative to a folder you define to hold your batch files, set by `BATCH_IMPORT_PATH` in your settings. Default folder is (assuming your game is called "mygame") `mygame/world/`. So if you want to run the example batch file in `mygame/world/batch_code.py`, you could simply use
> batchcode batch_code
This will try to run through the entire batch file in one go. For more gradual, *interactive* control you can use the `/interactive` switch. The switch `/debug` will put the processor in *debug* mode. Read below for more info.
## The batch file
A batch-code file is a normal Python file. The difference is that since the batch processor loads and executes the file rather than importing it, you can reliably update the file, then call it again, over and over and see your changes without needing to `reload` the server. This makes for easy testing. In the batch-code file you have also access to the following global variables:
- `caller` - This is a reference to the object running the batchprocessor.
- `DEBUG` - This is a boolean that lets you determine if this file is currently being run in debug-mode or not. See below how this can be useful.
Running a plain Python file through the processor will just execute the file from beginning to end. If you want to get more control over the execution you can use the processor's *interactive* mode. This runs certain code blocks on their own, rerunning only that part until you are happy with it. In order to do this you need to add special markers to your file to divide it up into smaller chunks. These take the form of comments, so the file remains valid Python.
- `#CODE` as the first on a line marks the start of a *code* block. It will last until the beginning of another marker or the end of the file. Code blocks contain functional python code. Each `#CODE` block will be run in complete isolation from other parts of the file, so make sure it's self- contained.
- `#HEADER` as the first on a line marks the start of a *header* block. It lasts until the next marker or the end of the file. This is intended to hold imports and variables you will need for all other blocks .All python code defined in a header block will always be inserted at the top of every `#CODE` blocks in the file. You may have more than one `#HEADER` block, but that is equivalent to having one big one. Note that you can't exchange data between code blocks, so editing a header- variable in one code block won't affect that variable in any other code block!
- `#INSERT path.to.file` will insert another batchcode (Python) file at that position. - A `#` that is not starting a `#HEADER`, `#CODE` or `#INSERT` instruction is considered a comment.
- Inside a block, normal Python syntax rules apply. For the sake of indentation, each block acts as a separate python module.
Below is a version of the example file found in `evennia/contrib/tutorial_examples/`.
```python
#
# This is an example batch-code build file for Evennia.
#
#HEADER
# This will be included in all other #CODE blocks
from evennia import create_object, search_object
from evennia.contrib.tutorial_examples import red_button
from typeclasses.objects import Object
limbo = search_object('Limbo')[0]
#CODE
red_button = create_object(red_button.RedButton, key="Red button",
location=limbo, aliases=["button"])
# caller points to the one running the script
caller.msg("A red button was created.")
# importing more code from another batch-code file
#INSERT batch_code_insert
#CODE
table = create_object(Object, key="Blue Table", location=limbo)
chair = create_object(Object, key="Blue Chair", location=limbo)
string = f"A {table} and {chair} were created."
if DEBUG:
table.delete()
chair.delete()
string += " Since debug was active, they were deleted again."
caller.msg(string)
```
This uses Evennia's Python API to create three objects in sequence.
## Debug mode
Try to run the example script with
> batchcode/debug tutorial_examples.example_batch_code
The batch script will run to the end and tell you it completed. You will also get messages that the button and the two pieces of furniture were created. Look around and you should see the button there. But you won't see any chair nor a table! This is because we ran this with the `/debug` switch, which is directly visible as `DEBUG==True` inside the script. In the above example we handled this state by deleting the chair and table again.
The debug mode is intended to be used when you test out a batchscript. Maybe you are looking for bugs in your code or try to see if things behave as they should. Running the script over and over would then create an ever-growing stack of chairs and tables, all with the same name. You would have to go back and painstakingly delete them later.
## Interactive mode
Interactive mode works very similar to the [batch-command processor counterpart](Batch-Command- Processor). It allows you more step-wise control over how the batch file is executed. This is useful for debugging or for picking and choosing only particular blocks to run. Use `batchcode` with the `/interactive` flag to enter interactive mode.
> batchcode/interactive tutorial_examples.batch_code
You should see the following:
01/02: red_button = create_object(red_button.RedButton, [...] (hh for help)
This shows that you are on the first `#CODE` block, the first of only two commands in this batch file. Observe that the block has *not* actually been executed at this point!
To take a look at the full code snippet you are about to run, use `ll` (a batch-processor version of `look`).
```python
from evennia.utils import create, search
from evennia.contrib.tutorial_examples import red_button
from typeclasses.objects import Object
limbo = search.objects(caller, 'Limbo', global_search=True)[0]
red_button = create.create_object(red_button.RedButton, key="Red button",
location=limbo, aliases=["button"])
# caller points to the one running the script
caller.msg("A red button was created.")
```
Compare with the example code given earlier. Notice how the content of `#HEADER` has been pasted at the top of the `#CODE` block. Use `pp` to actually execute this block (this will create the button
and give you a message). Use `nn` (next) to go to the next command. Use `hh` for a list of commands.
If there are tracebacks, fix them in the batch file, then use `rr` to reload the file. You will still be at the same code block and can rerun it easily with `pp` as needed. This makes for a simple debug cycle. It also allows you to rerun individual troublesome blocks - as mentioned, in a large batch file this can be very useful (don't forget the `/debug` mode either).
Use `nn` and `bb` (next and back) to step through the file; e.g. `nn 12` will jump 12 steps forward (without processing any blocks in between). All normal commands of Evennia should work too while working in interactive mode.
## Limitations and Caveats
The batch-code processor is by far the most flexible way to build a world in Evennia. There are however some caveats you need to keep in mind.
### Safety
Or rather the lack of it. There is a reason only *superusers* are allowed to run the batch-code
processor by default. The code-processor runs **without any Evennia security checks** and allows
full access to Python. If an untrusted party could run the code-processor they could execute
arbitrary python code on your machine, which is potentially a very dangerous thing. If you want to
allow other users to access the batch-code processor you should make sure to run Evennia as a
separate and very limited-access user on your machine (i.e. in a 'jail'). By comparison, the batch-
command processor is much safer since the user running it is still 'inside' the game and can't
really do anything outside what the game commands allow them to.
### No communication between code blocks
Global variables won't work in code batch files, each block is executed as stand-alone environments. `#HEADER` blocks are literally pasted on top of each `#CODE` block so updating some header-variable in your block will not make that change available in another block. Whereas a python execution limitation, allowing this would also lead to very hard-to-debug code when using the interactive mode - this would be a classical example of "spaghetti code".
The main practical issue with this is when building e.g. a room in one code block and later want to connect that room with a room you built in the current block. There are two ways to do this:
- Perform a database search for the name of the room you created (since you cannot know in advance which dbref it got assigned). The problem is that a name may not be unique (you may have a lot of "A dark forest" rooms). There is an easy way to handle this though - use [Tags](./Tags.md) or *Aliases*. You can assign any number of tags and/or aliases to any object. Make sure that one of those tags or aliases is unique to the room (like "room56") and you will henceforth be able to always uniquely search and find it later.
- Use the `caller` global property as an inter-block storage. For example, you could have a dictionary of room references in an `ndb`:
```python
#HEADER
if caller.ndb.all_rooms is None:
caller.ndb.all_rooms = {}
#CODE
# create and store the castle
castle = create_object("rooms.Room", key="Castle")
caller.ndb.all_rooms["castle"] = castle
#CODE
# in another node we want to access the castle
castle = caller.ndb.all_rooms.get("castle")
```
Note how we check in `#HEADER` if `caller.ndb.all_rooms` doesn't already exist before creating the dict. Remember that `#HEADER` is copied in front of every `#CODE` block. Without that `if` statement
we'd be wiping the dict every block!
### Don't treat a batchcode file like any Python file
Despite being a valid Python file, a batchcode file should *only* be run by the batchcode processor. You should not do things like define Typeclasses or Commands in them, or import them into other code. Importing a module in Python will execute base level of the module, which in the case of your average batchcode file could mean creating a lot of new objects every time.
### Don't let code rely on the batch-file's real file path
When you import things into your batchcode file, don't use relative imports but always import with paths starting from the root of your game directory or evennia library. Code that relies on the batch file's "actual" location *will fail*. Batch code files are read as text and the strings executed. When the code runs it has no knowledge of what file those strings where once a part of.

View file

@ -0,0 +1,130 @@
# Batch Command Processor
For an introduction and motivation to using batch processors, see [here](./Batch-Processors.md). This
page describes the Batch-*command* processor. The Batch-*code* one is covered [here](Batch-Code-
Processor).
The batch-command processor is a superuser-only function, invoked by
> batchcommand path.to.batchcmdfile
Where `path.to.batchcmdfile` is the path to a *batch-command file* with the "`.ev`" file ending.
This path is given like a python path relative to a folder you define to hold your batch files, set
with `BATCH_IMPORT_PATH` in your settings. Default folder is (assuming your game is in the `mygame`
folder) `mygame/world`. So if you want to run the example batch file in
`mygame/world/batch_cmds.ev`, you could use
> batchcommand batch_cmds
A batch-command file contains a list of Evennia in-game commands separated by comments. The
processor will run the batch file from beginning to end. Note that *it will not stop if commands in
it fail* (there is no universal way for the processor to know what a failure looks like for all
different commands). So keep a close watch on the output, or use *Interactive mode* (see below) to
run the file in a more controlled, gradual manner.
## The batch file
The batch file is a simple plain-text file containing Evennia commands. Just like you would write
them in-game, except you have more freedom with line breaks.
Here are the rules of syntax of an `*.ev` file. You'll find it's really, really simple:
- All lines having the `#` (hash)-symbol *as the first one on the line* are considered *comments*. All non-comment lines are treated as a command and/or their arguments.
- Comment lines have an actual function -- they mark the *end of the previous command definition*. So never put two commands directly after one another in the file - separate them with a comment, or the second of the two will be considered an argument to the first one. Besides, using plenty of comments is good practice anyway.
- A line that starts with the word `#INSERT` is a comment line but also signifies a special instruction. The syntax is `#INSERT <path.batchfile>` and tries to import a given batch-cmd file into this one. The inserted batch file (file ending `.ev`) will run normally from the point of the `#INSERT` instruction.
- Extra whitespace in a command definition is *ignored*. - A completely empty line translates in to a line break in texts. Two empty lines thus means a new paragraph (this is obviously only relevant for commands accepting such formatting, such as the `@desc` command).
- The very last command in the file is not required to end with a comment.
- You *cannot* nest another `batchcommand` statement into your batch file. If you want to link many batch-files together, use the `#INSERT` batch instruction instead. You also cannot launch the `batchcode` command from your batch file, the two batch processors are not compatible.
Below is a version of the example file found in `evennia/contrib/tutorial_examples/batch_cmds.ev`.
```bash
#
# This is an example batch build file for Evennia.
#
# This creates a red button
@create button:tutorial_examples.red_button.RedButton
# (This comment ends input for @create)
# Next command. Let's create something.
@set button/desc =
This is a large red button. Now and then
it flashes in an evil, yet strangely tantalizing way.
A big sign sits next to it. It says:
-----------
Press me!
-----------
... It really begs to be pressed! You
know you want to!
# This inserts the commands from another batch-cmd file named
# batch_insert_file.ev.
#INSERT examples.batch_insert_file
# (This ends the @set command). Note that single line breaks
# and extra whitespace in the argument are ignored. Empty lines
# translate into line breaks in the output.
# Now let's place the button where it belongs (let's say limbo #2 is
# the evil lair in our example)
@teleport #2
# (This comments ends the @teleport command.)
# Now we drop it so others can see it.
# The very last command in the file needs not be ended with #.
drop button
```
To test this, run `@batchcommand` on the file:
> batchcommand contrib.tutorial_examples.batch_cmds
A button will be created, described and dropped in Limbo. All commands will be executed by the user calling the command.
> Note that if you interact with the button, you might find that its description changes, loosing your custom-set description above. This is just the way this particular object works.
## Interactive mode
Interactive mode allows you to more step-wise control over how the batch file is executed. This is useful for debugging and also if you have a large batch file and is only updating a small part of it -- running the entire file again would be a waste of time (and in the case of `create`-ing objects you would to end up with multiple copies of same-named objects, for example). Use `batchcommand` with the `/interactive` flag to enter interactive mode.
> @batchcommand/interactive tutorial_examples.batch_cmds
You will see this:
01/04: @create button:tutorial_examples.red_button.RedButton (hh for help)
This shows that you are on the `@create` command, the first out of only four commands in this batch file. Observe that the command `@create` has *not* been actually processed at this point!
To take a look at the full command you are about to run, use `ll` (a batch-processor version of
`look`). Use `pp` to actually process the current command (this will actually `@create` the button) -- and make sure it worked as planned. Use `nn` (next) to go to the next command. Use `hh` for a list of commands.
If there are errors, fix them in the batch file, then use `rr` to reload the file. You will still be at the same command and can rerun it easily with `pp` as needed. This makes for a simple debug cycle. It also allows you to rerun individual troublesome commands - as mentioned, in a large batch file this can be very useful. Do note that in many cases, commands depend on the previous ones (e.g. if `create` in the example above had failed, the following commands would have had nothing to operate on).
Use `nn` and `bb` (next and back) to step through the file; e.g. `nn 12` will jump 12 steps forward (without processing any command in between). All normal commands of Evennia should work too while working in interactive mode.
## Limitations and Caveats
The batch-command processor is great for automating smaller builds or for testing new commands and objects repeatedly without having to write so much. There are several caveats you have to be aware of when using the batch-command processor for building larger, complex worlds though.
The main issue is that when you run a batch-command script you (*you*, as in your superuser
character) are actually moving around in the game creating and building rooms in sequence, just as if you had been entering those commands manually, one by one. You have to take this into account when creating the file, so that you can 'walk' (or teleport) to the right places in order.
This also means there are several pitfalls when designing and adding certain types of objects. Here are some examples:
- *Rooms that change your [Command Set](./Command-Sets.md)*: Imagine that you build a 'dark' room, which severely limits the cmdsets of those entering it (maybe you have to find the light switch to proceed). In your batch script you would create this room, then teleport to it - and promptly be shifted into the dark state where none of your normal build commands work ...
- *Auto-teleportation*: Rooms that automatically teleport those that enter them to another place (like a trap room, for example). You would be teleported away too.
- *Mobiles*: If you add aggressive mobs, they might attack you, drawing you into combat. If they have AI they might even follow you around when building - or they might move away from you before you've had time to finish describing and equipping them!
The solution to all these is to plan ahead. Make sure that superusers are never affected by whatever effects are in play. Add an on/off switch to objects and make sure it's always set to *off* upon creation. It's all doable, one just needs to keep it in mind.
## Editor highlighting for .ev files
- [GNU Emacs](https://www.gnu.org/software/emacs/) users might find it interesting to use emacs' *evennia mode*. This is an Emacs major mode found in `evennia/utils/evennia-mode.el`. It offers correct syntax highlighting and indentation with `<tab>` when editing `.ev` files in Emacs. See the header of that file for installation instructions.
- [VIM](https://www.vim.org/) users can use amfl's [vim-evennia](https://github.com/amfl/vim-evennia) mode instead, see its readme for install instructions.

View file

@ -0,0 +1,47 @@
# Batch Processors
```{toctree}
:maxdepth: 2
Batch-Command-Processor.md
Batch-Code-Processor.md
```
Building a game world is a lot of work, especially when starting out. Rooms should be created, descriptions have to be written, objects must be detailed and placed in their proper places. In many
traditional MUD setups you had to do all this online, line by line, over a telnet session.
Evennia already moves away from much of this by shifting the main coding work to external Python modules. But also building would be helped if one could do some or all of it externally. Enter Evennia's *batch processors* (there are two of them). The processors allows you, as a game admin, to build your game completely offline in normal text files (*batch files*) that the processors understands. Then, when you are ready, you use the processors to read it all into Evennia (and into the database) in one go.
You can of course still build completely online should you want to - this is certainly the easiest way to go when learning and for small build projects. But for major building work, the advantages of using the batch-processors are many:
- It's hard to compete with the comfort of a modern desktop text editor; Compared to a traditional MUD line input, you can get much better overview and many more features. Also, accidentally pressing Return won't immediately commit things to the database.
- You might run external spell checkers on your batch files. In the case of one of the batch- processors (the one that deals with Python code), you could also run external debuggers and code analyzers on your file to catch problems before feeding it to Evennia.
- The batch files (as long as you keep them) are records of your work. They make a natural starting point for quickly re-building your world should you ever decide to start over.
- If you are an Evennia developer, using a batch file is a fast way to setup a test-game after having reset the database.
- The batch files might come in useful should you ever decide to distribute all or part of your world to others.
There are two batch processors, the Batch-*command* processor and the Batch-*code* processor. The
first one is the simpler of the two. It doesn't require any programming knowledge - you basically
just list in-game commands in a text file. The code-processor on the other hand is much more
powerful but also more complex - it lets you use Evennia's API to code your world in full-fledged
Python code.
## A note on File Encodings
As mentioned, both the processors take text files as input and then proceed to process them. As long as you stick to the standard [ASCII](https://en.wikipedia.org/wiki/Ascii) character set (which means the normal English characters, basically) you should not have to worry much about this section.
Many languages however use characters outside the simple `ASCII` table. Common examples are various apostrophes and umlauts but also completely different symbols like those of the greek or cyrillic alphabets.
First, we should make it clear that Evennia itself handles international characters just fine. It (and Django) uses [unicode](https://en.wikipedia.org/wiki/Unicode) strings internally.
The problem is that when reading a text file like the batchfile, we need to know how to decode the byte-data stored therein to universal unicode. That means we need an *encoding* (a mapping) for how the file stores its data. There are many, many byte-encodings used around the world, with opaque names such as `Latin-1`, `ISO-8859-3` or `ARMSCII-8` to pick just a few examples. Problem is that it's practially impossible to determine which encoding was used to save a file just by looking at it (it's just a bunch of bytes!). You have to *know*.
With this little introduction it should be clear that Evennia can't guess but has to *assume* an encoding when trying to load a batchfile. The text editor and Evennia must speak the same "language" so to speak. Evennia will by default first try the international `UTF-8` encoding, but you can have Evennia try any sequence of different encodings by customizing the `ENCODINGS` list in your settings file. Evennia will use the first encoding in the list that do not raise any errors. Only if none work will the server give up and return an error message.
You can often change the text editor encoding (this depends on your editor though), otherwise you need to add the editor's encoding to Evennia's `ENCODINGS` list. If you are unsure, write a test file with lots of non-ASCII letters in the editor of your choice, then import to make sure it works as it should.
More help with encodings can be found in the entry [Text Encodings](../Concepts/Text-Encodings.md) and also in the Wikipedia article [here](https://en.wikipedia.org/wiki/Text_encodings).
**A footnote for the batch-code processor**: Just because *Evennia* can parse your file and your
fancy special characters, doesn't mean that *Python* allows their use. Python syntax only allows international characters inside *strings*. In all other source code only `ASCII` set characters are
allowed.

View file

@ -0,0 +1,357 @@
# Channels
In a multiplayer game, players often need other means of in-game communication
than moving to the same room and use `say` or `emote`.
_Channels_ allows Evennia's to act as a fancy chat program. When a player is
connected to a channel, sending a message to it will automatically distribute
it to every other subscriber.
Channels can be used both for chats between [Accounts](./Accounts.md) and between
[Objects](./Objects.md) (usually Characters). Chats could be both OOC
(out-of-character) or IC (in-charcter) in nature. Some examples:
- A support channel for contacting staff (OOC)
- A general chat for discussing anything and foster community (OOC)
- Admin channel for private staff discussions (OOC)
- Private guild channels for planning and organization (IC/OOC depending on game)
- Cyberpunk-style retro chat rooms (IC)
- In-game radio channels (IC)
- Group telephathy (IC)
- Walkie talkies (IC)
```{versionchanged} 1.0
Channel system changed to use a central 'channel' command and nicks instead of
auto-generated channel-commands and -cmdset. ChannelHandler was removed.
```
## Working with channels
### Viewing and joining channels
In the default command set, channels are all handled via the mighty [channel command](evennia.commands.default.comms.CmdChannel), `channel` (or `chan`). By default, this command will assume all entities dealing with channels are `Accounts`.
Viewing channels
channel - shows your subscriptions
channel/all - shows all subs available to you
channel/who - shows who subscribes to this channel
To join/unsub a channel do
channel/sub channelname
channel/unsub channelname
If you temporarily don't want to hear the channel for a while (without actually
unsubscribing), you can mute it:
channel/mute channelname
channel/unmute channelname
### Talk on channels
To speak on a channel, do
channel public Hello world!
If the channel-name has spaces in it, you need to use a '`=`':
channel rest room = Hello world!
Now, this is more to type than we'd like, so when you join a channel, the
system automatically sets up an personal alias so you can do this instead:
public Hello world
```{warning}
This shortcut will not work if the channel-name has spaces in it.
So channels with long names should make sure to provide a one-word alias as
well.
```
Any user can make up their own channel aliases:
channel/alias public = foo;bar
You can now just do
foo Hello world!
bar Hello again!
And even remove the default one if they don't want to use it
channel/unalias public
public Hello (gives a command-not-found error now)
But you can also use your alias with the `channel` command:
channel foo Hello world!
> What happens when aliasing is that a [nick](./Nicks.md) is created that maps your
> alias + argument onto calling the `channel` command. So when you enter `foo hello`,
> what the server sees is actually `channel foo = hello`. The system is also
> clever enough to know that whenever you search for channels, your channel-nicks
> should also be considered so as to convert your input to an existing channel name.
You can check if you missed channel conversations by viewing the channel's
scrollback with
channel/history public
This retrieves the last 20 lines of text (also from a time when you were
offline). You can step further back by specifying how many lines back to start:
channel/history public = 30
This again retrieve 20 lines, but starting 30 lines back (so you'll get lines
30-50 counting backwards).
### Channel administration
To create/destroy a new channel you can do
channel/create channelname;alias;alias = description
channel/destroy channelname
Aliases are optional but can be good for obvious shortcuts everyone may want to
use. The description is used in channel-listings. You will automatically join a
channel you created and will be controlling it. You can also use `channel/desc` to
change the description on a channel you wnn later.
If you control a channel you can also kick people off it:
channel/boot mychannel = annoyinguser123 : stop spamming!
The last part is an optional reason to send to the user before they are booted.
You can give a comma-separated list of channels to kick the same user from all
those channels at once. The user will be unsubbed from the channel and all
their aliases will be wiped. But they can still rejoin if they like.
channel/ban mychannel = annoyinguser123
channel/ban - view bans
channel/unban mychannel = annoyinguser123
Banning adds the user to the channels blacklist. This means they will not be
able to _rejoin_ if you boot them. You will need to run `channel/boot` to
actually kick them out.
See the [Channel command](evennia.commands.default.comms.CmdChannel) api docs (and in-game help) for more details.
Admin-level users can also modify channel's [locks](./Locks.md):
channel/lock buildchannel = listen:all();send:perm(Builders)
Channels use three lock-types by default:
- `listen` - who may listen to the channel. Users without this access will not
even be able to join the channel and it will not appear in listings for them.
- `send` - who may send to the channel.
- `control` - this is assigned to you automatically when you create the channel. With
control over the channel you can edit it, boot users and do other management tasks.
#### Restricting channel administration
By default everyone can use the channel command ([evennia.commands.default.comms.CmdChannel](evennia.commands.default.comms.CmdChannel)) to create channels and will then control the channels they created (to boot/ban people etc). If you as a developer does not want regular players to do this (perhaps you want only staff to be able to spawn new channels), you can override the `channel` command and change its `locks` property.
The default `help` command has the following `locks` property:
```python
locks = "cmd:not perm(channel_banned); admin:all(); manage:all(); changelocks: perm(Admin)"
```
This is a regular [lockstring](./Locks.md).
- `cmd: pperm(channel_banned)` - The `cmd` locktype is the standard one used for all Commands.
an accessing object failing this will not even know that the command exists. The `pperm()` lockfunc
checks an on-account [Permission](Building Permissions) 'channel_banned' - and the `not` means
that if they _have_ that 'permission' they are cut off from using the `channel` command. You usually
don't need to change this lock.
- `admin:all()` - this is a lock checked in the `channel` command itself. It controls access to the
`/boot`, `/ban` and `/unban` switches (by default letting everyone use them).
- `manage:all()` - this controls access to the `/create`, `/destroy`, `/desc` switches.
- `changelocks: perm(Admin)` - this controls access to the `/lock` and `/unlock` switches. By
default this is something only [Admins](Building Permissions) can change.
> Note - while `admin:all()` and `manage:all()` will let everyone use these switches, users
> will still only be able to admin or destroy channels they actually control!
If you only want (say) Builders and higher to be able to create and admin
channels you could override the `help` command and change the lockstring to:
```python
# in for example mygame/commands/commands.py
from evennia import default_cmds
class MyCustomChannelCmd(default_cmds.CmdChannel):
locks = "cmd: not pperm(channel_banned);admin:perm(Builder);manage:perm(Builder);changelocks:perm(Admin)"
```
Add this custom command to your default cmdset and regular users wil now get an
access-denied error when trying to use use these switches.
## Using channels in code
For most common changes, the default channel, the recipient hooks and possibly
overriding the `channel` command will get you very far. But you can also tweak
channels themselves.
### Allowing Characters to use Channels
The default `channel` command ([evennia.commands.default.comms.CmdChannel](evennia.commands.default.comms.CmdChannel)) sits in the `Account` [command set](./Command-Sets.md). It is set up such that it will always operate on `Accounts`, even if you were to add it to the `CharacterCmdSet`.
It's a one-line change to make this command accept non-account callers. But for convenience we provide a version for Characters/Objects. Just import [evennia.commands.default.comms.CmdObjectChannel](evennia.commands.default.comms.CmdObjectChannel) and inherit from that instead.
### Customizing channel output and behavior
When distributing a message, the channel will call a series of hooks on itself
and (more importantly) on each recipient. So you can customize things a lot by
just modifying hooks on your normal Object/Account typeclasses.
Internally, the message is sent with
`channel.msg(message, senders=sender, bypass_mute=False, **kwargs)`, where
`bypass_mute=True` means the message ignores muting (good for alerts or if you
delete the channel etc) and `**kwargs` are any extra info you may want to pass
to the hooks. The `senders` (it's always only one in the default implementation
but could in principle be multiple) and `bypass_mute` are part of the `kwargs`
below:
1. `channel.at_pre_msg(message, **kwargs)`
2. For each recipient:
- `message = recipient.at_pre_channel_msg(message, channel, **kwargs)` -
allows for the message to be tweaked per-receiver (for example coloring it depending
on the users' preferences). If this method returns `False/None`, that
recipient is skipped.
- `recipient.channel_msg(message, channel, **kwargs)` - actually sends to recipient.
- `recipient.at_post_channel_msg(message, channel, **kwargs)` - any post-receive effects.
3. `channel.at_post_channel_msg(message, **kwargs)`
Note that `Accounts` and `Objects` both have their have separate sets of hooks.
So make sure you modify the set actually used by your subcribers (or both).
Default channels all use `Account` subscribers.
### Channel class
Channels are [Typeclassed](./Typeclasses.md) entities. This means they are persistent in the database, can have [attributes](./Attributes.md) and [Tags](./Tags.md) and can be easily extended.
To change which channel typeclass Evennia uses for default commands, change `settings.BASE_CHANNEL_TYPECLASS`. The base command class is [`evennia.comms.comms.DefaultChannel`](evennia.comms.comms.DefaultChannel). There is an empty child class in `mygame/typeclasses/channels.py`, same as for other typelass-bases.
In code you create a new channel with `evennia.create_channel` or
`Channel.create`:
```python
from evennia import create_channel, search_object
from typeclasses.channels import Channel
channel = create_channel("my channel", aliases=["mychan"], locks=..., typeclass=...)
# alternative
channel = Channel.create("my channel", aliases=["mychan"], locks=...)
# connect to it
me = search_object(key="Foo")[0]
channel.connect(me)
# send to it (this will trigger the channel_msg hooks described earlier)
channel.msg("Hello world!", senders=me)
# view subscriptions (the SubscriptionHandler handles all subs under the hood)
channel.subscriptions.has(me) # check we subbed
channel.subscriptions.all() # get all subs
channel.subscriptions.online() # get only subs currently online
channel.subscriptions.clear() # unsub all
# leave channel
channel.disconnect(me)
# permanently delete channel (will unsub everyone)
channel.delete()
```
The Channel's `.connect` method will accept both `Account` and `Object` subscribers
and will handle them transparently.
The channel has many more hooks, both hooks shared with all typeclasses as well as special ones related to muting/banning etc. See the channel class for
details.
### Channel logging
```{versionchanged} 0.7
Channels changed from using Msg to TmpMsg and optional log files.
```
```{versionchanged} 1.0
Channels stopped supporting Msg and TmpMsg, using only log files.
```
The channel messages are not stored in the database. A channel is instead always logged to a regular text log-file `mygame/server/logs/channel_<channelname>.log`. This is where `channels/history channelname` gets its data from. A channel's log will rotate when it grows too big, which thus also automatically limits the max amount of history a user can view with
`/history`.
The log file name is set on the channel class as the `log_file` property. This
is a string that takes the formatting token `{channelname}` to be replaced with
the (lower-case) name of the channel. By default the log is written to in the
channel's `at_post_channel_msg` method.
### Properties on Channels
Channels have all the standard properties of a Typeclassed entity (`key`,
`aliases`, `attributes`, `tags`, `locks` etc). This is not an exhaustive list;
see the [Channel api docs](evennia.comms.comms.DefaultChannel) for details.
- `send_to_online_only` - this class boolean defaults to `True` and is a
sensible optimization since people offline people will not see the message anyway.
- `log_file` - this is a string that determines the name of the channel log file. Default
is `"channel_{channelname}.log"`. The log file will appear in `settings.LOG_DIR` (usually
`mygame/server/logs/`). You should usually not change this.
- `channel_prefix_string` - this property is a string to easily change how
the channel is prefixed. It takes the `channelname` format key. Default is `"[{channelname}] "`
and produces output like `[public] ...`.
- `subscriptions` - this is the [SubscriptionHandler](evennia.comms.models.SubscriptionHandler), which
has methods `has`, `add`, `remove`, `all`, `clear` and also `online` (to get
only actually online channel-members).
- `wholist`, `mutelist`, `banlist` are properties that return a list of subscribers,
as well as who are currently muted or banned.
- `channel_msg_nick_pattern` - this is a regex pattern for performing the in-place nick
replacement (detect that `channelalias <msg` means that you want to send a message to a channel).
This pattern accepts an `{alias}` formatting marker. Don't mess with this unless you really
want to change how channels work.
- `channel_msg_nick_replacement` - this is a string on the [nick replacement
- form](./Nicks.md). It accepts the `{channelname}` formatting tag. This is strongly tied to the
`channel` command and is by default `channel {channelname} = $1`.
Notable `Channel` hooks:
- `at_pre_channel_msg(message, **kwargs)` - called before sending a message, to
modify it. Not used by default.
- `msg(message, senders=..., bypass_mute=False, **kwargs)` - send the message onto
the channel. The `**kwargs` are passed on into the other call hooks (also on the recipient).
- `at_post_channel_msg(message, **kwargs)` - by default this is used to store the message
to the log file.
- `channel_prefix(message)` - this is called to allow the channel to prefix. This is called
by the object/account when they build the message, so if wanting something else one can
also just remove that call.
- every channel message. By default it just returns `channel_prefix_string`.
- `has_connection(subscriber)` - shortcut to check if an entity subscribes to
this channel.
- `mute/unmute(subscriber)` - this mutes the channel for this user.
- `ban/unban(subscriber)` - adds/remove user from banlist.
- `connect/disconnect(subscriber)` - adds/removes a subscriber.
- `add_user_channel_alias(user, alias, **kwargs)` - sets up a user-nick for this channel. This is
what maps e.g. `alias <msg>` to `channel channelname = <msg>`.
- `remove_user_channel_alias(user, alias, **kwargs)` - remove an alias. Note that this is
a class-method that will happily remove found channel-aliases from the user linked to _any_
channel, not only from the channel the method is called on.
- `pre_join_channel(subscriber)` - if this returns `False`, connection will be refused.
- `post_join_channel(subscriber)` - by default this sets up a users's channel-nicks/aliases.
- `pre_leave_channel(subscriber)` - if this returns `False`, the user is not allowed to leave.
- `post_leave_channel(subscriber)` - this will clean up any channel aliases/nicks of the user.
- `delete` the standard typeclass-delete mechanism will also automatically un-subscribe all
subscribers (and thus wipe all their aliases).

View file

@ -4,6 +4,8 @@
Evennia comes with many utilities to help with common coding tasks. Most are accessible directly
from the flat API, otherwise you can find them in the `evennia/utils/` folder.
> This is just a small selection of the tools in `evennia/utils`. It's worth to browse [the directory](evennia.utils) and in particular the content of [evennia/utils/utils.py](evennia.utils.utils) directly to find more useful stuff.
## Searching
A common thing to do is to search for objects. There it's easiest to use the `search` method defined
@ -13,13 +15,9 @@ on all objects. This will search for objects in the same location and inside the
obj = self.search(objname)
```
The most common time one needs to do this is inside a command body. `obj =
self.caller.search(objname)` will search inside the caller's (typically, the character that typed
the command) `.contents` (their "inventory") and `.location` (their "room").
The most common time one needs to do this is inside a command body. `obj = self.caller.search(objname)` will search inside the caller's (typically, the character that typed the command) `.contents` (their "inventory") and `.location` (their "room").
Give the keyword `global_search=True` to extend search to encompass entire database. Aliases will
also be matched by this search. You will find multiple examples of this functionality in the default
command set.
Give the keyword `global_search=True` to extend search to encompass entire database. Aliases will also be matched by this search. You will find multiple examples of this functionality in the default command set.
If you need to search for objects in a code module you can use the functions in
`evennia.utils.search`. You can access these as shortcuts `evennia.search_*`.
@ -31,40 +29,37 @@ If you need to search for objects in a code module you can use the functions in
- [evennia.search_account](evennia.accounts.manager.AccountDBManager.search_account)
- [evennia.search_object](evennia.objects.manager.ObjectDBManager.search_object)
- [evennia.search_tag](evennia.utils.search.search_tag)
- [evennia.search(object)_by_tag](evennia.utils.search.search_tag)
- [evennia.search_script](evennia.scripts.manager.ScriptDBManager.search_script)
- [evennia.search_channel](evennia.comms.managers.ChannelDBManager.search_channel)
- [evennia.search_message](evennia.comms.managers.MsgManager.search_message)
- [evennia.search_help](evennia.utils.search.search_help_entry)
- [evennia.search_help](evennia.help.manager.HelpEntryManager.search_help)
Note that these latter methods will always return a `list` of results, even if the list has one or
zero entries.
Note that these latter methods will always return a `list` of results, even if the list has one or zero entries.
## Create
Apart from the in-game build commands (`@create` etc), you can also build all of Evennia's game
entities directly in code (for example when defining new create commands).
Apart from the in-game build commands (`@create` etc), you can also build all of Evennia's game entities directly in code (for example when defining new create commands).
```python
import evennia
myobj = evennia.create_objects("game.gamesrc.objects.myobj.MyObj", key="MyObj")
```
- [evennia.create_account](create_account)
- [evennia.create_object](create_object)
- [evennia.create_script](create_script)
- [evennia.create_channel](create_channel)
- [evennia.create_help_entry](create_help_entry)
- [evennia.create_message](create_message)
- [evennia.create_account](evennia.utils.create.create_account)
- [evennia.create_object](evennia.utils.create.create_object)
- [evennia.create_script](evennia.utils.create.create_script)
- [evennia.create_channel](evennia.utils.create.create_channel)
- [evennia.create_help_entry](evennia.utils.create.create_help_entry)
- [evennia.create_message](evennia.utils.create.create_message)
Each of these create-functions have a host of arguments to further customize the created entity. See
`evennia/utils/create.py` for more information.
Each of these create-functions have a host of arguments to further customize the created entity. See `evennia/utils/create.py` for more information.
## Logging
Normally you can use Python `print` statements to see output to the terminal/log. The `print`
statement should only be used for debugging though. For producion output, use the `logger` which
will create proper logs either to terminal or to file.
statement should only be used for debugging though. For producion output, use the `logger` which will create proper logs either to terminal or to file.
```python
from evennia import logger
@ -75,8 +70,7 @@ will create proper logs either to terminal or to file.
logger.log_dep("This feature is deprecated")
```
There is a special log-message type, `log_trace()` that is intended to be called from inside a
traceback - this can be very useful for relaying the traceback message back to log without having it
There is a special log-message type, `log_trace()` that is intended to be called from inside a traceback - this can be very useful for relaying the traceback message back to log without having it
kill the server.
```python
@ -86,25 +80,21 @@ kill the server.
logger.log_trace("This text will show beneath the traceback itself.")
```
The `log_file` logger, finally, is a very useful logger for outputting arbitrary log messages. This
is a heavily optimized asynchronous log mechanism using
[threads](https://en.wikipedia.org/wiki/Thread_%28computing%29) to avoid overhead. You should be
able to use it for very heavy custom logging without fearing disk-write delays.
The `log_file` logger, finally, is a very useful logger for outputting arbitrary log messages. This is a heavily optimized asynchronous log mechanism using [threads](https://en.wikipedia.org/wiki/Thread_%28computing%29) to avoid overhead. You should be able to use it for very heavy custom logging without fearing disk-write delays.
```python
logger.log_file(message, filename="mylog.log")
```
If not an absolute path is given, the log file will appear in the `mygame/server/logs/` directory.
If the file already exists, it will be appended to. Timestamps on the same format as the normal
Evennia logs will be automatically added to each entry. If a filename is not specified, output will
be written to a file `game/logs/game.log`.
If not an absolute path is given, the log file will appear in the `mygame/server/logs/` directory. If the file already exists, it will be appended to. Timestamps on the same format as the normal Evennia logs will be automatically added to each entry. If a filename is not specified, output will be written to a file `game/logs/game.log`.
See also the [Debugging](../Coding/Debugging.md) documentation for help with finding elusive bugs.
## Time Utilities
### Game time
Evennia tracks the current server time. You can access this time via the `evennia.gametime`
shortcut:
Evennia tracks the current server time. You can access this time via the `evennia.gametime` shortcut:
```python
from evennia import gametime
@ -131,13 +121,8 @@ gametime.reset_gametime()
```
The setting `TIME_FACTOR` determines how fast/slow in-game time runs compared to the real world. The
setting `TIME_GAME_EPOCH` sets the starting game epoch (in seconds). The functions from the
`gametime` module all return their times in seconds. You can convert this to whatever units of time
you desire for your game. You can use the `@time` command to view the server time info.
You can also *schedule* things to happen at specific in-game times using the
[gametime.schedule](github:evennia.utils.gametime#schedule) function:
The setting `TIME_FACTOR` determines how fast/slow in-game time runs compared to the real world. The setting `TIME_GAME_EPOCH` sets the starting game epoch (in seconds). The functions from the `gametime` module all return their times in seconds. You can convert this to whatever units of time you desire for your game. You can use the `@time` command to view the server time info.
You can also *schedule* things to happen at specific in-game times using the [gametime.schedule](evennia.utils.gametime.schedule) function:
```python
import evennia
@ -151,9 +136,7 @@ gametime.schedule(church_clock, hour=2)
### utils.time_format()
This function takes a number of seconds as input (e.g. from the `gametime` module above) and
converts it to a nice text output in days, hours etc. It's useful when you want to show how old
something is. It converts to four different styles of output using the *style* keyword:
This function takes a number of seconds as input (e.g. from the `gametime` module above) and converts it to a nice text output in days, hours etc. It's useful when you want to show how old something is. It converts to four different styles of output using the *style* keyword:
- style 0 - `5d:45m:12s` (standard colon output)
- style 1 - `5d` (shows only the longest time unit)
@ -162,6 +145,8 @@ something is. It converts to four different styles of output using the *style* k
### utils.delay()
This allows for making a delayed call.
```python
from evennia import utils
@ -169,44 +154,23 @@ def _callback(obj, text):
obj.msg(text)
# wait 10 seconds before sending "Echo!" to obj (which we assume is defined)
deferred = utils.delay(10, _callback, obj, "Echo!", persistent=False)
utils.delay(10, _callback, obj, "Echo!", persistent=False)
# code here will run immediately, not waiting for the delay to fire!
```
This creates an asynchronous delayed call. It will fire the given callback function after the given
number of seconds. This is a very light wrapper over a Twisted
[Deferred](https://twistedmatrix.com/documents/current/core/howto/defer.html). Normally this is run
non-persistently, which means that if the server is `@reload`ed before the delay is over, the
callback will never run (the server forgets it). If setting `persistent` to True, the delay will be
stored in the database and survive a `@reload` - but for this to work it is susceptible to the same
limitations incurred when saving to an [Attribute](./Attributes.md).
See [The Asynchronous process](../Concepts/Async-Process.md#delay) for more information.
The `deferred` return object can usually be ignored, but calling its `.cancel()` method will abort
the delay prematurely.
## Finding Classes
`utils.delay` is the lightest form of delayed call in Evennia. For other way to create time-bound
tasks, see the [TickerHandler](./TickerHandler.md) and [Scripts](./Scripts.md).
> Note that many delayed effects can be achieved without any need for an active timer. For example
if you have a trait that should recover a point every 5 seconds you might just need its value when
it's needed, but checking the current time and calculating on the fly what value it should have.
## Object Classes
### utils.inherits_from()
This useful function takes two arguments - an object to check and a parent. It returns `True` if
object inherits from parent *at any distance* (as opposed to Python's in-built `is_instance()` that
This useful function takes two arguments - an object to check and a parent. It returns `True` if object inherits from parent *at any distance* (as opposed to Python's in-built `is_instance()` that
will only catch immediate dependence). This function also accepts as input any combination of
classes, instances or python-paths-to-classes.
Note that Python code should usually work with [duck
typing](http://en.wikipedia.org/wiki/Duck_typing). But in Evennia's case it can sometimes be useful
to check if an object inherits from a given [Typeclass](./Typeclasses.md) as a way of identification. Say
for example that we have a typeclass *Animal*. This has a subclass *Felines* which in turn has a
subclass *HouseCat*. Maybe there are a bunch of other animal types too, like horses and dogs. Using
`inherits_from` will allow you to check for all animals in one go:
Note that Python code should usually work with [duck typing](https://en.wikipedia.org/wiki/Duck_typing). But in Evennia's case it can sometimes be useful to check if an object inherits from a given [Typeclass](./Typeclasses.md) as a way of identification. Say for example that we have a typeclass *Animal*. This has a subclass *Felines* which in turn has a subclass *HouseCat*. Maybe there are a bunch of other animal types too, like horses and dogs. Using `inherits_from` will allow you to check for all animals in one go:
```python
from evennia import utils
@ -214,8 +178,6 @@ subclass *HouseCat*. Maybe there are a bunch of other animal types too, like hor
obj.msg("The bouncer stops you in the door. He says: 'No talking animals allowed.'")
```
## Text utilities
In a text game, you are naturally doing a lot of work shuffling text back and forth. Here is a *non-
@ -224,8 +186,7 @@ If nothing else it can be good to look here before starting to develop a solutio
### utils.fill()
This flood-fills a text to a given width (shuffles the words to make each line evenly wide). It also
indents as needed.
This flood-fills a text to a given width (shuffles the words to make each line evenly wide). It also indents as needed.
```python
outtxt = fill(intxt, width=78, indent=4)
@ -244,11 +205,7 @@ can be useful in listings when showing multiple lines would mess up things.
### utils.dedent()
This solves what may at first glance appear to be a trivial problem with text - removing
indentations. It is used to shift entire paragraphs to the left, without disturbing any further
formatting they may have. A common case for this is when using Python triple-quoted strings in code
- they will retain whichever indentation they have in the code, and to make easily-readable source
code one usually don't want to shift the string to the left edge.
This solves what may at first glance appear to be a trivial problem with text - removing indentations. It is used to shift entire paragraphs to the left, without disturbing any further formatting they may have. A common case for this is when using Python triple-quoted strings in code - they will retain whichever indentation they have in the code, and to make easily-readable source code one usually don't want to shift the string to the left edge.
```python
#python code is entered at a given indentation
@ -267,31 +224,6 @@ help entries).
### to_str() and to_bytes()
Evennia supplies two utility functions for converting text to the correct
encodings. `to_str()` and `to_bytes()`. Unless you are adding a custom protocol and
need to send byte-data over the wire, `to_str` is the only one you'll need.
Evennia supplies two utility functions for converting text to the correct encodings. `to_str()` and `to_bytes()`. Unless you are adding a custom protocol and need to send byte-data over the wire, `to_str` is the only one you'll need.
The difference from Python's in-built `str()` and `bytes()` operators are that
the Evennia ones makes use of the `ENCODINGS` setting and will try very hard to
never raise a traceback but instead echo errors through logging. See
[here](./Text-Encodings.md) for more info.
### Ansi Coloring Tools
- [evennia.ansi](evennia.utils.ansi)
## Display utilities
### Making ascii tables
The [EvTable](github:evennia.utils.evtable#evtable) class (`evennia/utils/evtable.py`) can be used
to create correctly formatted text tables. There is also
[EvForm](github:evennia.utils.evform#evform) (`evennia/utils/evform.py`). This reads a fixed-format
text template from a file in order to create any level of sophisticated ascii layout. Both evtable
and evform have lots of options and inputs so see the header of each module for help.
The third-party [PrettyTable](https://code.google.com/p/prettytable/) module is also included in
Evennia. PrettyTable is considered deprecated in favor of EvTable since PrettyTable cannot handle
ANSI colour. PrettyTable can be found in `evennia/utils/prettytable/`. See its homepage above for
instructions.
### Menus
- [evennia.EvMenu](github:evennia.utils.evmenu#evmenu)
The difference from Python's in-built `str()` and `bytes()` operators are that the Evennia ones makes use of the `ENCODINGS` setting and will try very hard to never raise a traceback but instead echo errors through logging. See [here](../Concepts/Text-Encodings.md) for more info.

View file

@ -11,7 +11,7 @@ classes in a command set is the way to make commands available to use in your ga
When storing a CmdSet on an object, you will make the commands in that command set available to the
object. An example is the default command set stored on new Characters. This command set contains
all the useful commands, from `look` and `inventory` to `@dig` and `@reload`
([permissions](./Locks.md#permissions) then limit which players may use them, but that's a separate
([permissions](./Permissions.md) then limit which players may use them, but that's a separate
topic).
When an account enters a command, cmdsets from the Account, Character, its location, and elsewhere
@ -26,7 +26,7 @@ on. The tutorial world included with Evennia showcases a dark room that replaces
commands with its own versions because the Character cannot see.
If you want a quick start into defining your first commands and using them with command sets, you
can head over to the [Adding Command Tutorial](./Adding-Command-Tutorial.md) which steps through things
can head over to the [Adding Command Tutorial](../Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Adding-Commands.md) which steps through things
without the explanations.
## Defining Command Sets
@ -98,7 +98,7 @@ remove the latest added cmdset.
If you want the cmdset to survive a reload, you can do:
```
@py self.cmdset.add(commands.mycmdset.MyCmdSet, permanent=True)
@py self.cmdset.add(commands.mycmdset.MyCmdSet, persistent=True)
```
Or you could add the cmdset as the *default* cmdset:
@ -112,7 +112,7 @@ back even if all other cmdsets fail or are removed. It is always persistent and
by `cmdset.delete()`. To remove a default cmdset you must explicitly call `cmdset.remove_default()`.
Command sets are often added to an object in its `at_object_creation` method. For more examples of
adding commands, read the [Step by step tutorial](./Adding-Command-Tutorial.md). Generally you can
adding commands, read the [Step by step tutorial](../Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Adding-Commands.md). Generally you can
customize which command sets are added to your objects by using `self.cmdset.add()` or
`self.cmdset.add_default()`.
@ -215,7 +215,7 @@ included if `no_objs` option is active in the merge stack.
`no_objs` option is active in the merge stack.
- The cmdsets of Exits in the location. Merge priority `+101`. Will not be included if `no_exits`
*or* `no_objs` option is active in the merge stack.
- The [channel](./Communications.md) cmdset containing commands for posting to all channels the account
- The [channel](./Channels.md) cmdset containing commands for posting to all channels the account
or character is currently connected to. Merge priority `+101`. Will not be included if `no_channels`
option is active in the merge stack.
@ -282,7 +282,7 @@ type **A** has, and which relative priorities the two sets have. By convention,
statement as "New command set **A** is merged onto the old command set **B** to form **?**".
Below are the available merge types and how they work. Names are partly borrowed from [Set
theory](http://en.wikipedia.org/wiki/Set_theory).
theory](https://en.wikipedia.org/wiki/Set_theory).
- **Union** (default) - The two cmdsets are merged so that as many commands as possible from each
cmdset ends up in the merged cmdset. Same-key commands are merged by priority.
@ -343,12 +343,12 @@ More advanced cmdset example:
from commands import mycommands
class MyCmdSet(CmdSet):
key = "MyCmdSet"
priority = 4
mergetype = "Replace"
key_mergetypes = {'MyOtherCmdSet':'Union'}
def at_cmdset_creation(self):
"""
The only thing this method should need
@ -373,4 +373,4 @@ exits are merged in), these two commands will be considered *identical* since th
means only one of them will remain after the merger. Each will also be compared with all other
commands having any combination of the keys and/or aliases "kick", "punch" or "fight".
... So avoid duplicate aliases, it will only cause confusion.
... So avoid duplicate aliases, it will only cause confusion.

View file

@ -0,0 +1,474 @@
# Commands
Commands are intimately linked to [Command Sets](./Command-Sets.md) and you need to read that page too to
be familiar with how the command system works. The two pages were split for easy reading.
The basic way for users to communicate with the game is through *Commands*. These can be commands directly related to the game world such as *look*, *get*, *drop* and so on, or administrative commands such as *examine* or *dig*.
The [default commands](./Default-Commands.md) coming with Evennia are 'MUX-like' in that they use @ for admin commands, support things like switches, syntax with the '=' symbol etc, but there is nothing that prevents you from implementing a completely different command scheme for your game. You can find the default commands in `evennia/commands/default`. You should not edit these directly - they will be updated by the Evennia team as new features are added. Rather you should look to them for inspiration and inherit your own designs from them.
There are two components to having a command running - the *Command* class and the [Command Set](./Command-Sets.md) (command sets were split into a separate wiki page for ease of reading).
1. A *Command* is a python class containing all the functioning code for what a command does - for example, a *get* command would contain code for picking up objects.
1. A *Command Set* (often referred to as a CmdSet or cmdset) is like a container for one or more Commands. A given Command can go into any number of different command sets. Only by putting the command set on a character object you will make all the commands therein available to use by that character. You can also store command sets on normal objects if you want users to be able to use the object in various ways. Consider a "Tree" object with a cmdset defining the commands *climb* and *chop down*. Or a "Clock" with a cmdset containing the single command *check time*.
This page goes into full detail about how to use Commands. To fully use them you must also read the page detailing [Command Sets](./Command-Sets.md). There is also a step-by-step [Adding Command Tutorial](../Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Adding-Commands.md) that will get you started quickly without the extra explanations.
## Defining Commands
All commands are implemented as normal Python classes inheriting from the base class `Command`
(`evennia.Command`). You will find that this base class is very "bare". The default commands of
Evennia actually inherit from a child of `Command` called `MuxCommand` - this is the class that
knows all the mux-like syntax like `/switches`, splitting by "=" etc. Below we'll avoid mux-
specifics and use the base `Command` class directly.
```python
# basic Command definition
from evennia import Command
class MyCmd(Command):
"""
This is the help-text for the command
"""
key = "mycommand"
def parse(self):
# parsing the command line here
def func(self):
# executing the command here
```
Here is a minimalistic command with no custom parsing:
```python
from evennia import Command
class CmdEcho(Command):
key = "echo"
def func(self):
# echo the caller's input back to the caller
self.caller.msg(f"Echo: {self.args}")
```
You define a new command by assigning a few class-global properties on your inherited class and
overloading one or two hook functions. The full gritty mechanic behind how commands work are found
towards the end of this page; for now you only need to know that the command handler creates an
instance of this class and uses that instance whenever you use this command - it also dynamically
assigns the new command instance a few useful properties that you can assume to always be available.
### Who is calling the command?
In Evennia there are three types of objects that may call the command. It is important to be aware
of this since this will also assign appropriate `caller`, `session`, `sessid` and `account`
properties on the command body at runtime. Most often the calling type is `Session`.
* A [Session](./Sessions.md). This is by far the most common case when a user is entering a command in
their client.
* `caller` - this is set to the puppeted [Object](./Objects.md) if such an object exists. If no
puppet is found, `caller` is set equal to `account`. Only if an Account is not found either (such as
before being logged in) will this be set to the Session object itself.
* `session` - a reference to the [Session](./Sessions.md) object itself.
* `sessid` - `sessid.id`, a unique integer identifier of the session.
* `account` - the [Account](./Accounts.md) object connected to this Session. None if not logged in.
* An [Account](./Accounts.md). This only happens if `account.execute_cmd()` was used. No Session
information can be obtained in this case.
* `caller` - this is set to the puppeted Object if such an object can be determined (without
Session info this can only be determined in `MULTISESSION_MODE=0` or `1`). If no puppet is found,
this is equal to `account`.
* `session` - `None*`
* `sessid` - `None*`
* `account` - Set to the Account object.
* An [Object](./Objects.md). This only happens if `object.execute_cmd()` was used (for example by an
NPC).
* `caller` - This is set to the calling Object in question.
* `session` - `None*`
* `sessid` - `None*`
* `account` - `None`
> `*)`: There is a way to make the Session available also inside tests run directly on Accounts and Objects, and that is to pass it to `execute_cmd` like so: `account.execute_cmd("...", session=<Session>)`. Doing so *will* make the `.session` and `.sessid` properties available in the command.
### Properties assigned to the command instance at run-time
Let's say account *Bob* with a character *BigGuy* enters the command *look at sword*. After the system having successfully identified this as the "look" command and determined that BigGuy really has access to a command named `look`, it chugs the `look` command class out of storage and either loads an existing Command instance from cache or creates one. After some more checks it then assigns it the following properties:
- `caller` - The character BigGuy, in this example. This is a reference to the object executing the command. The value of this depends on what type of object is calling the command; see the previous section.
- `session` - the [Session](./Sessions.md) Bob uses to connect to the game and control BigGuy (see also previous section).
- `sessid` - the unique id of `self.session`, for quick lookup.
- `account` - the [Account](./Accounts.md) Bob (see previous section).
- `cmdstring` - the matched key for the command. This would be *look* in our example.
- `args` - this is the rest of the string, except the command name. So if the string entered was *look at sword*, `args` would be " *at sword*". Note the space kept - Evennia would correctly interpret `lookat sword` too. This is useful for things like `/switches` that should not use space. In the `MuxCommand` class used for default commands, this space is stripped. Also see the `arg_regex` property if you want to enforce a space to make `lookat sword` give a command-not-found error.
- `obj` - the game [Object](./Objects.md) on which this command is defined. This need not be the caller, but since `look` is a common (default) command, this is probably defined directly on *BigGuy* - so `obj` will point to BigGuy. Otherwise `obj` could be an Account or any interactive object with commands defined on it, like in the example of the "check time" command defined on a "Clock" object. - `cmdset` - this is a reference to the merged CmdSet (see below) from which this command was
matched. This variable is rarely used, it's main use is for the [auto-help system](./Help-System.md#command-auto-help-system) (*Advanced note: the merged cmdset need NOT be the same as `BigGuy.cmdset`. The merged set can be a combination of the cmdsets from other objects in the room, for example*).
- `raw_string` - this is the raw input coming from the user, without stripping any surrounding
whitespace. The only thing that is stripped is the ending newline marker.
#### Other useful utility methods:
- `.get_help(caller, cmdset)` - Get the help entry for this command. By default the arguments are not used, but they could be used to implement alternate help-display systems.
- `.client_width()` - Shortcut for getting the client's screen-width. Note that not all clients will
truthfully report this value - that case the `settings.DEFAULT_SCREEN_WIDTH` will be returned. - `.styled_table(*args, **kwargs)` - This returns an [EvTable](module- evennia.utils.evtable) styled based on the session calling this command. The args/kwargs are the same as for EvTable, except styling defaults are set.
- `.styled_header`, `_footer`, `separator` - These will produce styled decorations for display to the user. They are useful for creating listings and forms with colors adjustable per-user.
### Defining your own command classes
Beyond the properties Evennia always assigns to the command at run-time (listed above), your job is to define the following class properties:
- `key` (string) - the identifier for the command, like `look`. This should (ideally) be unique. A key can consist of more than one word, like "press button" or "pull left lever". Note that *both* `key` and `aliases` below determine the identity of a command. So two commands are considered if either matches. This is important for merging cmdsets described below.
- `aliases` (optional list) - a list of alternate names for the command (`["glance", "see", "l"]`). Same name rules as for `key` applies.
- `locks` (string) - a [lock definition](./Locks.md), usually on the form `cmd:<lockfuncs>`. Locks is a rather big topic, so until you learn more about locks, stick to giving the lockstring `"cmd:all()"` to make the command available to everyone (if you don't provide a lock string, this will be assigned for you).
- `help_category` (optional string) - setting this helps to structure the auto-help into categories. If none is set, this will be set to *General*.
- `save_for_next` (optional boolean). This defaults to `False`. If `True`, a copy of this command object (along with any changes you have done to it) will be stored by the system and can be accessed by the next command by retrieving `self.caller.ndb.last_cmd`. The next run command will either clear or replace the storage.
- `arg_regex` (optional raw string): Used to force the parser to limit itself and tell it when the command-name ends and arguments begin (such as requiring this to be a space or a /switch). This is done with a regular expression. [See the arg_regex section](./Commands.md#arg_regex) for the details.
- `auto_help` (optional boolean). Defaults to `True`. This allows for turning off the [auto-help system](./Help-System.md#command-auto-help-system) on a per-command basis. This could be useful if you either want to write your help entries manually or hide the existence of a command from `help`'s generated list.
- `is_exit` (bool) - this marks the command as being used for an in-game exit. This is, by default, set by all Exit objects and you should not need to set it manually unless you make your own Exit system. It is used for optimization and allows the cmdhandler to easily disregard this command when the cmdset has its `no_exits` flag set.
- `is_channel` (bool)- this marks the command as being used for an in-game channel. This is, by default, set by all Channel objects and you should not need to set it manually unless you make your own Channel system. is used for optimization and allows the cmdhandler to easily disregard this command when its cmdset has its `no_channels` flag set.
- `msg_all_sessions` (bool): This affects the behavior of the `Command.msg` method. If unset (default), calling `self.msg(text)` from the Command will always only send text to the Session that actually triggered this Command. If set however, `self.msg(text)` will send to all Sessions relevant to the object this Command sits on. Just which Sessions receives the text depends on the object and the server's `MULTISESSION_MODE`.
You should also implement at least two methods, `parse()` and `func()` (You could also implement
`perm()`, but that's not needed unless you want to fundamentally change how access checks work).
- `at_pre_cmd()` is called very first on the command. If this function returns anything that evaluates to `True` the command execution is aborted at this point.
- `parse()` is intended to parse the arguments (`self.args`) of the function. You can do this in any way you like, then store the result(s) in variable(s) on the command object itself (i.e. on `self`). To take an example, the default mux-like system uses this method to detect "command switches" and store them as a list in `self.switches`. Since the parsing is usually quite similar inside a command scheme you should make `parse()` as generic as possible and then inherit from it rather than re- implementing it over and over. In this way, the default `MuxCommand` class implements a `parse()` for all child commands to use.
- `func()` is called right after `parse()` and should make use of the pre-parsed input to actually do whatever the command is supposed to do. This is the main body of the command. The return value from this method will be returned from the execution as a Twisted Deferred.
- `at_post_cmd()` is called after `func()` to handle eventual cleanup.
Finally, you should always make an informative [doc string](https://www.python.org/dev/peps/pep-0257/#what-is-a-docstring) (`__doc__`) at the top of your class. This string is dynamically read by the [Help System](./Help-System.md) to create the help entry for this command. You should decide on a way to format your help and stick to that.
Below is how you define a simple alternative "`smile`" command:
```python
from evennia import Command
class CmdSmile(Command):
"""
A smile command
Usage:
smile [at] [<someone>]
grin [at] [<someone>]
Smiles to someone in your vicinity or to the room
in general.
(This initial string (the __doc__ string)
is also used to auto-generate the help
for this command)
"""
key = "smile"
aliases = ["smile at", "grin", "grin at"]
locks = "cmd:all()"
help_category = "General"
def parse(self):
"Very trivial parser"
self.target = self.args.strip()
def func(self):
"This actually does things"
caller = self.caller
if not self.target or self.target == "here":
string = f"{caller.key} smiles"
else:
target = caller.search(self.target)
if not target:
return
string = f"{caller.key} smiles at {target.key}"
caller.location.msg_contents(string)
```
The power of having commands as classes and to separate `parse()` and `func()` lies in the ability to inherit functionality without having to parse every command individually. For example, as mentioned the default commands all inherit from `MuxCommand`. `MuxCommand` implements its own version of `parse()` that understands all the specifics of MUX-like commands. Almost none of the default commands thus need to implement `parse()` at all, but can assume the incoming string is already split up and parsed in suitable ways by its parent.
Before you can actually use the command in your game, you must now store it within a *command set*. See the [Command Sets](./Command-Sets.md) page.
### Command prefixes
Historically, many MU* servers used to use prefix, such as `@` or `&` to signify that a command is used for administration or requires staff privileges. The problem with this is that newcomers to MU often find such extra symbols confusing. Evennia allows commands that can be accessed both with- or without such a prefix.
CMD_IGNORE_PREFIXES = "@&/+`
This is a setting consisting of a string of characters. Each is a prefix that will be considered a skippable prefix - _if the command is still unique in its cmdset when skipping the prefix_.
So if you wanted to write `@look` instead of `look` you can do so - the `@` will be ignored. But If we added an actual `@look` command (with a `key` or alias `@look`) then we would need to use the `@` to separate between the two.
This is also used in the default commands. For example, `@open` is a building command that allows you to create new exits to link two rooms together. Its `key` is set to `@open`, including the `@` (no alias is set). By default you can use both `@open` and `open` for this command. But "open" is a pretty common word and let's say a developer adds a new `open` command for opening a door. Now `@open` and `open` are two different commands and the `@` must be used to separate them.
> The `help` command will prefer to show all command names without prefix if
> possible. Only if there is a collision, will the prefix be shown in the help system.
### arg_regex
The command parser is very general and does not require a space to end your command name. This means that the alias `:` to `emote` can be used like `:smiles` without modification. It also means `getstone` will get you the stone (unless there is a command specifically named `getstone`, then that will be used). If you want to tell the parser to require a certain separator between the command name and its arguments (so that `get stone` works but `getstone` gives you a 'command not found' error) you can do so with the `arg_regex` property.
The `arg_regex` is a [raw regular expression string](https://docs.python.org/library/re.html). The regex will be compiled by the system at runtime. This allows you to customize how the part *immediately following* the command name (or alias) must look in order for the parser to match for this command. Some examples:
- `commandname argument` (`arg_regex = r"\s.+"`): This forces the parser to require the command name to be followed by one or more spaces. Whatever is entered after the space will be treated as an argument. However, if you'd forget the space (like a command having no arguments), this would *not* match `commandname`.
- `commandname` or `commandname argument` (`arg_regex = r"\s.+|$"`): This makes both `look` and `look me` work but `lookme` will not.
- `commandname/switches arguments` (`arg_regex = r"(?:^(?:\s+|\/).*$)|^$"`. If you are using Evennia's `MuxCommand` Command parent, you may wish to use this since it will allow `/switche`s to work as well as having or not having a space.
The `arg_regex` allows you to customize the behavior of your commands. You can put it in the parent class of your command to customize all children of your Commands. However, you can also change the base default behavior for all Commands by modifying `settings.COMMAND_DEFAULT_ARG_REGEX`.
## Exiting a command
Normally you just use `return` in one of your Command class' hook methods to exit that method. That will however still fire the other hook methods of the Command in sequence. That's usually what you want but sometimes it may be useful to just abort the command, for example if you find some unacceptable input in your parse method. To exit the command this way you can raise `evennia.InterruptCommand`:
```python
from evennia import InterruptCommand
class MyCommand(Command):
# ...
def parse(self):
# ...
# if this fires, `func()` and `at_post_cmd` will not
# be called at all
raise InterruptCommand()
```
## Pauses in commands
Sometimes you want to pause the execution of your command for a little while before continuing - maybe you want to simulate a heavy swing taking some time to finish, maybe you want the echo of your voice to return to you with an ever-longer delay. Since Evennia is running asynchronously, you cannot use `time.sleep()` in your commands (or anywhere, really). If you do, the *entire game* will
be frozen for everyone! So don't do that. Fortunately, Evennia offers a really quick syntax for
making pauses in commands.
In your `func()` method, you can use the `yield` keyword. This is a Python keyword that will freeze
the current execution of your command and wait for more before processing.
> Note that you *cannot* just drop `yield` into any code and expect it to pause. Evennia will only pause for you if you `yield` inside the Command's `func()` method. Don't expect it to work anywhere else.
Here's an example of a command using a small pause of five seconds between messages:
```python
from evennia import Command
class CmdWait(Command):
"""
A dummy command to show how to wait
Usage:
wait
"""
key = "wait"
locks = "cmd:all()"
help_category = "General"
def func(self):
"""Command execution."""
self.msg("Beginner-Tutorial to wait ...")
yield 5
self.msg("... This shows after 5 seconds. Waiting ...")
yield 2
self.msg("... And now another 2 seconds have passed.")
```
The important line is the `yield 5` and `yield 2` lines. It will tell Evennia to pause execution here and not continue until the number of seconds given has passed.
There are two things to remember when using `yield` in your Command's `func` method:
1. The paused state produced by the `yield` is not saved anywhere. So if the server reloads in the middle of your command pausing, it will *not* resume when the server comes back up - the remainder of the command will never fire. So be careful that you are not freezing the character or account in a way that will not be cleared on reload.
2. If you use `yield` you may not also use `return <values>` in your `func` method. You'll get an error explaining this. This is due to how Python generators work. You can however use a "naked" `return` just fine. Usually there is no need for `func` to return a value, but if you ever do need to mix `yield` with a final return value in the same `func`, look at [twisted.internet.defer.returnValue](https://twistedmatrix.com/documents/current/api/twisted.internet.defer.html#returnValue).
## Asking for user input
The `yield` keyword can also be used to ask for user input. Again you can't use Python's `input` in your command, for it would freeze Evennia for everyone while waiting for that user to input their text. Inside a Command's `func` method, the following syntax can also be used:
```python
answer = yield("Your question")
```
Here's a very simple example:
```python
class CmdConfirm(Command):
"""
A dummy command to show confirmation.
Usage:
confirm
"""
key = "confirm"
def func(self):
answer = yield("Are you sure you want to go on?")
if answer.strip().lower() in ("yes", "y"):
self.msg("Yes!")
else:
self.msg("No!")
```
This time, when the user enters the 'confirm' command, she will be asked if she wants to go on. Entering 'yes' or "y" (regardless of case) will give the first reply, otherwise the second reply will show.
> Note again that the `yield` keyword does not store state. If the game reloads while waiting for the user to answer, the user will have to start over. It is not a good idea to use `yield` for important or complex choices, a persistent [EvMenu](./EvMenu.md) might be more appropriate in this case.
## System commands
*Note: This is an advanced topic. Skip it if this is your first time learning about commands.*
There are several command-situations that are exceptional in the eyes of the server. What happens if the account enters an empty string? What if the 'command' given is infact the name of a channel the user wants to send a message to? Or if there are multiple command possibilities?
Such 'special cases' are handled by what's called *system commands*. A system command is defined in the same way as other commands, except that their name (key) must be set to one reserved by the engine (the names are defined at the top of `evennia/commands/cmdhandler.py`). You can find (unused) implementations of the system commands in `evennia/commands/default/system_commands.py`. Since these are not (by default) included in any `CmdSet` they are not actually used, they are just there for show. When the special situation occurs, Evennia will look through all valid `CmdSet`s for your custom system command. Only after that will it resort to its own, hard-coded implementation.
Here are the exceptional situations that triggers system commands. You can find the command keys they use as properties on `evennia.syscmdkeys`:
- No input (`syscmdkeys.CMD_NOINPUT`) - the account just pressed return without any input. Default is to do nothing, but it can be useful to do something here for certain implementations such as line editors that interpret non-commands as text input (an empty line in the editing buffer).
- Command not found (`syscmdkeys.CMD_NOMATCH`) - No matching command was found. Default is to display the "Huh?" error message.
- Several matching commands where found (`syscmdkeys.CMD_MULTIMATCH`) - Default is to show a list of matches.
- User is not allowed to execute the command (`syscmdkeys.CMD_NOPERM`) - Default is to display the "Huh?" error message.
- Channel (`syscmdkeys.CMD_CHANNEL`) - This is a [Channel](./Channels.md) name of a channel you are subscribing to - Default is to relay the command's argument to that channel. Such commands are created by the Comm system on the fly depending on your subscriptions.
- New session connection (`syscmdkeys.CMD_LOGINSTART`). This command name should be put in the `settings.CMDSET_UNLOGGEDIN`. Whenever a new connection is established, this command is always called on the server (default is to show the login screen).
Below is an example of redefining what happens when the account doesn't provide any input (e.g. just presses return). Of course the new system command must be added to a cmdset as well before it will work.
```python
from evennia import syscmdkeys, Command
class MyNoInputCommand(Command):
"Usage: Just press return, I dare you"
key = syscmdkeys.CMD_NOINPUT
def func(self):
self.caller.msg("Don't just press return like that, talk to me!")
```
## Dynamic Commands
*Note: This is an advanced topic.*
Normally Commands are created as fixed classes and used without modification. There are however situations when the exact key, alias or other properties is not possible (or impractical) to pre- code.
To create a command with a dynamic call signature, first define the command body normally in a class (set your `key`, `aliases` to default values), then use the following call (assuming the command class you created is named `MyCommand`):
```python
cmd = MyCommand(key="newname",
aliases=["test", "test2"],
locks="cmd:all()",
...)
```
*All* keyword arguments you give to the Command constructor will be stored as a property on the command object. This will overload existing properties defined on the parent class.
Normally you would define your class and only overload things like `key` and `aliases` at run-time. But you could in principle also send method objects (like `func`) as keyword arguments in order to make your command completely customized at run-time.
### Dynamic commands - Exits
Exits are examples of the use of a [Dynamic Command](./Commands.md#dynamic-commands).
The functionality of [Exit](./Objects.md) objects in Evennia is not hard-coded in the engine. Instead Exits are normal [typeclassed](./Typeclasses.md) objects that auto-create a [CmdSet](./Command-Sets.md) on themselves when they load. This cmdset has a single dynamically created Command with the same properties (key, aliases and locks) as the Exit object itself. When entering the name of the exit, this dynamic exit-command is triggered and (after access checks) moves the Character to the exit's destination.
Whereas you could customize the Exit object and its command to achieve completely different behaviour, you will usually be fine just using the appropriate `traverse_*` hooks on the Exit object. But if you are interested in really changing how things work under the hood, check out `evennia/objects/objects.py` for how the `Exit` typeclass is set up.
## Command instances are re-used
*Note: This is an advanced topic that can be skipped when first learning about Commands.*
A Command class sitting on an object is instantiated once and then re-used. So if you run a command from object1 over and over you are in fact running the same command instance over and over (if you run the same command but sitting on object2 however, it will be a different instance). This is usually not something you'll notice, since every time the Command-instance is used, all the relevant properties on it will be overwritten. But armed with this knowledge you can implement some of the more exotic command mechanism out there, like the command having a 'memory' of what you last entered so that you can back-reference the previous arguments etc.
> Note: On a server reload, all Commands are rebuilt and memory is flushed.
To show this in practice, consider this command:
```python
class CmdTestID(Command):
key = "testid"
def func(self):
if not hasattr(self, "xval"):
self.xval = 0
self.xval += 1
self.caller.msg(f"Command memory ID: {id(self)} (xval={self.xval})")
```
Adding this to the default character cmdset gives a result like this in-game:
```
> testid
Command memory ID: 140313967648552 (xval=1)
> testid
Command memory ID: 140313967648552 (xval=2)
> testid
Command memory ID: 140313967648552 (xval=3)
```
Note how the in-memory address of the `testid` command never changes, but `xval` keeps ticking up.
## Create a command on the fly
*This is also an advanced topic.*
Commands can also be created and added to a cmdset on the fly. Creating a class instance with a keyword argument, will assign that keyword argument as a property on this paricular command:
```
class MyCmdSet(CmdSet):
def at_cmdset_creation(self):
self.add(MyCommand(myvar=1, foo="test")
```
This will start the `MyCommand` with `myvar` and `foo` set as properties (accessable as `self.myvar` and `self.foo`). How they are used is up to the Command. Remember however the discussion from the previous section - since the Command instance is re-used, those properties will *remain* on the command as long as this cmdset and the object it sits is in memory (i.e. until the next reload). Unless `myvar` and `foo` are somehow reset when the command runs, they can be modified and that change will be remembered for subsequent uses of the command.
## How commands actually work
*Note: This is an advanced topic mainly of interest to server developers.*
Any time the user sends text to Evennia, the server tries to figure out if the text entered
corresponds to a known command. This is how the command handler sequence looks for a logged-in user:
1. A user enters a string of text and presses enter.
2. The user's Session determines the text is not some protocol-specific control sequence or OOB command, but sends it on to the command handler.
3. Evennia's *command handler* analyzes the Session and grabs eventual references to Account and eventual puppeted Characters (these will be stored on the command object later). The *caller* property is set appropriately.
4. If input is an empty string, resend command as `CMD_NOINPUT`. If no such command is found in cmdset, ignore.
5. If command.key matches `settings.IDLE_COMMAND`, update timers but don't do anything more.
6. The command handler gathers the CmdSets available to *caller* at this time:
- The caller's own currently active CmdSet.
- CmdSets defined on the current account, if caller is a puppeted object.
- CmdSets defined on the Session itself.
- The active CmdSets of eventual objects in the same location (if any). This includes commands on [Exits](./Objects.md#exits).
- Sets of dynamically created *System commands* representing available [Communications](./Channels.md)
7. All CmdSets *of the same priority* are merged together in groups. Grouping avoids order- dependent issues of merging multiple same-prio sets onto lower ones.
8. All the grouped CmdSets are *merged* in reverse priority into one combined CmdSet according to each set's merge rules.
9. Evennia's *command parser* takes the merged cmdset and matches each of its commands (using its key and aliases) against the beginning of the string entered by *caller*. This produces a set of candidates.
10. The *cmd parser* next rates the matches by how many characters they have and how many percent matches the respective known command. Only if candidates cannot be separated will it return multiple matches.
- If multiple matches were returned, resend as `CMD_MULTIMATCH`. If no such command is found in cmdset, return hard-coded list of matches.
- If no match was found, resend as `CMD_NOMATCH`. If no such command is found in cmdset, give hard-coded error message.
11. If a single command was found by the parser, the correct command object is plucked out of storage. This usually doesn't mean a re-initialization.
12. It is checked that the caller actually has access to the command by validating the *lockstring* of the command. If not, it is not considered as a suitable match and `CMD_NOMATCH` is triggered.
13. If the new command is tagged as a channel-command, resend as `CMD_CHANNEL`. If no such command is found in cmdset, use hard-coded implementation.
14. Assign several useful variables to the command instance (see previous sections).
15. Call `at_pre_command()` on the command instance.
16. Call `parse()` on the command instance. This is fed the remainder of the string, after the name of the command. It's intended to pre-parse the string into a form useful for the `func()` method.
17. Call `func()` on the command instance. This is the functional body of the command, actually doing useful things.
18. Call `at_post_command()` on the command instance.
## Assorted notes
The return value of `Command.func()` is a Twisted [deferred](https://twistedmatrix.com/documents/current/core/howto/defer.html).
Evennia does not use this return value at all by default. If you do, you must
thus do so asynchronously, using callbacks.
```python
# in command class func()
def callback(ret, caller):
caller.msg(f"Returned is {ret}")
deferred = self.execute_command("longrunning")
deferred.addCallback(callback, self.caller)
```
This is probably not relevant to any but the most advanced/exotic designs (one might use it to create a "nested" command structure for example).
The `save_for_next` class variable can be used to implement state-persistent commands. For example it can make a command operate on "it", where it is determined by what the previous command operated on.

View file

@ -0,0 +1,75 @@
# Core Components
These are the 'building blocks' out of which Evennia is built. This documentation is complementary to, and often goes deeper than, the doc-strings of each component in the [API](../Evennia-API.md).
## Base components
These are base pieces used to make an Evennia game. Most are long-lived and are persisted in the database.
```{toctree}
:maxdepth: 2
Portal-And-Server.md
Sessions.md
Typeclasses.md
Accounts.md
Objects.md
Scripts.md
Channels.md
Msg.md
Attributes.md
Nicks.md
Tags.md
Prototypes.md
Help-System.md
Permissions.md
Locks.md
```
## Commands
Evennia's Command system handle everything sent to the server by the user.
```{toctree}
:maxdepth: 2
Commands.md
Command-Sets.md
Default-Commands.md
Batch-Processors.md
Inputfuncs.md
```
## Utils and tools
Evennia provides a library of code resources to help the creation of a game.
```{toctree}
:maxdepth: 2
Coding-Utils.md
EvEditor.md
EvForm.md
EvMenu.md
EvMore.md
EvTable.md
FuncParser.md
MonitorHandler.md
TickerHandler.md
Signals.md
```
## Web components
Evennia is also its own webserver, with a website and in-browser webclient you can expand on.
```{toctree}
:maxdepth: 2
Website.md
Webclient.md
Web-Admin.md
Webserver.md
Web-API.md
Web-Bootstrap-Framework.md
```

View file

@ -0,0 +1,96 @@
# Default Commands
The full set of default Evennia commands currently contains 89 commands in 9 source
files. Our policy for adding default commands is outlined [here](../Coding/Default-Command-Syntax.md). The
[Commands](./Commands.md) documentation explains how Commands work as well as how to make new or customize
existing ones.
> Note that this page is auto-generated. Report problems to the [issue tracker](github:issues).
```{note}
Some game-states add their own Commands which are not listed here. Examples include editing a text
with [EvEditor](./EvEditor.md), flipping pages in [EvMore](./EvMore.md) or using the
[Batch-Processor](./Batch-Processors.md)'s interactive mode.
```
- [**@about** [@version]](CmdAbout) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _System_)
- [**@accounts** [@account]](CmdAccounts) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _System_)
- [**@alias** [setobjalias]](CmdSetObjAlias) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@channel** [@chan, @channels]](CmdChannel) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _Comms_)
- [**@cmdsets**](CmdListCmdSets) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@copy**](CmdCopy) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@cpattr**](CmdCpAttr) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@create**](CmdCreate) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@desc**](CmdDesc) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@destroy** [@del, @delete]](CmdDestroy) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@dig**](CmdDig) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@examine** [@ex, @exam]](CmdExamine) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _Building_)
- [**@find** [@locate, @search]](CmdFind) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@link**](CmdLink) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@lock** [@locks]](CmdLock) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@mvattr**](CmdMvAttr) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@name** [@rename]](CmdName) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@objects**](CmdObjects) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _System_)
- [**@open**](CmdOpen) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@py** [@!]](CmdPy) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _System_)
- [**@reload** [@restart]](CmdReload) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _System_)
- [**@reset** [@reboot]](CmdReset) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _System_)
- [**@scripts** [@script]](CmdScripts) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _System_)
- [**@server** [@serverload]](CmdServerLoad) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _System_)
- [**@service** [@services]](CmdService) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _System_)
- [**@set**](CmdSetAttribute) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@sethome**](CmdSetHome) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@shutdown**](CmdShutdown) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _System_)
- [**@spawn** [@olc]](CmdSpawn) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@tag** [@tags]](CmdTag) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@tasks** [@delays, @task]](CmdTasks) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _System_)
- [**@teleport** [@tel]](CmdTeleport) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@tickers**](CmdTickers) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _System_)
- [**@time** [@uptime]](CmdTime) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _System_)
- [**@tunnel** [@tun]](CmdTunnel) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@typeclass** [@parent, @swap, @type, @typeclasses, @update]](CmdTypeclass) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**@wipe**](CmdWipe) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**__unloggedin_look_command** [l, look]](CmdUnconnectedLook) (cmdset: [UnloggedinCmdSet](UnloggedinCmdSet), help-category: _General_)
- [**access** [groups, hierarchy]](CmdAccess) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _General_)
- [**batchcode** [batchcodes]](CmdBatchCode) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**batchcommands** [batchcmd, batchcommand]](CmdBatchCommands) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**charcreate**](CmdCharCreate) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _General_)
- [**chardelete**](CmdCharDelete) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _General_)
- [**color**](CmdColorTest) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _General_)
- [**connect** [co, con, conn]](CmdUnconnectedConnect) (cmdset: [UnloggedinCmdSet](UnloggedinCmdSet), help-category: _General_)
- [**create** [cr, cre]](CmdUnconnectedCreate) (cmdset: [UnloggedinCmdSet](UnloggedinCmdSet), help-category: _General_)
- [**discord2chan** [discord]](CmdDiscord2Chan) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _Comms_)
- [**drop**](CmdDrop) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _General_)
- [**encoding** [encode]](CmdUnconnectedEncoding) (cmdset: [UnloggedinCmdSet](UnloggedinCmdSet), help-category: _General_)
- [**get** [grab]](CmdGet) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _General_)
- [**give**](CmdGive) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _General_)
- [**grapevine2chan**](CmdGrapevine2Chan) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _Comms_)
- [**help** [?]](CmdHelp) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _General_)
- [**help** [?, h]](CmdUnconnectedHelp) (cmdset: [UnloggedinCmdSet](UnloggedinCmdSet), help-category: _General_)
- [**home**](CmdHome) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _General_)
- [**ic** [puppet]](CmdIC) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _General_)
- [**info**](CmdUnconnectedInfo) (cmdset: [UnloggedinCmdSet](UnloggedinCmdSet), help-category: _General_)
- [**inventory** [i, inv]](CmdInventory) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _General_)
- [**irc2chan**](CmdIRC2Chan) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _Comms_)
- [**ircstatus**](CmdIRCStatus) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _Comms_)
- [**look** [l, ls]](CmdOOCLook) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _General_)
- [**look** [l, ls]](CmdLook) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _General_)
- [**nick** [nickname, nicks]](CmdNick) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _General_)
- [**ooc** [unpuppet]](CmdOOC) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _General_)
- [**option** [options]](CmdOption) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _General_)
- [**page** [tell]](CmdPage) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _Comms_)
- [**password**](CmdPassword) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _General_)
- [**pose** [:, emote]](CmdPose) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _General_)
- [**quell** [unquell]](CmdQuell) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _General_)
- [**quit**](CmdQuit) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _General_)
- [**quit** [q, qu]](CmdUnconnectedQuit) (cmdset: [UnloggedinCmdSet](UnloggedinCmdSet), help-category: _General_)
- [**rss2chan**](CmdRSS2Chan) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _Comms_)
- [**say** [", ']](CmdSay) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _General_)
- [**screenreader**](CmdUnconnectedScreenreader) (cmdset: [UnloggedinCmdSet](UnloggedinCmdSet), help-category: _General_)
- [**sessions**](CmdSessions) (cmdset: [SessionCmdSet](SessionCmdSet), help-category: _General_)
- [**setdesc**](CmdSetDesc) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _General_)
- [**sethelp**](CmdSetHelp) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**style**](CmdStyle) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _General_)
- [**unlink**](CmdUnLink) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _Building_)
- [**whisper**](CmdWhisper) (cmdset: [CharacterCmdSet](CharacterCmdSet), help-category: _General_)
- [**who** [doing]](CmdWho) (cmdset: [AccountCmdSet](AccountCmdSet), help-category: _General_)

View file

@ -5,7 +5,7 @@ Evennia offers a powerful in-game line editor in `evennia.utils.eveditor.EvEdito
mimicking the well-known VI line editor. It offers line-by-line editing, undo/redo, line deletes,
search/replace, fill, dedent and more.
### Launching the editor
## Launching the editor
The editor is created as follows:
@ -29,7 +29,7 @@ cleanup and exit messages to the user must be handled by this function.
It has no other mechanical function.
- `persistent` (default `False`): if set to `True`, the editor will survive a reboot.
### Example of usage
## Working with EvEditor
This is an example command for setting a specific Attribute using the editor.
@ -54,11 +54,11 @@ class CmdSetTestAttr(Command):
return caller.attributes.get("test")
def save(caller, buffer):
"save the buffer"
caller.attributes.set("test", buffer)
caller.attributes.add("test", buffer)
def quit(caller):
"Since we define it, we must handle messages"
caller.msg("Editor exited")
key = "%s/test" % self.caller
key = f"{self.caller}/test"
# launch the editor
eveditor.EvEditor(self.caller,
loadfunc=load, savefunc=save, quitfunc=quit,
@ -82,7 +82,7 @@ def load(caller):
def save(caller, buffer):
"save the buffer"
caller.attributes.set("test", buffer)
caller.attributes.add("test", buffer)
def quit(caller):
"Since we define it, we must handle messages"
@ -100,7 +100,7 @@ class CmdSetTestAttr(Command):
key = "settestattr"
def func(self):
"Set up the callbacks and launch the editor"
key = "%s/test" % self.caller
key = f"{self.caller}/test"
# launch the editor
eveditor.EvEditor(self.caller,
loadfunc=load, savefunc=save, quitfunc=quit,
@ -156,26 +156,17 @@ the in-editor help command (`:h`).
### The EvEditor to edit code
The `EvEditor` is also used to edit some Python code in Evennia. The `@py` command supports an
`/edit` switch that will open the EvEditor in code mode. This mode isn't significantly different
from the standard one, except it handles automatic indentation of blocks and a few options to
control this behavior.
The `EvEditor` is also used to edit some Python code in Evennia. The `py` command supports an `/edit` switch that will open the EvEditor in code mode. This mode isn't significantly different from the standard one, except it handles automatic indentation of blocks and a few options to control this behavior.
- `:<` to remove a level of indentation for the future lines.
- `:+` to add a level of indentation for the future lines.
- `:=` to disable automatic indentation altogether.
Automatic indentation is there to make code editing more simple. Python needs correct indentation,
not as an aesthetic addition, but as a requirement to determine beginning and ending of blocks. The
EvEditor will try to guess the next level of indentation. If you type a block "if", for instance,
the EvEditor will propose you an additional level of indentation at the next line. This feature
cannot be perfect, however, and sometimes, you will have to use the above options to handle
indentation.
Automatic indentation is there to make code editing more simple. Python needs correct indentation, not as an aesthetic addition, but as a requirement to determine beginning and ending of blocks. The EvEditor will try to guess the next level of indentation. If you type a block "if", for instance, the EvEditor will propose you an additional level of indentation at the next line. This feature cannot be perfect, however, and sometimes, you will have to use the above options to handle indentation.
`:=` can be used to turn automatic indentation off completely. This can be very useful when trying
to paste several lines of code that are already correctly indented, for instance.
To see the EvEditor in code mode, you can use the `@py/edit` command. Type in your code (on one or
several lines). You can then use the `:w` option (save without quitting) and the code you have
To see the EvEditor in code mode, you can use the `@py/edit` command. Type in your code (on one or several lines). You can then use the `:w` option (save without quitting) and the code you have
typed will be executed. The `:!` will do the same thing. Executing code while not closing the
editor can be useful if you want to test the code you have typed but add new lines after your test.
editor can be useful if you want to test the code you have typed but add new lines after your test.

View file

@ -0,0 +1,3 @@
# EvForm
[Docstring in evennia/utils/evform.py](evennia.utils.evform)

Some files were not shown because too many files have changed in this diff Show more