forked from vantablack/vantaMOO
Compare commits
48 Commits
Author | SHA1 | Date |
---|---|---|
vanta black | 5a4212f862 | |
vanta black | 5ade61d666 | |
Alexander Yakovlev | c66b8914f1 | |
Alexander Yakovlev | 8b34061620 | |
vanta black | 6d40d8f49c | |
Alexander Yakovlev | 991814c43f | |
Alexander Yakovlev | 839b4431a2 | |
Alexander Yakovlev | c644f274ff | |
Alexander Yakovlev | 2be244b5b6 | |
vanta black | 3267572f56 | |
vanta black | 945be61e78 | |
lunacb | b56d747a6f | |
lunacb | 08facf7d00 | |
Alexander Yakovlev | bab90c1ba7 | |
vanta black | 8a458b21b5 | |
vanta black | 384c0bacfc | |
Alexander Yakovlev | 5f27514eb1 | |
lunacb | 88ba451426 | |
lunacb | 7e34080666 | |
lunacb | d1f33e858a | |
lunacb | fbe670651e | |
lunacb | 9780740ab9 | |
lunacb | 125252078a | |
lunacb | c359ae2f1c | |
lunacb | d2f6684026 | |
vanta black | 02e2c8832c | |
lunacb | 8daecbf9fc | |
lunacb | 488595e2b8 | |
Alexander Yakovlev | 7881903b7e | |
vanta black | cd464ce40e | |
Alexander Yakovlev | a004641121 | |
Alexander Yakovlev | 7c34a93874 | |
Alexander Yakovlev | c9d5639d18 | |
Erin Nova | 0bb41b771c | |
Erin Nova | b99bc291c3 | |
vanta black | 91d2c19d1e | |
Alexander Yakovlev | 0b0b1f3fa5 | |
vanta black | 1e3b6bf0ca | |
vanta black | f9caf42356 | |
Alexander Yakovlev | b25671f224 | |
Alexander Yakovlev | c42e0b45f2 | |
Kaypar | a69417d3bd | |
Kaypar | 64c00188b5 | |
vanta black | 486326bced | |
Alexander Yakovlev | 62bf0de9d2 | |
Alexander Yakovlev | 5427718b1a | |
vanta black | ea4aa2eb6a | |
vanta black | ddb7d687f5 |
|
@ -0,0 +1 @@
|
|||
*.gif filter=lfs diff=lfs merge=lfs -text
|
|
@ -14,7 +14,6 @@ var
|
|||
sdist
|
||||
develop-eggs
|
||||
.installed.cfg
|
||||
lib
|
||||
lib64
|
||||
__pycache__
|
||||
|
||||
|
@ -52,3 +51,13 @@ nosetests.xml
|
|||
# PyCharm config
|
||||
.idea
|
||||
|
||||
# Virtualenv
|
||||
evenv
|
||||
|
||||
# Compiled CSS
|
||||
web/static/website/css/
|
||||
|
||||
# JS modules
|
||||
node_modules/
|
||||
pnpm-lock.yaml
|
||||
package.json.lock
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
fail_fast: true
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: check-ast
|
18
README.md
18
README.md
|
@ -1,4 +1,18 @@
|
|||
# Welcome to Evennia!
|
||||
# vantaMOO
|
||||
This is the git repo for all the vantaMOO code.
|
||||
|
||||
Feel free to fork and make your own changes.
|
||||
|
||||
## Building the frontend
|
||||
|
||||
Install Node.js first.
|
||||
|
||||
* npm install
|
||||
* npm run build
|
||||
|
||||
Restart Evennia after everytime you change and rebuild SCSS to pick up the changes. Do not edit CSS directly.
|
||||
|
||||
## Welcome to Evennia!
|
||||
|
||||
This is your game directory, set up to let you start with
|
||||
your new game right away. An overview of this directory is found here:
|
||||
|
@ -29,7 +43,7 @@ to your new game using a MUD client on `localhost`, port `4000`. You can
|
|||
also log into the web client by pointing a browser to
|
||||
`http://localhost:4001`.
|
||||
|
||||
# Getting started
|
||||
## Getting started
|
||||
|
||||
From here on you might want to look at one of the beginner tutorials:
|
||||
http://github.com/evennia/evennia/wiki/Tutorials.
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
from evennia.commands.default.building import CmdExamine
|
||||
|
||||
class CmdCustomExamine(CmdExamine):
|
||||
def format_attributes(self, obj):
|
||||
caller = self.caller
|
||||
output = "\n "
|
||||
for attr in obj.db_attributes.all():
|
||||
if attr.db_key == "creator_ip" and not caller.permissions.check("Admin"):
|
||||
continue
|
||||
output += self.format_single_attribute(attr)
|
||||
output += "\n "
|
||||
if output.strip():
|
||||
# we don't want just an empty line
|
||||
return output
|
|
@ -16,12 +16,16 @@ own cmdsets by inheriting from them or directly from `evennia.CmdSet`.
|
|||
|
||||
from evennia import default_cmds
|
||||
|
||||
from commands.cmdcustomexamine import CmdCustomExamine
|
||||
from evennia.contrib.base_systems.building_menu import GenericBuildingCmd
|
||||
from evennia.contrib.game_systems import mail
|
||||
from evennia.contrib.game_systems.clothing import ClothedCharacterCmdSet
|
||||
from evennia.contrib.game_systems.multidescer import CmdMultiDesc
|
||||
from evennia.contrib.grid.ingame_map_display import MapDisplayCmdSet
|
||||
from evennia.contrib.grid import simpledoor
|
||||
from evennia.contrib.rpg.rpsystem import RPSystemCmdSet
|
||||
from evennia.contrib.grid.ingame_map_display import MapDisplayCmdSet
|
||||
from lib.pronounsub.commands import *
|
||||
from lib.rpsystem.rpsystem import RPSystemCmdSet
|
||||
from evennia.contrib.rpg import dice
|
||||
|
||||
|
||||
class CharacterCmdSet(default_cmds.CharacterCmdSet):
|
||||
|
@ -61,6 +65,19 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
|
|||
# rpsystem
|
||||
self.add(RPSystemCmdSet())
|
||||
|
||||
# Clothing: wear
|
||||
self.add(ClothedCharacterCmdSet())
|
||||
|
||||
# gendersub
|
||||
self.add(PronounAdminCommand())
|
||||
self.add(PronounsCommand())
|
||||
|
||||
# Overrides @examine
|
||||
self.add(CmdCustomExamine())
|
||||
|
||||
# RPG dice
|
||||
self.add(dice.CmdDice())
|
||||
|
||||
class AccountCmdSet(default_cmds.AccountCmdSet):
|
||||
"""
|
||||
This is the cmdset available to the Account at all times. It is
|
||||
|
|
|
@ -1,247 +0,0 @@
|
|||
<#
|
||||
.Synopsis
|
||||
Activate a Python virtual environment for the current PowerShell session.
|
||||
|
||||
.Description
|
||||
Pushes the python executable for a virtual environment to the front of the
|
||||
$Env:PATH environment variable and sets the prompt to signify that you are
|
||||
in a Python virtual environment. Makes use of the command line switches as
|
||||
well as the `pyvenv.cfg` file values present in the virtual environment.
|
||||
|
||||
.Parameter VenvDir
|
||||
Path to the directory that contains the virtual environment to activate. The
|
||||
default value for this is the parent of the directory that the Activate.ps1
|
||||
script is located within.
|
||||
|
||||
.Parameter Prompt
|
||||
The prompt prefix to display when this virtual environment is activated. By
|
||||
default, this prompt is the name of the virtual environment folder (VenvDir)
|
||||
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
|
||||
|
||||
.Example
|
||||
Activate.ps1
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -Verbose
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||
and shows extra information about the activation as it executes.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
|
||||
Activates the Python virtual environment located in the specified location.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -Prompt "MyPython"
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||
and prefixes the current prompt with the specified string (surrounded in
|
||||
parentheses) while the virtual environment is active.
|
||||
|
||||
.Notes
|
||||
On Windows, it may be required to enable this Activate.ps1 script by setting the
|
||||
execution policy for the user. You can do this by issuing the following PowerShell
|
||||
command:
|
||||
|
||||
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
|
||||
For more information on Execution Policies:
|
||||
https://go.microsoft.com/fwlink/?LinkID=135170
|
||||
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$VenvDir,
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$Prompt
|
||||
)
|
||||
|
||||
<# Function declarations --------------------------------------------------- #>
|
||||
|
||||
<#
|
||||
.Synopsis
|
||||
Remove all shell session elements added by the Activate script, including the
|
||||
addition of the virtual environment's Python executable from the beginning of
|
||||
the PATH variable.
|
||||
|
||||
.Parameter NonDestructive
|
||||
If present, do not remove this function from the global namespace for the
|
||||
session.
|
||||
|
||||
#>
|
||||
function global:deactivate ([switch]$NonDestructive) {
|
||||
# Revert to original values
|
||||
|
||||
# The prior prompt:
|
||||
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
|
||||
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
|
||||
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
|
||||
}
|
||||
|
||||
# The prior PYTHONHOME:
|
||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
|
||||
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
|
||||
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
|
||||
}
|
||||
|
||||
# The prior PATH:
|
||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
|
||||
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
|
||||
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
|
||||
}
|
||||
|
||||
# Just remove the VIRTUAL_ENV altogether:
|
||||
if (Test-Path -Path Env:VIRTUAL_ENV) {
|
||||
Remove-Item -Path env:VIRTUAL_ENV
|
||||
}
|
||||
|
||||
# Just remove VIRTUAL_ENV_PROMPT altogether.
|
||||
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
|
||||
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
|
||||
}
|
||||
|
||||
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
|
||||
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
|
||||
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
|
||||
}
|
||||
|
||||
# Leave deactivate function in the global namespace if requested:
|
||||
if (-not $NonDestructive) {
|
||||
Remove-Item -Path function:deactivate
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.Description
|
||||
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
|
||||
given folder, and returns them in a map.
|
||||
|
||||
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
|
||||
two strings separated by `=` (with any amount of whitespace surrounding the =)
|
||||
then it is considered a `key = value` line. The left hand string is the key,
|
||||
the right hand is the value.
|
||||
|
||||
If the value starts with a `'` or a `"` then the first and last character is
|
||||
stripped from the value before being captured.
|
||||
|
||||
.Parameter ConfigDir
|
||||
Path to the directory that contains the `pyvenv.cfg` file.
|
||||
#>
|
||||
function Get-PyVenvConfig(
|
||||
[String]
|
||||
$ConfigDir
|
||||
) {
|
||||
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
|
||||
|
||||
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
|
||||
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
|
||||
|
||||
# An empty map will be returned if no config file is found.
|
||||
$pyvenvConfig = @{ }
|
||||
|
||||
if ($pyvenvConfigPath) {
|
||||
|
||||
Write-Verbose "File exists, parse `key = value` lines"
|
||||
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
|
||||
|
||||
$pyvenvConfigContent | ForEach-Object {
|
||||
$keyval = $PSItem -split "\s*=\s*", 2
|
||||
if ($keyval[0] -and $keyval[1]) {
|
||||
$val = $keyval[1]
|
||||
|
||||
# Remove extraneous quotations around a string value.
|
||||
if ("'""".Contains($val.Substring(0, 1))) {
|
||||
$val = $val.Substring(1, $val.Length - 2)
|
||||
}
|
||||
|
||||
$pyvenvConfig[$keyval[0]] = $val
|
||||
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
|
||||
}
|
||||
}
|
||||
}
|
||||
return $pyvenvConfig
|
||||
}
|
||||
|
||||
|
||||
<# Begin Activate script --------------------------------------------------- #>
|
||||
|
||||
# Determine the containing directory of this script
|
||||
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$VenvExecDir = Get-Item -Path $VenvExecPath
|
||||
|
||||
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
|
||||
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
|
||||
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
|
||||
|
||||
# Set values required in priority: CmdLine, ConfigFile, Default
|
||||
# First, get the location of the virtual environment, it might not be
|
||||
# VenvExecDir if specified on the command line.
|
||||
if ($VenvDir) {
|
||||
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
|
||||
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
||||
Write-Verbose "VenvDir=$VenvDir"
|
||||
}
|
||||
|
||||
# Next, read the `pyvenv.cfg` file to determine any required value such
|
||||
# as `prompt`.
|
||||
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
||||
|
||||
# Next, set the prompt from the command line, or the config file, or
|
||||
# just use the name of the virtual environment folder.
|
||||
if ($Prompt) {
|
||||
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
||||
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
||||
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
||||
$Prompt = $pyvenvCfg['prompt'];
|
||||
}
|
||||
else {
|
||||
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
|
||||
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
|
||||
$Prompt = Split-Path -Path $venvDir -Leaf
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose "Prompt = '$Prompt'"
|
||||
Write-Verbose "VenvDir='$VenvDir'"
|
||||
|
||||
# Deactivate any currently active virtual environment, but leave the
|
||||
# deactivate function in place.
|
||||
deactivate -nondestructive
|
||||
|
||||
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
|
||||
# that there is an activated venv.
|
||||
$env:VIRTUAL_ENV = $VenvDir
|
||||
|
||||
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
||||
|
||||
Write-Verbose "Setting prompt to '$Prompt'"
|
||||
|
||||
# Set the prompt to include the env name
|
||||
# Make sure _OLD_VIRTUAL_PROMPT is global
|
||||
function global:_OLD_VIRTUAL_PROMPT { "" }
|
||||
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
|
||||
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
|
||||
|
||||
function global:prompt {
|
||||
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
|
||||
_OLD_VIRTUAL_PROMPT
|
||||
}
|
||||
$env:VIRTUAL_ENV_PROMPT = $Prompt
|
||||
}
|
||||
|
||||
# Clear PYTHONHOME
|
||||
if (Test-Path -Path Env:PYTHONHOME) {
|
||||
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
|
||||
Remove-Item -Path Env:PYTHONHOME
|
||||
}
|
||||
|
||||
# Add the venv to the PATH
|
||||
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
|
||||
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
|
|
@ -1,69 +0,0 @@
|
|||
# This file must be used with "source bin/activate" *from bash*
|
||||
# you cannot run it directly
|
||||
|
||||
deactivate () {
|
||||
# reset old environment variables
|
||||
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||
PATH="${_OLD_VIRTUAL_PATH:-}"
|
||||
export PATH
|
||||
unset _OLD_VIRTUAL_PATH
|
||||
fi
|
||||
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
||||
export PYTHONHOME
|
||||
unset _OLD_VIRTUAL_PYTHONHOME
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||
hash -r 2> /dev/null
|
||||
fi
|
||||
|
||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||
PS1="${_OLD_VIRTUAL_PS1:-}"
|
||||
export PS1
|
||||
unset _OLD_VIRTUAL_PS1
|
||||
fi
|
||||
|
||||
unset VIRTUAL_ENV
|
||||
unset VIRTUAL_ENV_PROMPT
|
||||
if [ ! "${1:-}" = "nondestructive" ] ; then
|
||||
# Self destruct!
|
||||
unset -f deactivate
|
||||
fi
|
||||
}
|
||||
|
||||
# unset irrelevant variables
|
||||
deactivate nondestructive
|
||||
|
||||
VIRTUAL_ENV="/home/ubuntu/vantamoo/evenv"
|
||||
export VIRTUAL_ENV
|
||||
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
export PATH
|
||||
|
||||
# unset PYTHONHOME if set
|
||||
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
||||
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
||||
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
||||
unset PYTHONHOME
|
||||
fi
|
||||
|
||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||
PS1="(evenv) ${PS1:-}"
|
||||
export PS1
|
||||
VIRTUAL_ENV_PROMPT="(evenv) "
|
||||
export VIRTUAL_ENV_PROMPT
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||
hash -r 2> /dev/null
|
||||
fi
|
|
@ -1,26 +0,0 @@
|
|||
# This file must be used with "source bin/activate.csh" *from csh*.
|
||||
# You cannot run it directly.
|
||||
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
||||
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
||||
|
||||
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
setenv VIRTUAL_ENV "/home/ubuntu/vantamoo/evenv"
|
||||
|
||||
set _OLD_VIRTUAL_PATH="$PATH"
|
||||
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
|
||||
|
||||
|
||||
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||
|
||||
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||
set prompt = "(evenv) $prompt"
|
||||
setenv VIRTUAL_ENV_PROMPT "(evenv) "
|
||||
endif
|
||||
|
||||
alias pydoc python -m pydoc
|
||||
|
||||
rehash
|
|
@ -1,66 +0,0 @@
|
|||
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
|
||||
# (https://fishshell.com/); you cannot run it directly.
|
||||
|
||||
function deactivate -d "Exit virtual environment and return to normal shell environment"
|
||||
# reset old environment variables
|
||||
if test -n "$_OLD_VIRTUAL_PATH"
|
||||
set -gx PATH $_OLD_VIRTUAL_PATH
|
||||
set -e _OLD_VIRTUAL_PATH
|
||||
end
|
||||
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
||||
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
||||
set -e _OLD_VIRTUAL_PYTHONHOME
|
||||
end
|
||||
|
||||
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
||||
functions -e fish_prompt
|
||||
set -e _OLD_FISH_PROMPT_OVERRIDE
|
||||
functions -c _old_fish_prompt fish_prompt
|
||||
functions -e _old_fish_prompt
|
||||
end
|
||||
|
||||
set -e VIRTUAL_ENV
|
||||
set -e VIRTUAL_ENV_PROMPT
|
||||
if test "$argv[1]" != "nondestructive"
|
||||
# Self-destruct!
|
||||
functions -e deactivate
|
||||
end
|
||||
end
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
set -gx VIRTUAL_ENV "/home/ubuntu/vantamoo/evenv"
|
||||
|
||||
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
|
||||
|
||||
# Unset PYTHONHOME if set.
|
||||
if set -q PYTHONHOME
|
||||
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
||||
set -e PYTHONHOME
|
||||
end
|
||||
|
||||
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||
# fish uses a function instead of an env var to generate the prompt.
|
||||
|
||||
# Save the current fish_prompt function as the function _old_fish_prompt.
|
||||
functions -c fish_prompt _old_fish_prompt
|
||||
|
||||
# With the original prompt function renamed, we can override with our own.
|
||||
function fish_prompt
|
||||
# Save the return status of the last command.
|
||||
set -l old_status $status
|
||||
|
||||
# Output the venv prompt; color taken from the blue of the Python logo.
|
||||
printf "%s%s%s" (set_color 4B8BBE) "(evenv) " (set_color normal)
|
||||
|
||||
# Restore the return status of the previous command.
|
||||
echo "exit $old_status" | .
|
||||
# Output the original/"old" prompt.
|
||||
_old_fish_prompt
|
||||
end
|
||||
|
||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||
set -gx VIRTUAL_ENV_PROMPT "(evenv) "
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
#!/home/ubuntu/vantamoo/evenv/bin/python3.11
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
|
@ -1,8 +0,0 @@
|
|||
#!/home/ubuntu/vantamoo/evenv/bin/python3.11
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
|
@ -1,8 +0,0 @@
|
|||
#!/home/ubuntu/vantamoo/evenv/bin/python3.11
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
|
@ -1,8 +0,0 @@
|
|||
#!/home/ubuntu/vantamoo/evenv/bin/python3.11
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
|
@ -1 +0,0 @@
|
|||
python3.11
|
|
@ -1 +0,0 @@
|
|||
python3.11
|
|
@ -1 +0,0 @@
|
|||
/usr/bin/python3.11
|
|
@ -1,5 +0,0 @@
|
|||
home = /usr/bin
|
||||
include-system-site-packages = false
|
||||
version = 3.11.0
|
||||
executable = /usr/bin/python3.11
|
||||
command = /usr/bin/python3.11 -m venv /home/ubuntu/vantamoo/evenv
|
|
@ -0,0 +1,49 @@
|
|||
from .common import *
|
||||
|
||||
# This should be called before every use of a Character to make sure it's
|
||||
# initialized right.
|
||||
def object_init(character):
|
||||
if character.db.pronouns == None:
|
||||
character.db.pronouns = DEFAULT_CHARACTER_PRONOUNS[:]
|
||||
if character.db.pronoun_specs == None:
|
||||
character.db.pronoun_specs = []
|
||||
|
||||
def msg(character, text=None, from_obj=None, session=None, **kwargs):
|
||||
object_init(character)
|
||||
|
||||
"""
|
||||
Emits something to a session attached to the object.
|
||||
Overloads the default msg() implementation to include
|
||||
gender-aware markers in output.
|
||||
|
||||
Args:
|
||||
text (str or tuple, optional): The message to send. This
|
||||
is treated internally like any send-command, so its
|
||||
value can be a tuple if sending multiple arguments to
|
||||
the `text` oob command.
|
||||
from_obj (obj, optional): object that is sending. If
|
||||
given, at_msg_send will be called
|
||||
session (Session or list, optional): session or list of
|
||||
sessions to relay to, if any. If set, will
|
||||
force send regardless of MULTISESSION_MODE.
|
||||
Note:
|
||||
`at_msg_receive` will be called on this Object.
|
||||
All extra kwargs will be passed on to the protocol.
|
||||
|
||||
"""
|
||||
if text is None:
|
||||
super().msg(from_obj=from_obj, session=session, **kwargs)
|
||||
return
|
||||
|
||||
try:
|
||||
repl = lambda a: pronoun_repl(character, a)
|
||||
|
||||
if text and isinstance(text, tuple):
|
||||
text = (RE_GENDER_PRONOUN.sub(repl, text[0]), *text[1:])
|
||||
else:
|
||||
text = RE_GENDER_PRONOUN.sub(repl, text)
|
||||
except TypeError:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.log_trace(e)
|
||||
return (text, from_obj, session, kwargs)
|
|
@ -0,0 +1,225 @@
|
|||
from .common import *
|
||||
from .character import object_init
|
||||
|
||||
class PronounAdminCommand(Command):
|
||||
"""
|
||||
Manages the list of known server-wide pronouns.
|
||||
|
||||
Usage:
|
||||
@pronounadmin help
|
||||
@pronounadmin list_known
|
||||
@pronounadmin spec <specific>
|
||||
@pronounadmin unspec <specific>
|
||||
|
||||
On each character and the server are stored character pronouns and specific
|
||||
pronouns. The character pronouns specify what pronouns should actually be
|
||||
used for your character, and the specific pronouns define all the
|
||||
grammatical forms of a pronoun so the character pronouns can be used in
|
||||
text substitution.
|
||||
|
||||
The specific format is: <subjective>,<objective>,<possessive>,<absolute>
|
||||
subjective: *They* went to the store.
|
||||
objective: You took *them* to the store.
|
||||
possessive: You took *their* friend to the store.
|
||||
absolute: The sandwich in the fridge was *theirs*.
|
||||
|
||||
The `list_known` command lists all specific pronouns known by the server.
|
||||
|
||||
The `spec` command adds a new specific pronoun.
|
||||
|
||||
The `unspec` command removes an existing specific pronoun.
|
||||
|
||||
Examples:
|
||||
@pronounadmin spec they,them,their,theirs
|
||||
"""
|
||||
key = "pronounadmin"
|
||||
locks = "cmd:id(1) or perm(Admin)"
|
||||
|
||||
def parse(self):
|
||||
caller = self.caller
|
||||
|
||||
args = self.args.strip().lower().split()
|
||||
if len(args) == 0:
|
||||
caller.msg(self.get_help(caller, self.cmdset))
|
||||
raise InterruptCommand()
|
||||
|
||||
command = args[0]
|
||||
|
||||
if command == "list_known" and len(args) == 1:
|
||||
self.args = tuple([command])
|
||||
return
|
||||
|
||||
if len(args) != 2:
|
||||
caller.msg(self.get_help(caller, self.cmdset))
|
||||
raise InterruptCommand()
|
||||
|
||||
if not command in [ "spec", "unspec" ] or command == "help":
|
||||
caller.msg(self.get_help(caller, self.cmdset))
|
||||
raise InterruptCommand()
|
||||
|
||||
pronoun = parse_specific_pronoun(args[1])
|
||||
if pronoun == None:
|
||||
caller.msg(self.get_help(caller, self.cmdset))
|
||||
raise InterruptCommand()
|
||||
|
||||
self.args = ( command, pronoun )
|
||||
|
||||
def func(self):
|
||||
caller = self.caller
|
||||
|
||||
command = self.args[0]
|
||||
|
||||
if command == "list_known":
|
||||
caller.msg("Known server-defined pronouns:")
|
||||
for spec in get_pronoun_list():
|
||||
caller.msg(f" {stringify_specific_pronoun(spec)}")
|
||||
return
|
||||
|
||||
_, pronoun = self.args
|
||||
pronoun_list = get_pronoun_list()
|
||||
|
||||
if command == "spec":
|
||||
if not pronoun in pronoun_list:
|
||||
pronoun_list.append(pronoun)
|
||||
self.caller.msg(f"Added the pronoun {stringify_specific_pronoun(pronoun)}.")
|
||||
elif command == "unspec":
|
||||
if pronoun in pronoun_list:
|
||||
pronoun_list.remove(pronoun)
|
||||
self.caller.msg(f"Removed the pronoun {stringify_specific_pronoun(pronoun)}.")
|
||||
|
||||
class PronounsCommand(Command):
|
||||
"""
|
||||
Manages your pronouns.
|
||||
|
||||
Usage:
|
||||
@pronouns help
|
||||
@pronouns set <pronouns>
|
||||
@pronouns get
|
||||
@pronouns list_known
|
||||
@pronouns spec <specific>
|
||||
@pronouns unspec <specific>
|
||||
|
||||
On each character and the server are stored character pronouns and specific
|
||||
pronouns. The character pronouns specify what pronouns should actually be
|
||||
used for your character, and the specific pronouns define all the
|
||||
grammatical forms of a pronoun so the character pronouns can be used in
|
||||
text substitution.
|
||||
|
||||
The server already has some specific pronouns defined, so it's likely you
|
||||
can just give your character pronouns and be done. If the server doesn't
|
||||
know about your pronouns already then you'll have to tell the server how to
|
||||
use them with the `spec` command.
|
||||
|
||||
The specific format is: <subjective>,<objective>,<possessive>,<absolute>
|
||||
subjective: *They* went to the store.
|
||||
objective: You took *them* to the store.
|
||||
possessive: You took *their* friend to the store.
|
||||
absolute: The sandwich in the fridge was *theirs*.
|
||||
|
||||
The `set` command:
|
||||
|
||||
Sets your current character pronouns. Pronouns look the same as how you'd
|
||||
write them anywhere online. They're separated by a slash ("/"), like
|
||||
"she/they/it", or "he/him". Each pronoun should match to the grammatical
|
||||
form of a specific pronoun. When multiple pronouns are given, like both
|
||||
"she" and "they", a random one will be used in each text substitution.
|
||||
|
||||
The `get` command prints your current configured character pronouns.
|
||||
|
||||
The `list_known` command lists all specific pronouns known by the
|
||||
server and specified for your acount.
|
||||
|
||||
The `spec` command adds a new specific pronoun.
|
||||
|
||||
The `unspec` command removes an existing specific pronoun.
|
||||
|
||||
Examples:
|
||||
@pronouns set he/him
|
||||
|
||||
@pronouns set she/they/it
|
||||
|
||||
@pronouns spec fae,faer,faer,faers
|
||||
"""
|
||||
|
||||
key = "pronouns"
|
||||
|
||||
def parse(self):
|
||||
caller = self.caller
|
||||
object_init(caller)
|
||||
|
||||
args = self.args.strip().lower().split()
|
||||
if len(args) == 1:
|
||||
command = args[0]
|
||||
if not command in [ "get", "list_known" ] or command == "help":
|
||||
caller.msg(self.get_help(caller, self.cmdset))
|
||||
raise InterruptCommand()
|
||||
|
||||
self.args = tuple([command])
|
||||
return
|
||||
|
||||
if len(args) != 2:
|
||||
caller.msg(self.get_help(caller, self.cmdset))
|
||||
raise InterruptCommand()
|
||||
|
||||
command = args[0]
|
||||
|
||||
if command == "spec" or command == "unspec":
|
||||
new = parse_specific_pronoun(args[1])
|
||||
if new == None:
|
||||
caller.msg(self.get_help(caller, self.cmdset))
|
||||
raise InterruptCommand()
|
||||
else:
|
||||
res = new
|
||||
|
||||
elif command == "set":
|
||||
res = args[1].split('/')
|
||||
|
||||
self.args = (command, res)
|
||||
|
||||
def func(self):
|
||||
caller = self.caller
|
||||
object_init(caller)
|
||||
|
||||
command = self.args[0]
|
||||
|
||||
match self.args[0]:
|
||||
case "set":
|
||||
_, pronouns = self.args
|
||||
has_unmatched = False
|
||||
for pronoun in pronouns:
|
||||
if not specific_pronoun_from_character(pronoun, caller):
|
||||
has_unmatched = True
|
||||
caller.msg(f"Warning: The character pronoun \"{pronoun}\" is "
|
||||
"not in any list of specific pronouns. It will "
|
||||
"be ignored. |/")
|
||||
if has_unmatched:
|
||||
caller.msg("You have unkown pronouns set! If you want to "
|
||||
"actually use them, you'll need to set their "
|
||||
"specific form. Type `pronouns help` for "
|
||||
"details. |/")
|
||||
|
||||
caller.db.pronouns = pronouns
|
||||
caller.msg(f"Your pronouns have been set to {stringify_pronouns(caller)}.")
|
||||
|
||||
case "get":
|
||||
caller.msg(f"Your pronouns are {stringify_pronouns(caller)}.")
|
||||
|
||||
case "list_known":
|
||||
caller.msg("Known user-defined pronouns:")
|
||||
for spec in caller.db.pronoun_specs:
|
||||
caller.msg(f" {stringify_specific_pronoun(spec)}")
|
||||
caller.msg("Known server-defined pronouns:")
|
||||
for spec in get_pronoun_list():
|
||||
caller.msg(f" {stringify_specific_pronoun(spec)}")
|
||||
|
||||
case "spec":
|
||||
_, pronoun = self.args
|
||||
if not pronoun in caller.db.pronoun_specs:
|
||||
caller.db.pronoun_specs.append(pronoun)
|
||||
self.caller.msg(f"Added the pronoun {stringify_specific_pronoun(pronoun)}.")
|
||||
|
||||
case "unspec":
|
||||
_, pronoun = self.args
|
||||
if pronoun in caller.db.pronoun_specs:
|
||||
caller.db.pronoun_specs.remove(pronoun)
|
||||
self.caller.msg(f"Remobed the pronoun {stringify_specific_pronoun(pronoun)}.")
|
|
@ -0,0 +1,137 @@
|
|||
"""
|
||||
Pronounsub
|
||||
|
||||
Griatch 2015
|
||||
Copyright (c) 2023 lunacb <lunacb@disroot.org>
|
||||
|
||||
This is a gender-aware Character class that allows people to set pronouns and
|
||||
use them in text.
|
||||
|
||||
Usage
|
||||
|
||||
When in use, messages can contain special tags to indicate pronouns gendered
|
||||
based on the one being addressed. Capitalization will be retained.
|
||||
|
||||
- `|s`, `|S`: Subjective form, like They
|
||||
- `|o`, `|O`: Objective form, like Them
|
||||
- `|p`, `|P`: Possessive form, like Their
|
||||
- `|a`, `|A`: Absolute Possessive form, like Theirs
|
||||
|
||||
For example,
|
||||
|
||||
```
|
||||
char.msg("%s falls on |p face with a thud." % char.key)
|
||||
"Tom falls on their face with a thud"
|
||||
```
|
||||
|
||||
To use, have DefaultCharacter inherit from this, or change
|
||||
setting.DEFAULT_CHARACTER to point to this class.
|
||||
|
||||
The `pronouns` command is used to set pronouns. It needs to be added to the
|
||||
default cmdset before it becomes available.
|
||||
|
||||
"""
|
||||
|
||||
import copy
|
||||
import random
|
||||
import re
|
||||
|
||||
from evennia import Command, DefaultCharacter
|
||||
from evennia import DefaultScript
|
||||
from evennia import InterruptCommand
|
||||
from evennia.utils import logger
|
||||
from evennia.utils.create import create_script
|
||||
from evennia.utils.search import search_script
|
||||
|
||||
class PronounDbScript(DefaultScript):
|
||||
def at_script_creation(self):
|
||||
self.db.pronouns = copy.deepcopy(DEFAULT_SPECIFIC_PRONOUNS)
|
||||
|
||||
DEFAULT_SPECIFIC_NEUTRAL_PRONOUNS = {"s": "they", "o": "them", "p": "their", "a": "theirs"}
|
||||
|
||||
DEFAULT_SPECIFIC_PRONOUNS = [
|
||||
{"s": "he", "o": "him", "p": "his", "a": "his"},
|
||||
{"s": "she", "o": "her", "p": "her", "a": "hers"},
|
||||
{"s": "it", "o": "it", "p": "its", "a": "its"},
|
||||
{"s": "they", "o": "them", "p": "their", "a": "theirs"}
|
||||
]
|
||||
|
||||
DEFAULT_CHARACTER_PRONOUNS = [ "they", "them" ]
|
||||
|
||||
PRONOUN_FORMS = [ "s", "o", "p", "a" ]
|
||||
|
||||
RE_GENDER_PRONOUN = re.compile(r"(?<!\|)\|(?!\|)[sSoOpPaA]")
|
||||
|
||||
# in-game command for setting the gender
|
||||
|
||||
def parse_specific_pronoun(pronoun):
|
||||
specific = [ p.strip() for p in pronoun.split(",") ]
|
||||
if len(specific) != len(PRONOUN_FORMS):
|
||||
return None
|
||||
|
||||
new = {}
|
||||
for i in range(len(specific)):
|
||||
new[PRONOUN_FORMS[i]] = specific[i]
|
||||
|
||||
return new
|
||||
|
||||
def get_pronoun_list():
|
||||
search = search_script("pronoun_db")
|
||||
if not any(search):
|
||||
script = create_script("lib.pronounsub.PronounDbScript", key="pronoun_db")
|
||||
else:
|
||||
script = search[0]
|
||||
|
||||
return script.db.pronouns
|
||||
|
||||
def specific_pronoun_from_character(character, caller=None):
|
||||
pronoun_list = get_pronoun_list()[:]
|
||||
if caller != None:
|
||||
pronoun_list = caller.db.pronoun_specs + pronoun_list
|
||||
for pronoun in pronoun_list:
|
||||
if character in pronoun.values():
|
||||
return pronoun
|
||||
|
||||
return None
|
||||
|
||||
def stringify_specific_pronoun(spec):
|
||||
return ",".join([ spec[form] for form in PRONOUN_FORMS ])
|
||||
|
||||
def stringify_pronouns(character):
|
||||
pronouns = character.attributes.get("pronouns", default=DEFAULT_CHARACTER_PRONOUNS[:])
|
||||
return "/".join(pronouns)
|
||||
|
||||
def pronoun_repl(character, regex_match):
|
||||
"""
|
||||
Get pronoun from the pronoun marker in the text. This is used as
|
||||
the callable for the re.sub function.
|
||||
|
||||
Args:
|
||||
regex_match (MatchObject): the regular expression match.
|
||||
|
||||
Notes:
|
||||
- `|s`, `|S`: Subjective form, like They
|
||||
- `|o`, `|O`: Objective form, like Them
|
||||
- `|p`, `|P`: Possessive form, like Their
|
||||
- `|a`, `|A`: Absolute Possessive form, like Theirs
|
||||
|
||||
"""
|
||||
typ = regex_match.group()[1] # "s", "O" etc
|
||||
dup_specifics = []
|
||||
for pronoun in character.attributes.get("pronouns", default=DEFAULT_CHARACTER_PRONOUNS[:]):
|
||||
specific = specific_pronoun_from_character(pronoun, character)
|
||||
if specific != None:
|
||||
dup_specifics.append(specific)
|
||||
|
||||
specifics = []
|
||||
for s in dup_specifics:
|
||||
if not s in specifics:
|
||||
specifics.append(s)
|
||||
|
||||
if len(specifics) == 0:
|
||||
choice = DEFAULT_SPECIFIC_NEUTRAL_PRONOUNS
|
||||
else:
|
||||
choice = random.choice(specifics)
|
||||
|
||||
pronoun = choice[typ.lower()]
|
||||
return pronoun.capitalize() if typ.isupper() else pronoun
|
|
@ -0,0 +1,267 @@
|
|||
# Roleplaying base system for Evennia
|
||||
|
||||
Contribution by Griatch, 2015
|
||||
|
||||
A full roleplaying emote system. Short-descriptions and recognition (only
|
||||
know people by their looks until you assign a name to them). Room poses. Masks/disguises
|
||||
(hide your description). Speak directly in emote, with optional language obscuration
|
||||
(words get garbled if you don't know the language, you can also have different languages
|
||||
with different 'sounding' garbling). Whispers can be partly overheard from a distance. A
|
||||
very powerful in-emote reference system, for referencing and differentiate targets
|
||||
(including objects).
|
||||
|
||||
The system contains of two main modules - the roleplaying emote system and the language
|
||||
obscuration module.
|
||||
|
||||
## Roleplaying emotes
|
||||
|
||||
This module contains the ContribRPObject, ContribRPRoom and
|
||||
ContribRPCharacter typeclasses. If you inherit your
|
||||
objects/rooms/character from these (or make them the defaults) from
|
||||
these you will get the following features:
|
||||
|
||||
- Objects/Rooms will get the ability to have poses and will report
|
||||
the poses of items inside them (the latter most useful for Rooms).
|
||||
- Characters will get poses and also sdescs (short descriptions)
|
||||
that will be used instead of their keys. They will gain commands
|
||||
for managing recognition (custom sdesc-replacement), masking
|
||||
themselves as well as an advanced free-form emote command.
|
||||
|
||||
In more detail, This RP base system introduces the following features
|
||||
to a game, common to many RP-centric games:
|
||||
|
||||
- emote system using director stance emoting (names/sdescs).
|
||||
This uses a customizable replacement noun (/me, @ etc) to
|
||||
represent you in the emote. You can use /sdesc, /nick, /key or
|
||||
/alias to reference objects in the room. You can use any
|
||||
number of sdesc sub-parts to differentiate a local sdesc, or
|
||||
use /1-sdesc etc to differentiate them. The emote also
|
||||
identifies nested says and separates case.
|
||||
- sdesc obscuration of real character names for use in emotes
|
||||
and in any referencing such as object.search(). This relies
|
||||
on an SdescHandler `sdesc` being set on the Character and
|
||||
makes use of a custom Character.get_display_name hook. If
|
||||
sdesc is not set, the character's `key` is used instead. This
|
||||
is particularly used in the emoting system.
|
||||
- recog system to assign your own nicknames to characters, can then
|
||||
be used for referencing. The user may recog a user and assign
|
||||
any personal nick to them. This will be shown in descriptions
|
||||
and used to reference them. This is making use of the nick
|
||||
functionality of Evennia.
|
||||
- masks to hide your identity (using a simple lock).
|
||||
- pose system to set room-persistent poses, visible in room
|
||||
descriptions and when looking at the person/object. This is a
|
||||
simple Attribute that modifies how the characters is viewed when
|
||||
in a room as sdesc + pose.
|
||||
- in-emote says, including seamless integration with language
|
||||
obscuration routine (such as contrib/rplanguage.py)
|
||||
|
||||
### Installation:
|
||||
|
||||
Add `RPSystemCmdSet` from this module to your CharacterCmdSet:
|
||||
|
||||
```python
|
||||
# mygame/commands/default_cmdsets.py
|
||||
|
||||
# ...
|
||||
|
||||
from evennia.contrib.rpg.rpsystem import RPSystemCmdSet <---
|
||||
|
||||
class CharacterCmdSet(default_cmds.CharacterCmdset):
|
||||
# ...
|
||||
def at_cmdset_creation(self):
|
||||
# ...
|
||||
self.add(RPSystemCmdSet()) # <---
|
||||
|
||||
```
|
||||
|
||||
You also need to make your Characters/Objects/Rooms inherit from
|
||||
the typeclasses in this module:
|
||||
|
||||
```python
|
||||
# in mygame/typeclasses/characters.py
|
||||
|
||||
from evennia.contrib.rpg import ContribRPCharacter
|
||||
|
||||
class Character(ContribRPCharacter):
|
||||
# ...
|
||||
|
||||
```
|
||||
|
||||
```python
|
||||
# in mygame/typeclasses/objects.py
|
||||
|
||||
from evennia.contrib.rpg import ContribRPObject
|
||||
|
||||
class Object(ContribRPObject):
|
||||
# ...
|
||||
|
||||
```
|
||||
|
||||
```python
|
||||
# in mygame/typeclasses/rooms.py
|
||||
|
||||
from evennia.contrib.rpg import ContribRPRoom
|
||||
|
||||
class Room(ContribRPRoom):
|
||||
# ...
|
||||
|
||||
```
|
||||
|
||||
You will then need to reload the server and potentially force-reload
|
||||
your objects, if you originally created them without this.
|
||||
|
||||
Example for your character:
|
||||
|
||||
> type/reset/force me = typeclasses.characters.Character
|
||||
|
||||
|
||||
Examples:
|
||||
|
||||
> look
|
||||
Tavern
|
||||
The tavern is full of nice people
|
||||
|
||||
*A tall man* is standing by the bar.
|
||||
|
||||
Above is an example of a player with an sdesc "a tall man". It is also
|
||||
an example of a static *pose*: The "standing by the bar" has been set
|
||||
by the player of the tall man, so that people looking at him can tell
|
||||
at a glance what is going on.
|
||||
|
||||
> emote /me looks at /Tall and says "Hello!"
|
||||
|
||||
I see:
|
||||
Griatch looks at Tall man and says "Hello".
|
||||
Tall man (assuming his name is Tom) sees:
|
||||
The godlike figure looks at Tom and says "Hello".
|
||||
|
||||
Note that by default, the case of the tag matters, so `/tall` will
|
||||
lead to 'tall man' while `/Tall` will become 'Tall man' and /TALL
|
||||
becomes /TALL MAN. If you don't want this behavior, you can pass
|
||||
case_sensitive=False to the `send_emote` function.
|
||||
|
||||
|
||||
## Language and whisper obfuscation system
|
||||
|
||||
This module is intented to be used with an emoting system (such as
|
||||
`contrib/rpg/rpsystem.py`). It offers the ability to obfuscate spoken words
|
||||
in the game in various ways:
|
||||
|
||||
- Language: The language functionality defines a pseudo-language map
|
||||
to any number of languages. The string will be obfuscated depending
|
||||
on a scaling that (most likely) will be input as a weighted average of
|
||||
the language skill of the speaker and listener.
|
||||
- Whisper: The whisper functionality will gradually "fade out" a
|
||||
whisper along as scale 0-1, where the fading is based on gradually
|
||||
removing sections of the whisper that is (supposedly) easier to
|
||||
overhear (for example "s" sounds tend to be audible even when no other
|
||||
meaning can be determined).
|
||||
|
||||
|
||||
### Installation
|
||||
|
||||
This module adds no new commands; embed it in your say/emote/whisper commands.
|
||||
|
||||
### Usage:
|
||||
|
||||
```python
|
||||
from evennia.contrib import rplanguage
|
||||
|
||||
# need to be done once, here we create the "default" lang
|
||||
rplanguage.add_language()
|
||||
|
||||
say = "This is me talking."
|
||||
whisper = "This is me whispering.
|
||||
|
||||
print rplanguage.obfuscate_language(say, level=0.0)
|
||||
<<< "This is me talking."
|
||||
print rplanguage.obfuscate_language(say, level=0.5)
|
||||
<<< "This is me byngyry."
|
||||
print rplanguage.obfuscate_language(say, level=1.0)
|
||||
<<< "Daly ly sy byngyry."
|
||||
|
||||
result = rplanguage.obfuscate_whisper(whisper, level=0.0)
|
||||
<<< "This is me whispering"
|
||||
result = rplanguage.obfuscate_whisper(whisper, level=0.2)
|
||||
<<< "This is m- whisp-ring"
|
||||
result = rplanguage.obfuscate_whisper(whisper, level=0.5)
|
||||
<<< "---s -s -- ---s------"
|
||||
result = rplanguage.obfuscate_whisper(whisper, level=0.7)
|
||||
<<< "---- -- -- ----------"
|
||||
result = rplanguage.obfuscate_whisper(whisper, level=1.0)
|
||||
<<< "..."
|
||||
|
||||
```
|
||||
|
||||
To set up new languages, import and use the `add_language()`
|
||||
helper method in this module. This allows you to customize the
|
||||
"feel" of the semi-random language you are creating. Especially
|
||||
the `word_length_variance` helps vary the length of translated
|
||||
words compared to the original and can help change the "feel" for
|
||||
the language you are creating. You can also add your own
|
||||
dictionary and "fix" random words for a list of input words.
|
||||
|
||||
Below is an example of "elvish", using "rounder" vowels and sounds:
|
||||
|
||||
```python
|
||||
# vowel/consonant grammar possibilities
|
||||
grammar = ("v vv vvc vcc vvcc cvvc vccv vvccv vcvccv vcvcvcc vvccvvcc "
|
||||
"vcvvccvvc cvcvvcvvcc vcvcvvccvcvv")
|
||||
|
||||
# all not in this group is considered a consonant
|
||||
vowels = "eaoiuy"
|
||||
|
||||
# you need a representative of all of the minimal grammars here, so if a
|
||||
# grammar v exists, there must be atleast one phoneme available with only
|
||||
# one vowel in it
|
||||
phonemes = ("oi oh ee ae aa eh ah ao aw ay er ey ow ia ih iy "
|
||||
"oy ua uh uw y p b t d f v t dh s z sh zh ch jh k "
|
||||
"ng g m n l r w")
|
||||
|
||||
# how much the translation varies in length compared to the original. 0 is
|
||||
# smallest, higher values give ever bigger randomness (including removing
|
||||
# short words entirely)
|
||||
word_length_variance = 1
|
||||
|
||||
# if a proper noun (word starting with capitalized letter) should be
|
||||
# translated or not. If not (default) it means e.g. names will remain
|
||||
# unchanged across languages.
|
||||
noun_translate = False
|
||||
|
||||
# all proper nouns (words starting with a capital letter not at the beginning
|
||||
# of a sentence) can have either a postfix or -prefix added at all times
|
||||
noun_postfix = "'la"
|
||||
|
||||
# words in dict will always be translated this way. The 'auto_translations'
|
||||
# is instead a list or filename to file with words to use to help build a
|
||||
# bigger dictionary by creating random translations of each word in the
|
||||
# list *once* and saving the result for subsequent use.
|
||||
manual_translations = {"the":"y'e", "we":"uyi", "she":"semi", "he":"emi",
|
||||
"you": "do", 'me':'mi','i':'me', 'be':"hy'e", 'and':'y'}
|
||||
|
||||
rplanguage.add_language(key="elvish", phonemes=phonemes, grammar=grammar,
|
||||
word_length_variance=word_length_variance,
|
||||
noun_translate=noun_translate,
|
||||
noun_postfix=noun_postfix, vowels=vowels,
|
||||
manual_translations=manual_translations,
|
||||
auto_translations="my_word_file.txt")
|
||||
|
||||
```
|
||||
|
||||
This will produce a decicively more "rounded" and "soft" language than the
|
||||
default one. The few `manual_translations` also make sure to make it at least
|
||||
look superficially "reasonable".
|
||||
|
||||
The `auto_translations` keyword is useful, this accepts either a
|
||||
list or a path to a text-file (with one word per line). This listing
|
||||
of words is used to 'fix' translations for those words according to the
|
||||
grammatical rules. These translations are stored persistently as long as the
|
||||
language exists.
|
||||
|
||||
This allows to quickly build a large corpus of translated words
|
||||
that never change. This produces a language that seem moderately
|
||||
consistent, since words like 'the' will always be translated to the same thing.
|
||||
The disadvantage (or advantage, depending on your game) is that players can
|
||||
end up learn what words mean even if their characters don't know the
|
||||
langauge.
|
|
@ -0,0 +1,36 @@
|
|||
"""
|
||||
Roleplaying emotes and language - Griatch, 2015
|
||||
|
||||
"""
|
||||
|
||||
from .rplanguage import LanguageExistsError # noqa
|
||||
from .rplanguage import LanguageHandler # noqa
|
||||
from .rplanguage import (
|
||||
LanguageError,
|
||||
add_language,
|
||||
available_languages,
|
||||
obfuscate_language,
|
||||
obfuscate_whisper,
|
||||
)
|
||||
from .rpsystem import CmdSay # noqa
|
||||
from .rpsystem import ContribRPCharacter # noqa
|
||||
from .rpsystem import ContribRPObject # noqa
|
||||
from .rpsystem import ContribRPRoom # noqa
|
||||
from .rpsystem import RPSystemCmdSet # noqa
|
||||
from .rpsystem import (
|
||||
CmdEmote,
|
||||
CmdMask,
|
||||
CmdPose,
|
||||
CmdRecog,
|
||||
CmdSdesc,
|
||||
EmoteError,
|
||||
LanguageError,
|
||||
RecogError,
|
||||
RecogHandler,
|
||||
RPCommand,
|
||||
SdescError,
|
||||
SdescHandler,
|
||||
parse_language,
|
||||
parse_sdescs_and_recogs,
|
||||
send_emote,
|
||||
)
|
|
@ -0,0 +1,609 @@
|
|||
"""
|
||||
Language and whisper obfuscation system
|
||||
|
||||
Evennia contrib - Griatch 2015
|
||||
|
||||
This module is intented to be used with an emoting system (such as
|
||||
contrib/rpsystem.py). It offers the ability to obfuscate spoken words
|
||||
in the game in various ways:
|
||||
|
||||
- Language: The language functionality defines a pseudo-language map
|
||||
to any number of languages. The string will be obfuscated depending
|
||||
on a scaling that (most likely) will be input as a weighted average of
|
||||
the language skill of the speaker and listener.
|
||||
- Whisper: The whisper functionality will gradually "fade out" a
|
||||
whisper along as scale 0-1, where the fading is based on gradually
|
||||
removing sections of the whisper that is (supposedly) easier to
|
||||
overhear (for example "s" sounds tend to be audible even when no other
|
||||
meaning can be determined).
|
||||
|
||||
## Usage
|
||||
|
||||
```python
|
||||
from evennia.contrib import rplanguage
|
||||
|
||||
# need to be done once, here we create the "default" lang
|
||||
rplanguage.add_language()
|
||||
|
||||
say = "This is me talking."
|
||||
whisper = "This is me whispering.
|
||||
|
||||
print rplanguage.obfuscate_language(say, level=0.0)
|
||||
<<< "This is me talking."
|
||||
print rplanguage.obfuscate_language(say, level=0.5)
|
||||
<<< "This is me byngyry."
|
||||
print rplanguage.obfuscate_language(say, level=1.0)
|
||||
<<< "Daly ly sy byngyry."
|
||||
|
||||
result = rplanguage.obfuscate_whisper(whisper, level=0.0)
|
||||
<<< "This is me whispering"
|
||||
result = rplanguage.obfuscate_whisper(whisper, level=0.2)
|
||||
<<< "This is m- whisp-ring"
|
||||
result = rplanguage.obfuscate_whisper(whisper, level=0.5)
|
||||
<<< "---s -s -- ---s------"
|
||||
result = rplanguage.obfuscate_whisper(whisper, level=0.7)
|
||||
<<< "---- -- -- ----------"
|
||||
result = rplanguage.obfuscate_whisper(whisper, level=1.0)
|
||||
<<< "..."
|
||||
|
||||
```
|
||||
|
||||
## Custom languages
|
||||
|
||||
To set up new languages, you need to run `add_language()`
|
||||
helper function in this module. The arguments of this function (see below)
|
||||
are used to store the new language in the database (in the LanguageHandler,
|
||||
which is a type of Script).
|
||||
|
||||
If you want to remember the language definitions, you could put them all
|
||||
in a module along with the `add_language` call as a quick way to
|
||||
rebuild the language on a db reset:
|
||||
|
||||
```python
|
||||
# a stand-alone module somewhere under mygame. Just import this
|
||||
# once to automatically add the language!
|
||||
|
||||
from evennia.contrib.rpg.rpsystem import rplanguage
|
||||
grammar = (...)
|
||||
vowels = "eaouy"
|
||||
# etc
|
||||
|
||||
rplanguage.add_language(grammar=grammar, vowels=vowels, ...)
|
||||
```
|
||||
|
||||
The variables of `add_language` allows you to customize the "feel" of
|
||||
the semi-random language you are creating. Especially
|
||||
the `word_length_variance` helps vary the length of translated
|
||||
words compared to the original. You can also add your own
|
||||
dictionary and "fix" random words for a list of input words.
|
||||
|
||||
## Example
|
||||
|
||||
Below is an example module creating "elvish", using "rounder" vowels and sounds:
|
||||
|
||||
```python
|
||||
# vowel/consonant grammar possibilities
|
||||
grammar = ("v vv vvc vcc vvcc cvvc vccv vvccv vcvccv vcvcvcc vvccvvcc "
|
||||
"vcvvccvvc cvcvvcvvcc vcvcvvccvcvv")
|
||||
|
||||
# all not in this group is considered a consonant
|
||||
vowels = "eaoiuy"
|
||||
|
||||
# you need a representative of all of the minimal grammars here, so if a
|
||||
# grammar v exists, there must be atleast one phoneme available with only
|
||||
# one vowel in it
|
||||
phonemes = ("oi oh ee ae aa eh ah ao aw ay er ey ow ia ih iy "
|
||||
"oy ua uh uw y p b t d f v t dh s z sh zh ch jh k "
|
||||
"ng g m n l r w")
|
||||
|
||||
# how much the translation varies in length compared to the original. 0 is
|
||||
# smallest, higher values give ever bigger randomness (including removing
|
||||
# short words entirely)
|
||||
word_length_variance = 1
|
||||
|
||||
# if a proper noun (word starting with capitalized letter) should be
|
||||
# translated or not. If not (default) it means e.g. names will remain
|
||||
# unchanged across languages.
|
||||
noun_translate = False
|
||||
|
||||
# all proper nouns (words starting with a capital letter not at the beginning
|
||||
# of a sentence) can have either a postfix or -prefix added at all times
|
||||
noun_postfix = "'la"
|
||||
|
||||
# words in dict will always be translated this way. The 'auto_translations'
|
||||
# is instead a list or filename to file with words to use to help build a
|
||||
# bigger dictionary by creating random translations of each word in the
|
||||
# list *once* and saving the result for subsequent use.
|
||||
manual_translations = {"the":"y'e", "we":"uyi", "she":"semi", "he":"emi",
|
||||
"you": "do", 'me':'mi','i':'me', 'be':"hy'e", 'and':'y'}
|
||||
|
||||
rplanguage.add_language(key="elvish", phonemes=phonemes, grammar=grammar,
|
||||
word_length_variance=word_length_variance,
|
||||
noun_translate=noun_translate,
|
||||
noun_postfix=noun_postfix, vowels=vowels,
|
||||
manual_translations=manual_translations,
|
||||
auto_translations="my_word_file.txt")
|
||||
|
||||
```
|
||||
|
||||
This will produce a decicively more "rounded" and "soft" language
|
||||
than the default one. The few manual_translations also make sure
|
||||
to make it at least look superficially "reasonable".
|
||||
|
||||
The `auto_translations` keyword is useful, this accepts either a
|
||||
list or a path to a file of words (one per line) to automatically
|
||||
create fixed translations for according to the grammatical rules.
|
||||
This allows to quickly build a large corpus of translated words
|
||||
that never change (if this is desired).
|
||||
|
||||
"""
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from random import choice, randint
|
||||
|
||||
from evennia import DefaultScript
|
||||
from evennia.utils import logger
|
||||
|
||||
# ------------------------------------------------------------
|
||||
#
|
||||
# Obfuscate language
|
||||
#
|
||||
# ------------------------------------------------------------
|
||||
|
||||
# default language grammar
|
||||
_PHONEMES = (
|
||||
"ea oh ae aa eh ah ao aw ai er ey ow ia ih iy oy ua uh uw a e i u y p b t d f v t dh "
|
||||
"s z sh zh ch jh k ng g m n l r w"
|
||||
)
|
||||
_VOWELS = "eaoiuy"
|
||||
# these must be able to be constructed from phonemes (so for example,
|
||||
# if you have v here, there must exist at least one single-character
|
||||
# vowel phoneme defined above)
|
||||
_GRAMMAR = "v cv vc cvv vcc vcv cvcc vccv cvccv cvcvcc cvccvcv vccvccvc cvcvccvv cvcvcvcvv"
|
||||
|
||||
_RE_FLAGS = re.MULTILINE + re.IGNORECASE + re.DOTALL + re.UNICODE
|
||||
_RE_GRAMMAR = re.compile(r"vv|cc|v|c", _RE_FLAGS)
|
||||
_RE_WORD = re.compile(r"\w+", _RE_FLAGS)
|
||||
# superfluous chars, except ` ... `
|
||||
_RE_EXTRA_CHARS = re.compile(r"\s+(?!... )(?=\W)|[,.?;](?!.. )(?=[,?;]|\s+[,.?;])", _RE_FLAGS)
|
||||
|
||||
|
||||
class LanguageError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
class LanguageExistsError(LanguageError):
|
||||
pass
|
||||
|
||||
|
||||
class LanguageHandler(DefaultScript):
|
||||
"""
|
||||
This is a storage class that should usually not be created on its
|
||||
own. It's automatically created by a call to `obfuscate_language`
|
||||
or `add_language` below.
|
||||
|
||||
Languages are implemented as a "logical" pseudo- consistent language
|
||||
algorith here. The idea is that a language is built up from
|
||||
phonemes. These are joined together according to a "grammar" of
|
||||
possible phoneme- combinations and allowed characters. It may
|
||||
sound simplistic, but this allows to easily make
|
||||
"similar-sounding" languages. One can also custom-define a
|
||||
dictionary of some common words to give further consistency.
|
||||
Optionally, the system also allows an input list of common words
|
||||
to be loaded and given random translations. These will be stored
|
||||
to disk and will thus not change. This gives a decent "stability"
|
||||
of the language but if the goal is to obfuscate, this may allow
|
||||
players to eventually learn to understand the gist of a sentence
|
||||
even if their characters can not. Any number of languages can be
|
||||
created this way.
|
||||
|
||||
This nonsense language will partially replace the actual spoken
|
||||
language when so desired (usually because the speaker/listener
|
||||
don't know the language well enough).
|
||||
|
||||
"""
|
||||
|
||||
def at_script_creation(self):
|
||||
"Called when script is first started"
|
||||
self.key = "language_handler"
|
||||
self.persistent = True
|
||||
self.db.language_storage = {}
|
||||
|
||||
def add(
|
||||
self,
|
||||
key="default",
|
||||
phonemes=_PHONEMES,
|
||||
grammar=_GRAMMAR,
|
||||
word_length_variance=0,
|
||||
noun_translate=False,
|
||||
noun_prefix="",
|
||||
noun_postfix="",
|
||||
vowels=_VOWELS,
|
||||
manual_translations=None,
|
||||
auto_translations=None,
|
||||
force=False,
|
||||
):
|
||||
"""
|
||||
Add a new language. Note that you generally only need to do
|
||||
this once per language and that adding an existing language
|
||||
will re-initialize all the random components to new permanent
|
||||
values.
|
||||
|
||||
Args:
|
||||
key (str, optional): The name of the language. This
|
||||
will be used as an identifier for the language so it
|
||||
should be short and unique.
|
||||
phonemes (str, optional): Space-separated string of all allowed
|
||||
phonemes in this language. If either of the base phonemes
|
||||
(c, v, cc, vv) are present in the grammar, the phoneme list must
|
||||
at least include one example of each.
|
||||
grammar (str): All allowed consonant (c) and vowel (v) combinations
|
||||
allowed to build up words. Grammars are broken into the base phonemes
|
||||
(c, v, cc, vv) prioritizing the longer bases. So cvv would be a
|
||||
the c + vv (would allow for a word like 'die' whereas
|
||||
cvcvccc would be c+v+c+v+cc+c (a word like 'galosch').
|
||||
word_length_variance (real): The variation of length of words.
|
||||
0 means a minimal variance, higher variance may mean words
|
||||
have wildly varying length; this strongly affects how the
|
||||
language "looks".
|
||||
noun_translate (bool, optional): If a proper noun should be translated or
|
||||
not. By default they will not, allowing for e.g. the names of characters
|
||||
to be understandable. A 'noun' is identified as a capitalized word
|
||||
*not at the start of a sentence*. This simple metric means that names
|
||||
starting a sentence always will be translated (- but hey, maybe
|
||||
the fantasy language just never uses a noun at the beginning of
|
||||
sentences, who knows?)
|
||||
noun_prefix (str, optional): A prefix to go before every noun
|
||||
in this language (if any).
|
||||
noun_postfix (str, optuonal): A postfix to go after every noun
|
||||
in this language (if any, usually best to avoid combining
|
||||
with `noun_prefix` or language becomes very wordy).
|
||||
vowels (str, optional): Every vowel allowed in this language.
|
||||
manual_translations (dict, optional): This allows for custom-setting
|
||||
certain words in the language to mean the same thing. It is
|
||||
on the form `{real_word: fictional_word}`, for example
|
||||
`{"the", "y'e"}` .
|
||||
auto_translations (str or list, optional): These are lists
|
||||
words that should be auto-translated with a random, but
|
||||
fixed, translation. If a path to a file, this file should
|
||||
contain a list of words to produce translations for, one
|
||||
word per line. If a list, the list's elements should be
|
||||
the words to translate. The `manual_translations` will
|
||||
always override overlapping translations created
|
||||
automatically.
|
||||
force (bool, optional): Unless true, will not allow the addition
|
||||
of a language that is already created.
|
||||
|
||||
Raises:
|
||||
LanguageExistsError: Raised if trying to adding a language
|
||||
with a key that already exists, without `force` being set.
|
||||
Notes:
|
||||
The `word_file` is for example a word-frequency list for
|
||||
the N most common words in the host language. The
|
||||
translations will be random, but will be stored
|
||||
persistently to always be the same. This allows for
|
||||
building a quick, decently-sounding fictive language that
|
||||
tend to produce the same "translation" (mostly) with the
|
||||
same input sentence.
|
||||
|
||||
"""
|
||||
if key in self.db.language_storage and not force:
|
||||
raise LanguageExistsError(
|
||||
"Language is already created. Re-adding it will re-build"
|
||||
" its dictionary map. Use 'force=True' keyword if you are sure."
|
||||
)
|
||||
|
||||
# create grammar_component->phoneme mapping
|
||||
# {"vv": ["ea", "oh", ...], ...}
|
||||
grammar2phonemes = defaultdict(list)
|
||||
for phoneme in phonemes.split():
|
||||
if re.search(r"\W", phoneme, re.U):
|
||||
raise LanguageError("The phoneme '%s' contains an invalid character." % phoneme)
|
||||
gram = "".join(["v" if char in vowels else "c" for char in phoneme])
|
||||
grammar2phonemes[gram].append(phoneme)
|
||||
|
||||
# allowed grammar are grouped by length
|
||||
gramdict = defaultdict(list)
|
||||
for gram in grammar.split():
|
||||
if re.search(r"\W|(!=[cv])", gram):
|
||||
raise LanguageError(
|
||||
"The grammar '%s' is invalid (only 'c' and 'v' are allowed)" % gram
|
||||
)
|
||||
gramdict[len(gram)].append(gram)
|
||||
grammar = dict(gramdict)
|
||||
|
||||
# create automatic translation
|
||||
translation = {}
|
||||
|
||||
if auto_translations:
|
||||
if isinstance(auto_translations, str):
|
||||
# path to a file rather than a list
|
||||
with open(auto_translations, "r") as f:
|
||||
auto_translations = f.readlines()
|
||||
for word in auto_translations:
|
||||
word = word.strip()
|
||||
lword = len(word)
|
||||
new_word = ""
|
||||
wlen = max(0, lword + sum(randint(-1, 1) for i in range(word_length_variance)))
|
||||
if wlen not in grammar:
|
||||
# always create a translation, use random length
|
||||
structure = choice(grammar[choice(list(grammar))])
|
||||
else:
|
||||
# use the corresponding length
|
||||
structure = choice(grammar[wlen])
|
||||
for match in _RE_GRAMMAR.finditer(structure):
|
||||
try:
|
||||
new_word += choice(grammar2phonemes[match.group()])
|
||||
except IndexError:
|
||||
raise IndexError(
|
||||
"Could not find a matching phoneme for the grammar "
|
||||
f"'{match.group()}'. Make there is at least one phoneme matching this "
|
||||
"combination of consonants and vowels."
|
||||
)
|
||||
translation[word.lower()] = new_word.lower()
|
||||
|
||||
if manual_translations:
|
||||
# update with manual translations
|
||||
translation.update(
|
||||
dict((key.lower(), value.lower()) for key, value in manual_translations.items())
|
||||
)
|
||||
|
||||
# store data
|
||||
storage = {
|
||||
"translation": translation,
|
||||
"grammar": grammar,
|
||||
"grammar2phonemes": dict(grammar2phonemes),
|
||||
"word_length_variance": word_length_variance,
|
||||
"noun_translate": noun_translate,
|
||||
"noun_prefix": noun_prefix,
|
||||
"noun_postfix": noun_postfix,
|
||||
}
|
||||
self.db.language_storage[key] = storage
|
||||
|
||||
def _translate_sub(self, match):
|
||||
"""
|
||||
Replacer method called by re.sub when
|
||||
traversing the language string.
|
||||
|
||||
Args:
|
||||
match (re.matchobj): Match object from regex.
|
||||
|
||||
Returns:
|
||||
converted word.
|
||||
Notes:
|
||||
Assumes self.lastword and self.level is available
|
||||
on the object.
|
||||
|
||||
"""
|
||||
word = match.group()
|
||||
lword = len(word)
|
||||
|
||||
# find out what preceeded this word
|
||||
wpos = match.start()
|
||||
preceeding = match.string[:wpos].strip()
|
||||
start_sentence = preceeding.endswith((".", "!", "?")) or not preceeding
|
||||
|
||||
if len(word) <= self.level:
|
||||
# below level. Don't translate
|
||||
new_word = word
|
||||
else:
|
||||
# try to translate the word from dictionary
|
||||
new_word = self.language["translation"].get(word.lower(), "")
|
||||
if not new_word:
|
||||
# no dictionary translation. Generate one
|
||||
|
||||
# make up translation on the fly. Length can
|
||||
# vary from un-translated word.
|
||||
wlen = max(
|
||||
0,
|
||||
lword
|
||||
+ sum(randint(-1, 1) for i in range(self.language["word_length_variance"])),
|
||||
)
|
||||
grammar = self.language["grammar"]
|
||||
if wlen not in grammar:
|
||||
if randint(0, 1) == 0:
|
||||
# this word has no direct translation!
|
||||
wlen = 0
|
||||
new_word = ""
|
||||
else:
|
||||
# use random word length
|
||||
wlen = choice(list(grammar.keys()))
|
||||
|
||||
if wlen:
|
||||
structure = choice(grammar[wlen])
|
||||
grammar2phonemes = self.language["grammar2phonemes"]
|
||||
for match in _RE_GRAMMAR.finditer(structure):
|
||||
# there are only four combinations: vv,cc,c,v
|
||||
try:
|
||||
new_word += choice(grammar2phonemes[match.group()])
|
||||
except KeyError:
|
||||
logger.log_trace(
|
||||
"You need to supply at least one example of each of "
|
||||
"the four base phonemes (c, v, cc, vv)"
|
||||
)
|
||||
# abort translation here
|
||||
new_word = ""
|
||||
break
|
||||
|
||||
if word.istitle():
|
||||
if not start_sentence:
|
||||
# this is a noun. We miss nouns at the start of
|
||||
# sentences this way, but it's as good as we can get
|
||||
# with this simple analysis. Maybe the fantasy language
|
||||
# just don't consider nouns at the beginning of
|
||||
# sentences, who knows?
|
||||
if not self.language.get("noun_translate", False):
|
||||
# don't translate what we identify as proper nouns (names)
|
||||
new_word = word
|
||||
|
||||
# add noun prefix and/or postfix
|
||||
new_word = "{prefix}{word}{postfix}".format(
|
||||
prefix=self.language["noun_prefix"],
|
||||
word=new_word.capitalize(),
|
||||
postfix=self.language["noun_postfix"],
|
||||
)
|
||||
|
||||
if len(word) > 1 and word.isupper():
|
||||
# keep LOUD words loud also when translated
|
||||
new_word = new_word.upper()
|
||||
|
||||
if start_sentence:
|
||||
new_word = new_word.capitalize()
|
||||
|
||||
return new_word
|
||||
|
||||
def translate(self, text, level=0.0, language="default"):
|
||||
"""
|
||||
Translate the text according to the given level.
|
||||
|
||||
Args:
|
||||
text (str): The text to translate
|
||||
level (real): Value between 0.0 and 1.0, where
|
||||
0.0 means no obfuscation (text returned unchanged) and
|
||||
1.0 means full conversion of every word. The closer to
|
||||
1, the shorter words will be translated.
|
||||
language (str): The language key identifier.
|
||||
|
||||
Returns:
|
||||
text (str): A translated string.
|
||||
|
||||
"""
|
||||
if level == 0.0:
|
||||
# no translation
|
||||
return text
|
||||
language = self.db.language_storage.get(language, None)
|
||||
if not language:
|
||||
return text
|
||||
self.language = language
|
||||
|
||||
# configuring the translation
|
||||
self.level = int(10 * (1.0 - max(0, min(level, 1.0))))
|
||||
translation = _RE_WORD.sub(self._translate_sub, text)
|
||||
# the substitution may create too long empty spaces, remove those
|
||||
return _RE_EXTRA_CHARS.sub("", translation)
|
||||
|
||||
|
||||
# Language access functions
|
||||
|
||||
_LANGUAGE_HANDLER = None
|
||||
|
||||
|
||||
def obfuscate_language(text, level=0.0, language="default"):
|
||||
"""
|
||||
Main access method for the language parser.
|
||||
|
||||
Args:
|
||||
text (str): Text to obfuscate.
|
||||
level (real, optional): A value from 0.0-1.0 determining
|
||||
the level of obfuscation where 0 means no obfuscation
|
||||
(string returned unchanged) and 1.0 means the entire
|
||||
string is obfuscated.
|
||||
language (str, optional): The identifier of a language
|
||||
the system understands.
|
||||
|
||||
Returns:
|
||||
translated (str): The translated text.
|
||||
|
||||
"""
|
||||
# initialize the language handler and cache it
|
||||
global _LANGUAGE_HANDLER
|
||||
if not _LANGUAGE_HANDLER:
|
||||
try:
|
||||
_LANGUAGE_HANDLER = LanguageHandler.objects.get(db_key="language_handler")
|
||||
except LanguageHandler.DoesNotExist:
|
||||
if not _LANGUAGE_HANDLER:
|
||||
from evennia import create_script
|
||||
|
||||
_LANGUAGE_HANDLER = create_script(LanguageHandler)
|
||||
return _LANGUAGE_HANDLER.translate(text, level=level, language=language)
|
||||
|
||||
|
||||
def add_language(**kwargs):
|
||||
"""
|
||||
Access function to creating a new language. See the docstring of
|
||||
`LanguageHandler.add` for list of keyword arguments.
|
||||
|
||||
"""
|
||||
global _LANGUAGE_HANDLER
|
||||
if not _LANGUAGE_HANDLER:
|
||||
try:
|
||||
_LANGUAGE_HANDLER = LanguageHandler.objects.get(db_key="language_handler")
|
||||
except LanguageHandler.DoesNotExist:
|
||||
if not _LANGUAGE_HANDLER:
|
||||
from evennia import create_script
|
||||
|
||||
_LANGUAGE_HANDLER = create_script(LanguageHandler)
|
||||
_LANGUAGE_HANDLER.add(**kwargs)
|
||||
|
||||
|
||||
def available_languages():
|
||||
"""
|
||||
Returns all available language keys.
|
||||
|
||||
Returns:
|
||||
languages (list): List of key strings of all available
|
||||
languages.
|
||||
"""
|
||||
global _LANGUAGE_HANDLER
|
||||
if not _LANGUAGE_HANDLER:
|
||||
try:
|
||||
_LANGUAGE_HANDLER = LanguageHandler.objects.get(db_key="language_handler")
|
||||
except LanguageHandler.DoesNotExist:
|
||||
if not _LANGUAGE_HANDLER:
|
||||
from evennia import create_script
|
||||
|
||||
_LANGUAGE_HANDLER = create_script(LanguageHandler)
|
||||
return list(_LANGUAGE_HANDLER.attributes.get("language_storage", {}))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
#
|
||||
# Whisper obscuration
|
||||
#
|
||||
# This obsucration table is designed by obscuring certain vowels first,
|
||||
# following by consonants that tend to be more audible over long distances,
|
||||
# like s. Finally it does non-auditory replacements, like exclamation marks and
|
||||
# capitalized letters (assumed to be spoken louder) that may still give a user
|
||||
# some idea of the sentence structure. Then the word lengths are also
|
||||
# obfuscated and finally the whisper length itself.
|
||||
#
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
|
||||
_RE_WHISPER_OBSCURE = [
|
||||
re.compile(r"^$", _RE_FLAGS), # This is a Test! #0 full whisper
|
||||
re.compile(r"[ae]", _RE_FLAGS), # This -s - Test! #1 add uy
|
||||
re.compile(r"[aeuy]", _RE_FLAGS), # This -s - Test! #2 add oue
|
||||
re.compile(r"[aeiouy]", _RE_FLAGS), # Th-s -s - T-st! #3 add all consonants
|
||||
re.compile(r"[aeiouybdhjlmnpqrv]", _RE_FLAGS), # T--s -s - T-st! #4 add hard consonants
|
||||
re.compile(r"[a-eg-rt-z]", _RE_FLAGS), # T--s -s - T-s-! #5 add all capitals
|
||||
re.compile(r"[A-EG-RT-Za-eg-rt-z]", _RE_FLAGS), # ---s -s - --s-! #6 add f
|
||||
re.compile(r"[A-EG-RT-Za-rt-z]", _RE_FLAGS), # ---s -s - --s-! #7 add s
|
||||
re.compile(r"[A-EG-RT-Za-z]", _RE_FLAGS), # ---- -- - ----! #8 add capital F
|
||||
re.compile(r"[A-RT-Za-z]", _RE_FLAGS), # ---- -- - ----! #9 add capital S
|
||||
re.compile(r"[\w]", _RE_FLAGS), # ---- -- - ----! #10 non-alphanumerals
|
||||
re.compile(r"[\S]", _RE_FLAGS), # ---- -- - ---- #11 words
|
||||
re.compile(r"[\w\W]", _RE_FLAGS), # -------------- #12 whisper length
|
||||
re.compile(r".*", _RE_FLAGS),
|
||||
] # ... #13 (always same length)
|
||||
|
||||
|
||||
def obfuscate_whisper(whisper, level=0.0):
|
||||
"""
|
||||
Obfuscate whisper depending on a pre-calculated level
|
||||
(that may depend on distance, listening skill etc)
|
||||
|
||||
Args:
|
||||
whisper (str): The whisper string to obscure. The
|
||||
entire string will be considered in the obscuration.
|
||||
level (real, optional): This is a value 0-1, where 0
|
||||
means not obscured (whisper returned unchanged) and 1
|
||||
means fully obscured.
|
||||
|
||||
"""
|
||||
level = min(max(0.0, level), 1.0)
|
||||
olevel = int(13.0 * level)
|
||||
if olevel == 13:
|
||||
return "..."
|
||||
else:
|
||||
return _RE_WHISPER_OBSCURE[olevel].sub("-", whisper)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,364 @@
|
|||
"""
|
||||
Tests for RP system
|
||||
|
||||
"""
|
||||
import time
|
||||
|
||||
from anything import Anything
|
||||
|
||||
from evennia import create_object
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from evennia.utils.test_resources import BaseEvenniaTest
|
||||
|
||||
from . import rplanguage, rpsystem
|
||||
|
||||
mtrans = {"testing": "1", "is": "2", "a": "3", "human": "4"}
|
||||
atrans = ["An", "automated", "advantageous", "repeatable", "faster"]
|
||||
|
||||
text = (
|
||||
"Automated testing is advantageous for a number of reasons: "
|
||||
"tests may be executed Continuously without the need for human "
|
||||
"intervention, They are easily repeatable, and often faster."
|
||||
)
|
||||
|
||||
|
||||
class TestLanguage(BaseEvenniaTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
rplanguage.add_language(
|
||||
key="testlang",
|
||||
word_length_variance=1,
|
||||
noun_prefix="bara",
|
||||
noun_postfix="'y",
|
||||
manual_translations=mtrans,
|
||||
auto_translations=atrans,
|
||||
force=True,
|
||||
)
|
||||
rplanguage.add_language(
|
||||
key="binary",
|
||||
phonemes="oo ii a ck w b d t",
|
||||
grammar="cvvv cvv cvvcv cvvcvv cvvvc cvvvcvv cvvc",
|
||||
noun_prefix="beep-",
|
||||
word_length_variance=4,
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
rplanguage._LANGUAGE_HANDLER.delete()
|
||||
rplanguage._LANGUAGE_HANDLER = None
|
||||
|
||||
def test_obfuscate_language(self):
|
||||
result0 = rplanguage.obfuscate_language(text, level=0.0, language="testlang")
|
||||
self.assertEqual(result0, text)
|
||||
result1 = rplanguage.obfuscate_language(text, level=1.0, language="testlang")
|
||||
result2 = rplanguage.obfuscate_language(text, level=1.0, language="testlang")
|
||||
result3 = rplanguage.obfuscate_language(text, level=1.0, language="binary")
|
||||
|
||||
self.assertNotEqual(result1, text)
|
||||
self.assertNotEqual(result3, text)
|
||||
result1, result2 = result1.split(), result2.split()
|
||||
self.assertEqual(result1[:4], result2[:4])
|
||||
self.assertEqual(result1[1], "1")
|
||||
self.assertEqual(result1[2], "2")
|
||||
self.assertEqual(result2[-1], result2[-1])
|
||||
|
||||
def test_faulty_language(self):
|
||||
self.assertRaises(
|
||||
rplanguage.LanguageError,
|
||||
rplanguage.add_language,
|
||||
key="binary2",
|
||||
phonemes="w b d t oe ee, oo e o a wh dw bw", # erroneous comma
|
||||
grammar="cvvv cvv cvvcv cvvcvvo cvvvc cvvvcvv cvvc c v cc vv ccvvc ccvvccvv ",
|
||||
vowels="oea",
|
||||
word_length_variance=4,
|
||||
)
|
||||
|
||||
def test_available_languages(self):
|
||||
self.assertEqual(list(sorted(rplanguage.available_languages())), ["binary", "testlang"])
|
||||
|
||||
def test_obfuscate_whisper(self):
|
||||
self.assertEqual(rplanguage.obfuscate_whisper(text, level=0.0), text)
|
||||
assert rplanguage.obfuscate_whisper(text, level=0.1).startswith(
|
||||
"-utom-t-d t-sting is -dv-nt-g-ous for - numb-r of r--sons: t-sts m-y b- -x-cut-d Continuously"
|
||||
)
|
||||
assert rplanguage.obfuscate_whisper(text, level=0.5).startswith(
|
||||
"--------- --s---- -s -----------s f-- - ------ -f ---s--s: --s-s "
|
||||
)
|
||||
self.assertEqual(rplanguage.obfuscate_whisper(text, level=1.0), "...")
|
||||
|
||||
|
||||
# Testing of emoting / sdesc / recog system
|
||||
|
||||
sdesc0 = "A nice sender of emotes"
|
||||
sdesc1 = "The first receiver of emotes."
|
||||
sdesc2 = "Another nice colliding sdesc-guy for tests"
|
||||
recog01 = "Mr Receiver"
|
||||
recog02 = "Mr Receiver2"
|
||||
recog10 = "Mr Sender"
|
||||
emote = 'With a flair, /me looks at /first and /colliding sdesc-guy. She says "This is a test."'
|
||||
fallback_emote = "/Me is distracted from /first by /nomatch."
|
||||
case_emote = "/Me looks at /first. Then, /me looks at /FIRST, /First and /Colliding twice."
|
||||
poss_emote = "/Me frowns at /first for trying to steal /me's test."
|
||||
|
||||
|
||||
class TestRPSystem(BaseEvenniaTest):
|
||||
maxDiff = None
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.room = create_object(rpsystem.ContribRPRoom, key="Location")
|
||||
self.speaker = create_object(rpsystem.ContribRPCharacter, key="Sender", location=self.room)
|
||||
self.receiver1 = create_object(
|
||||
rpsystem.ContribRPCharacter, key="Receiver1", location=self.room
|
||||
)
|
||||
self.receiver2 = create_object(
|
||||
rpsystem.ContribRPCharacter, key="Receiver2", location=self.room
|
||||
)
|
||||
|
||||
def test_posed_contents(self):
|
||||
self.obj1 = create_object(rpsystem.ContribRPObject, key="thing", location=self.room)
|
||||
self.obj2 = create_object(rpsystem.ContribRPObject, key="thing", location=self.room)
|
||||
self.obj3 = create_object(rpsystem.ContribRPObject, key="object", location=self.room)
|
||||
room_display = self.room.return_appearance(self.speaker)
|
||||
self.assertIn("An object and two things are here.", room_display)
|
||||
self.obj3.db.pose = "is on the ground."
|
||||
room_display = self.room.return_appearance(self.speaker)
|
||||
self.assertIn("Two things are here.", room_display)
|
||||
self.assertIn("An object is on the ground.", room_display)
|
||||
|
||||
def test_sdesc_handler(self):
|
||||
self.speaker.sdesc.add(sdesc0)
|
||||
self.assertEqual(self.speaker.sdesc.get(), sdesc0)
|
||||
self.speaker.sdesc.add("This is {#324} ignored")
|
||||
self.assertEqual(self.speaker.sdesc.get(), "This is 324 ignored")
|
||||
|
||||
def test_recog_handler(self):
|
||||
self.speaker.sdesc.add(sdesc0)
|
||||
self.receiver1.sdesc.add(sdesc1)
|
||||
self.speaker.recog.add(self.receiver1, recog01)
|
||||
self.speaker.recog.add(self.receiver2, recog02)
|
||||
self.assertEqual(self.speaker.recog.get(self.receiver1), recog01)
|
||||
self.assertEqual(self.speaker.recog.get(self.receiver2), recog02)
|
||||
self.speaker.recog.remove(self.receiver1)
|
||||
self.assertEqual(self.speaker.recog.get(self.receiver1), None)
|
||||
|
||||
self.assertEqual(self.speaker.recog.all(), {"Mr Receiver2": self.receiver2})
|
||||
|
||||
def test_parse_language(self):
|
||||
self.assertEqual(
|
||||
rpsystem.parse_language(self.speaker, emote),
|
||||
(
|
||||
"With a flair, /me looks at /first and /colliding sdesc-guy. She says {##0}",
|
||||
{"##0": (None, '"This is a test."')},
|
||||
),
|
||||
)
|
||||
|
||||
def test_parse_sdescs_and_recogs(self):
|
||||
speaker = self.speaker
|
||||
speaker.sdesc.add(sdesc0)
|
||||
self.receiver1.sdesc.add(sdesc1)
|
||||
self.receiver2.sdesc.add(sdesc2)
|
||||
id0 = f"#{speaker.id}"
|
||||
id1 = f"#{self.receiver1.id}"
|
||||
id2 = f"#{self.receiver2.id}"
|
||||
candidates = (self.receiver1, self.receiver2)
|
||||
result = (
|
||||
"With a flair, {"
|
||||
+ id0
|
||||
+ "} looks at {"
|
||||
+ id1
|
||||
+ "} and {"
|
||||
+ id2
|
||||
+ '}. She says "This is a test."',
|
||||
{
|
||||
id2: self.receiver2,
|
||||
id1: self.receiver1,
|
||||
id0: speaker,
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
rpsystem.parse_sdescs_and_recogs(speaker, candidates, emote, case_sensitive=False),
|
||||
result,
|
||||
)
|
||||
self.speaker.recog.add(self.receiver1, recog01)
|
||||
self.assertEqual(
|
||||
rpsystem.parse_sdescs_and_recogs(speaker, candidates, emote, case_sensitive=False),
|
||||
result,
|
||||
)
|
||||
|
||||
def test_possessive_selfref(self):
|
||||
speaker = self.speaker
|
||||
speaker.sdesc.add(sdesc0)
|
||||
self.receiver1.sdesc.add(sdesc1)
|
||||
self.receiver2.sdesc.add(sdesc2)
|
||||
id0 = f"#{speaker.id}"
|
||||
id1 = f"#{self.receiver1.id}"
|
||||
id2 = f"#{self.receiver2.id}"
|
||||
candidates = (self.receiver1, self.receiver2)
|
||||
result = (
|
||||
"{" + id0 + "} frowns at {" + id1 + "} for trying to steal {" + id0 + "}'s test.",
|
||||
{
|
||||
id1: self.receiver1,
|
||||
id0: speaker,
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
rpsystem.parse_sdescs_and_recogs(speaker, candidates, poss_emote, case_sensitive=False),
|
||||
result,
|
||||
)
|
||||
|
||||
def test_get_sdesc(self):
|
||||
looker = self.speaker # Sender
|
||||
target = self.receiver1 # Receiver1
|
||||
looker.sdesc.add(sdesc0) # A nice sender of emotes
|
||||
target.sdesc.add(sdesc1) # The first receiver of emotes.
|
||||
|
||||
# sdesc with no processing
|
||||
self.assertEqual(looker.get_sdesc(target), "The first receiver of emotes.")
|
||||
# sdesc with processing
|
||||
self.assertEqual(
|
||||
looker.get_sdesc(target, process=True), "|bThe first receiver of emotes.|n"
|
||||
)
|
||||
|
||||
looker.recog.add(target, recog01) # Mr Receiver
|
||||
|
||||
# recog with no processing
|
||||
self.assertEqual(looker.get_sdesc(target), "Mr Receiver")
|
||||
# recog with processing
|
||||
self.assertEqual(looker.get_sdesc(target, process=True), "|mMr Receiver|n")
|
||||
|
||||
def test_send_emote(self):
|
||||
speaker = self.speaker
|
||||
receiver1 = self.receiver1
|
||||
receiver2 = self.receiver2
|
||||
receivers = [speaker, receiver1, receiver2]
|
||||
speaker.sdesc.add(sdesc0)
|
||||
receiver1.sdesc.add(sdesc1)
|
||||
receiver2.sdesc.add(sdesc2)
|
||||
speaker.msg = lambda text, **kwargs: setattr(self, "out0", text)
|
||||
receiver1.msg = lambda text, **kwargs: setattr(self, "out1", text)
|
||||
receiver2.msg = lambda text, **kwargs: setattr(self, "out2", text)
|
||||
rpsystem.send_emote(speaker, receivers, emote, case_sensitive=False)
|
||||
self.assertEqual(
|
||||
self.out0[0],
|
||||
"With a flair, |mSender|n looks at |bThe first receiver of emotes.|n "
|
||||
'and |bAnother nice colliding sdesc-guy for tests|n. She says |w"This is a test."|n',
|
||||
)
|
||||
self.assertEqual(
|
||||
self.out1[0],
|
||||
"With a flair, |bA nice sender of emotes|n looks at |mReceiver1|n and "
|
||||
'|bAnother nice colliding sdesc-guy for tests|n. She says |w"This is a test."|n',
|
||||
)
|
||||
self.assertEqual(
|
||||
self.out2[0],
|
||||
"With a flair, |bA nice sender of emotes|n looks at |bThe first "
|
||||
'receiver of emotes.|n and |mReceiver2|n. She says |w"This is a test."|n',
|
||||
)
|
||||
|
||||
def test_send_emote_fallback(self):
|
||||
speaker = self.speaker
|
||||
receiver1 = self.receiver1
|
||||
receiver2 = self.receiver2
|
||||
receivers = [speaker, receiver1, receiver2]
|
||||
speaker.sdesc.add(sdesc0)
|
||||
receiver1.sdesc.add(sdesc1)
|
||||
receiver2.sdesc.add(sdesc2)
|
||||
speaker.msg = lambda text, **kwargs: setattr(self, "out0", text)
|
||||
receiver1.msg = lambda text, **kwargs: setattr(self, "out1", text)
|
||||
receiver2.msg = lambda text, **kwargs: setattr(self, "out2", text)
|
||||
rpsystem.send_emote(speaker, receivers, fallback_emote, fallback="something")
|
||||
self.assertEqual(
|
||||
self.out0[0],
|
||||
"|mSender|n is distracted from |bthe first receiver of emotes.|n by something.",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.out1[0],
|
||||
"|bA nice sender of emotes|n is distracted from |mReceiver1|n by something.",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.out2[0],
|
||||
"|bA nice sender of emotes|n is distracted from |bthe first receiver of emotes.|n by something.",
|
||||
)
|
||||
|
||||
def test_send_case_sensitive_emote(self):
|
||||
"""Test new case-sensitive rp-parsing"""
|
||||
speaker = self.speaker
|
||||
receiver1 = self.receiver1
|
||||
receiver2 = self.receiver2
|
||||
receivers = [speaker, receiver1, receiver2]
|
||||
speaker.sdesc.add(sdesc0)
|
||||
receiver1.sdesc.add(sdesc1)
|
||||
receiver2.sdesc.add(sdesc2)
|
||||
speaker.msg = lambda text, **kwargs: setattr(self, "out0", text)
|
||||
receiver1.msg = lambda text, **kwargs: setattr(self, "out1", text)
|
||||
receiver2.msg = lambda text, **kwargs: setattr(self, "out2", text)
|
||||
rpsystem.send_emote(speaker, receivers, case_emote)
|
||||
self.assertEqual(
|
||||
self.out0[0],
|
||||
"|mSender|n looks at |bthe first receiver of emotes.|n. Then, |mSender|n "
|
||||
"looks at |bTHE FIRST RECEIVER OF EMOTES.|n, |bThe first receiver of emotes.|n "
|
||||
"and |bAnother nice colliding sdesc-guy for tests|n twice.",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.out1[0],
|
||||
"|bA nice sender of emotes|n looks at |mReceiver1|n. Then, "
|
||||
"|ba nice sender of emotes|n looks at |mReceiver1|n, |mReceiver1|n "
|
||||
"and |bAnother nice colliding sdesc-guy for tests|n twice.",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.out2[0],
|
||||
"|bA nice sender of emotes|n looks at |bthe first receiver of emotes.|n. "
|
||||
"Then, |ba nice sender of emotes|n looks at |bTHE FIRST RECEIVER OF EMOTES.|n, "
|
||||
"|bThe first receiver of emotes.|n and |mReceiver2|n twice.",
|
||||
)
|
||||
|
||||
def test_rpsearch(self):
|
||||
self.speaker.sdesc.add(sdesc0)
|
||||
self.receiver1.sdesc.add(sdesc1)
|
||||
self.receiver2.sdesc.add(sdesc2)
|
||||
self.speaker.msg = lambda text, **kwargs: setattr(self, "out0", text)
|
||||
self.assertEqual(self.speaker.search("receiver of emotes"), self.receiver1)
|
||||
self.assertEqual(self.speaker.search("colliding"), self.receiver2)
|
||||
|
||||
|
||||
class TestRPSystemCommands(BaseEvenniaCommandTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.char1.swap_typeclass(rpsystem.ContribRPCharacter)
|
||||
self.char2.swap_typeclass(rpsystem.ContribRPCharacter)
|
||||
|
||||
def test_commands(self):
|
||||
|
||||
self.call(
|
||||
rpsystem.CmdSdesc(), "Foobar Character", "Char's sdesc was set to 'Foobar Character'."
|
||||
)
|
||||
self.call(
|
||||
rpsystem.CmdSdesc(),
|
||||
"BarFoo Character",
|
||||
"Char2's sdesc was set to 'BarFoo Character'.",
|
||||
caller=self.char2,
|
||||
)
|
||||
self.call(rpsystem.CmdSay(), "Hello!", 'Char says, "Hello!"')
|
||||
self.call(rpsystem.CmdEmote(), "/me smiles to /BarFoo.", "Char smiles to BarFoo Character")
|
||||
self.call(
|
||||
rpsystem.CmdPose(),
|
||||
"stands by the bar",
|
||||
"Pose will read 'Foobar Character stands by the bar.'.",
|
||||
)
|
||||
self.call(
|
||||
rpsystem.CmdRecog(),
|
||||
"barfoo as friend",
|
||||
"You will now remember BarFoo Character as friend.",
|
||||
)
|
||||
self.call(
|
||||
rpsystem.CmdRecog(),
|
||||
"",
|
||||
"Currently recognized (use 'recog <sdesc> as <alias>' to add new "
|
||||
"and 'forget <alias>' to remove):\n friend (BarFoo Character)",
|
||||
)
|
||||
self.call(
|
||||
rpsystem.CmdRecog(),
|
||||
"friend",
|
||||
"You will now know them only as 'BarFoo Character'",
|
||||
cmdstring="forget",
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"scripts": {
|
||||
"build": "./node_modules/sass/sass.js scss/main.scss web/static/website/css/main.css"
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.2.3",
|
||||
"sass": "^1.58.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,330 @@
|
|||
// Vapor 5.2.2
|
||||
// Bootswatch
|
||||
|
||||
|
||||
// Variables
|
||||
|
||||
$web-font-path: "https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap" !default;
|
||||
@if $web-font-path {
|
||||
@import url($web-font-path);
|
||||
}
|
||||
|
||||
$outrun: false !default;
|
||||
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
// Mixins
|
||||
|
||||
@mixin text-shadow($color) {
|
||||
text-shadow: 0 0 1px rgba($color, .6), 0 0 3px rgba($color, .5), 0 0 .5rem rgba($color, .3), 0 0 2rem rgba($color, .2);
|
||||
}
|
||||
|
||||
@mixin text-shadow-sm($color) {
|
||||
text-shadow: 0 0 1px rgba($color, .3), 0 0 2px rgba($color, .3), 0 0 5px rgba($color, .2);
|
||||
}
|
||||
|
||||
@mixin box-shadow($color) {
|
||||
box-shadow: 0 0 2px rgba($color, .9), 0 0 4px rgba($color, .4), 0 0 1rem rgba($color, .3), 0 0 4rem rgba($color, .1);
|
||||
}
|
||||
|
||||
@mixin box-shadow-lg($color) {
|
||||
box-shadow: 0 0 2rem rgba(tint-color($color, 10%), .4), 0 0 8rem rgba(tint-color($color, 10%), .3);
|
||||
}
|
||||
|
||||
@mixin header-shadow($color) {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
.h1,
|
||||
.h2,
|
||||
.h3,
|
||||
.h4,
|
||||
.h5,
|
||||
.h6 {
|
||||
@include text-shadow($color);
|
||||
}
|
||||
}
|
||||
|
||||
// Body
|
||||
|
||||
body {
|
||||
background-image: linear-gradient(shade-color($body-bg, 10%) 0%, $body-bg 7%, $body-bg 80%, shade-color(#173e98, 50%) 100%);
|
||||
@include text-shadow-sm($body-color);
|
||||
|
||||
@if ($outrun) {
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: -50vw;
|
||||
z-index: -1;
|
||||
display: block;
|
||||
width: 200vw;
|
||||
height: 100vh;
|
||||
content: "";
|
||||
background-image: repeating-linear-gradient(rgba($blue, .6) 0 1px, transparent 1px 100%), repeating-linear-gradient(90deg, rgba($blue, .6) 0 1px, transparent 1px 100%);
|
||||
background-size: 200px 200px;
|
||||
transform: perspective(50vh) rotateX(50deg) translateY(-11.3vh) translateZ(10px);
|
||||
transform-origin: center center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Typography
|
||||
|
||||
@include header-shadow($cyan);
|
||||
|
||||
.text {
|
||||
@each $color, $value in $theme-colors {
|
||||
&-#{$color} {
|
||||
@include text-shadow-sm($value);
|
||||
}
|
||||
}
|
||||
|
||||
&-white {
|
||||
@include text-shadow-sm($white);
|
||||
@include header-shadow($white);
|
||||
}
|
||||
|
||||
&-muted {
|
||||
@include text-shadow-sm(shade-color($text-muted, 20%));
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
@include text-shadow-sm($link-color);
|
||||
}
|
||||
|
||||
.blockquote {
|
||||
|
||||
&-footer {
|
||||
@include text-shadow-sm(shade-color($blockquote-footer-color, 20%));
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: 1px solid $primary;
|
||||
@include box-shadow($primary);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
// Tables
|
||||
|
||||
table,
|
||||
.table {
|
||||
@include text-shadow-sm($white);
|
||||
}
|
||||
|
||||
// Buttons
|
||||
|
||||
.btn {
|
||||
@each $color, $value in $theme-colors {
|
||||
@if (($color != dark) or ($color != link)) {
|
||||
&-#{$color},
|
||||
&-outline-#{$color} {
|
||||
@include box-shadow($value);
|
||||
}
|
||||
}
|
||||
|
||||
&-#{$color} {
|
||||
@include text-shadow-sm($white);
|
||||
}
|
||||
|
||||
&-outline-#{$color} {
|
||||
color: $white;
|
||||
border-width: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
&-dark {
|
||||
@include box-shadow($primary);
|
||||
}
|
||||
|
||||
&-link {
|
||||
box-shadow: none;
|
||||
@include text-shadow($body-color);
|
||||
}
|
||||
|
||||
&-outline-dark {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
// Navbars
|
||||
|
||||
.navbar {
|
||||
@each $color, $value in $theme-colors {
|
||||
&.bg-#{$color} {
|
||||
@include box-shadow($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-dark {
|
||||
&,
|
||||
a {
|
||||
@include text-shadow-sm($navbar-dark-color);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
@include text-shadow($navbar-dark-color);
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-light {
|
||||
&,
|
||||
a {
|
||||
@include text-shadow-sm($navbar-light-color);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
@include text-shadow($navbar-light-color);
|
||||
}
|
||||
}
|
||||
|
||||
// Navs
|
||||
|
||||
.nav-link {
|
||||
&.disabled {
|
||||
@include text-shadow-sm($nav-link-disabled-color);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link.active,
|
||||
.nav-tabs .nav-item.show .nav-link {
|
||||
@include text-shadow-sm($component-active-bg);
|
||||
}
|
||||
|
||||
.nav-pills .nav-link.active,
|
||||
.nav-pills .show > .nav-link {
|
||||
@include box-shadow($component-active-bg);
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
&-item.active {
|
||||
@include text-shadow-sm($breadcrumb-active-color);
|
||||
}
|
||||
|
||||
&-item + .breadcrumb-item::before {
|
||||
@include text-shadow-sm($breadcrumb-divider-color);
|
||||
}
|
||||
}
|
||||
|
||||
.page-link {
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
.page-item.active {
|
||||
.page-link {
|
||||
@include box-shadow($component-active-bg);
|
||||
}
|
||||
}
|
||||
|
||||
// Forms
|
||||
|
||||
legend {
|
||||
@include text-shadow($body-color);
|
||||
}
|
||||
|
||||
.valid-feedback {
|
||||
@include text-shadow-sm($success);
|
||||
}
|
||||
|
||||
.invalid-feedback {
|
||||
@include text-shadow-sm($danger);
|
||||
}
|
||||
|
||||
// Indicators
|
||||
|
||||
.alert {
|
||||
@each $color, $value in $theme-colors {
|
||||
&-#{$color} {
|
||||
color: $white;
|
||||
background-color: $value;
|
||||
@include text-shadow-sm($white);
|
||||
@include box-shadow-lg($value);
|
||||
}
|
||||
}
|
||||
|
||||
.alert-link,
|
||||
a {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
@include header-shadow($white);
|
||||
}
|
||||
|
||||
.progress {
|
||||
overflow: visible;
|
||||
|
||||
&-bar {
|
||||
@include box-shadow($primary);
|
||||
|
||||
@each $color, $value in $theme-colors {
|
||||
&.bg-#{$color} {
|
||||
@include box-shadow($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
|
||||
&-inner,
|
||||
.arrow {
|
||||
@include box-shadow-lg($primary);
|
||||
}
|
||||
}
|
||||
|
||||
.modal,
|
||||
.popover,
|
||||
.toast {
|
||||
@include text-shadow-sm($white);
|
||||
@include header-shadow($white);
|
||||
}
|
||||
|
||||
.popover,
|
||||
.toast {
|
||||
@include box-shadow-lg($primary);
|
||||
}
|
||||
|
||||
.modal {
|
||||
&-content {
|
||||
@include box-shadow-lg($primary);
|
||||
}
|
||||
}
|
||||
|
||||
// Containers
|
||||
|
||||
.list-group {
|
||||
|
||||
&-item.active {
|
||||
@include header-shadow($white);
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: transparent;
|
||||
@include text-shadow-sm($white);
|
||||
|
||||
@each $color, $value in $theme-colors {
|
||||
@if ($color != dark) {
|
||||
&.border-#{$color} {
|
||||
@include box-shadow($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.border-dark {
|
||||
@include box-shadow($primary);
|
||||
}
|
||||
|
||||
@include header-shadow($white);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
.navbar {
|
||||
a.nav-link,
|
||||
.navbar-brand {
|
||||
color: yellow;
|
||||
text-shadow: #FC0 1px 0 10px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
// Vapor 5.2.2
|
||||
// Bootswatch
|
||||
|
||||
$theme: "vapor" !default;
|
||||
|
||||
//
|
||||
// Color system
|
||||
//
|
||||
|
||||
$white: #fff !default;
|
||||
$gray-100: #f8f9fa !default;
|
||||
$gray-200: #e9ecef !default;
|
||||
$gray-300: #dee2e6 !default;
|
||||
$gray-400: #ced4da !default;
|
||||
$gray-500: #adb5bd !default;
|
||||
$gray-600: #6c757d !default;
|
||||
$gray-700: #495057 !default;
|
||||
$gray-800: #343a40 !default;
|
||||
$gray-900: #170229 !default;
|
||||
$black: #000 !default;
|
||||
|
||||
$blue: #1ba2f6 !default;
|
||||
$indigo: #6610f2 !default;
|
||||
$purple: #6f42c1 !default;
|
||||
$pink: #ea39b8 !default;
|
||||
$red: #e44c55 !default;
|
||||
$orange: #f1b633 !default;
|
||||
$yellow: #ffc107 !default;
|
||||
$green: #3cf281 !default;
|
||||
$teal: #3f81a2 !default;
|
||||
$cyan: #32fbe2 !default;
|
||||
|
||||
$primary: $purple !default;
|
||||
$secondary: $pink !default;
|
||||
$success: $green !default;
|
||||
$info: $blue !default;
|
||||
$warning: $yellow !default;
|
||||
$danger: $red !default;
|
||||
$light: #44d9e8 !default;
|
||||
$dark: $gray-900 !default;
|
||||
|
||||
$min-contrast-ratio: 1.2 !default;
|
||||
|
||||
// Options
|
||||
|
||||
// $enable-rounded: false !default;
|
||||
|
||||
// Body
|
||||
|
||||
$body-bg: #1a0933 !default;
|
||||
$body-color: $cyan !default;
|
||||
|
||||
// Links
|
||||
|
||||
$link-color: $body-color !default;
|
||||
|
||||
// Components
|
||||
|
||||
$border-width: 0 !default;
|
||||
|
||||
$border-radius: .15rem !default;
|
||||
$border-radius-sm: .05rem !default;
|
||||
|
||||
$component-active-bg: $pink !default;
|
||||
|
||||
// Fonts
|
||||
|
||||
// stylelint-disable-next-line value-keyword-case
|
||||
$font-family-sans-serif: Lato, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !default;
|
||||
|
||||
$text-muted: rgba($body-color, .3) !default;
|
||||
|
||||
$blockquote-footer-color: $text-muted !default;
|
||||
|
||||
// Tables
|
||||
|
||||
$table-color: $white !default;
|
||||
|
||||
$table-bg-scale: 0% !default;
|
||||
|
||||
// Forms
|
||||
|
||||
$input-bg: lighten($body-bg, 10%) !default;
|
||||
$input-disabled-bg: lighten($body-bg, 5%) !default;
|
||||
|
||||
$input-color: $white !default;
|
||||
|
||||
$input-placeholder-color: rgba($white, .4) !default;
|
||||
|
||||
$input-group-addon-bg: $input-disabled-bg !default;
|
||||
|
||||
$form-range-track-bg: $input-bg !default;
|
||||
|
||||
$form-range-thumb-disabled-bg: $purple !default;
|
||||
|
||||
// Navs
|
||||
|
||||
$nav-link-disabled-color: $text-muted !default;
|
||||
|
||||
$nav-tabs-link-active-color: $component-active-bg !default;
|
||||
|
||||
// Pagination
|
||||
|
||||
$pagination-bg: transparent !default;
|
||||
|
||||
$pagination-focus-bg: transparent !default;
|
||||
|
||||
$pagination-hover-bg: transparent !default;
|
||||
|
||||
$pagination-disabled-color: $text-muted !default;
|
||||
$pagination-disabled-bg: transparent !default;
|
||||
|
||||
// Cards
|
||||
|
||||
$card-border-width: 2px !default;
|
||||
$card-cap-color: $white !default;
|
||||
$card-color: $white !default;
|
||||
|
||||
// Tooltips
|
||||
|
||||
$tooltip-bg: $dark !default;
|
||||
$tooltip-opacity: 1 !default;
|
||||
|
||||
// Popovers
|
||||
|
||||
$popover-bg: $primary !default;
|
||||
|
||||
$popover-header-color: $white !default;
|
||||
|
||||
$popover-body-color: $white !default;
|
||||
|
||||
// Toasts
|
||||
|
||||
$toast-color: $white !default;
|
||||
$toast-background-color: $primary !default;
|
||||
|
||||
$toast-header-color: $toast-color !default;
|
||||
$toast-header-background-color: $toast-background-color !default;
|
||||
|
||||
// Modals
|
||||
|
||||
$modal-content-color: $white !default;
|
||||
$modal-content-bg: $primary !default;
|
||||
|
||||
// Progress bars
|
||||
|
||||
$progress-bg: $input-disabled-bg !default;
|
||||
|
||||
// List group
|
||||
|
||||
$list-group-color: $white !default;
|
||||
$list-group-bg: $input-disabled-bg !default;
|
||||
$list-group-hover-bg: $pink !default;
|
||||
$list-group-disabled-color: $text-muted !default;
|
||||
$list-group-action-color: $body-color !default;
|
||||
$list-group-action-hover-color: $white !default;
|
||||
$list-group-action-active-bg: $list-group-hover-bg !default;
|
||||
|
||||
// Breadcrumbs
|
||||
|
||||
$breadcrumb-divider-color: $text-muted !default;
|
||||
$breadcrumb-active-color: $component-active-bg !default;
|
|
@ -0,0 +1,28 @@
|
|||
// Bootstrap functions and mixins
|
||||
@import '../node_modules/bootstrap/scss/_mixins.scss';
|
||||
@import '../node_modules/bootstrap/scss/_functions.scss';
|
||||
// Theme variables to redefine Bootstrap vars
|
||||
@import '_variables';
|
||||
@import '_customvars';
|
||||
// Bootstrap base variables
|
||||
@import '../node_modules/bootstrap/scss/_variables.scss';
|
||||
// Base Bootstrap styles
|
||||
@import '../node_modules/bootstrap/scss/bootstrap.scss';
|
||||
// Bootswatch rules to override defaults
|
||||
@import '_bootswatch';
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
clip: rect(0,0,0,0);
|
||||
}
|
||||
.navbar {
|
||||
background-image: url('../images/stars.gif');
|
||||
background-color: transparent;
|
||||
}
|
|
@ -25,16 +25,32 @@ from django.conf import settings
|
|||
from evennia import utils
|
||||
|
||||
CONNECTION_SCREEN = """
|
||||
|b==============================================================|n
|
||||
Welcome to |g{}|n, version {}!
|
||||
|
||||
If you have an existing account, connect to it by typing:
|
||||
|wconnect <username> <password>|n
|
||||
If you need to create an account, type (without the <>'s):
|
||||
|wcreate <username> <password>|n
|
||||
|n |wansi (c) garu|n |n
|
||||
|n |wWelcome to..|n |n
|
||||
|n |n
|
||||
|n |c████ ███|n |c█|n |n
|
||||
|n |m▀█▀ ▀█▀ ▄█████▄ ▀██▀ █ ▀█████████▀ █████ |C▓▓▓|m████|C▓▓▓|m███|C▓▓▓▓▓|m█|n |n
|
||||
|n |m█ █ █░░░░░█ █░█ █ ░░░░█░░░░ █░░░░░█ ▓▓▓|n|w████|M▓▓▓|n|w███|M▓▓▓▓▓|n|w█|n |n
|
||||
|n |w|[m░|n |w|[m░|n |w|[m░|n |w|[m░|n |w|[m░|n |w|[m░|n |w|[m░|n |w|[m░|n |w|[m░|n |w|[m░|n |w▓▓▓|m████|n|w▓▓▓|m███|n|w▓▓▓▓▓|M█|n |n
|
||||
|n |M░|m|[w▒|n |m|[w▒|n░ |M|[w░░░░░░░|n |m|[w▒|n |M░|[w▒|n |m|[w▒|n |m|[w▒|n |m|[w▒▒▒▒▒▒▒|n |m▓▓▓|C████|M▓▓▓|C███|M▓▓▓▓▓|C█|n |n
|
||||
|n |M░|w█ █|M░ |w█|M░░░░░|w█ █ |M░|w█ █|n |w█|n |w█|M░░░░░|w█ |C▓▓▓ ▓▓▓ ▓▓▓▓▓|n |n
|
||||
|n |x|[W░|n|w█|x|[W░|n |w█|n |w█ █ ██|n |w█|n |w█|n |w█|n |n
|
||||
|n |x|[W░|n |x|[W░|n |x|[W░|n |x|[W░|n |x|[W░░|n |x|[W░|n |x|[W░|n |x|[W░|n |n
|
||||
|n |m▀█▀ ▀█▀ █████ █████▓▓▒▒▒▒░░░░░|n |n
|
||||
|n |wBROUGHT TO YOU BY|n |m██ ██ █|n|m░░░░░█ █░░░░░█▓▒▒▒░░░░|n |n
|
||||
|n |mVANTABLACK|n |m█░█ █░█ █|n |m█ █|n |m█▓▒▒░░░|n |n
|
||||
|n |m|[w▒|n |M░|w|[m▒▒|n|M░|n |m|[w▒|n |m|[w▒|n |m|[w▒|n |m|[w▒|n |m|[w▒|n|m▒▒░░░|n |n
|
||||
|n |w█ |M░░ |w█ █|n |w█ █|n |w█|n |n
|
||||
|n |w█|n |w█ |x|[W░|w█████|x|[W░|n |x|[W░|w█████|x|[W░|n |n
|
||||
|n |x|[W░|n |x|[W░|n |x|[W░░░░░█|n |x|[W░░░░░█|n |n
|
||||
|n|x |rNew users: |ncreate <user> <password>|n |n
|
||||
|n |n
|
||||
|n|w |GExisting users: |nconnect <user> <password>|n |n
|
||||
|n |n
|
||||
|n |n
|
||||
|
||||
If you have spaces in your username, enclose it in quotes.
|
||||
Enter |whelp|n for more info. |wlook|n will re-show this screen.
|
||||
|b==============================================================|n""".format(
|
||||
""".format(
|
||||
settings.SERVERNAME, utils.get_evennia_version("short")
|
||||
)
|
||||
|
|
|
@ -7,8 +7,18 @@ is setup to be the "default" character type created by the default
|
|||
creation commands.
|
||||
|
||||
"""
|
||||
from evennia.contrib.rpg.rpsystem import ContribRPCharacter
|
||||
from lib.rpsystem import ContribRPCharacter
|
||||
|
||||
from lib.pronounsub import character as pronounsub
|
||||
|
||||
# rpsystem
|
||||
class Character(ContribRPCharacter):
|
||||
pass
|
||||
def at_object_creation(self):
|
||||
pronounsub.object_init(self)
|
||||
|
||||
super().at_object_creation()
|
||||
|
||||
def msg(self, text=None, from_obj=None, session=None, **kwargs):
|
||||
text, from_obj, session, kwargs = pronounsub.msg(self, text, from_obj, session, **kwargs)
|
||||
|
||||
super().msg(text, from_obj, session, **kwargs)
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
"""
|
||||
Tracery Object
|
||||
|
||||
This is a joke object that changes its description every time you look at it.
|
||||
Requires installing pytracery as a pip dependency.
|
||||
|
||||
As Kate Compton puts it: "I can easily generate 10,000 bowls of plain oatmeal, with each oat being in a different position and different orientation, and mathematically speaking they will all be completely unique. But the user will likely just see a lot of oatmeal."
|
||||
|
||||
This relies on default Tracery and does not have a model memory (i.e. you cannot procgen object attributs based on its description and vice versa).
|
||||
"""
|
||||
from typeclasses.objects import Object
|
||||
import tracery
|
||||
from tracery.modifiers import base_english
|
||||
|
||||
"""
|
||||
An oatmeal object has an "origin" attribute.
|
||||
You make a table of rules with attributes, like:
|
||||
|
||||
set movie/origin:rules = ['This is #genre.a# #movie# about #subject.s#.', '#subject.s.capitalize# #sequel#: The #family.capitalize# Of All #movie.s.capitalize#!']
|
||||
|
||||
The table has to have category "rules" and the main entry point is called "origin". The rest is up to you:
|
||||
|
||||
set movie/sequel:rules = ['from hell #number#','#number#','the Resurrection','Origins','Rebooted','Reimagined','Again']
|
||||
set movie/family:rules = ['mother','father','aunt','xyther','grunkle','grandpa','grandma']
|
||||
set movie/number:rules = ['2','3','4','5','6','II','III','IV','V','VI','XXIII','MCMXCIII']
|
||||
set movie/movie:rules = ['movie', 'flick', 'animation', 'anime', 'moving picture', 'picture']
|
||||
set movie/genre:rules = ['action', 'romance', 'thriller', 'horror', 'abstract', 'slice-of-life', 'comedy', 'documentary']
|
||||
set movie/subject:rules = ['wombat', '#pair# love #polycule#', 'smartphone', 'astronaut', 'explosion']
|
||||
set movie/pair:rules = ['gay','aromantic','nonbinary','clueless','crazy']
|
||||
set movie/polycule:rules = ['triange', 'pair', '3D icosahedron', 'polycule']
|
||||
|
||||
And you'll get stuff like "Crazy love polycules Reimagined: The Father Of All Animations!"
|
||||
"""
|
||||
class OatmealObject(Object):
|
||||
"""
|
||||
Can't be a self.db.desc because we *need* a function here.
|
||||
"""
|
||||
def get_display_desc(self, looker, **kwargs):
|
||||
rules = {}
|
||||
attrs = self.attributes.get(category='rules', return_obj=True)
|
||||
for attr in attrs:
|
||||
v = attr.value.deserialize()
|
||||
if isinstance(v, list) or isinstance(v, str):
|
||||
rules[attr.key] = v
|
||||
if rules == {}:
|
||||
return "No set of rules detected. Set the intro point with: |wset object/origin:rules = 'text'|n"
|
||||
grammar = tracery.Grammar(rules)
|
||||
grammar.add_modifiers(base_english)
|
||||
return grammar.flatten("#origin#")
|
||||
pass
|
|
@ -10,8 +10,8 @@ the other types, you can do so by adding this as a multiple
|
|||
inheritance.
|
||||
|
||||
"""
|
||||
from evennia.contrib.rpg.rpsystem import ContribRPObject
|
||||
|
||||
from lib.rpsystem import ContribRPObject
|
||||
from evennia.contrib.game_systems.clothing import ContribClothing as Clothes
|
||||
|
||||
class ObjectParent:
|
||||
"""
|
||||
|
@ -27,3 +27,6 @@ class ObjectParent:
|
|||
# rpsystem
|
||||
class Object(ContribRPObject):
|
||||
pass
|
||||
|
||||
class Clothing(Clothes):
|
||||
pass
|
||||
|
|
|
@ -4,7 +4,7 @@ Room
|
|||
Rooms are simple containers that has no location of their own.
|
||||
|
||||
"""
|
||||
from evennia.contrib.rpg.rpsystem import ContribRPRoom
|
||||
from lib.rpsystem import ContribRPRoom
|
||||
|
||||
# rpsystem
|
||||
class Room(ContribRPRoom):
|
||||
|
|
|
@ -14,6 +14,13 @@ just overloads its hooks to have it perform its function.
|
|||
|
||||
from evennia.scripts.scripts import DefaultScript
|
||||
|
||||
class TestScript(DefaultScript):
|
||||
def at_script_creation(self):
|
||||
self.key = "testscript"
|
||||
self.interval = 60
|
||||
|
||||
def at_repeat(self):
|
||||
self.obj.msg_contents("hewwo")
|
||||
|
||||
class Script(DefaultScript):
|
||||
"""
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
@ -0,0 +1,90 @@
|
|||
{% comment %}
|
||||
Allow to customize the menu that appears at the top of every Evennia
|
||||
webpage. Copy this file to your game dir's web/template_overrides/website
|
||||
folder and edit it to add/remove links to the menu.
|
||||
{% endcomment %}
|
||||
{% load static %}
|
||||
<nav class="navbar navbar-expand-md bg-body-tertiary">
|
||||
<div class="container-fluid">
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<a class="navbar-brand" href="/">
|
||||
<div class="media">
|
||||
<img class="d-flex navbar-brand-logo mx-3" src="{% static "website/images/evennia_logo.png" %}" alt="{{game_name}} logo" />
|
||||
<div class="media-body">
|
||||
{{ game_name }}<br />
|
||||
<small>{{game_slogan}}</small>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<li class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mg-lg-0">
|
||||
{% block navbar_left %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'index' %}">Home</a>
|
||||
</li>
|
||||
<!-- game views -->
|
||||
<li class="nav-item"><a class="nav-link" href="{% url 'characters' %}">Characters</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{% url 'channels' %}">Channels</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{% url 'help' %}">Help</a></li>
|
||||
<!-- end game views -->
|
||||
|
||||
{% if webclient_enabled %}
|
||||
<li class="nav-item"><a class="nav-link" href="{% url 'webclient:index' %}">Play Online</a></li>
|
||||
{% endif %}
|
||||
|
||||
{% if user.is_staff %}
|
||||
<li class="nav-item"><a class="nav-link" href="{% url 'admin:index' %}">Admin</a></li>
|
||||
{% if rest_api_enabled %}
|
||||
<li class="nav-item"><a class="nav-link" href="/api">API</a></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0 justify-content-end">
|
||||
{% block navbar_right %}
|
||||
{% endblock %}
|
||||
|
||||
{% block navbar_user %}
|
||||
{% if account %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" id="user_options" aria-expanded="false">
|
||||
{% if puppet %}
|
||||
Welcome, {{ puppet }}! <span class="text-muted">({{ account.username }})</span> <span class="caret"></span>
|
||||
{% else %}
|
||||
Logged in as {{ account.username }} <span class="caret"></span>
|
||||
{% endif %}
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="user_options">
|
||||
<li><a class="dropdown-item" href="{% url 'character-create' %}">Create Character</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'character-manage' %}">Manage Characters</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
{% for character in account.characters|slice:"10" %}
|
||||
<li><a class="dropdown-item" href="{{ character.web_get_puppet_url }}?next={{ request.path }}">{{ character }}</a></li>
|
||||
{% empty %}
|
||||
<li><a class="dropdown-item" href="#">No characters found!</a></li>
|
||||
{% endfor %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="{% url 'password_change' %}">Change Password</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'logout' %}">Log Out</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'logout' %}">Log Out</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'login' %}?next={{ request.path }}">Log In</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'register' %}">Register</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
|
@ -0,0 +1,67 @@
|
|||
{% load static sekizai_tags %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="author" content="Evennia Contributors" />
|
||||
<meta name="generator" content="Evennia" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<link rel="icon" type="image/x-icon" href="{% static "website/images/evennia_logo.png" %}" />
|
||||
|
||||
{% comment %}
|
||||
Base Evennia, then our CSS, then whatever's in custom CSS.
|
||||
You can edit both SCSS and CSS.
|
||||
{% endcomment %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static "website/css/website.css" %}">
|
||||
<link rel="stylesheet" type="text/css" href="{% static "website/css/main.css" %}">
|
||||
<link rel="stylesheet" type="text/css" href="{% static "website/css/custom.css" %}">
|
||||
|
||||
{% block header_ext %}
|
||||
{% endblock %}
|
||||
|
||||
<title>{{game_name}} - {% if flatpage %}{{flatpage.title}}{% else %}{% block titleblock %}{{page_title}}{% endblock %}{% endif %}</title>
|
||||
</head>
|
||||
<body>
|
||||
{% block body %}
|
||||
|
||||
<div id="top"><a href="#main-content" class="sr-only sr-only-focusable">Skip to main content.</a></div>
|
||||
{% include "website/_menu.html" %}
|
||||
<div class="container main-content mt-4" id="main-copy">
|
||||
<div class="row">
|
||||
{% if sidebar %}
|
||||
<div class="col-4">
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="{% if sidebar %}col-8{% else %}col{% endif %}">
|
||||
{% include 'website/messages.html' %}
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
|
||||
{% include 'website/pagination.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
{% block footer %}
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<span class="text-white">Powered by <a class="text-white font-weight-bold" href="https://evennia.com">Evennia.</a></span>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</footer>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
<!-- jQuery first, then Tether, then Bootstrap JS. -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js" integrity="sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.min.js" integrity="sha384-mQ93GR66B00ZXjt0YO5KlohRA5SY2XofN4zfuZxLkoj1gXtW8ANNCe9d5Y3eG5eD" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue