mirror of
https://github.com/evennia/evennia.git
synced 2026-03-17 05:16:31 +01:00
Merge branch 'main' of https://github.com/kenneaal/evennia
This commit is contained in:
commit
eff8b4aa66
1346 changed files with 170356 additions and 49772 deletions
27
.github/ISSUE_TEMPLATE/bug-report-develop.md
vendored
27
.github/ISSUE_TEMPLATE/bug-report-develop.md
vendored
|
|
@ -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.)-->
|
||||
12
.github/ISSUE_TEMPLATE/bug-report.md
vendored
12
.github/ISSUE_TEMPLATE/bug-report.md
vendored
|
|
@ -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.)-->
|
||||
|
|
|
|||
|
|
@ -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)-->
|
||||
|
|
|
|||
12
.github/ISSUE_TEMPLATE/feature-request.md
vendored
12
.github/ISSUE_TEMPLATE/feature-request.md
vendored
|
|
@ -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.-->
|
||||
|
|
|
|||
86
.github/actions/setup-database/action.yml
vendored
Normal file
86
.github/actions/setup-database/action.yml
vendored
Normal 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
|
||||
|
|
@ -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
|
||||
45
.github/workflows/github_action_build_docs.yml
vendored
45
.github/workflows/github_action_build_docs.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
222
.github/workflows/github_action_test_suite.yml
vendored
222
.github/workflows/github_action_test_suite.yml
vendored
|
|
@ -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
16
.gitignore
vendored
|
|
@ -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
44
.release.sh
Executable 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."
|
||||
269
CHANGELOG.md
269
CHANGELOG.md
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
|
|
|
|||
151
CODING_STYLE.md
151
CODING_STYLE.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
31
Dockerfile
31
Dockerfile
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
44
LICENSE.txt
44
LICENSE.txt
|
|
@ -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.
|
||||
|
|
|
|||
17
Makefile
17
Makefile
|
|
@ -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
|
||||
|
|
|
|||
87
README.md
87
README.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
29
docs/LICENSE.txt
Normal 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.
|
||||
|
|
@ -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."
|
||||
|
|
|
|||
229
docs/README.md
229
docs/README.md
|
|
@ -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
89
docs/deploy.py
Normal 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()
|
||||
|
|
@ -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."
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
||||
|
|
|
|||
|
|
@ -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__)))
|
||||
|
|
|
|||
250
docs/pylib/contrib_readmes2docs.py
Normal file
250
docs/pylib/contrib_readmes2docs.py
Normal 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)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
|
|
|||
101
docs/pylib/update_dynamic_pages.py
Normal file
101
docs/pylib/update_dynamic_pages.py
Normal 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()
|
||||
|
|
@ -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
8
docs/source/.vale.ini
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
StylesPath = .vale
|
||||
|
||||
Vocab = docs
|
||||
Packages = write-good
|
||||
MinAlertLevel = error
|
||||
|
||||
[*.md]
|
||||
BasedOnStyles = Vale, write-good
|
||||
19
docs/source/.vale/Vocab/docs/accept.txt
Normal file
19
docs/source/.vale/Vocab/docs/accept.txt
Normal 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)*
|
||||
702
docs/source/.vale/write-good/Cliches.yml
Normal file
702
docs/source/.vale/write-good/Cliches.yml
Normal 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
|
||||
32
docs/source/.vale/write-good/E-Prime.yml
Normal file
32
docs/source/.vale/write-good/E-Prime.yml
Normal 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
|
||||
11
docs/source/.vale/write-good/Illusions.yml
Normal file
11
docs/source/.vale/write-good/Illusions.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
extends: repetition
|
||||
message: "'%s' is repeated!"
|
||||
level: warning
|
||||
alpha: true
|
||||
action:
|
||||
name: edit
|
||||
params:
|
||||
- truncate
|
||||
- " "
|
||||
tokens:
|
||||
- '[^\s]+'
|
||||
183
docs/source/.vale/write-good/Passive.yml
Normal file
183
docs/source/.vale/write-good/Passive.yml
Normal 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
|
||||
5
docs/source/.vale/write-good/So.yml
Normal file
5
docs/source/.vale/write-good/So.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
extends: existence
|
||||
message: "Don't start a sentence with '%s'."
|
||||
level: error
|
||||
raw:
|
||||
- '(?:[;-]\s)so[\s,]|\bSo[\s,]'
|
||||
6
docs/source/.vale/write-good/ThereIs.yml
Normal file
6
docs/source/.vale/write-good/ThereIs.yml
Normal 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'
|
||||
221
docs/source/.vale/write-good/TooWordy.yml
Normal file
221
docs/source/.vale/write-good/TooWordy.yml
Normal 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
|
||||
207
docs/source/.vale/write-good/Weasel.yml
Normal file
207
docs/source/.vale/write-good/Weasel.yml
Normal 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
|
||||
4
docs/source/.vale/write-good/meta.json
Normal file
4
docs/source/.vale/write-good/meta.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"feed": "https://github.com/errata-ai/write-good/releases.atom",
|
||||
"vale_version": ">=1.0.0"
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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!
|
||||
|
|
@ -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.
|
||||
|
|
@ -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()]
|
||||
|
||||
```
|
||||
|
|
@ -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
|
||||
|
||||
```
|
||||
|
|
@ -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!
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
||||
```
|
||||
|
|
@ -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.
|
||||
|
|
@ -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*.
|
||||
Let’s 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
|
||||
Evennia’s default commands may look vaguely MUX-like, you can change the syntax to look like
|
||||
whatever interface style you prefer.)
|
||||
|
||||
Before we continue, let’s 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 mech’s gun
|
||||
|
||||
Usage:
|
||||
shoot [target]
|
||||
|
||||
This will fire your mech’s 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 (let’s 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 won’t 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. Let’s 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! Let’s 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 we’ve 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 Character’s 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.
|
||||
|
||||
That’s it. When Objects of this type are created, they will always start out with the mech’s 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
|
||||
shouldn’t 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 you’ll 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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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!
|
||||
780
docs/source/Coding/Changelog.md
Normal file
780
docs/source/Coding/Changelog.md
Normal 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.
|
||||
33
docs/source/Coding/Coding-Overview.md
Normal file
33
docs/source/Coding/Coding-Overview.md
Normal 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
|
||||
```
|
||||
198
docs/source/Coding/Continuous-Integration-TeamCity.md
Normal file
198
docs/source/Coding/Continuous-Integration-TeamCity.md
Normal 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!
|
||||
39
docs/source/Coding/Continuous-Integration-Travis.md
Normal file
39
docs/source/Coding/Continuous-Integration-Travis.md
Normal 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.
|
||||
28
docs/source/Coding/Continuous-Integration.md
Normal file
28
docs/source/Coding/Continuous-Integration.md
Normal 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).
|
||||
|
|
@ -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/).
|
||||
24
docs/source/Coding/Default-Command-Syntax.md
Normal file
24
docs/source/Coding/Default-Command-Syntax.md
Normal 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
|
||||
```
|
||||
278
docs/source/Coding/Evennia-Code-Style.md
Normal file
278
docs/source/Coding/Evennia-Code-Style.md
Normal 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
|
||||
197
docs/source/Coding/Profiling.md
Normal file
197
docs/source/Coding/Profiling.md
Normal 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.
|
||||
|
||||
151
docs/source/Coding/Release-Notes-1.0.md
Normal file
151
docs/source/Coding/Release-Notes-1.0.md
Normal 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).
|
||||
|
||||
|
||||
|
|
@ -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.
|
||||

|
||||
|
||||
## 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:
|
||||

|
||||
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:
|
||||

|
||||
1. Navigate to the `Logs` tab and add the log files you would like to follow. The picture shows
|
||||
67
docs/source/Coding/Soft-Code.md
Normal file
67
docs/source/Coding/Soft-Code.md
Normal 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.
|
||||
306
docs/source/Coding/Unit-Testing.md
Normal file
306
docs/source/Coding/Unit-Testing.md
Normal 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 .
|
||||
```
|
||||
344
docs/source/Coding/Version-Control.md
Normal file
344
docs/source/Coding/Version-Control.md
Normal 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:
|
||||
|
||||

|
||||
|
||||
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>
|
||||
```
|
||||
|
|
@ -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.
|
||||
|
|
@ -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).
|
||||
|
|
@ -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)
|
||||
|
|
@ -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.
|
||||
|
|
@ -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`.
|
||||
90
docs/source/Components/Accounts.md
Normal file
90
docs/source/Components/Accounts.md
Normal 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.
|
||||
598
docs/source/Components/Attributes.md
Normal file
598
docs/source/Components/Attributes.md
Normal 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!
|
||||
163
docs/source/Components/Batch-Code-Processor.md
Normal file
163
docs/source/Components/Batch-Code-Processor.md
Normal 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.
|
||||
130
docs/source/Components/Batch-Command-Processor.md
Normal file
130
docs/source/Components/Batch-Command-Processor.md
Normal 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.
|
||||
47
docs/source/Components/Batch-Processors.md
Normal file
47
docs/source/Components/Batch-Processors.md
Normal 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.
|
||||
357
docs/source/Components/Channels.md
Normal file
357
docs/source/Components/Channels.md
Normal 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).
|
||||
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
474
docs/source/Components/Commands.md
Normal file
474
docs/source/Components/Commands.md
Normal 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.
|
||||
75
docs/source/Components/Components-Overview.md
Normal file
75
docs/source/Components/Components-Overview.md
Normal 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
|
||||
```
|
||||
96
docs/source/Components/Default-Commands.md
Normal file
96
docs/source/Components/Default-Commands.md
Normal 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_)
|
||||
|
|
@ -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.
|
||||
3
docs/source/Components/EvForm.md
Normal file
3
docs/source/Components/EvForm.md
Normal 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
Loading…
Add table
Add a link
Reference in a new issue