From 0ab1c307167648bae88dfd9a9cbb5d6485d863ee Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 18 Dec 2021 18:02:37 +0100 Subject: [PATCH] Refactoring contribs --- .../contrib/base_systems/awsstorage/README.md | 41 +- .../base_systems/awsstorage/__init__.py | 3 +- .../base_systems/building_menu/README.md | 127 +++++ .../base_systems/building_menu/__init__.py | 6 + .../{ => building_menu}/building_menu.py | 9 +- .../base_systems/color_markups/README.md | 57 +++ .../base_systems/color_markups/__init__.py | 6 + .../{ => color_markups}/color_markups.py | 4 +- .../base_systems/custom_gametime/README.md | 47 ++ .../base_systems/custom_gametime/__init__.py | 6 + .../{ => custom_gametime}/custom_gametime.py | 0 .../base_systems/email_login/README.md | 30 ++ .../base_systems/email_login/__init__.py | 7 + .../email_login/connection_screens.py | 38 ++ .../{ => email_login}/email_login.py | 6 +- .../base_systems/ingame_python/README.md | 178 +++---- .../base_systems/ingame_python/__init__.py | 12 + .../base_systems/ingame_python/commands.py | 3 +- .../base_systems/ingame_python/eventfuncs.py | 4 +- .../base_systems/ingame_python/scripts.py | 6 +- .../base_systems/ingame_python/tests.py | 4 +- .../base_systems/ingame_python/typeclasses.py | 4 +- .../base_systems/ingame_python/utils.py | 8 +- .../contrib/base_systems/menu_login/README.md | 22 + .../base_systems/menu_login/__init__.py | 7 + .../menu_login/connection_screens.py | 33 ++ .../{ => menu_login}/menu_login.py | 11 +- .../base_systems/mux_comms_cmds/README.md | 42 ++ .../base_systems/mux_comms_cmds/__init__.py | 6 + .../{ => mux_comms_cmds}/mux_comms_cmds.py | 4 +- .../base_systems/unixcommand/README.md | 65 +++ .../base_systems/unixcommand/__init__.py | 6 + .../{ => unixcommand}/unixcommand.py | 3 + .../full_systems/evscaperoom/README.md | 32 +- .../full_systems/evscaperoom/__init__.py | 13 + evennia/contrib/game_systems/barter/README.md | 128 +++++ evennia/contrib/game_systems/barter/] | 83 ++++ .../contrib/game_systems/barter/__init__.py | 6 + .../game_systems/{ => barter}/barter.py | 4 +- .../contrib/game_systems/clothing/README.md | 84 ++++ .../contrib/game_systems/clothing/__init__.py | 7 + .../game_systems/{ => clothing}/clothing.py | 0 .../contrib/game_systems/cooldowns/README.md | 58 +++ .../game_systems/cooldowns/__init__.py | 6 + .../game_systems/{ => cooldowns}/cooldowns.py | 3 +- .../contrib/game_systems/crafting/README.md | 2 +- .../contrib/game_systems/crafting/__init__.py | 7 + .../game_systems/crafting/example_recipes.py | 5 +- .../contrib/game_systems/crafting/tests.py | 3 +- .../contrib/game_systems/gendersub/README.md | 49 ++ .../game_systems/gendersub/__init__.py | 7 + .../game_systems/{ => gendersub}/gendersub.py | 8 +- evennia/contrib/game_systems/mail/README.md | 40 ++ evennia/contrib/game_systems/mail/__init__.py | 7 + .../contrib/game_systems/{ => mail}/mail.py | 11 +- .../game_systems/multidescer/README.md | 24 + .../game_systems/multidescer/__init__.py | 6 + .../{ => multidescer}/multidescer.py | 5 +- .../contrib/game_systems/puzzles/README.md | 68 +++ .../contrib/game_systems/puzzles/__init__.py | 7 + .../puzzles}/puzzles.py | 34 +- .../contrib/game_systems/turnbattle/README.md | 32 +- .../game_systems/turnbattle/__init__.py | 9 + evennia/contrib/grid/__init__.py | 5 + evennia/contrib/grid/extended_room/README.md | 76 +++ .../contrib/grid/extended_room/__init__.py | 11 + .../grid/{ => extended_room}/extended_room.py | 6 +- evennia/contrib/grid/mapbuilder/README.md | 278 +++++++++++ evennia/contrib/grid/mapbuilder/__init__.py | 6 + .../grid/{ => mapbuilder}/mapbuilder.py | 75 +-- evennia/contrib/grid/simpledoor/README.md | 44 ++ evennia/contrib/grid/simpledoor/__init__.py | 7 + .../grid/{ => simpledoor}/simpledoor.py | 32 +- evennia/contrib/grid/slow_exit/README.md | 61 +++ evennia/contrib/grid/slow_exit/__init__.py | 9 + .../contrib/grid/{ => slow_exit}/slow_exit.py | 54 ++- evennia/contrib/grid/wilderness/README.md | 113 +++++ evennia/contrib/grid/wilderness/__init__.py | 14 + .../grid/{ => wilderness}/wilderness.py | 137 +++--- evennia/contrib/grid/xyzgrid/__init__.py | 15 + evennia/contrib/grid/xyzgrid/commands.py | 4 +- evennia/contrib/grid/xyzgrid/example.py | 2 +- evennia/contrib/grid/xyzgrid/launchcmd.py | 2 +- evennia/contrib/grid/xyzgrid/tests.py | 1 - evennia/contrib/grid/xyzgrid/xyzgrid.py | 2 +- evennia/contrib/grid/xyzgrid/xyzroom.py | 1 - evennia/contrib/rpg/chargen.py | 194 -------- evennia/contrib/rpg/dice/README.md | 61 +++ evennia/contrib/rpg/dice/__init__.py | 9 + evennia/contrib/rpg/{ => dice}/dice.py | 50 +- evennia/contrib/rpg/health_bar/README.md | 36 ++ evennia/contrib/rpg/health_bar/__init__.py | 6 + .../rpg/{ => health_bar}/health_bar.py | 21 +- evennia/contrib/rpg/rpsystem/README.md | 257 ++++++++++ evennia/contrib/rpg/rpsystem/__init__.py | 19 + .../contrib/rpg/{ => rpsystem}/rplanguage.py | 1 - .../contrib/rpg/{ => rpsystem}/rpsystem.py | 165 ++++--- evennia/contrib/rpg/traits/README.md | 443 ++++++++++++++++++ evennia/contrib/rpg/traits/__init__.py | 10 + .../rpg/{test_traits.py => traits/tests.py} | 3 +- evennia/contrib/rpg/{ => traits}/traits.py | 4 +- .../contrib/tutorials/talking_npc/__init__.py | 6 + .../talking_npc}/talking_npc.py | 14 +- 103 files changed, 3203 insertions(+), 604 deletions(-) create mode 100644 evennia/contrib/base_systems/building_menu/README.md create mode 100644 evennia/contrib/base_systems/building_menu/__init__.py rename evennia/contrib/base_systems/{ => building_menu}/building_menu.py (99%) create mode 100644 evennia/contrib/base_systems/color_markups/README.md create mode 100644 evennia/contrib/base_systems/color_markups/__init__.py rename evennia/contrib/base_systems/{ => color_markups}/color_markups.py (98%) create mode 100644 evennia/contrib/base_systems/custom_gametime/README.md create mode 100644 evennia/contrib/base_systems/custom_gametime/__init__.py rename evennia/contrib/base_systems/{ => custom_gametime}/custom_gametime.py (100%) create mode 100644 evennia/contrib/base_systems/email_login/README.md create mode 100644 evennia/contrib/base_systems/email_login/__init__.py create mode 100644 evennia/contrib/base_systems/email_login/connection_screens.py rename evennia/contrib/base_systems/{ => email_login}/email_login.py (98%) create mode 100644 evennia/contrib/base_systems/menu_login/README.md create mode 100644 evennia/contrib/base_systems/menu_login/__init__.py create mode 100644 evennia/contrib/base_systems/menu_login/connection_screens.py rename evennia/contrib/base_systems/{ => menu_login}/menu_login.py (96%) create mode 100644 evennia/contrib/base_systems/mux_comms_cmds/README.md create mode 100644 evennia/contrib/base_systems/mux_comms_cmds/__init__.py rename evennia/contrib/base_systems/{ => mux_comms_cmds}/mux_comms_cmds.py (99%) create mode 100644 evennia/contrib/base_systems/unixcommand/README.md create mode 100644 evennia/contrib/base_systems/unixcommand/__init__.py rename evennia/contrib/base_systems/{ => unixcommand}/unixcommand.py (99%) create mode 100644 evennia/contrib/game_systems/barter/README.md create mode 100644 evennia/contrib/game_systems/barter/] create mode 100644 evennia/contrib/game_systems/barter/__init__.py rename evennia/contrib/game_systems/{ => barter}/barter.py (99%) create mode 100644 evennia/contrib/game_systems/clothing/README.md create mode 100644 evennia/contrib/game_systems/clothing/__init__.py rename evennia/contrib/game_systems/{ => clothing}/clothing.py (100%) create mode 100644 evennia/contrib/game_systems/cooldowns/README.md create mode 100644 evennia/contrib/game_systems/cooldowns/__init__.py rename evennia/contrib/game_systems/{ => cooldowns}/cooldowns.py (99%) create mode 100644 evennia/contrib/game_systems/gendersub/README.md create mode 100644 evennia/contrib/game_systems/gendersub/__init__.py rename evennia/contrib/game_systems/{ => gendersub}/gendersub.py (97%) create mode 100644 evennia/contrib/game_systems/mail/README.md create mode 100644 evennia/contrib/game_systems/mail/__init__.py rename evennia/contrib/game_systems/{ => mail}/mail.py (98%) create mode 100644 evennia/contrib/game_systems/multidescer/README.md create mode 100644 evennia/contrib/game_systems/multidescer/__init__.py rename evennia/contrib/game_systems/{ => multidescer}/multidescer.py (99%) create mode 100644 evennia/contrib/game_systems/puzzles/README.md create mode 100644 evennia/contrib/game_systems/puzzles/__init__.py rename evennia/contrib/{base_systems => game_systems/puzzles}/puzzles.py (98%) create mode 100644 evennia/contrib/grid/__init__.py create mode 100644 evennia/contrib/grid/extended_room/README.md create mode 100644 evennia/contrib/grid/extended_room/__init__.py rename evennia/contrib/grid/{ => extended_room}/extended_room.py (99%) create mode 100644 evennia/contrib/grid/mapbuilder/README.md create mode 100644 evennia/contrib/grid/mapbuilder/__init__.py rename evennia/contrib/grid/{ => mapbuilder}/mapbuilder.py (93%) create mode 100644 evennia/contrib/grid/simpledoor/README.md create mode 100644 evennia/contrib/grid/simpledoor/__init__.py rename evennia/contrib/grid/{ => simpledoor}/simpledoor.py (88%) create mode 100644 evennia/contrib/grid/slow_exit/README.md create mode 100644 evennia/contrib/grid/slow_exit/__init__.py rename evennia/contrib/grid/{ => slow_exit}/slow_exit.py (78%) create mode 100644 evennia/contrib/grid/wilderness/README.md create mode 100644 evennia/contrib/grid/wilderness/__init__.py rename evennia/contrib/grid/{ => wilderness}/wilderness.py (88%) delete mode 100644 evennia/contrib/rpg/chargen.py create mode 100644 evennia/contrib/rpg/dice/README.md create mode 100644 evennia/contrib/rpg/dice/__init__.py rename evennia/contrib/rpg/{ => dice}/dice.py (91%) create mode 100644 evennia/contrib/rpg/health_bar/README.md create mode 100644 evennia/contrib/rpg/health_bar/__init__.py rename evennia/contrib/rpg/{ => health_bar}/health_bar.py (93%) create mode 100644 evennia/contrib/rpg/rpsystem/README.md create mode 100644 evennia/contrib/rpg/rpsystem/__init__.py rename evennia/contrib/rpg/{ => rpsystem}/rplanguage.py (99%) rename evennia/contrib/rpg/{ => rpsystem}/rpsystem.py (93%) create mode 100644 evennia/contrib/rpg/traits/README.md create mode 100644 evennia/contrib/rpg/traits/__init__.py rename evennia/contrib/rpg/{test_traits.py => traits/tests.py} (99%) rename evennia/contrib/rpg/{ => traits}/traits.py (99%) create mode 100644 evennia/contrib/tutorials/talking_npc/__init__.py rename evennia/contrib/{rpg => tutorials/talking_npc}/talking_npc.py (91%) diff --git a/evennia/contrib/base_systems/awsstorage/README.md b/evennia/contrib/base_systems/awsstorage/README.md index 028b89342b..5779c0c4e9 100644 --- a/evennia/contrib/base_systems/awsstorage/README.md +++ b/evennia/contrib/base_systems/awsstorage/README.md @@ -1,24 +1,25 @@ -# AWSstorage system +# AWSstorage system Contrib by The Right Honourable Reverend (trhr) 2020 -## What is this for? +## What is this for? This plugin migrates the Web-based portion of Evennia, namely images, -javascript, and other items located inside staticfiles into Amazon AWS (S3) for hosting. +javascript, and other items located inside staticfiles into Amazon AWS (S3) for +hosting. Files hosted on S3 are "in the cloud," and while your personal server may be sufficient for serving multimedia to a minimal number of users, the perfect use case for this plugin would be: -- Servers supporting heavy web-based traffic (webclient, etc) ... +- Servers supporting heavy web-based traffic (webclient, etc) ... - With a sizable number of users ... - Where the users are globally distributed ... - Where multimedia files are served to users as a part of gameplay Bottom line - if you're sending an image to a player every time they traverse a -map, the bandwidth reduction of using this will be substantial. If not, -probably skip this contrib. +map, the bandwidth reduction of using this will be substantial. If not, probably +skip this contrib. ## On costs @@ -35,14 +36,14 @@ pricing structure. This is a drop-in replacement that operates deeper than all of Evennia's code, so your existing code does not need to change at all to support it. -For example, when Evennia (or Django), tries to save a file permanently -(say, an image uploaded by a user), the save (or load) communication follows the path: +For example, when Evennia (or Django), tries to save a file permanently (say, an +image uploaded by a user), the save (or load) communication follows the path: -Evennia -> Django -Django -> Storage backend -Storage backend -> file storage location (e.g. hard drive) + Evennia -> Django + Django -> Storage backend + Storage backend -> file storage location (e.g. hard drive) -https://docs.djangoproject.com/en/3.0/ref/settings/#std:setting-STATICFILES_STORAGE +[django docs](https://docs.djangoproject.com/en/3.0/ref/settings/#std:setting-STATICFILES_STORAGE) This plugin, when enabled, overrides the default storage backend, which defaults to saving files at mygame/website/, instead, @@ -111,7 +112,7 @@ Advanced Users: The second IAM statement, CreateBucket, is only needed for initial installation. You can remove it later, or you can create the bucket and set the ACL yourself before you continue. -## Dependencies +## Dependencies This package requires the dependency "boto3 >= 1.4.4", the official @@ -120,14 +121,14 @@ extra requirements; - Activate your `virtualenv` - `cd` to the root of the Evennia repository. There should be an `requirements_extra.txt` -file here. -- `pip install -r requirements_extra.txt` +file here. +- `pip install -r requirements_extra.txt` -## Configure Evennia +## Configure Evennia Customize the variables defined below in `secret_settings.py`. No further configuration is needed. Note the three lines that you need to set to your -actual values. +actual values. ```python # START OF SECRET_SETTINGS.PY COPY/PASTE >>> @@ -145,7 +146,7 @@ AWS_S3_OBJECT_PARAMETERS = { 'Expires': 'Thu, 31 Dec 2099 20:00:00 GMT', AWS_DEFAULT_ACL = 'public-read' AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % settings.AWS_BUCKET_NAME AWS_AUTO_CREATE_BUCKET = True -STATICFILES_STORAGE = 'evennia.contrib.awsstorage.aws-s3-cdn.S3Boto3Storage' +STATICFILES_STORAGE = 'evennia.contrib.base_systems.awsstorage.aws-s3-cdn.S3Boto3Storage' # <<< END OF SECRET_SETTINGS.PY COPY/PASTE ``` @@ -153,14 +154,14 @@ STATICFILES_STORAGE = 'evennia.contrib.awsstorage.aws-s3-cdn.S3Boto3Storage' You may also store these keys as environment variables of the same name. For advanced configuration, refer to the docs for django-storages. -After copying the above, run `evennia reboot`. +After copying the above, run `evennia reboot`. ## Check that it works Confirm that web assets are being served from S3 by visiting your website, then checking the source of any image (for instance, the logo). It should read `https://your-bucket-name.s3.amazonaws.com/path/to/file`. If so, the system -works and you shouldn't need to do anything else. +works and you shouldn't need to do anything else. # Uninstallation diff --git a/evennia/contrib/base_systems/awsstorage/__init__.py b/evennia/contrib/base_systems/awsstorage/__init__.py index 2ba7f2b5e2..a62ce83e4e 100644 --- a/evennia/contrib/base_systems/awsstorage/__init__.py +++ b/evennia/contrib/base_systems/awsstorage/__init__.py @@ -1,3 +1,4 @@ """ -Intended to be a collecting folder for Django-specific contribs that do not have observable effects to players. +AWS storage system contrib - trhr 2020 + """ diff --git a/evennia/contrib/base_systems/building_menu/README.md b/evennia/contrib/base_systems/building_menu/README.md new file mode 100644 index 0000000000..7fc7710d69 --- /dev/null +++ b/evennia/contrib/base_systems/building_menu/README.md @@ -0,0 +1,127 @@ +# Building menu + +Module containing the building menu system. + +Evennia contributor: vincent-lg 2018 + +Building menus are in-game menus, not unlike `EvMenu` though using a +different approach. Building menus have been specifically designed to edit +information as a builder. Creating a building menu in a command allows +builders quick-editing of a given object, like a room. If you follow the +steps below to add the contrib, you will have access to an `@edit` command +that will edit any default object offering to change its key and description. + +## Install + +1. Import the `GenericBuildingCmd` class from this contrib in your `mygame/commands/default_cmdset.py` file: + + ```python + from evennia.base_systems.contrib.building_menu import GenericBuildingCmd + ``` + +2. Below, add the command in the `CharacterCmdSet`: + + ```python + # ... These lines should exist in the file + class CharacterCmdSet(default_cmds.CharacterCmdSet): + key = "DefaultCharacter" + + def at_cmdset_creation(self): + super(CharacterCmdSet, self).at_cmdset_creation() + # ... add the line below + self.add(GenericBuildingCmd()) + ``` + +## Usage + +The `edit` command will allow you to edit any object. You will need to +specify the object name or ID as an argument. For instance: `edit here` +will edit the current room. However, building menus can perform much more +than this very simple example, read on for more details. + +Building menus can be set to edit about anything. Here is an example of +output you could obtain when editing the room: + +``` + Editing the room: Limbo(#2) + + [T]itle: the limbo room + [D]escription + This is the limbo room. You can easily change this default description, + either by using the |y@desc/edit|n command, or simply by entering this + menu (enter |yd|n). + [E]xits: + north to A parking(#4) + [Q]uit this menu +``` + +From there, you can open the title choice by pressing t. You can then +change the room title by simply entering text, and go back to the +main menu entering @ (all this is customizable). Press q to quit this menu. + +The first thing to do is to create a new module and place a class +inheriting from `BuildingMenu` in it. + +```python +from evennia.contrib.base_systems.building_menu import BuildingMenu + +class RoomBuildingMenu(BuildingMenu): + # ... + +``` + +Next, override the `init` method (not `__init__`!). You can add +choices (like the title, description, and exits choices as seen above) by using +the `add_choice` method. + +```python +class RoomBuildingMenu(BuildingMenu): + def init(self, room): + self.add_choice("title", "t", attr="key") + +``` + +That will create the first choice, the title choice. If one opens your menu +and enter t, she will be in the title choice. She can change the title +(it will write in the room's `key` attribute) and then go back to the +main menu using `@`. + +`add_choice` has a lot of arguments and offers a great deal of +flexibility. The most useful ones is probably the usage of callbacks, +as you can set almost any argument in `add_choice` to be a callback, a +function that you have defined above in your module. This function will be +called when the menu element is triggered. + +Notice that in order to edit a description, the best method to call isn't +`add_choice`, but `add_choice_edit`. This is a convenient shortcut +which is available to quickly open an `EvEditor` when entering this choice +and going back to the menu when the editor closes. + +```python +class RoomBuildingMenu(BuildingMenu): + def init(self, room): + self.add_choice("title", "t", attr="key") + self.add_choice_edit("description", key="d", attr="db.desc") + +``` + +When you wish to create a building menu, you just need to import your +class, create it specifying your intended caller and object to edit, +then call `open`: + +```python +from import RoomBuildingMenu + +class CmdEdit(Command): + + key = "redit" + + def func(self): + menu = RoomBuildingMenu(self.caller, self.caller.location) + menu.open() + +``` + +This is a very short introduction. For more details, see the [online +tutorial](https://github.com/evennia/evennia/wiki/Building-menus) or read the +heavily-documented code of the contrib itself. diff --git a/evennia/contrib/base_systems/building_menu/__init__.py b/evennia/contrib/base_systems/building_menu/__init__.py new file mode 100644 index 0000000000..7606f80dc5 --- /dev/null +++ b/evennia/contrib/base_systems/building_menu/__init__.py @@ -0,0 +1,6 @@ +""" +Build-menu contrib - vincent-lg 2018 + +""" +from .building_menu import GenericBuildingCmd # noqa +from .building_menu import BuildingMenu # noqa diff --git a/evennia/contrib/base_systems/building_menu.py b/evennia/contrib/base_systems/building_menu/building_menu.py similarity index 99% rename from evennia/contrib/base_systems/building_menu.py rename to evennia/contrib/base_systems/building_menu/building_menu.py index 0bf3e40d2d..32834d7712 100644 --- a/evennia/contrib/base_systems/building_menu.py +++ b/evennia/contrib/base_systems/building_menu/building_menu.py @@ -10,10 +10,12 @@ builders quick-editing of a given object, like a room. If you follow the steps below to add the contrib, you will have access to an `@edit` command that will edit any default object offering to change its key and description. -1. Import the `GenericBuildingCmd` class from this contrib in your `mygame/commands/default_cmdset.py` file: +1. Import the `GenericBuildingCmd` class from this contrib in your + `mygame/commands/default_cmdset.py` file: ```python - from evennia.contrib.building_menu import GenericBuildingCmd + from evennia.contrib.base_systems.building_menu import GenericBuildingCmd + ``` 2. Below, add the command in the `CharacterCmdSet`: @@ -58,10 +60,11 @@ The first thing to do is to create a new module and place a class inheriting from `BuildingMenu` in it. ```python -from evennia.contrib.building_menu import BuildingMenu +from evennia.contrib.building_menu.building_menu import BuildingMenu class RoomBuildingMenu(BuildingMenu): # ... + ``` Next, override the `init` method. You can add choices (like the title, diff --git a/evennia/contrib/base_systems/color_markups/README.md b/evennia/contrib/base_systems/color_markups/README.md new file mode 100644 index 0000000000..ef116e996c --- /dev/null +++ b/evennia/contrib/base_systems/color_markups/README.md @@ -0,0 +1,57 @@ +# Color markups + +Contribution, Griatch 2017 + +Additional color markup styles for Evennia (extending or replacing the default +`|r`, `|234` etc). + + +## Installation + +Import the desired style variables from this module into +mygame/server/conf/settings.py and add them to the settings variables below. +Each are specified as a list, and multiple such lists can be added to each +variable to support multiple formats. Note that list order affects which regexes +are applied first. You must restart both Portal and Server for color tags to +update. + +Assign to the following settings variables (see below for example): + + COLOR_ANSI_EXTRA_MAP - a mapping between regexes and ANSI colors + COLOR_XTERM256_EXTRA_FG - regex for defining XTERM256 foreground colors + COLOR_XTERM256_EXTRA_BG - regex for defining XTERM256 background colors + COLOR_XTERM256_EXTRA_GFG - regex for defining XTERM256 grayscale foreground colors + COLOR_XTERM256_EXTRA_GBG - regex for defining XTERM256 grayscale background colors + COLOR_ANSI_BRIGHT_BG_EXTRA_MAP = ANSI does not support bright backgrounds; we fake + this by mapping ANSI markup to matching bright XTERM256 backgrounds + + COLOR_NO_DEFAULT - Set True/False. If False (default), extend the default + markup, otherwise replace it completely. + +## Example + +To add the {- "curly-bracket" style, add the following to your settings file, +then reboot both Server and Portal: + +```python +from evennia.contrib.base_systems import color_markups +COLOR_ANSI_EXTRA_MAP = color_markups.CURLY_COLOR_ANSI_EXTRA_MAP +COLOR_XTERM256_EXTRA_FG = color_markups.CURLY_COLOR_XTERM256_EXTRA_FG +COLOR_XTERM256_EXTRA_BG = color_markups.CURLY_COLOR_XTERM256_EXTRA_BG +COLOR_XTERM256_EXTRA_GFG = color_markups.CURLY_COLOR_XTERM256_EXTRA_GFG +COLOR_XTERM256_EXTRA_GBG = color_markups.CURLY_COLOR_XTERM256_EXTRA_GBG +COLOR_ANSI_BRIGHT_BG_EXTRA_MAP = color_markups.CURLY_COLOR_ANSI_BRIGHT_BG_EXTRA_MAP +``` + +To add the `%c-` "mux/mush" style, add the following to your settings file, then +reboot both Server and Portal: + +```python +from evennia.contrib import color_markups +COLOR_ANSI_EXTRA_MAP = color_markups.MUX_COLOR_ANSI_EXTRA_MAP +COLOR_XTERM256_EXTRA_FG = color_markups.MUX_COLOR_XTERM256_EXTRA_FG +COLOR_XTERM256_EXTRA_BG = color_markups.MUX_COLOR_XTERM256_EXTRA_BG +COLOR_XTERM256_EXTRA_GFG = color_markups.MUX_COLOR_XTERM256_EXTRA_GFG +COLOR_XTERM256_EXTRA_GBG = color_markups.MUX_COLOR_XTERM256_EXTRA_GBG +COLOR_ANSI_BRIGHT_BGS_EXTRA_MAP = color_markups.CURLY_COLOR_ANSI_BRIGHT_BGS_EXTRA_MAP +``` diff --git a/evennia/contrib/base_systems/color_markups/__init__.py b/evennia/contrib/base_systems/color_markups/__init__.py new file mode 100644 index 0000000000..f3d497516f --- /dev/null +++ b/evennia/contrib/base_systems/color_markups/__init__.py @@ -0,0 +1,6 @@ +""" +Color markups contrib - Griatch 2017 + +""" + +from .color_markups import * # noqa diff --git a/evennia/contrib/base_systems/color_markups.py b/evennia/contrib/base_systems/color_markups/color_markups.py similarity index 98% rename from evennia/contrib/base_systems/color_markups.py rename to evennia/contrib/base_systems/color_markups/color_markups.py index 8478a9ee2c..df11794254 100644 --- a/evennia/contrib/base_systems/color_markups.py +++ b/evennia/contrib/base_systems/color_markups/color_markups.py @@ -30,7 +30,7 @@ Assign to the following settings variables: To add the {- "curly-bracket" style, add the following to your settings file, then reboot both Server and Portal: -from evennia.contrib import color_markups +from evennia.contrib.base_systems import color_markups COLOR_ANSI_EXTRA_MAP = color_markups.CURLY_COLOR_ANSI_EXTRA_MAP COLOR_XTERM256_EXTRA_FG = color_markups.CURLY_COLOR_XTERM256_EXTRA_FG COLOR_XTERM256_EXTRA_BG = color_markups.CURLY_COLOR_XTERM256_EXTRA_BG @@ -42,7 +42,7 @@ COLOR_ANSI_BRIGHT_BG_EXTRA_MAP = color_markups.CURLY_COLOR_ANSI_BRIGHT_BG_EXTRA_ To add the %c- "mux/mush" style, add the following to your settings file, then reboot both Server and Portal: -from evennia.contrib import color_markups +from evennia.contrib.base_systems import color_markups COLOR_ANSI_EXTRA_MAP = color_markups.MUX_COLOR_ANSI_EXTRA_MAP COLOR_XTERM256_EXTRA_FG = color_markups.MUX_COLOR_XTERM256_EXTRA_FG COLOR_XTERM256_EXTRA_BG = color_markups.MUX_COLOR_XTERM256_EXTRA_BG diff --git a/evennia/contrib/base_systems/custom_gametime/README.md b/evennia/contrib/base_systems/custom_gametime/README.md new file mode 100644 index 0000000000..394ff754b5 --- /dev/null +++ b/evennia/contrib/base_systems/custom_gametime/README.md @@ -0,0 +1,47 @@ +# Custom gameime + +Contrib - Griatch 2017, vlgeoff 2017 + +This reimplements the `evennia.utils.gametime` module but supporting a custom +calendar for your game world. It allows for scheduling events to happen at given +in-game times, taking this custom calendar into account. + +## Installation + +Import and use this in the same way as you would the normal +`evennia.utils.gametime` module. + +Customize the calendar by adding a `TIME_UNITS` dict to your settings (see +example below). + + +## Usage: + +```python + from evennia.contrib.base_systems import custom_gametime + + gametime = custom_gametime.realtime_to_gametime(days=23) + + # scedule an event to fire every in-game 10 hours + custom_gametime.schedule(callback, repeat=True, hour=10) + +``` + +The calendar can be customized by adding the `TIME_UNITS` dictionary to your +settings file. This maps unit names to their length, expressed in the smallest +unit. Here's the default as an example: + + TIME_UNITS = { + "sec": 1, + "min": 60, + "hr": 60 * 60, + "hour": 60 * 60, + "day": 60 * 60 * 24, + "week": 60 * 60 * 24 * 7, + "month": 60 * 60 * 24 * 7 * 4, + "yr": 60 * 60 * 24 * 7 * 4 * 12, + "year": 60 * 60 * 24 * 7 * 4 * 12, } + +When using a custom calendar, these time unit names are used as kwargs to +the converter functions in this module. Even if your calendar uses other names +for months/weeks etc the system needs the default names internally. diff --git a/evennia/contrib/base_systems/custom_gametime/__init__.py b/evennia/contrib/base_systems/custom_gametime/__init__.py new file mode 100644 index 0000000000..c797e34dde --- /dev/null +++ b/evennia/contrib/base_systems/custom_gametime/__init__.py @@ -0,0 +1,6 @@ +""" +Custom gametime contrib - Griatch, vlgeoff 2017 + +""" + +from .custom_gametime import * # noqa diff --git a/evennia/contrib/base_systems/custom_gametime.py b/evennia/contrib/base_systems/custom_gametime/custom_gametime.py similarity index 100% rename from evennia/contrib/base_systems/custom_gametime.py rename to evennia/contrib/base_systems/custom_gametime/custom_gametime.py diff --git a/evennia/contrib/base_systems/email_login/README.md b/evennia/contrib/base_systems/email_login/README.md new file mode 100644 index 0000000000..533952ca4f --- /dev/null +++ b/evennia/contrib/base_systems/email_login/README.md @@ -0,0 +1,30 @@ +# Email-based login system + +Evennia contrib - Griatch 2012 + +This is a variant of the login system that requires an email-address +instead of a username to login. + +This used to be the default Evennia login before replacing it with a +more standard username + password system (having to supply an email +for some reason caused a lot of confusion when people wanted to expand +on it. The email is not strictly needed internally, nor is any +confirmation email sent out anyway). + +## Installation + +To your settings file, add/edit the line: + +```python +CMDSET_UNLOGGEDIN = "contrib.base_systems.email_login.UnloggedinCmdSet" +CONNECTION_SCREEN_MODULE = "contrib.base_systems.email_login.connection_screens" + +``` + +That's it. Reload the server and reconnect to see it. + +## Notes: + +If you want to modify the way the connection screen looks, point +`CONNECTION_SCREEN_MODULE` to your own module. Use the default as a +guide (see also Evennia docs). diff --git a/evennia/contrib/base_systems/email_login/__init__.py b/evennia/contrib/base_systems/email_login/__init__.py new file mode 100644 index 0000000000..6d5bbf2bb4 --- /dev/null +++ b/evennia/contrib/base_systems/email_login/__init__.py @@ -0,0 +1,7 @@ +""" +Email login contrib - Griatch 2012 + +""" + +from .email_login import UnloggedinCmdSet # noqa +from . import connection_screens # noqa diff --git a/evennia/contrib/base_systems/email_login/connection_screens.py b/evennia/contrib/base_systems/email_login/connection_screens.py new file mode 100644 index 0000000000..de87311581 --- /dev/null +++ b/evennia/contrib/base_systems/email_login/connection_screens.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +""" +Connection screen + +This is the text to show the user when they first connect to the game (before +they log in). + +To change the login screen in this module, do one of the following: + +- Define a function `connection_screen()`, taking no arguments. This will be + called first and must return the full string to act as the connection screen. + This can be used to produce more dynamic screens. +- Alternatively, define a string variable in the outermost scope of this module + with the connection string that should be displayed. If more than one such + variable is given, Evennia will pick one of them at random. + +The commands available to the user when the connection screen is shown +are defined in evennia.default_cmds.UnloggedinCmdSet. The parsing and display +of the screen is done by the unlogged-in "look" command. + +""" + +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 |n + If you need to create an account, type (without the <>'s): + |wcreate |n + + Enter |whelp|n for more info. |wlook|n will re-show this screen. +|b==============================================================|n""".format( + settings.SERVERNAME, utils.get_evennia_version("short") +) diff --git a/evennia/contrib/base_systems/email_login.py b/evennia/contrib/base_systems/email_login/email_login.py similarity index 98% rename from evennia/contrib/base_systems/email_login.py rename to evennia/contrib/base_systems/email_login/email_login.py index cb18e65e3c..8ff83045fd 100644 --- a/evennia/contrib/base_systems/email_login.py +++ b/evennia/contrib/base_systems/email_login/email_login.py @@ -16,10 +16,12 @@ confirmation email sent out anyway). Installation is simple: -To your settings file, add/edit the line: +To your settings file, add/edit settings as follows: ```python -CMDSET_UNLOGGEDIN = "contrib.email_login.UnloggedinCmdSet" +CMDSET_UNLOGGEDIN = "contrib.base_systems.email_login.email_login.UnloggedinCmdSet" +CONNECTION_SCREEN_MODULE = "contrib.base_systems.email_login.connection_screens" + ``` That's it. Reload the server and try to log in to see it. diff --git a/evennia/contrib/base_systems/ingame_python/README.md b/evennia/contrib/base_systems/ingame_python/README.md index d4de7dc957..60d581dd74 100644 --- a/evennia/contrib/base_systems/ingame_python/README.md +++ b/evennia/contrib/base_systems/ingame_python/README.md @@ -2,54 +2,62 @@ Vincent Le Goff 2017 -This contrib adds the system of in-game Python in Evennia, allowing immortals (or other trusted builders) to -dynamically add features to individual objects. Using custom Python set in-game, every immortal or privileged users -could have a specific room, exit, character, object or something else behave differently from its -"cousins". For these familiar with the use of softcode in MU`*`, like SMAUG MudProgs, the ability to -add arbitrary behavior to individual objects is a step toward freedom. Keep in mind, however, the -warning below, and read it carefully before the rest of the documentation. +This contrib adds the system of in-game Python in Evennia, allowing immortals +(or other trusted builders) to dynamically add features to individual objects. +Using custom Python set in-game, every immortal or privileged users could have a +specific room, exit, character, object or something else behave differently from +its "cousins". For these familiar with the use of softcode in MU`*`, like SMAUG +MudProgs, the ability to add arbitrary behavior to individual objects is a step +toward freedom. Keep in mind, however, the warning below, and read it carefully +before the rest of the documentation. ## A WARNING REGARDING SECURITY -Evennia's in-game Python system will run arbitrary Python code without much restriction. Such a system is as -powerful as potentially dangerous, and you will have to keep in mind these points before deciding to -install it: +Evennia's in-game Python system will run arbitrary Python code without much +restriction. Such a system is as powerful as potentially dangerous, and you +will have to keep in mind these points before deciding to install it: -1. Untrusted people can run Python code on your game server with this system. Be careful about who - can use this system (see the permissions below). -2. You can do all of this in Python outside the game. The in-game Python system is not to replace all your - game feature. +1. Untrusted people can run Python code on your game server with this system. + Be careful about who can use this system (see the permissions below). +2. You can do all of this in Python outside the game. The in-game Python system + is not to replace all your game feature. ## Basic structure and vocabulary -- At the basis of the in-game Python system are **events**. An **event** defines the context in which we - would like to call some arbitrary code. For instance, one event is - defined on exits and will fire every time a character traverses through this exit. Events are described - on a [typeclass](https://github.com/evennia/evennia/wiki/Typeclasses) (like - [exits](https://github.com/evennia/evennia/wiki/Objects#exits) in our example). All objects inheriting - from this typeclass will have access to this event. -- **Callbacks** can be set on individual objects, on events defined in code. These **callbacks** - can contain arbitrary code and describe a specific behavior for an object. When the event fires, - all callbacks connected to this object's event are executed. +- At the basis of the in-game Python system are **events**. An **event** + defines the context in which we would like to call some arbitrary code. For +instance, one event is defined on exits and will fire every time a character +traverses through this exit. Events are described on a [typeclass](Typeclasses) +([exits](Objects#exits) in our example). All objects inheriting from this +typeclass will have access to this event. +- **Callbacks** can be set on individual objects, on events defined in code. + These **callbacks** can contain arbitrary code and describe a specific +behavior for an object. When the event fires, all callbacks connected to this +object's event are executed. -To see the system in context, when an object is picked up (using the default `get` command), a -specific event is fired: +To see the system in context, when an object is picked up (using the default +`get` command), a specific event is fired: 1. The event "get" is set on objects (on the `Object` typeclass). -2. When using the "get" command to pick up an object, this object's `at_get` hook is called. -3. A modified hook of DefaultObject is set by the event system. This hook will execute (or call) - the "get" event on this object. -4. All callbacks tied to this object's "get" event will be executed in order. These callbacks act - as functions containing Python code that you can write in-game, using specific variables that - will be listed when you edit the callback itself. -5. In individual callbacks, you can add multiple lines of Python code that will be fired at this - point. In this example, the `character` variable will contain the character who has picked up - the object, while `obj` will contain the object that was picked up. +2. When using the "get" command to pick up an object, this object's `at_get` + hook is called. +3. A modified hook of DefaultObject is set by the event system. This hook will + execute (or call) the "get" event on this object. +4. All callbacks tied to this object's "get" event will be executed in order. + These callbacks act as functions containing Python code that you can write + in-game, using specific variables that will be listed when you edit the callback + itself. +5. In individual callbacks, you can add multiple lines of Python code that will + be fired at this point. In this example, the `character` variable will + contain the character who has picked up the object, while `obj` will contain the + object that was picked up. -Following this example, if you create a callback "get" on the object "a sword", and put in it: +Following this example, if you create a callback "get" on the object "a sword", +and put in it: ```python character.msg("You have picked up {} and have completed this quest!".format(obj.get_display_name(character))) + ``` When you pick up this object you should see something like: @@ -59,11 +67,13 @@ When you pick up this object you should see something like: ## Installation -Being in a separate contrib, the in-game Python system isn't installed by default. You need to do it -manually, following these steps: +Being in a separate contrib, the in-game Python system isn't installed by +default. You need to do it manually, following these steps: + +This is the quick summary. Scroll down for more detailed help on each step. 1. Launch the main script (important!): - ```@py evennia.create_script("evennia.contrib.ingame_python.scripts.EventHandler")``` + ```py evennia.create_script("evennia.contrib.base_systems.ingame_python.scripts.EventHandler")``` 2. Set the permissions (optional): - `EVENTS_WITH_VALIDATION`: a group that can edit callbacks, but will need approval (default to `None`). @@ -72,7 +82,7 @@ manually, following these steps: - `EVENTS_VALIDATING`: a group that can validate callbacks (default to `"immortals"`). - `EVENTS_CALENDAR`: type of the calendar to be used (either `None`, `"standard"` or `"custom"`, default to `None`). -3. Add the `@call` command. +3. Add the `call` command. 4. Inherit from the custom typeclasses of the in-game Python system. - `evennia.contrib.ingame_python.typeclasses.EventCharacter`: to replace `DefaultCharacter`. - `evennia.contrib.ingame_python.typeclasses.EventExit`: to replace `DefaultExit`. @@ -89,7 +99,7 @@ that a 'callback' property is not defined. After performing step `1` the error w To start the event script, you only need a single command, using `@py`. - @py evennia.create_script("evennia.contrib.ingame_python.scripts.EventHandler") + py evennia.create_script("evennia.contrib.base_systems.ingame_python.scripts.EventHandler") This command will create a global script (that is, a script independent from any object). This script will hold basic configuration, individual callbacks and so on. You may access it directly, @@ -107,8 +117,7 @@ By default, callbacks can only be created by immortals: no one except the immort callbacks, and immortals don't need validation. It can easily be changed, either through settings or dynamically by changing permissions of users. -The events contrib adds three -[permissions](https://github.com/evennia/evennia/wiki/Locks#permissions) in the settings. You can +The ingame-python contrib adds three [permissions](Permissions)) in the settings. You can override them by changing the settings into your `server/conf/settings.py` file (see below for an example). The settings defined in the events contrib are: @@ -142,9 +151,7 @@ calendar you are using. By default, time-related events are disabled. You can `EVENTS_CALENDAR` to set it to: - `"standard"`: the standard calendar, with standard days, months, years and so on. -- `"custom"`: a custom calendar that will use the - [custom_gametime](https://github.com/evennia/evennia/blob/master/evennia/contrib/custom_gametime.py) -contrib to schedule events. +- `"custom"`: a custom calendar that will use the `custom_gametime` contrib to schedule events. This contrib defines two additional permissions that can be set on individual users: @@ -156,17 +163,17 @@ This contrib defines two additional permissions that can be set on individual us For instance, to give the right to edit callbacks without needing approval to the player 'kaldara', you might do something like: - @perm *kaldara = events_without_validation + perm *kaldara = events_without_validation To remove this same permission, just use the `/del` switch: - @perm/del *kaldara = events_without_validation + perm/del *kaldara = events_without_validation -The rights to use the `@call` command are directly related to these permissions: by default, only +The rights to use the `call` command are directly related to these permissions: by default, only users who have the `events_without_validation` permission or are in (or above) the group defined in the `EVENTS_WITH_VALIDATION` setting will be able to call the command (with different switches). -### Adding the `@call` command +### Adding the `call` command You also have to add the `@call` command to your Character CmdSet. This command allows your users to add, edit and delete callbacks in-game. In your `commands/default_cmdsets, it might look like @@ -199,32 +206,34 @@ classes. For instance, in your `typeclasses/characters.py` module, you should c like this: ```python -from evennia.contrib.ingame_python.typeclasses import EventCharacter +from evennia.contrib.base_systems.ingame_python.typeclasses import EventCharacter class Character(EventCharacter): # ... ``` -You should do the same thing for your rooms, exits and objects. Note that the in-game Python system works by -overriding some hooks. Some of these features might not be accessible in your game if you don't -call the parent methods when overriding hooks. +You should do the same thing for your rooms, exits and objects. Note that the +in-game Python system works by overriding some hooks. Some of these features +might not be accessible in your game if you don't call the parent methods when +overriding hooks. -## Using the `@call` command +## Using the `call` command -The in-game Python system relies, to a great extent, on its `@call` command. Who can execute this command, -and who can do what with it, will depend on your set of permissions. +The in-game Python system relies, to a great extent, on its `call` command. +Who can execute this command, and who can do what with it, will depend on your +set of permissions. -The `@call` command allows to add, edit and delete callbacks on specific objects' events. The event +The `call` command allows to add, edit and delete callbacks on specific objects' events. The event system can be used on most Evennia objects, mostly typeclassed objects (excluding players). The -first argument of the `@call` command is the name of the object you want to edit. It can also be +first argument of the `call` command is the name of the object you want to edit. It can also be used to know what events are available for this specific object. ### Examining callbacks and events -To see the events connected to an object, use the `@call` command and give the name or ID of the -object to examine. For instance, @call here` to examine the events on your current location. Or -`@call self` to see the events on yourself. +To see the events connected to an object, use the `call` command and give the name or ID of the +object to examine. For instance, `call here` to examine the events on your current location. Or +`call self` to see the events on yourself. This command will display a table, containing: @@ -233,7 +242,7 @@ This command will display a table, containing: second column. - A short help to tell you when the event is triggered in the third column. -If you execute `@call #1` for instance, you might see a table like this: +If you execute `call #1` for instance, you might see a table like this: ``` +------------------+---------+-----------------------------------------------+ @@ -292,7 +301,7 @@ If we want to prevent a character from traversing through this exit, the best ev When we edit the event, we have some more information: - @call/add north = can_traverse + call/add north = can_traverse Can the character traverse through this exit? This event is called when a character is about to traverse this @@ -308,7 +317,7 @@ Variables you can use in this event: The section dedicated to [eventfuncs](#the-eventfuncs) will elaborate on the `deny()` function and other eventfuncs. Let us say, for the time being, that it can prevent an action (in this case, it can prevent the character from traversing through this exit). In the editor that opened when you -used `@call/add`, you can type something like: +used `call/add`, you can type something like: ```python if character.id == 1: @@ -320,11 +329,11 @@ else: You can now enter `:wq` to leave the editor by saving the callback. -If you enter `@call north`, you should see that "can_traverse" now has an active callback. You can -use `@call north = can_traverse` to see more details on the connected callbacks: +If you enter `call north`, you should see that "can_traverse" now has an active callback. You can +use `call north = can_traverse` to see more details on the connected callbacks: ``` -@call north = can_traverse +call north = can_traverse +--------------+--------------+----------------+--------------+--------------+ | Number | Author | Updated | Param | Valid | +~~~~~~~~~~~~~~+~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~+~~~~~~~~~~~~~~+ @@ -336,7 +345,7 @@ The left column contains callback numbers. You can use them to have even more i specific event. Here, for instance: ``` -@call north = can_traverse 1 +call north = can_traverse 1 Callback can_traverse 1 of north: Created by XXXXX on 2017-04-02 17:58:05. Updated by XXXXX on 2017-04-02 18:02:50 @@ -360,11 +369,11 @@ the name of the object to edit and the equal sign: 1. The name of the event (as seen above). 2. A number, if several callbacks are connected at this location. -You can type `@call/edit = ` to see the callbacks that are linked at this +You can type `call/edit = ` to see the callbacks that are linked at this location. If there is only one callback, it will be opened in the editor; if more are defined, you -will be asked for a number to provide (for instance, `@call/edit north = can_traverse 2`). +will be asked for a number to provide (for instance, `call/edit north = can_traverse 2`). -The command `@call` also provides a `/del` switch to remove a callback. It takes the same arguments +The command `call` also provides a `/del` switch to remove a callback. It takes the same arguments as the `/edit` switch. When removed, callbacks are logged, so an administrator can retrieve its content, assuming the @@ -435,7 +444,7 @@ One example that will illustrate this system is the "msg_leave" event that can b This event can alter the message that will be sent to other characters when someone leaves through this exit. - @call/add down = msg_leave + call/add down = msg_leave Which should display: @@ -467,6 +476,7 @@ If you write something like this in your event: ```python message = "{character} falls into a hole in the ground!" + ``` And if the character Wilfred takes this exit, others in the room will see: @@ -488,18 +498,18 @@ For instance, let's say we want to create a cool voice-operated elevator. You e elevator and say the floor number... and the elevator moves in the right direction. In this case, we could create an callback with the parameter "one": - @call/add here = say one + call/add here = say one This callback will only fire when the user says a sentence that contains "one". But what if we want to have a callback that would fire if the user says 1 or one? We can provide several parameters, separated by a comma. - @call/add here = say 1, one + call/add here = say 1, one Or, still more keywords: - @call/add here = say 1, one, ground + call/add here = say 1, one, ground This time, the user could say something like "take me to the ground floor" ("ground" is one of our keywords defined in the above callback). @@ -524,11 +534,12 @@ a mandatory parameter, which is the time you expect this event to fire. For instance, let's add an event on this room that should trigger every day, at precisely 12:00 PM (the time is given as game time, not real time): - @call here = time 12:00 + call here = time 12:00 ```python # This will be called every MUD day at 12:00 PM room.msg_contents("It's noon, time to have lunch!") + ``` Now, at noon every MUD day, this event will fire and this callback will be executed. You can use @@ -580,7 +591,7 @@ the next at regular times. Connecting exits (opening its doors), waiting a bit, rolling around and stopping at a different station. That's quite a complex set of callbacks, as it is, but let's only look at the part that opens and closes the doors: - @call/add here = time 10:00 + call/add here = time 10:00 ```python # At 10:00 AM, the subway arrives in the room of ID 22. @@ -617,7 +628,7 @@ This callback will: And now, what should we have in "chain_1"? - @call/add here = chain_1 + call/add here = chain_1 ```python # Close the doors @@ -795,7 +806,7 @@ see a message about a "beautiful ant-hill". ### Adding new eventfuncs -Eventfuncs, like `deny(), are defined in `contrib/events/eventfuncs.py`. You can add your own +Eventfuncs, like `deny()`, are defined in `contrib/events/eventfuncs.py`. You can add your own eventfuncs by creating a file named `eventfuncs.py` in your `world` directory. The functions defined in this file will be added as helpers. @@ -814,7 +825,7 @@ EVENTFUNCS_LOCATIONS = [ If you want to create events with parameters (if you create a "whisper" or "ask" command, for instance, and need to have some characters automatically react to words), you can set an additional -argument in the tuple of events in your typeclass' ```_events``` class variable. This third argument +argument in the tuple of events in your typeclass' `_events` class variable. This third argument must contain a callback that will be called to filter through the list of callbacks when the event fires. Two types of parameters are commonly used (but you can define more parameter types, although this is out of the scope of this documentation). @@ -825,8 +836,9 @@ this is out of the scope of this documentation). The "say" command uses phrase parameters (you can set a "say" callback to fires if a phrase contains one specific word). -In both cases, you need to import a function from `evennia.contrib.ingame_python.utils` and use it as third -parameter in your event definition. +In both cases, you need to import a function from +`evennia.contrib.base_systems.ingame_python.utils` and use it as third parameter in your +event definition. - `keyword_event` should be used for keyword parameters. - `phrase_event` should be used for phrase parameters. @@ -834,7 +846,7 @@ parameter in your event definition. For example, here is the definition of the "say" event: ```python -from evennia.contrib.ingame_python.utils import register_events, phrase_event +from evennia.contrib.base_systems.ingame_python.utils import register_events, phrase_event # ... @register_events class SomeTypeclass: @@ -865,5 +877,5 @@ The best way to do this is to use a custom setting, in your setting file EVENTS_DISABLED = True ``` -The in-game Python system will still be accessible (you will have access to the `@call` command, to debug), +The in-game Python system will still be accessible (you will have access to the `call` command, to debug), but no event will be called automatically. diff --git a/evennia/contrib/base_systems/ingame_python/__init__.py b/evennia/contrib/base_systems/ingame_python/__init__.py index e69de29bb2..87472ff1bf 100644 --- a/evennia/contrib/base_systems/ingame_python/__init__.py +++ b/evennia/contrib/base_systems/ingame_python/__init__.py @@ -0,0 +1,12 @@ +""" +In-game Python - vlgeoff 2017 + +""" + +from . import callbackhandler +from . import commands +from . import eventfuncs +from . import scripts +from . import tests +from . import typeclasses +from . import utils diff --git a/evennia/contrib/base_systems/ingame_python/commands.py b/evennia/contrib/base_systems/ingame_python/commands.py index 9fe57c8856..3b2d4063d3 100644 --- a/evennia/contrib/base_systems/ingame_python/commands.py +++ b/evennia/contrib/base_systems/ingame_python/commands.py @@ -5,12 +5,11 @@ Module containing the commands of the in-game Python system. from datetime import datetime from django.conf import settings -from evennia import Command from evennia.utils.ansi import raw from evennia.utils.eveditor import EvEditor from evennia.utils.evtable import EvTable from evennia.utils.utils import class_from_module, time_format -from evennia.contrib.ingame_python.utils import get_event_handler +from evennia.contrib.base_systems.ingame_python.utils import get_event_handler COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) diff --git a/evennia/contrib/base_systems/ingame_python/eventfuncs.py b/evennia/contrib/base_systems/ingame_python/eventfuncs.py index 200459ed86..05e3dfd077 100644 --- a/evennia/contrib/base_systems/ingame_python/eventfuncs.py +++ b/evennia/contrib/base_systems/ingame_python/eventfuncs.py @@ -5,8 +5,8 @@ Eventfuncs are just Python functions that can be used inside of calllbacks. """ -from evennia import ObjectDB, ScriptDB -from evennia.contrib.ingame_python.utils import InterruptEvent +from evennia import ObjectDB +from evennia.contrib.base_systems.ingame_python.utils import InterruptEvent def deny(): diff --git a/evennia/contrib/base_systems/ingame_python/scripts.py b/evennia/contrib/base_systems/ingame_python/scripts.py index 5fdc87ccab..cee407c2ae 100644 --- a/evennia/contrib/base_systems/ingame_python/scripts.py +++ b/evennia/contrib/base_systems/ingame_python/scripts.py @@ -10,13 +10,13 @@ import traceback from django.conf import settings from evennia import DefaultObject, DefaultScript, ChannelDB, ScriptDB -from evennia import logger, ObjectDB +from evennia import logger from evennia.utils.ansi import raw from evennia.utils.create import create_channel from evennia.utils.dbserialize import dbserialize from evennia.utils.utils import all_from_module, delay, pypath_to_realpath -from evennia.contrib.ingame_python.callbackhandler import CallbackHandler -from evennia.contrib.ingame_python.utils import get_next_wait, EVENTS, InterruptEvent +from evennia.contrib.base_systems.ingame_python.callbackhandler import CallbackHandler +from evennia.contrib.base_systems.ingame_python.utils import get_next_wait, EVENTS, InterruptEvent # Constants RE_LINE_ERROR = re.compile(r'^ File "\", line (\d+)') diff --git a/evennia/contrib/base_systems/ingame_python/tests.py b/evennia/contrib/base_systems/ingame_python/tests.py index be135ed63b..9531b230c4 100644 --- a/evennia/contrib/base_systems/ingame_python/tests.py +++ b/evennia/contrib/base_systems/ingame_python/tests.py @@ -12,8 +12,8 @@ from evennia.objects.objects import ExitCommand from evennia.utils import ansi, utils from evennia.utils.create import create_object, create_script from evennia.utils.test_resources import EvenniaTest -from evennia.contrib.ingame_python.commands import CmdCallback -from evennia.contrib.ingame_python.callbackhandler import CallbackHandler +from evennia.contrib.base_systems.ingame_python.commands import CmdCallback +from evennia.contrib.base_systems.ingame_python.callbackhandler import CallbackHandler # Force settings settings.EVENTS_CALENDAR = "standard" diff --git a/evennia/contrib/base_systems/ingame_python/typeclasses.py b/evennia/contrib/base_systems/ingame_python/typeclasses.py index 7d66c0a4a8..e2b42daf79 100644 --- a/evennia/contrib/base_systems/ingame_python/typeclasses.py +++ b/evennia/contrib/base_systems/ingame_python/typeclasses.py @@ -10,8 +10,8 @@ default ones in evennia core. from evennia import DefaultCharacter, DefaultExit, DefaultObject, DefaultRoom from evennia import ScriptDB from evennia.utils.utils import delay, inherits_from, lazy_property -from evennia.contrib.ingame_python.callbackhandler import CallbackHandler -from evennia.contrib.ingame_python.utils import register_events, time_event, phrase_event +from evennia.contrib.base_systems.ingame_python.callbackhandler import CallbackHandler +from evennia.contrib.base_systems.ingame_python.utils import register_events, time_event, phrase_event # Character help CHARACTER_CAN_DELETE = """ diff --git a/evennia/contrib/base_systems/ingame_python/utils.py b/evennia/contrib/base_systems/ingame_python/utils.py index 8b86bd25cd..d4ff77dcb6 100644 --- a/evennia/contrib/base_systems/ingame_python/utils.py +++ b/evennia/contrib/base_systems/ingame_python/utils.py @@ -5,17 +5,15 @@ These functions are to be used by developers to customize events and callbacks. """ -from textwrap import dedent - from django.conf import settings from evennia import logger from evennia import ScriptDB from evennia.utils.create import create_script from evennia.utils.gametime import real_seconds_until as standard_rsu from evennia.utils.utils import class_from_module -from evennia.contrib.custom_gametime import UNITS -from evennia.contrib.custom_gametime import gametime_to_realtime -from evennia.contrib.custom_gametime import real_seconds_until as custom_rsu +from evennia.contrib.base_systems.custom_gametime import UNITS +from evennia.contrib.base_systems.custom_gametime import gametime_to_realtime +from evennia.contrib.base_systems.custom_gametime import real_seconds_until as custom_rsu # Temporary storage for events waiting for the script to be started EVENTS = [] diff --git a/evennia/contrib/base_systems/menu_login/README.md b/evennia/contrib/base_systems/menu_login/README.md new file mode 100644 index 0000000000..f875b3233c --- /dev/null +++ b/evennia/contrib/base_systems/menu_login/README.md @@ -0,0 +1,22 @@ +# Menu-based login system + +Contribution - Vincent-lg 2016, Griatch 2019 (rework for modern EvMenu) + +This changes the Evennia login to ask for the account name and password in +sequence instead of requiring you to enter both at once. It uses EvMenu under +the hood. + +## Installation + +To install, add this to `mygame/server/conf/settings.py`: + + CMDSET_UNLOGGEDIN = "evennia.contrib.base_systems.menu_login.UnloggedinCmdSet" + CONNECTION_SCREEN_MODULE = "contrib.base_systems.menu_login.connection_screens" + +Reload the server and reconnect to see the changes. + +## Notes + +If you want to modify the way the connection screen looks, point +`CONNECTION_SCREEN_MODULE` to your own module. Use the default as a +guide (see also Evennia docs). diff --git a/evennia/contrib/base_systems/menu_login/__init__.py b/evennia/contrib/base_systems/menu_login/__init__.py new file mode 100644 index 0000000000..35a5637d91 --- /dev/null +++ b/evennia/contrib/base_systems/menu_login/__init__.py @@ -0,0 +1,7 @@ +""" +Menu-login - Vinvent-lg 2016, Griatch 2019 + +""" + +from .menu_login import UnloggedinCmdSet # noqa +from .menu_login import connection_screens # noqa diff --git a/evennia/contrib/base_systems/menu_login/connection_screens.py b/evennia/contrib/base_systems/menu_login/connection_screens.py new file mode 100644 index 0000000000..1eeaa64baa --- /dev/null +++ b/evennia/contrib/base_systems/menu_login/connection_screens.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +""" +Connection screen + +This is the text to show the user when they first connect to the game (before +they log in). + +To change the login screen in this module, do one of the following: + +- Define a function `connection_screen()`, taking no arguments. This will be + called first and must return the full string to act as the connection screen. + This can be used to produce more dynamic screens. +- Alternatively, define a string variable in the outermost scope of this module + with the connection string that should be displayed. If more than one such + variable is given, Evennia will pick one of them at random. + +The commands available to the user when the connection screen is shown +are defined in evennia.default_cmds.UnloggedinCmdSet. The parsing and display +of the screen is done by the unlogged-in "look" command. + +""" + +from django.conf import settings +from evennia import utils + +CONNECTION_SCREEN = """ +|b==============================================================|n + Welcome to |g{}|n, version {}! + + Enter |wh|nelp for more info. |wlook|n will re-show this screen. +|b==============================================================|n""".format( + settings.SERVERNAME, utils.get_evennia_version("short") +) diff --git a/evennia/contrib/base_systems/menu_login.py b/evennia/contrib/base_systems/menu_login/menu_login.py similarity index 96% rename from evennia/contrib/base_systems/menu_login.py rename to evennia/contrib/base_systems/menu_login/menu_login.py index 566f116694..3d50641387 100644 --- a/evennia/contrib/base_systems/menu_login.py +++ b/evennia/contrib/base_systems/menu_login/menu_login.py @@ -4,17 +4,17 @@ A login menu using EvMenu. Contribution - Vincent-lg 2016, Griatch 2019 (rework for modern EvMenu) This changes the Evennia login to ask for the account name and password in -sequence instead of requiring you to enter both at once. +sequence instead of requiring you to enter both at once. To install, add this line to the settings file (`mygame/server/conf/settings.py`): - CMDSET_UNLOGGEDIN = "evennia.contrib.menu_login.UnloggedinCmdSet" + CMDSET_UNLOGGEDIN = "evennia.base_systems.contrib.menu_login.UnloggedinCmdSet" Reload the server and the new connection method will be active. Note that you must -independently change the connection screen to match this login style, by editing +independently change the connection screen to match this login style, by editing `mygame/server/conf/connection_screens.py`. -This uses Evennia's menu system EvMenu and is triggered by a command that is +This uses Evennia's menu system EvMenu and is triggered by a command that is called automatically when a new user connects. """ @@ -32,8 +32,7 @@ _ACCOUNT = class_from_module(settings.BASE_ACCOUNT_TYPECLASS) _GUEST = class_from_module(settings.BASE_GUEST_TYPECLASS) _ACCOUNT_HELP = ( - "Enter the name you used to log into the game before, " "or a new account-name if you are new." -) + "Enter a new or existing login name.") _PASSWORD_HELP = ( "Password should be a minimum of 8 characters (preferably longer) and " "can contain a mix of letters, spaces, digits and @/./+/-/_/'/, only." diff --git a/evennia/contrib/base_systems/mux_comms_cmds/README.md b/evennia/contrib/base_systems/mux_comms_cmds/README.md new file mode 100644 index 0000000000..7a6e610bdd --- /dev/null +++ b/evennia/contrib/base_systems/mux_comms_cmds/README.md @@ -0,0 +1,42 @@ +# Legacy Comms-commands + +Contribution - Griatch 2021 + +In Evennia 1.0, the old Channel commands (originally inspired by MUX) were +replaced by the single `channel` command that performs all these function. +That command is still required to talk on channels. This contrib (extracted +from Evennia 0.9.5) reuses the channel-management of the base Channel command +but breaks out its functionality into separate Commands with MUX-familiar names. + +- `allcom` - `channel/all` and `channel` +- `addcom` - `channel/alias`, `channel/sub` and `channel/unmute` +- `delcom` - `channel/unalias`, `alias/unsub` and `channel/mute` +- `cboot` - `channel/boot` (`channel/ban` and `/unban` not supported) +- `cwho` - `channel/who` +- `ccreate` - `channel/create` +- `cdestroy` - `channel/destroy` +- `clock` - `channel/lock` +- `cdesc` - `channel/desc` + +## Installation + +- Import the `CmdSetLegacyComms` cmdset from this module into `mygame/commands/default_cmdsets.py` +- Add it to the CharacterCmdSet's `at_cmdset_creation` method (see below). +- Reload the server. + +```python +# in mygame/commands/default_cmdsets.py + +# .. +from evennia.contrib.base_systems.mux_comms_cmds import CmdSetLegacyComms # <---- + +class CharacterCmdSet(default_cmds.CharacterCmdSet): + # ... + def at_cmdset_creation(self): + # ... + self.add(CmdSetLegacyComms) # <---- + +``` + +Note that you will still be able to use the `channel` command; this is actually +still used under the hood by these commands. diff --git a/evennia/contrib/base_systems/mux_comms_cmds/__init__.py b/evennia/contrib/base_systems/mux_comms_cmds/__init__.py new file mode 100644 index 0000000000..d0bf42b9b5 --- /dev/null +++ b/evennia/contrib/base_systems/mux_comms_cmds/__init__.py @@ -0,0 +1,6 @@ +""" +Mux-style comms commands - Griatch 2021 + +""" + +from .mux_comms_cmds import CmdSetLegacyComms # noqa diff --git a/evennia/contrib/base_systems/mux_comms_cmds.py b/evennia/contrib/base_systems/mux_comms_cmds/mux_comms_cmds.py similarity index 99% rename from evennia/contrib/base_systems/mux_comms_cmds.py rename to evennia/contrib/base_systems/mux_comms_cmds/mux_comms_cmds.py index a50eb0b3b9..2e22d2b025 100644 --- a/evennia/contrib/base_systems/mux_comms_cmds.py +++ b/evennia/contrib/base_systems/mux_comms_cmds/mux_comms_cmds.py @@ -30,8 +30,8 @@ Example: ```python # in mygame/commands/default_cmdsets.py -# ... -from evennia.contrib.legacy_comms import CmdSetLegacyComms # <---- +# .. +from evennia.contrib.base_systems.mux_comms_cmds import CmdSetLegacyComms # <---- class CharacterCmdSet(default_cmds.CharacterCmdSet): # ... diff --git a/evennia/contrib/base_systems/unixcommand/README.md b/evennia/contrib/base_systems/unixcommand/README.md new file mode 100644 index 0000000000..b92e814815 --- /dev/null +++ b/evennia/contrib/base_systems/unixcommand/README.md @@ -0,0 +1,65 @@ +# Unix-like Command style parent + +Evennia contribution, Vincent Le Geoff 2017 + +This module contains a command class that allows for unix-style command syntax +in-game, using --options, positional arguments and stuff like -n 10 etc +similarly to a unix command. It might not the best syntax for the average player +but can be really useful for builders when they need to have a single command do +many things with many options. It uses the ArgumentParser from Python's standard +library under the hood. + +## Installation + +To use, inherit `UnixCommand` from this module from your own commands. You need +to override two methods: + +- The `init_parser` method, which adds options to the parser. Note that you + should normally *not* override the normal `parse` method when inheriting from + `UnixCommand`. +- The `func` method, called to execute the command once parsed (like any Command). + +Here's a short example: + +```python +from evennia.contrib.base_systems.unixcommand import UnixCommand + + +class CmdPlant(UnixCommand): + + ''' + Plant a tree or plant. + + This command is used to plant something in the room you are in. + + Examples: + plant orange -a 8 + plant strawberry --hidden + plant potato --hidden --age 5 + + ''' + + key = "plant" + + def init_parser(self): + "Add the arguments to the parser." + # 'self.parser' inherits `argparse.ArgumentParser` + self.parser.add_argument("key", + help="the key of the plant to be planted here") + self.parser.add_argument("-a", "--age", type=int, + default=1, help="the age of the plant to be planted") + self.parser.add_argument("--hidden", action="store_true", + help="should the newly-planted plant be hidden to players?") + + def func(self): + "func is called only if the parser succeeded." + # 'self.opts' contains the parsed options + key = self.opts.key + age = self.opts.age + hidden = self.opts.hidden + self.msg("Going to plant '{}', age={}, hidden={}.".format( + key, age, hidden)) +``` + +To see the full power of argparse and the types of supported options, visit +[the documentation of argparse](https://docs.python.org/2/library/argparse.html). diff --git a/evennia/contrib/base_systems/unixcommand/__init__.py b/evennia/contrib/base_systems/unixcommand/__init__.py new file mode 100644 index 0000000000..82b7c85d85 --- /dev/null +++ b/evennia/contrib/base_systems/unixcommand/__init__.py @@ -0,0 +1,6 @@ +""" +Unix-like Command style - vlgeoff 2017 + +""" + +from .unixcommand import UnixCommand # noqa diff --git a/evennia/contrib/base_systems/unixcommand.py b/evennia/contrib/base_systems/unixcommand/unixcommand.py similarity index 99% rename from evennia/contrib/base_systems/unixcommand.py rename to evennia/contrib/base_systems/unixcommand/unixcommand.py index afa491fdfb..de1c73ab8f 100644 --- a/evennia/contrib/base_systems/unixcommand.py +++ b/evennia/contrib/base_systems/unixcommand/unixcommand.py @@ -19,6 +19,9 @@ to override two methods: Here's a short example: ```python +from evennia.contrib.base_systems.unixcommand import UnixCommand + + class CmdPlant(UnixCommand): ''' diff --git a/evennia/contrib/full_systems/evscaperoom/README.md b/evennia/contrib/full_systems/evscaperoom/README.md index 113897b409..0c799b3560 100644 --- a/evennia/contrib/full_systems/evscaperoom/README.md +++ b/evennia/contrib/full_systems/evscaperoom/README.md @@ -6,19 +6,19 @@ This 'Evennia escaperoom game engine' was created for the MUD Coders Guild game Jam, April 14-May 15 2019. The theme for the jam was "One Room". This contains the utilities and base classes and an empty example room. -The original code for the contest is found at https://github.com/Griatch/evscaperoom -but the version on the public Evennia demo is more updated, so if you really -want the latest bug fixes etc you should rather look at -https://github.com/evennia/evdemo/tree/master/evdemo/evscaperoom instead. -A copy of the full game can also be played on the Evennia demo server at -https://demo.evennia.com - just connect to the server and write `evscaperoom` +The original code for the contest is found at +https://github.com/Griatch/evscaperoom but the version on the public Evennia +demo is more updated, so if you really want the latest bug fixes etc you should +rather look at https://github.com/evennia/evdemo/tree/master/evdemo/evscaperoom +instead. A copy of the full game can also be played on the Evennia demo server +at https://demo.evennia.com - just connect to the server and write `evscaperoom` in the first room to start! # Introduction Evscaperoom is, as it sounds, an escaperoom in text form. You start locked into -a room and have to figure out how to get out. This engine contains everything needed -to make a fully-featured puzzle game of this type! +a room and have to figure out how to get out. This engine contains everything +needed to make a fully-featured puzzle game of this type! # Installation @@ -38,8 +38,9 @@ class CharacterCmdSet(...): self.add(CmdEvscapeRoomStart()) ``` -Reload the server and the `evscaperoom` command will be available. The contrib comes -with a small (very small) escape room as an example. + +Reload the server and the `evscaperoom` command will be available. The contrib +comes with a small (very small) escape room as an example. # Making your own evscaperoom @@ -57,21 +58,20 @@ the following to your `mygame/server/conf/settings.py` file: ``` -Reload and the example evscaperoom should still work, but you can now modify and expand -it from your game dir! +Reload and the example evscaperoom should still work, but you can now modify and +expand it from your game dir! ## Other useful settings There are a few other settings that may be useful: -- `EVSCAPEROOM_START_STATE` - default is `state_001_start` and is the name of the - state-module to start from (without `.py`). You can change this if you want some - other naming scheme. +- `EVSCAPEROOM_START_STATE` - default is `state_001_start` and is the name of + the state-module to start from (without `.py`). You can change this if you + want some other naming scheme. - `HELP_SUMMARY_TEXT` - this is the help blurb shown when entering `help` in the room without an argument. The original is found at the top of `evennia/contrib/evscaperoom/commands.py`. - # Playing the game You should start by `look`ing around and at objects. diff --git a/evennia/contrib/full_systems/evscaperoom/__init__.py b/evennia/contrib/full_systems/evscaperoom/__init__.py index e69de29bb2..6c837f57a3 100644 --- a/evennia/contrib/full_systems/evscaperoom/__init__.py +++ b/evennia/contrib/full_systems/evscaperoom/__init__.py @@ -0,0 +1,13 @@ +""" +Evscaperoom - Griatch 2019 + +""" + +from .evscaperoom import commands # noqa +from .evscaperoom import menu # noqa +from .evscaperoom import objects # noqa +from .evscaperoom import room # noqa +from .evscaperoom import scripts # noqa +from .evscaperoom import state # noqa +from .evscaperoom import tests # noqa +from .evscaperoom import utils # noqa diff --git a/evennia/contrib/game_systems/barter/README.md b/evennia/contrib/game_systems/barter/README.md new file mode 100644 index 0000000000..8b273a6ad3 --- /dev/null +++ b/evennia/contrib/game_systems/barter/README.md @@ -0,0 +1,128 @@ +# Barter system + +Evennia contribution - Griatch 2012 + +This implements a full barter system - a way for players to safely +trade items between each other using code rather than simple free-form +talking. The advantage of this is increased buy/sell safety but it +also streamlines the process and makes it faster when doing many +transactions (since goods are automatically exchanged once both +agree). + +This system is primarily intended for a barter economy, but can easily +be used in a monetary economy as well -- just let the "goods" on one +side be coin objects (this is more flexible than a simple "buy" +command since you can mix coins and goods in your trade). + +## Installation + +Just import the CmdsetTrade command into (for example) the default +cmdset. This will make the trade (or barter) command available +in-game. + +```python +# in mygame/commands/default_cmdsets.py + +from evennia.contrib.game_systems import barter # <--- + +# ... +class CharacterCmdSet(default_cmds.CharacterCmdSet): + # ... + def at cmdset_creation(self): + # ... + self.add(barter.CmdsetTrade) # <--- + +``` + +## Usage + +In this module, a "barter" is generally referred to as a "trade". + +Below is an example of a barter sequence. A and B are the parties. +The `A>` and `B>` are their inputs. + +1) opening a trade + + A> trade B: Hi, I have a nice extra sword. You wanna trade? + + B sees: + A says: "Hi, I have a nice extra sword. You wanna trade?" + A wants to trade with you. Enter 'trade A ' to accept. + + B> trade A: Hm, I could use a good sword ... + + A sees: + B says: "Hm, I could use a good sword ... + B accepts the trade. Use 'trade help' for aid. + + B sees: + You are now trading with A. Use 'trade help' for aid. + +2) negotiating + + A> offer sword: This is a nice sword. I would need some rations in trade. + + B sees: A says: "This is a nice sword. I would need some rations in trade." + [A offers Sword of might.] + + B> evaluate sword + B sees: + + + B> offer ration: This is a prime ration. + + A sees: + B says: "This is a prime ration." + [B offers iron ration] + + A> say Hey, this is a nice sword, I need something more for it. + + B sees: + A says: "Hey this is a nice sword, I need something more for it." + + B> offer sword,apple: Alright. I will also include a magic apple. That's my last offer. + + A sees: + B says: "Alright, I will also include a magic apple. That's my last offer." + [B offers iron ration and magic apple] + + A> accept: You are killing me here, but alright. + + B sees: A says: "You are killing me here, but alright." + [A accepts your offer. You must now also accept.] + + B> accept: Good, nice making business with you. + You accept the deal. Deal is made and goods changed hands. + + A sees: B says: "Good, nice making business with you." + B accepts the deal. Deal is made and goods changed hands. + +At this point the trading system is exited and the negotiated items +are automatically exchanged between the parties. In this example B was +the only one changing their offer, but also A could have changed their +offer until the two parties found something they could agree on. The +emotes are optional but useful for RP-heavy worlds. + +## Technical info + +The trade is implemented by use of a TradeHandler. This object is a +common place for storing the current status of negotiations. It is +created on the object initiating the trade, and also stored on the +other party once that party agrees to trade. The trade request times +out after a certain time - this is handled by a Script. Once trade +starts, the CmdsetTrade cmdset is initiated on both parties along with +the commands relevant for the trading. + +## Ideas for NPC bartering + +This module is primarily intended for trade between two players. But +it can also in principle be used for a player negotiating with an +AI-controlled NPC. If the NPC uses normal commands they can use it +directly -- but more efficient is to have the NPC object send its +replies directly through the tradehandler to the player. One may want +to add some functionality to the decline command, so players can +decline specific objects in the NPC offer (decline ) and allow +the AI to maybe offer something else and make it into a proper +barter. Along with an AI that "needs" things or has some sort of +personality in the trading, this can make bartering with NPCs at least +moderately more interesting than just plain 'buy'. diff --git a/evennia/contrib/game_systems/barter/] b/evennia/contrib/game_systems/barter/] new file mode 100644 index 0000000000..72d1cbe47a --- /dev/null +++ b/evennia/contrib/game_systems/barter/] @@ -0,0 +1,83 @@ +# Clothing + +Evennia contribution - Tim Ashley Jenkins 2017 + +Provides a typeclass and commands for wearable clothing, +which is appended to a character's description when worn. + +Clothing items, when worn, are added to the character's description +in a list. For example, if wearing the following clothing items: + + a thin and delicate necklace + a pair of regular ol' shoes + one nice hat + a very pretty dress + +## Installation + +To install, import this module and have your default character +inherit from ClothedCharacter in your game's characters.py file: + +```python + +from evennia.contrib.game_systems.clothing import ClothedCharacter + +class Character(ClothedCharacter): + +``` + +And then add `ClothedCharacterCmdSet` in your character set in +`mygame/commands/default_cmdsets.py`: + +```python + +from evennia.contrib.game_systems.clothing import ClothedCharacterCmdSet <-- + +class CharacterCmdSet(default_cmds.CharacterCmdSet): + ... + at_cmdset_creation(self): + + super().at_cmdset_creation() + ... + self.add(ClothedCharacterCmdSet) # <-- + +``` + +From here, you can use the default builder commands to create clothes +with which to test the system: + + create a pretty shirt : evennia.contrib.clothing.Clothing + set shirt/clothing_type = 'top' + wear shirt + +A character's description may look like this: + + Superuser(#1) + This is User #1. + + Superuser is wearing one nice hat, a thin and delicate necklace, + a very pretty dress and a pair of regular ol' shoes. + +Characters can also specify the style of wear for their clothing - I.E. +to wear a scarf 'tied into a tight knot around the neck' or 'draped +loosely across the shoulders' - to add an easy avenue of customization. +For example, after entering: + + wear scarf draped loosely across the shoulders + +The garment appears like so in the description: + + Superuser(#1) + This is User #1. + + Superuser is wearing a fanciful-looking scarf draped loosely + across the shoulders. + +Items of clothing can be used to cover other items, and many options +are provided to define your own clothing types and their limits and +behaviors. For example, to have undergarments automatically covered +by outerwear, or to put a limit on the number of each type of item +that can be worn. The system as-is is fairly freeform - you +can cover any garment with almost any other, for example - but it +can easily be made more restrictive, and can even be tied into a +system for armor or other equipment. diff --git a/evennia/contrib/game_systems/barter/__init__.py b/evennia/contrib/game_systems/barter/__init__.py new file mode 100644 index 0000000000..3da5f68d46 --- /dev/null +++ b/evennia/contrib/game_systems/barter/__init__.py @@ -0,0 +1,6 @@ +""" +Barter contrib - Griatch 2012 + +""" + +from .barter import CmdsetTrade # noqa diff --git a/evennia/contrib/game_systems/barter.py b/evennia/contrib/game_systems/barter/barter.py similarity index 99% rename from evennia/contrib/game_systems/barter.py rename to evennia/contrib/game_systems/barter/barter.py index 3d693ea5a2..9bef89f6ea 100644 --- a/evennia/contrib/game_systems/barter.py +++ b/evennia/contrib/game_systems/barter/barter.py @@ -94,7 +94,9 @@ in-game. """ -from evennia import Command, DefaultScript, CmdSet +from evennia.commands.command import Command +from evennia.commands.cmdset import CmdSet +from evennia.scripts.scripts import DefaultScript TRADE_TIMEOUT = 60 # timeout for B to accept trade diff --git a/evennia/contrib/game_systems/clothing/README.md b/evennia/contrib/game_systems/clothing/README.md new file mode 100644 index 0000000000..0c07ef8dd2 --- /dev/null +++ b/evennia/contrib/game_systems/clothing/README.md @@ -0,0 +1,84 @@ +# Clothing + +Evennia contribution - Tim Ashley Jenkins 2017 + +Provides a typeclass and commands for wearable clothing, +which is appended to a character's description when worn. + +Clothing items, when worn, are added to the character's description +in a list. For example, if wearing the following clothing items: + + a thin and delicate necklace + a pair of regular ol' shoes + one nice hat + a very pretty dress + +## Installation + +To install, import this module and have your default character +inherit from ClothedCharacter in your game's characters.py file: + +```python + +from evennia.contrib.game_systems.clothing import ClothedCharacter + +class Character(ClothedCharacter): + +``` + +And then add `ClothedCharacterCmdSet` in your character set in +`mygame/commands/default_cmdsets.py`: + +```python + +from evennia.contrib.game_systems.clothing import ClothedCharacterCmdSet <-- + +class CharacterCmdSet(default_cmds.CharacterCmdSet): + # ... + at_cmdset_creation(self): + + super().at_cmdset_creation() + ... + self.add(ClothedCharacterCmdSet) # <-- + +``` + +From here, you can use the default builder commands to create clothes +with which to test the system: + + create a pretty shirt : evennia.contrib.clothing.Clothing + set shirt/clothing_type = 'top' + wear shirt + +A character's description may look like this: + + Superuser(#1) + This is User #1. + + Superuser is wearing one nice hat, a thin and delicate necklace, + a very pretty dress and a pair of regular ol' shoes. + +Characters can also specify the style of wear for their clothing - I.E. +to wear a scarf 'tied into a tight knot around the neck' or 'draped +loosely across the shoulders' - to add an easy avenue of customization. +For example, after entering: + + wear scarf draped loosely across the shoulders + +The garment appears like so in the description: + + Superuser(#1) + This is User #1. + + Superuser is wearing a fanciful-looking scarf draped loosely + across the shoulders. + +Items of clothing can be used to cover other items, and many options +are provided to define your own clothing types and their limits and +behaviors. For example, to have undergarments automatically covered +by outerwear, or to put a limit on the number of each type of item +that can be worn. The system as-is is fairly freeform - you +can cover any garment with almost any other, for example - but it +can easily be made more restrictive, and can even be tied into a +system for armor or other equipment. + diff --git a/evennia/contrib/game_systems/clothing/__init__.py b/evennia/contrib/game_systems/clothing/__init__.py new file mode 100644 index 0000000000..89008459e0 --- /dev/null +++ b/evennia/contrib/game_systems/clothing/__init__.py @@ -0,0 +1,7 @@ +""" +Clothing contrib - Tim Ashley Jenkins 2017 + +""" + +from .clothing import ClothedCharacter # noqa +from .clothing import ClothedCharacterCmdSet # noqa diff --git a/evennia/contrib/game_systems/clothing.py b/evennia/contrib/game_systems/clothing/clothing.py similarity index 100% rename from evennia/contrib/game_systems/clothing.py rename to evennia/contrib/game_systems/clothing/clothing.py diff --git a/evennia/contrib/game_systems/cooldowns/README.md b/evennia/contrib/game_systems/cooldowns/README.md new file mode 100644 index 0000000000..204502991b --- /dev/null +++ b/evennia/contrib/game_systems/cooldowns/README.md @@ -0,0 +1,58 @@ +# Cooldown contrib module. + +Evennia contrib - owllex, 2021 + +This contrib provides a simple cooldown handler that can be attached to any +typeclassed Object or Account. A cooldown is a lightweight persistent +asynchronous timer that you can query to see if it is ready. + +Cooldowns are good for modelling rate-limited actions, like how often a +character can perform a given command. + +Cooldowns are completely asynchronous and must be queried to know their +state. They do not fire callbacks, so are not a good fit for use cases +where something needs to happen on a specific schedule (use delay or +a TickerHandler for that instead). + +See also the evennia documentation for command cooldowns +(https://github.com/evennia/evennia/wiki/Command-Cooldown) for more information +about the concept. + +## Installation + +To use, simply add the following property to the typeclass definition of any +object type that you want to support cooldowns. It will expose a new `cooldowns` +property that persists data to the object's attribute storage. You can set this +on your base `Object` typeclass to enable cooldown tracking on every kind of +object, or just put it on your `Character` typeclass. + +By default the CooldownHandler will use the `cooldowns` property, but you can +customize this if desired by passing a different value for the `db_attribute` +parameter. + +```python +from evennia.game_systems.contrib.cooldowns import Cooldownhandler +from evennia.utils.utils import lazy_property + +@lazy_property +def cooldowns(self): + return CooldownHandler(self, db_attribute="cooldowns") +``` + +# Example + +Assuming you've installed cooldowns on your Character typeclasses, you can use a +cooldown to limit how often you can perform a command. The following code +snippet will limit the use of a Power Attack command to once every 10 seconds +per character. + +```python +class PowerAttack(Command): + def func(self): + if self.caller.cooldowns.ready("power attack"): + self.do_power_attack() + self.caller.cooldowns.add("power attack", 10) + else: + self.caller.msg("That's not ready yet!") + +``` diff --git a/evennia/contrib/game_systems/cooldowns/__init__.py b/evennia/contrib/game_systems/cooldowns/__init__.py new file mode 100644 index 0000000000..b02ddbc3ff --- /dev/null +++ b/evennia/contrib/game_systems/cooldowns/__init__.py @@ -0,0 +1,6 @@ +""" +Cooldown contrib - owllex 2021 + +""" + +from .cooldowns import CooldownHandler # noqa diff --git a/evennia/contrib/game_systems/cooldowns.py b/evennia/contrib/game_systems/cooldowns/cooldowns.py similarity index 99% rename from evennia/contrib/game_systems/cooldowns.py rename to evennia/contrib/game_systems/cooldowns/cooldowns.py index 3f9ba66bb6..b8c9118bbb 100644 --- a/evennia/contrib/game_systems/cooldowns.py +++ b/evennia/contrib/game_systems/cooldowns/cooldowns.py @@ -31,7 +31,7 @@ By default the CooldownHandler will use the `cooldowns` property, but you can customize this if desired by passing a different value for the db_attribute parameter. - from evennia.contrib.cooldowns import Cooldownhandler + from evennia.game_systems.contrib.cooldowns import Cooldownhandler from evennia.utils.utils import lazy_property @lazy_property @@ -52,6 +52,7 @@ class PowerAttack(Command): self.caller.cooldowns.add("power attack", 10) else: self.caller.msg("That's not ready yet!") + """ import math diff --git a/evennia/contrib/game_systems/crafting/README.md b/evennia/contrib/game_systems/crafting/README.md index 9f89b2935c..1bd02f5423 100644 --- a/evennia/contrib/game_systems/crafting/README.md +++ b/evennia/contrib/game_systems/crafting/README.md @@ -44,7 +44,7 @@ in there: ```python -from evennia.contrib.crafting.crafting import CraftingRecipe, CraftingValidationError +from evennia.contrib.game_systems.crafting import CraftingRecipe, CraftingValidationError class RecipeBread(CraftingRecipe): diff --git a/evennia/contrib/game_systems/crafting/__init__.py b/evennia/contrib/game_systems/crafting/__init__.py index e69de29bb2..e0b4fa26e7 100644 --- a/evennia/contrib/game_systems/crafting/__init__.py +++ b/evennia/contrib/game_systems/crafting/__init__.py @@ -0,0 +1,7 @@ +""" +Crafting - Griatch 2020 + +""" + +from .crafting import CraftingRecipe # noqa +from .crafting import CraftingValidationError # noqa diff --git a/evennia/contrib/game_systems/crafting/example_recipes.py b/evennia/contrib/game_systems/crafting/example_recipes.py index 9d12b6e2a7..3c307ff25b 100644 --- a/evennia/contrib/game_systems/crafting/example_recipes.py +++ b/evennia/contrib/game_systems/crafting/example_recipes.py @@ -73,9 +73,10 @@ from random import random, randint from evennia.commands.command import Command, InterruptCommand from .crafting import craft, CraftingRecipe, CraftingValidationError -#------------------------------------------------------------ + +# ------------------------------------------------------------ # Sword recipe -#------------------------------------------------------------ +# ------------------------------------------------------------ class PigIronRecipe(CraftingRecipe): """ diff --git a/evennia/contrib/game_systems/crafting/tests.py b/evennia/contrib/game_systems/crafting/tests.py index 9200c9426b..0d24c0c0ec 100644 --- a/evennia/contrib/game_systems/crafting/tests.py +++ b/evennia/contrib/game_systems/crafting/tests.py @@ -4,11 +4,10 @@ Unit tests for the crafting system contrib. """ from unittest import mock -from anything import Something from django.test import override_settings from django.core.exceptions import ObjectDoesNotExist from evennia.commands.default.tests import CommandTest -from evennia.utils.test_resources import TestCase, EvenniaTest +from evennia.utils.test_resources import TestCase from evennia.utils.create import create_object from . import crafting, example_recipes diff --git a/evennia/contrib/game_systems/gendersub/README.md b/evennia/contrib/game_systems/gendersub/README.md new file mode 100644 index 0000000000..e5d6c07f34 --- /dev/null +++ b/evennia/contrib/game_systems/gendersub/README.md @@ -0,0 +1,49 @@ +# Gendersub + +Contrib - Griatch 2015 + +This is a simple gender-aware Character class for allowing users to +insert custom markers in their text to indicate gender-aware +messaging. It relies on a modified msg() and is meant as an +inspiration and starting point to how to do stuff like this. + +An object can have the following genders: + +- male (he/his) +- female (her/hers) +- neutral (it/its) +- ambiguous (they/them/their/theirs) + +## Installation + +Import and add the `SetGender` command to your default cmdset in +`mygame/commands/default_cmdset.py` + +Make your `Character` inherit from `GenderCharacter`. + + +## 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: he, she, it, He, She, It, They +- `|o`, `|O`: Objective form: him, her, it, Him, Her, It, Them +- `|p`, `|P`: Possessive form: his, her, its, His, Her, Its, Their +- `|a`, `|A`: Absolute Possessive form: his, hers, its, His, Hers, Its, Theirs + +For example, + +``` +char.msg("%s falls on |p face with a thud." % char.key) +"Tom falls on his face with a thud" +``` + +The default gender is "ambiguous" (they/them/their/theirs). + +To use, have DefaultCharacter inherit from this, or change +setting.DEFAULT_CHARACTER to point to this class. + +The `gender` command is used to set the gender. It needs to be added to the +default cmdset before it becomes available. + diff --git a/evennia/contrib/game_systems/gendersub/__init__.py b/evennia/contrib/game_systems/gendersub/__init__.py new file mode 100644 index 0000000000..77a23e0a90 --- /dev/null +++ b/evennia/contrib/game_systems/gendersub/__init__.py @@ -0,0 +1,7 @@ +""" +Gendersub - Griatch 2015 + +""" + +from .gendersub import GenderCharacter # noqa +from .gendersub import SetGender # noqa diff --git a/evennia/contrib/game_systems/gendersub.py b/evennia/contrib/game_systems/gendersub/gendersub.py similarity index 97% rename from evennia/contrib/game_systems/gendersub.py rename to evennia/contrib/game_systems/gendersub/gendersub.py index 7a1fde2069..5eb6e5a3a8 100644 --- a/evennia/contrib/game_systems/gendersub.py +++ b/evennia/contrib/game_systems/gendersub/gendersub.py @@ -14,6 +14,8 @@ An object can have the following genders: - neutral (it/its) - ambiguous (they/them/their/theirs) +Usage + When in use, messages can contain special tags to indicate pronouns gendered based on the one being addressed. Capitalization will be retained. @@ -34,7 +36,7 @@ The default gender is "ambiguous" (they/them/their/theirs). To use, have DefaultCharacter inherit from this, or change setting.DEFAULT_CHARACTER to point to this class. -The `@gender` command is used to set the gender. It needs to be added to the +The `gender` command is used to set the gender. It needs to be added to the default cmdset before it becomes available. """ @@ -66,8 +68,8 @@ class SetGender(Command): """ - key = "@gender" - aliases = "@sex" + key = "gender" + aliases = "sex" locks = "call:all()" def func(self): diff --git a/evennia/contrib/game_systems/mail/README.md b/evennia/contrib/game_systems/mail/README.md new file mode 100644 index 0000000000..f0e321cb46 --- /dev/null +++ b/evennia/contrib/game_systems/mail/README.md @@ -0,0 +1,40 @@ +# In-Game Mail system + +Evennia Contribution - grungies1138 2016 + +A simple Brandymail style mail system that uses the Msg class from Evennia +Core. It has two Commands, both of which can be used on their own: + + - CmdMail - this should sit on the Account cmdset and makes the `mail` command + available both IC and OOC. Mails will always go to Accounts (other players). + - CmdMailCharacter - this should sit on the Character cmdset and makes the `mail` + command ONLY available when puppeting a character. Mails will be sent to other + Characters only and will not be available when OOC. + - If adding *both* commands to their respective cmdsets, you'll get two separate + IC and OOC mailing systems, with different lists of mail for IC and OOC modes. + +## Installation: + +Install one or both of the following (see above): + +- CmdMail (IC + OOC mail, sent between players) + + # mygame/commands/default_cmds.py + + from evennia.contrib.game_systems import mail + + # in AccountCmdSet.at_cmdset_creation: + self.add(mail.CmdMail()) + +- CmdMailCharacter (optional, IC only mail, sent between characters) + + # mygame/commands/default_cmds.py + + from evennia.contrib.game_systems import mail + + # in CharacterCmdSet.at_cmdset_creation: + self.add(mail.CmdMailCharacter()) + +Once installed, use `help mail` in game for help with the mail command. Use +ic/ooc to switch in and out of IC/OOC modes. + diff --git a/evennia/contrib/game_systems/mail/__init__.py b/evennia/contrib/game_systems/mail/__init__.py new file mode 100644 index 0000000000..ecc9e825c4 --- /dev/null +++ b/evennia/contrib/game_systems/mail/__init__.py @@ -0,0 +1,7 @@ +""" +Mail system - grungies 2016 + +""" + +from .mail import CmdMail # noqa +from .mail import CmdMailCharacter # noqa diff --git a/evennia/contrib/game_systems/mail.py b/evennia/contrib/game_systems/mail/mail.py similarity index 98% rename from evennia/contrib/game_systems/mail.py rename to evennia/contrib/game_systems/mail/mail.py index 2f458651f0..e326206464 100644 --- a/evennia/contrib/game_systems/mail.py +++ b/evennia/contrib/game_systems/mail/mail.py @@ -5,9 +5,10 @@ Evennia Contribution - grungies1138 2016 A simple Brandymail style @mail system that uses the Msg class from Evennia Core. It has two Commands, both of which can be used on their own: - - CmdMail - this should sit on the Account cmdset and makes the @mail command + + - CmdMail - this should sit on the Account cmdset and makes the `mail` command available both IC and OOC. Mails will always go to Accounts (other players). - - CmdMailCharacter - this should sit on the Character cmdset and makes the @mail + - CmdMailCharacter - this should sit on the Character cmdset and makes the `mail` command ONLY available when puppeting a character. Mails will be sent to other Characters only and will not be available when OOC. - If adding *both* commands to their respective cmdsets, you'll get two separate @@ -21,7 +22,7 @@ Install one or both of the following (see above): # mygame/commands/default_cmds.py - from evennia.contrib import mail + from evennia.contrib.game_systems import mail # in AccountCmdSet.at_cmdset_creation: self.add(mail.CmdMail()) @@ -30,13 +31,13 @@ Install one or both of the following (see above): # mygame/commands/default_cmds.py - from evennia.contrib import mail + from evennia.contrib.game_systems import mail # in CharacterCmdSet.at_cmdset_creation: self.add(mail.CmdMailCharacter()) Once installed, use `help mail` in game for help with the mail command. Use -@ic/@ooc to switch in and out of IC/OOC modes. +ic/ooc to switch in and out of IC/OOC modes. """ diff --git a/evennia/contrib/game_systems/multidescer/README.md b/evennia/contrib/game_systems/multidescer/README.md new file mode 100644 index 0000000000..e3805d5b45 --- /dev/null +++ b/evennia/contrib/game_systems/multidescer/README.md @@ -0,0 +1,24 @@ +# Evennia Multidescer + +Contrib - Griatch 2016 + +A "multidescer" is a concept from the MUSH world. It allows for +creating, managing and switching between multiple character +descriptions. This multidescer will not require any changes to the +Character class, rather it will use the `multidescs` Attribute (a +list) and create it if it does not exist. + +This contrib also works well together with the rpsystem contrib (which +also adds the short descriptions and the `sdesc` command). + +## Installation + +Edit `mygame/commands/default_cmdsets.py` and add +`from evennia.contrib.multidescer import CmdMultiDesc` to the top. + +Next, look up the `at_cmdset_create` method of the `CharacterCmdSet` +class and add a line `self.add(CmdMultiDesc())` to the end +of it. + +Reload the server and you should have the +desc command available (it +will replace the default `desc` command). diff --git a/evennia/contrib/game_systems/multidescer/__init__.py b/evennia/contrib/game_systems/multidescer/__init__.py new file mode 100644 index 0000000000..24b2ff2986 --- /dev/null +++ b/evennia/contrib/game_systems/multidescer/__init__.py @@ -0,0 +1,6 @@ +""" +Multidescer - Griatch 2016 + +""" + +from .multidescer import CmdMultiDesc # noqa diff --git a/evennia/contrib/game_systems/multidescer.py b/evennia/contrib/game_systems/multidescer/multidescer.py similarity index 99% rename from evennia/contrib/game_systems/multidescer.py rename to evennia/contrib/game_systems/multidescer/multidescer.py index 3c99d4ada7..fb5fc61b62 100644 --- a/evennia/contrib/game_systems/multidescer.py +++ b/evennia/contrib/game_systems/multidescer/multidescer.py @@ -1,5 +1,5 @@ """ -Evennia Mutltidescer +Evennia Multidescer Contrib - Griatch 2016 @@ -12,11 +12,10 @@ list) and create it if it does not exist. This contrib also works well together with the rpsystem contrib (which also adds the short descriptions and the `sdesc` command). - Installation: Edit `mygame/commands/default_cmdsets.py` and add -`from evennia.contrib.multidescer import CmdMultiDesc` to the top. +`from evennia.contrib.game_system.multidescer import CmdMultiDesc` to the top. Next, look up the `at_cmdset_create` method of the `CharacterCmdSet` class and add a line `self.add(CmdMultiDesc())` to the end diff --git a/evennia/contrib/game_systems/puzzles/README.md b/evennia/contrib/game_systems/puzzles/README.md new file mode 100644 index 0000000000..63a00fe357 --- /dev/null +++ b/evennia/contrib/game_systems/puzzles/README.md @@ -0,0 +1,68 @@ +# Puzzles System + +Evennia contribution - Henddher 2018 + +Provides a typeclass and commands for objects that can be combined (i.e. 'use'd) +to produce new objects. + +A Puzzle is a recipe of what objects (aka parts) must be combined by a player so +a new set of objects (aka results) are automatically created. + +## Installation + +Add the PuzzleSystemCmdSet to all players (e.g. in their Character typeclass). + +Alternatively: + + py self.cmdset.add('evennia.contrib.game_systems.puzzles.PuzzleSystemCmdSet') + +## Usage + +Consider this simple Puzzle: + + orange, mango, yogurt, blender = fruit smoothie + +As a Builder: + + create/drop orange + create/drop mango + create/drop yogurt + create/drop blender + create/drop fruit smoothie + + puzzle smoothie, orange, mango, yogurt, blender = fruit smoothie + ... + Puzzle smoothie(#1234) created successfuly. + + destroy/force orange, mango, yogurt, blender, fruit smoothie + + armpuzzle #1234 + Part orange is spawned at ... + Part mango is spawned at ... + .... + Puzzle smoothie(#1234) has been armed successfully + +As Player: + + use orange, mango, yogurt, blender + ... + Genius, you blended all fruits to create a fruit smoothie! + +## Details + +Puzzles are created from existing objects. The given +objects are introspected to create prototypes for the +puzzle parts and results. These prototypes become the +puzzle recipe. (See PuzzleRecipe and `puzzle` +command). Once the recipe is created, all parts and result +can be disposed (i.e. destroyed). + +At a later time, a Builder or a Script can arm the puzzle +and spawn all puzzle parts in their respective +locations (See armpuzzle). + +A regular player can collect the puzzle parts and combine +them (See use command). If player has specified +all pieces, the puzzle is considered solved and all +its puzzle parts are destroyed while the puzzle results +are spawened on their corresponding location. diff --git a/evennia/contrib/game_systems/puzzles/__init__.py b/evennia/contrib/game_systems/puzzles/__init__.py new file mode 100644 index 0000000000..4366b57385 --- /dev/null +++ b/evennia/contrib/game_systems/puzzles/__init__.py @@ -0,0 +1,7 @@ +""" +Puzzles - Henddher 2018 + +""" + +from .puzzles import PuzzleSystemCmdSet # noqa +from .puzzles import PuzzleRecipe # noqa diff --git a/evennia/contrib/base_systems/puzzles.py b/evennia/contrib/game_systems/puzzles/puzzles.py similarity index 98% rename from evennia/contrib/base_systems/puzzles.py rename to evennia/contrib/game_systems/puzzles/puzzles.py index 6052b7d43f..9e19ecf575 100644 --- a/evennia/contrib/base_systems/puzzles.py +++ b/evennia/contrib/game_systems/puzzles/puzzles.py @@ -9,25 +9,35 @@ A Puzzle is a recipe of what objects (aka parts) must be combined by a player so a new set of objects (aka results) are automatically created. +Installation: + +Add the PuzzleSystemCmdSet to all players (e.g. in their Character typeclass). + +Alternatively: + + py self.cmdset.add('evennia.contrib.game_systems.puzzles.PuzzleSystemCmdSet') + +Usage: + Consider this simple Puzzle: orange, mango, yogurt, blender = fruit smoothie As a Builder: - @create/drop orange - @create/drop mango - @create/drop yogurt - @create/drop blender - @create/drop fruit smoothie + create/drop orange + create/drop mango + create/drop yogurt + create/drop blender + create/drop fruit smoothie - @puzzle smoothie, orange, mango, yogurt, blender = fruit smoothie + puzzle smoothie, orange, mango, yogurt, blender = fruit smoothie ... Puzzle smoothie(#1234) created successfuly. - @destroy/force orange, mango, yogurt, blender, fruit smoothie + destroy/force orange, mango, yogurt, blender, fruit smoothie - @armpuzzle #1234 + armpuzzle #1234 Part orange is spawned at ... Part mango is spawned at ... .... @@ -50,7 +60,7 @@ can be disposed (i.e. destroyed). At a later time, a Builder or a Script can arm the puzzle and spawn all puzzle parts in their respective -locations (See @armpuzzle). +locations (See armpuzzle). A regular player can collect the puzzle parts and combine them (See use command). If player has specified @@ -58,12 +68,6 @@ all pieces, the puzzle is considered solved and all its puzzle parts are destroyed while the puzzle results are spawened on their corresponding location. -Installation: - -Add the PuzzleSystemCmdSet to all players. -Alternatively: - - @py self.cmdset.add('evennia.contrib.puzzles.PuzzleSystemCmdSet') """ diff --git a/evennia/contrib/game_systems/turnbattle/README.md b/evennia/contrib/game_systems/turnbattle/README.md index fd2563bceb..bae31e13ea 100644 --- a/evennia/contrib/game_systems/turnbattle/README.md +++ b/evennia/contrib/game_systems/turnbattle/README.md @@ -16,25 +16,25 @@ implemented and customized: tb_basic.py - The simplest system, which implements initiative and turn order, attack rolls against defense values, and damage to hit points. Only very basic game mechanics are included. - + tb_equip.py - Adds weapons and armor to the basic implementation of the battle system, including commands for wielding weapons and donning armor, and modifiers to accuracy and damage based on currently used equipment. - - tb_items.py - Adds usable items and conditions/status effects, and gives - a lot of examples for each. Items can perform nearly any sort of - function, including healing, adding or curing conditions, or - being used to attack. Conditions affect a fighter's attributes - and options in combat and persist outside of fights, counting - down per turn in combat and in real time outside combat. - - tb_magic.py - Adds a spellcasting system, allowing characters to cast - spells with a variety of effects by spending MP. Spells are - linked to functions, and as such can perform any sort of action - the developer can imagine - spells for attacking, healing and - conjuring objects are included as examples. - + + tb_items.py - Adds usable items and conditions/status effects, and gives + a lot of examples for each. Items can perform nearly any sort of + function, including healing, adding or curing conditions, or + being used to attack. Conditions affect a fighter's attributes + and options in combat and persist outside of fights, counting + down per turn in combat and in real time outside combat. + + tb_magic.py - Adds a spellcasting system, allowing characters to cast + spells with a variety of effects by spending MP. Spells are + linked to functions, and as such can perform any sort of action + the developer can imagine - spells for attacking, healing and + conjuring objects are included as examples. + tb_range.py - Adds a system for abstract positioning and movement, which tracks the distance between different characters and objects in combat, as well as differentiates between melee and ranged @@ -49,7 +49,7 @@ meant to closely emulate the experience of playing a tabletop RPG. Each of these modules contains the full functionality of the battle system with different customizations added in - the instructions to install each one is contained in the module itself. It's recommended that you install -and test tb_basic first, so you can better understand how the other +and test `tb_basic` first, so you can better understand how the other modules expand on it and get a better idea of how you can customize the system to your liking and integrate the subsystems presented here into your own combat system. diff --git a/evennia/contrib/game_systems/turnbattle/__init__.py b/evennia/contrib/game_systems/turnbattle/__init__.py index 8b13789179..08c8f55eb5 100644 --- a/evennia/contrib/game_systems/turnbattle/__init__.py +++ b/evennia/contrib/game_systems/turnbattle/__init__.py @@ -1 +1,10 @@ +""" +Turnbattle system - Tim Ashley Jenkins 2017 +""" + +from . import tb_basic # noqa +from . import tb_equip # noqa +from . import tb_items # noqa +from . import tb_magic # noqa +from . import tb_range # noqa diff --git a/evennia/contrib/grid/__init__.py b/evennia/contrib/grid/__init__.py new file mode 100644 index 0000000000..dfd05a1844 --- /dev/null +++ b/evennia/contrib/grid/__init__.py @@ -0,0 +1,5 @@ +""" +Extened Room - Griatch 2012, vincent-lg 2019 + +""" + diff --git a/evennia/contrib/grid/extended_room/README.md b/evennia/contrib/grid/extended_room/README.md new file mode 100644 index 0000000000..83c1e1735c --- /dev/null +++ b/evennia/contrib/grid/extended_room/README.md @@ -0,0 +1,76 @@ +# Extended Room + +Evennia Contribution - Griatch 2012, vincent-lg 2019 + +This is an extended Room typeclass for Evennia. It is supported +by an extended `Look` command and an extended `desc` command, also +in this module. + +## Installation/testing: + +Adding the `ExtendedRoomCmdset` to the default character cmdset will add all +new commands for use. + +In more detail, in `mygame/commands/default_cmdsets.py`: + +```python +... +from evennia.contrib import extended_room # <--- + +class CharacterCmdset(default_cmds.Character_CmdSet): + ... + def at_cmdset_creation(self): + ... + self.add(extended_room.ExtendedRoomCmdSet) # <--- + +``` + +Then reload to make the bew commands available. Note that they only work +on rooms with the typeclass `ExtendedRoom`. Create new rooms with the right +typeclass or use the `typeclass` command to swap existing rooms. + +## Features + +### Time-changing description slots + +This allows to change the full description text the room shows +depending on larger time variations. Four seasons (spring, summer, +autumn and winter) are used by default. The season is calculated +on-demand (no Script or timer needed) and updates the full text block. + +There is also a general description which is used as fallback if +one or more of the seasonal descriptions are not set when their +time comes. + +An updated `desc` command allows for setting seasonal descriptions. + +The room uses the `evennia.utils.gametime.GameTime` global script. This is +started by default, but if you have deactivated it, you need to +supply your own time keeping mechanism. + +### In-description changing tags + +Within each seasonal (or general) description text, you can also embed +time-of-day dependent sections. Text inside such a tag will only show +during that particular time of day. The tags looks like ` ... +`. By default there are four timeslots per day - morning, +afternoon, evening and night. + +### Details + +The Extended Room can be "detailed" with special keywords. This makes +use of a special `Look` command. Details are "virtual" targets to look +at, without there having to be a database object created for it. The +Details are simply stored in a dictionary on the room and if the look +command cannot find an object match for a `look ` command it +will also look through the available details at the current location +if applicable. The `detail` command is used to change details. + +### Extra commands + +- `CmdExtendedRoomLook` - look command supporting room details +- `CmdExtendedRoomDesc` - desc command allowing to add seasonal descs, +- `CmdExtendedRoomDetail` - command allowing to manipulate details in this room + as well as listing them +- `CmdExtendedRoomGameTime` - A simple `time` command, displaying the current + time and season. diff --git a/evennia/contrib/grid/extended_room/__init__.py b/evennia/contrib/grid/extended_room/__init__.py new file mode 100644 index 0000000000..45c61d8638 --- /dev/null +++ b/evennia/contrib/grid/extended_room/__init__.py @@ -0,0 +1,11 @@ +""" +Extended Room - Griatch 2012, vincent-lg 2019 + +""" + +from .extended_room import ExtendedRoom # noqa +from .extended_room import ExtendedRoomCmdSet # noqa +from .extended_room import CmdExtendedRoomLook # noqa +from .extended_room import CmdExtendedRoomDesc # noqa +from .extended_room import CmdExtendedRoomDetail # noqa +from .extended_room import CmdExtendedRoomGameTime # noqa diff --git a/evennia/contrib/grid/extended_room.py b/evennia/contrib/grid/extended_room/extended_room.py similarity index 99% rename from evennia/contrib/grid/extended_room.py rename to evennia/contrib/grid/extended_room/extended_room.py index c75ee35ec0..184ebf73d3 100644 --- a/evennia/contrib/grid/extended_room.py +++ b/evennia/contrib/grid/extended_room/extended_room.py @@ -45,7 +45,7 @@ at, without there having to be a database object created for it. The Details are simply stored in a dictionary on the room and if the look command cannot find an object match for a `look ` command it will also look through the available details at the current location -if applicable. The `@detail` command is used to change details. +if applicable. The `detail` command is used to change details. 4) Extra commands @@ -67,13 +67,13 @@ In more detail, in mygame/commands/default_cmdsets.py: ``` ... -from evennia.contrib import extended_room # <-new +from evennia.contrib import extended_room # <--- class CharacterCmdset(default_cmds.Character_CmdSet): ... def at_cmdset_creation(self): ... - self.add(extended_room.ExtendedRoomCmdSet) # <-new + self.add(extended_room.ExtendedRoomCmdSet) # <--- ``` diff --git a/evennia/contrib/grid/mapbuilder/README.md b/evennia/contrib/grid/mapbuilder/README.md new file mode 100644 index 0000000000..3ddad7d8ab --- /dev/null +++ b/evennia/contrib/grid/mapbuilder/README.md @@ -0,0 +1,278 @@ +# Map Builder + +Contribution - Cloud_Keeper 2016 + +Build a map from a 2D ASCII map. + +This is a command which takes two inputs: + + ≈≈≈≈≈ + ≈♣n♣≈ MAP_LEGEND = {("♣", "♠"): build_forest, + ≈∩▲∩≈ ("∩", "n"): build_mountains, + ≈♠n♠≈ ("▲"): build_temple} + ≈≈≈≈≈ + +A string of ASCII characters representing a map and a dictionary of functions +containing build instructions. The characters of the map are iterated over and +compared to a list of trigger characters. When a match is found the +corresponding function is executed generating the rooms, exits and objects as +defined by the users build instructions. If a character is not a match to +a provided trigger character (including spaces) it is simply skipped and the +process continues. + +For instance, the above map represents a temple (▲) amongst mountains (n,∩) +in a forest (♣,♠) on an island surrounded by water (≈). Each character on the +first line is iterated over but as there is no match with our `MAP_LEGEND`, it +is skipped. On the second line it finds "♣" which is a match and so the +`build_forest` function is called. Next the `build_mountains` function is +called and so on until the map is completed. Building instructions are passed +the following arguments: + + x - The rooms position on the maps x axis + y - The rooms position on the maps y axis + caller - The account calling the command + iteration - The current iterations number (0, 1 or 2) + room_dict - A dictionary containing room references returned by build + functions where tuple coordinates are the keys (x, y). + ie room_dict[(2, 2)] will return the temple room above. + +Building functions should return the room they create. By default these rooms +are used to create exits between valid adjacent rooms to the north, south, +east and west directions. This behaviour can turned off with the use of switch +arguments. In addition to turning off automatic exit generation the switches +allow the map to be iterated over a number of times. This is important for +something like custom exit building. Exits require a reference to both the +exits location and the exits destination. During the first iteration it is +possible that an exit is created pointing towards a destination that +has not yet been created resulting in error. By iterating over the map twice +the rooms can be created on the first iteration and room reliant code can be +be used on the second iteration. The iteration number and a dictionary of +references to rooms previously created is passed to the build commands. + +You then call the command in-game using the path to the MAP and MAP_LEGEND vars +The path you provide is relative to the evennia or mygame folder. + +# Installation + +Use by importing and including the command in your default_cmdsets module. +For example: + +```python + # mygame/commands/default_cmdsets.py + + from evennia.contrib.grid import mapbuilder + + ... + + self.add(mapbuilder.CmdMapBuilder()) +``` + + +# Usage: + + mapbuilder[/switch] + + one - execute build instructions once without automatic exit creation. + two - execute build instructions twice without automatic exit creation. + +# Examples + + mapbuilder world.gamemap.MAP world.maplegend.MAP_LEGEND + mapbuilder evennia.contrib.grid.mapbuilder.EXAMPLE1_MAP EXAMPLE1_LEGEND + mapbuilder/two evennia.contrib.mapbuilder.EXAMPLE2_MAP EXAMPLE2_LEGEND + (Legend path defaults to map path) + +Below are two examples showcasing the use of automatic exit generation and +custom exit generation. Whilst located, and can be used, from this module for +convenience The below example code should be in mymap.py in mygame/world. + +## Example One + +```python + +from django.conf import settings +from evennia.utils import utils + +# mapbuilder evennia.contrib.mapbuilder.EXAMPLE1_MAP EXAMPLE1_LEGEND + +# -*- coding: utf-8 -*- + +# Add the necessary imports for your instructions here. +from evennia import create_object +from typeclasses import rooms, exits +from random import randint +import random + + +# A map with a temple (▲) amongst mountains (n,∩) in a forest (♣,♠) on an +# island surrounded by water (≈). By giving no instructions for the water +# characters we effectively skip it and create no rooms for those squares. +EXAMPLE1_MAP = ''' +≈≈≈≈≈ +≈♣n♣≈ +≈∩▲∩≈ +≈♠n♠≈ +≈≈≈≈≈ +''' + +def example1_build_forest(x, y, **kwargs): + '''A basic example of build instructions. Make sure to include **kwargs + in the arguments and return an instance of the room for exit generation.''' + + # Create a room and provide a basic description. + room = create_object(rooms.Room, key="forest" + str(x) + str(y)) + room.db.desc = "Basic forest room." + + # Send a message to the account + kwargs["caller"].msg(room.key + " " + room.dbref) + + # This is generally mandatory. + return room + + +def example1_build_mountains(x, y, **kwargs): + '''A room that is a little more advanced''' + + # Create the room. + room = create_object(rooms.Room, key="mountains" + str(x) + str(y)) + + # Generate a description by randomly selecting an entry from a list. + room_desc = [ + "Mountains as far as the eye can see", + "Your path is surrounded by sheer cliffs", + "Haven't you seen that rock before?", + ] + room.db.desc = random.choice(room_desc) + + # Create a random number of objects to populate the room. + for i in range(randint(0, 3)): + rock = create_object(key="Rock", location=room) + rock.db.desc = "An ordinary rock." + + # Send a message to the account + kwargs["caller"].msg(room.key + " " + room.dbref) + + # This is generally mandatory. + return room + + +def example1_build_temple(x, y, **kwargs): + '''A unique room that does not need to be as general''' + + # Create the room. + room = create_object(rooms.Room, key="temple" + str(x) + str(y)) + + # Set the description. + room.db.desc = ( + "In what, from the outside, appeared to be a grand and " + "ancient temple you've somehow found yourself in the the " + "Evennia Inn! It consists of one large room filled with " + "tables. The bardisk extends along the east wall, where " + "multiple barrels and bottles line the shelves. The " + "barkeep seems busy handing out ale and chatting with " + "the patrons, which are a rowdy and cheerful lot, " + "keeping the sound level only just below thunderous. " + "This is a rare spot of mirth on this dread moor." + ) + + # Send a message to the account + kwargs["caller"].msg(room.key + " " + room.dbref) + + # This is generally mandatory. + return room + + +# Include your trigger characters and build functions in a legend dict. +EXAMPLE1_LEGEND = { + ("♣", "♠"): example1_build_forest, + ("∩", "n"): example1_build_mountains, + ("▲"): example1_build_temple, +} +``` + +## Example Two + +```python +# @mapbuilder/two evennia.contrib.mapbuilder.EXAMPLE2_MAP EXAMPLE2_LEGEND + +# -*- coding: utf-8 -*- + +# Add the necessary imports for your instructions here. +# from evennia import create_object +# from typeclasses import rooms, exits +# from evennia.utils import utils +# from random import randint +# import random + +# This is the same layout as Example 1 but included are characters for exits. +# We can use these characters to determine which rooms should be connected. +EXAMPLE2_MAP = ''' +≈ ≈ ≈ ≈ ≈ + +≈ ♣-♣-♣ ≈ + | | +≈ ♣ ♣ ♣ ≈ + | | | +≈ ♣-♣-♣ ≈ + +≈ ≈ ≈ ≈ ≈ +''' + +def example2_build_forest(x, y, **kwargs): + '''A basic room''' + # If on anything other than the first iteration - Do nothing. + if kwargs["iteration"] > 0: + return None + + room = create_object(rooms.Room, key="forest" + str(x) + str(y)) + room.db.desc = "Basic forest room." + + kwargs["caller"].msg(room.key + " " + room.dbref) + + return room + +def example2_build_verticle_exit(x, y, **kwargs): + '''Creates two exits to and from the two rooms north and south.''' + # If on the first iteration - Do nothing. + if kwargs["iteration"] == 0: + return + + north_room = kwargs["room_dict"][(x, y - 1)] + south_room = kwargs["room_dict"][(x, y + 1)] + + # create exits in the rooms + create_object( + exits.Exit, key="south", aliases=["s"], location=north_room, destination=south_room + ) + + create_object( + exits.Exit, key="north", aliases=["n"], location=south_room, destination=north_room + ) + + kwargs["caller"].msg("Connected: " + north_room.key + " & " + south_room.key) + + +def example2_build_horizontal_exit(x, y, **kwargs): + '''Creates two exits to and from the two rooms east and west.''' + # If on the first iteration - Do nothing. + if kwargs["iteration"] == 0: + return + + west_room = kwargs["room_dict"][(x - 1, y)] + east_room = kwargs["room_dict"][(x + 1, y)] + + create_object(exits.Exit, key="east", aliases=["e"], location=west_room, destination=east_room) + + create_object(exits.Exit, key="west", aliases=["w"], location=east_room, destination=west_room) + + kwargs["caller"].msg("Connected: " + west_room.key + " & " + east_room.key) + + +# Include your trigger characters and build functions in a legend dict. +EXAMPLE2_LEGEND = { + ("♣", "♠"): example2_build_forest, + ("|"): example2_build_verticle_exit, + ("-"): example2_build_horizontal_exit, +} + +``` diff --git a/evennia/contrib/grid/mapbuilder/__init__.py b/evennia/contrib/grid/mapbuilder/__init__.py new file mode 100644 index 0000000000..0a666c8c89 --- /dev/null +++ b/evennia/contrib/grid/mapbuilder/__init__.py @@ -0,0 +1,6 @@ +""" +Mapbuilder - Cloud Keeper 2016 + +""" + +from .mapbuilder import CmdMapBuilder # noqa diff --git a/evennia/contrib/grid/mapbuilder.py b/evennia/contrib/grid/mapbuilder/mapbuilder.py similarity index 93% rename from evennia/contrib/grid/mapbuilder.py rename to evennia/contrib/grid/mapbuilder/mapbuilder.py index 9d8e283dcd..1776ba525f 100644 --- a/evennia/contrib/grid/mapbuilder.py +++ b/evennia/contrib/grid/mapbuilder/mapbuilder.py @@ -51,43 +51,46 @@ the rooms can be created on the first iteration and room reliant code can be be used on the second iteration. The iteration number and a dictionary of references to rooms previously created is passed to the build commands. +You then call the command in-game using the path to the MAP and MAP_LEGEND vars +The path you provide is relative to the evennia or mygame folder. + +## Installation: + Use by importing and including the command in your default_cmdsets module. For example: +```python # mygame/commands/default_cmdsets.py - from evennia.contrib import mapbuilder + from evennia.contrib.grid import mapbuilder ... self.add(mapbuilder.CmdMapBuilder()) +``` -You then call the command in-game using the path to the MAP and MAP_LEGEND vars -The path you provide is relative to the evennia or mygame folder. -Usage: - @mapbuilder[/switch] +## Usage -Switches: - one - execute build instructions once without automatic exit creation. - two - execute build instructions twice without automatic exit creation. + mapbuilder[/switch] -Example: - @mapbuilder world.gamemap.MAP world.maplegend.MAP_LEGEND - @mapbuilder evennia.contrib.mapbuilder.EXAMPLE1_MAP EXAMPLE1_LEGEND - @mapbuilder/two evennia.contrib.mapbuilder.EXAMPLE2_MAP EXAMPLE2_LEGEND + one - execute build instructions once without automatic exit creation. + two - execute build instructions twice without automatic exit creation. + +## Example + + mapbuilder world.gamemap.MAP world.maplegend.MAP_LEGEND + mapbuilder evennia.contrib.grid.mapbuilder.EXAMPLE1_MAP EXAMPLE1_LEGEND + mapbuilder/two evennia.contrib.mapbuilder.EXAMPLE2_MAP EXAMPLE2_LEGEND (Legend path defaults to map path) Below are two examples showcasing the use of automatic exit generation and custom exit generation. Whilst located, and can be used, from this module for convenience The below example code should be in mymap.py in mygame/world. -""" +# Example One: -from django.conf import settings -from evennia.utils import utils - -# ---------- EXAMPLE 1 ---------- # +```python # @mapbuilder evennia.contrib.mapbuilder.EXAMPLE1_MAP EXAMPLE1_LEGEND # -*- coding: utf-8 -*- @@ -102,18 +105,17 @@ import random # A map with a temple (▲) amongst mountains (n,∩) in a forest (♣,♠) on an # island surrounded by water (≈). By giving no instructions for the water # characters we effectively skip it and create no rooms for those squares. -EXAMPLE1_MAP = """\ +EXAMPLE1_MAP = ''' ≈≈≈≈≈ ≈♣n♣≈ ≈∩▲∩≈ ≈♠n♠≈ ≈≈≈≈≈ -""" - +''' def example1_build_forest(x, y, **kwargs): - """A basic example of build instructions. Make sure to include **kwargs - in the arguments and return an instance of the room for exit generation.""" + '''A basic example of build instructions. Make sure to include **kwargs + in the arguments and return an instance of the room for exit generation.''' # Create a room and provide a basic description. room = create_object(rooms.Room, key="forest" + str(x) + str(y)) @@ -127,7 +129,7 @@ def example1_build_forest(x, y, **kwargs): def example1_build_mountains(x, y, **kwargs): - """A room that is a little more advanced""" + '''A room that is a little more advanced''' # Create the room. room = create_object(rooms.Room, key="mountains" + str(x) + str(y)) @@ -153,7 +155,7 @@ def example1_build_mountains(x, y, **kwargs): def example1_build_temple(x, y, **kwargs): - """A unique room that does not need to be as general""" + '''A unique room that does not need to be as general''' # Create the room. room = create_object(rooms.Room, key="temple" + str(x) + str(y)) @@ -184,8 +186,11 @@ EXAMPLE1_LEGEND = { ("∩", "n"): example1_build_mountains, ("▲"): example1_build_temple, } +``` -# ---------- EXAMPLE 2 ---------- # +# Example two + +```python # @mapbuilder/two evennia.contrib.mapbuilder.EXAMPLE2_MAP EXAMPLE2_LEGEND # -*- coding: utf-8 -*- @@ -199,7 +204,7 @@ EXAMPLE1_LEGEND = { # This is the same layout as Example 1 but included are characters for exits. # We can use these characters to determine which rooms should be connected. -EXAMPLE2_MAP = """\ +EXAMPLE2_MAP = ''' ≈ ≈ ≈ ≈ ≈ ≈ ♣-♣-♣ ≈ @@ -209,11 +214,10 @@ EXAMPLE2_MAP = """\ ≈ ♣-♣-♣ ≈ ≈ ≈ ≈ ≈ ≈ -""" - +''' def example2_build_forest(x, y, **kwargs): - """A basic room""" + '''A basic room''' # If on anything other than the first iteration - Do nothing. if kwargs["iteration"] > 0: return None @@ -227,7 +231,7 @@ def example2_build_forest(x, y, **kwargs): def example2_build_verticle_exit(x, y, **kwargs): - """Creates two exits to and from the two rooms north and south.""" + '''Creates two exits to and from the two rooms north and south.''' # If on the first iteration - Do nothing. if kwargs["iteration"] == 0: return @@ -248,7 +252,7 @@ def example2_build_verticle_exit(x, y, **kwargs): def example2_build_horizontal_exit(x, y, **kwargs): - """Creates two exits to and from the two rooms east and west.""" + '''Creates two exits to and from the two rooms east and west.''' # If on the first iteration - Do nothing. if kwargs["iteration"] == 0: return @@ -270,7 +274,14 @@ EXAMPLE2_LEGEND = { ("-"): example2_build_horizontal_exit, } -# ---------- END OF EXAMPLES ---------- # +``` + +--- +""" +from django.conf import settings +from evennia.utils import utils +from evennia import create_object +from typeclasses import exits COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS) diff --git a/evennia/contrib/grid/simpledoor/README.md b/evennia/contrib/grid/simpledoor/README.md new file mode 100644 index 0000000000..ceeb858b7e --- /dev/null +++ b/evennia/contrib/grid/simpledoor/README.md @@ -0,0 +1,44 @@ +# SimpleDoor + +Contribution - Griatch 2016 + +A simple two-way exit that represents a door that can be opened and +closed. Can easily be expanded from to make it lockable, destroyable +etc. Note that the simpledoor is based on Evennia locks, so it will +not work for a superuser (which bypasses all locks) - the superuser +will always appear to be able to close/open the door over and over +without the locks stopping you. To use the door, use `@quell` or a +non-superuser account. + +## Installation: + +Import `SimpleDoorCmdSet` from this module into `mygame/commands/default_cmdsets` +and add it to your `CharacterCmdSet`: + +```python +# in mygame/commands/default_cmdsets.py + +from evennia.contrib.grid import simpledoor <--- + +class CharacterCmdSet(default_cmds.CharacterCmdSet): + # ... + def at_cmdset_creation(self): + # ... + self.add(simpledoor.SimpleDoorCmdSet) + +``` + +## Usage: + +To try it out, `dig` a new room and then use the (overloaded) `@open` +commmand to open a new doorway to it like this: + + @open doorway:contrib.grid.simpledoor.SimpleDoor = otherroom + + open doorway + close doorway + +Note: This uses locks, so if you are a superuser you will not be blocked by +a locked door - `quell` yourself, if so. Normal users will find that they +cannot pass through either side of the door once it's closed from the other +side. diff --git a/evennia/contrib/grid/simpledoor/__init__.py b/evennia/contrib/grid/simpledoor/__init__.py new file mode 100644 index 0000000000..8643fa4607 --- /dev/null +++ b/evennia/contrib/grid/simpledoor/__init__.py @@ -0,0 +1,7 @@ +""" +Simple Door - Griatch 2016 + +""" + +from .simpledoor import SimpleDoorCmdSet # noqa +from .simpledoor import SimpleDoor # noqa diff --git a/evennia/contrib/grid/simpledoor.py b/evennia/contrib/grid/simpledoor/simpledoor.py similarity index 88% rename from evennia/contrib/grid/simpledoor.py rename to evennia/contrib/grid/simpledoor/simpledoor.py index 152a8bf711..6c22926a75 100644 --- a/evennia/contrib/grid/simpledoor.py +++ b/evennia/contrib/grid/simpledoor/simpledoor.py @@ -13,14 +13,29 @@ non-superuser account. Installation: - Import this module in mygame/commands/default_cmdsets and add - the CmdOpen and CmdOpenCloseDoor commands to the CharacterCmdSet; - then reload the server. -To try it out, `@dig` a new room and then use the (overloaded) `@open` +Import this module in mygame/commands/default_cmdsets and add +the SimpleDoorCmdSet to your CharacterCmdSet: + +```python +# in mygame/commands/default_cmdsets.py + +from evennia.contrib.grid import simpledoor <--- + +class CharacterCmdSet(default_cmds.CharacterCmdSet): + # ... + def at_cmdset_creation(self): + # ... + self.add(simpledoor.SimpleDoorCmdSet) + +``` + +Usage: + +To try it out, `dig` a new room and then use the (overloaded) `@open` commmand to open a new doorway to it like this: - @open doorway:contrib.simpledoor.SimpleDoor = otherroom + @open doorway:contrib.grid.simpledoor.SimpleDoor = otherroom You can then use `open doorway' and `close doorway` to change the open state. If you are not superuser (`@quell` yourself) you'll find you @@ -30,6 +45,7 @@ other side. """ from evennia import DefaultExit, default_cmds +from evennia.commands.cmdset import CmdSet from evennia.utils.utils import inherits_from @@ -170,3 +186,9 @@ class CmdOpenCloseDoor(default_cmds.MuxCommand): else: door.setlock("traverse:false()") self.caller.msg("You close %s." % door.key) + + +class SimpleDoorCmdSet(CmdSet): + def at_cmdset_creation(self): + self.add(CmdOpen()) + self.add(CmdOpenCloseDoor()) diff --git a/evennia/contrib/grid/slow_exit/README.md b/evennia/contrib/grid/slow_exit/README.md new file mode 100644 index 0000000000..d3813d85c1 --- /dev/null +++ b/evennia/contrib/grid/slow_exit/README.md @@ -0,0 +1,61 @@ +# Slow Exit + +Contribution - Griatch 2014 + +This is an example of an Exit-type that delays its traversal. This simulates +slow movement, common in many different types of games. The contrib also +contains two commands, `CmdSetSpeed` and CmdStop for changing the movement speed +and abort an ongoing traversal, respectively. + +## Installation: + +To try out an exit of this type, you could connect two existing rooms +using something like this: + + @open north:contrib.grid.slow_exit.SlowExit = + +To make this your new default exit, modify `mygame/typeclasses/exits.py` +to import this module and change the default `Exit` class to inherit +from `SlowExit` instead. + +``` +# in mygame/typeclasses/exits.py + +from evennia.contrib.grid.slowexit import SlowExit + +class Exit(SlowExit): + # ... + +``` + +To get the ability to change your speed and abort your movement, import + +```python +# in mygame/commands/default_cmdsets.py + +from evennia.contrib.grid import slow_exit <--- + +class CharacterCmdSet(default_cmds.CharacterCmdSet): + # ... + def at_cmdset_creation(self): + # ... + self.add(slow_exit.SlowDoorCmdSet) <--- + +``` + +simply import and add CmdSetSpeed and CmdStop from this module to your +default cmdset (see tutorials on how to do this if you are unsure). + +To try out an exit of this type, you could connect two existing rooms using +something like this: + + @open north:contrib.grid.slow_exit.SlowExit = + + +## Notes: + +This implementation is efficient but not persistent; so incomplete +movement will be lost in a server reload. This is acceptable for most +game types - to simulate longer travel times (more than the couple of +seconds assumed here), a more persistent variant using Scripts or the +TickerHandler might be better. diff --git a/evennia/contrib/grid/slow_exit/__init__.py b/evennia/contrib/grid/slow_exit/__init__.py new file mode 100644 index 0000000000..510066a372 --- /dev/null +++ b/evennia/contrib/grid/slow_exit/__init__.py @@ -0,0 +1,9 @@ +""" +Slow Exit - Griatch 2014 + +""" + +from .slow_exit import SlowExitCmdSet # noqa +from .slow_exit import SlowExit # noqa +from .slow_exit import CmdSetSpeed # noqa +from .slow_exit import CmdStop # noqa diff --git a/evennia/contrib/grid/slow_exit.py b/evennia/contrib/grid/slow_exit/slow_exit.py similarity index 78% rename from evennia/contrib/grid/slow_exit.py rename to evennia/contrib/grid/slow_exit/slow_exit.py index 68b4eeddd9..2b4ff0f003 100644 --- a/evennia/contrib/grid/slow_exit.py +++ b/evennia/contrib/grid/slow_exit/slow_exit.py @@ -4,28 +4,48 @@ Slow Exit typeclass Contribution - Griatch 2014 -This is an example of an Exit-type that delays its traversal.This +This is an example of an Exit-type that delays its traversal. This simulates slow movement, common in many different types of games. The -contrib also contains two commands, CmdSetSpeed and CmdStop for changing +contrib also contains two commands, `CmdSetSpeed` and CmdStop for changing the movement speed and abort an ongoing traversal, respectively. +## Installation: + To try out an exit of this type, you could connect two existing rooms using something like this: -@open north:contrib.slow_exit.SlowExit = +@open north:contrib.grid.slow_exit.SlowExit = +To make this your new default exit, modify `mygame/typeclasses/exits.py` +to import this module and change the default `Exit` class to inherit +from `SlowExit` instead. -Installation: +``` +# in mygame/typeclasses/exits.py -To make this your new default exit, modify mygame/typeclasses/exits.py -to import this module and change the default Exit class to inherit -from SlowExit instead. +from evennia.contrib.grid.slowexit import SlowExit -To get the ability to change your speed and abort your movement, -simply import and add CmdSetSpeed and CmdStop from this module to your -default cmdset (see tutorials on how to do this if you are unsure). +class Exit(SlowExit): + # ... -Notes: +``` + +To get the ability to change your speed and abort your movement, import + +```python +# in mygame/commands/default_cmdsets.py + +from evennia.contrib.grid import slow_exit <--- + +class CharacterCmdSet(default_cmds.CharacterCmdSet): + # ... + def at_cmdset_creation(self): + # ... + self.add(slow_exit.SlowDoorCmdSet) <--- + +``` + +## Notes: This implementation is efficient but not persistent; so incomplete movement will be lost in a server reload. This is acceptable for most @@ -35,7 +55,11 @@ TickerHandler might be better. """ -from evennia import DefaultExit, utils, Command +from evennia.objects.objects import DefaultExit +from evennia.utils import utils +from evennia.commands.command import Command +from evennia.commands.cmdset import CmdSet + MOVE_DELAY = {"stroll": 6, "walk": 4, "run": 2, "sprint": 1} @@ -142,3 +166,9 @@ class CmdStop(Command): observer.msg("%s stops." % self.caller.get_display_name(observer)) else: self.caller.msg("You are not moving.") + + +class SlowExitCmdSet(CmdSet): + def at_cmdset_creation(self): + self.add(CmdSetSpeed()) + self.add(CmdStop()) diff --git a/evennia/contrib/grid/wilderness/README.md b/evennia/contrib/grid/wilderness/README.md new file mode 100644 index 0000000000..2a29c7d19a --- /dev/null +++ b/evennia/contrib/grid/wilderness/README.md @@ -0,0 +1,113 @@ +# Wilderness system + +Evennia contrib - titeuf87 2017 + +This contrib provides a wilderness map without actually creating a large number +of rooms - as you move, your room is instead updated with different +descriptions. This means you can make huge areas with little database use as +long as the rooms are relatively similar (name/desc changing). + +## Installation + +This contrib does not provide any new commands. Instead the default `py` command +is used to call functions/classes in this contrib directly. + +## Usage + +A wilderness map needs to created first. There can be different maps, all +with their own name. If no name is provided, then a default one is used. Internally, +the wilderness is stored as a Script with the name you specify. If you don't +specify the name, a script named "default" will be created and used. + + @py from evennia.contrib.grid import wilderness; wilderness.create_wilderness() + +Once created, it is possible to move into that wilderness map: + + @py from evennia.contrib.grid import wilderness; wilderness.enter_wilderness(me) + +All coordinates used by the wilderness map are in the format of `(x, y)` +tuples. x goes from left to right and y goes from bottom to top. So `(0, 0)` +is the bottom left corner of the map. + +## Customisation + +The defaults, while useable, are meant to be customised. When creating a +new wilderness map it is possible to give a "map provider": this is a +python object that is smart enough to create the map. + +The default provider, `WildernessMapProvider`, just creates a grid area that +is unlimited in size. +This `WildernessMapProvider` can be subclassed to create more interesting +maps and also to customize the room/exit typeclass used. + +There is also no command that allows players to enter the wilderness. This +still needs to be added: it can be a command or an exit, depending on your +needs. + +## Example + +To give an example of how to customize, we will create a very simple (and +small) wilderness map that is shaped like a pyramid. The map will be +provided as a string: a "." symbol is a location we can walk on. + +Let's create a file `world/pyramid.py`: + +```python +# mygame/world/pyramid.py + +map_str = ''' + . + ... + ..... + ....... +''' + +from evennia.contrib.grid import wilderness + +class PyramidMapProvider(wilderness.WildernessMapProvider): + + def is_valid_coordinates(self, wilderness, coordinates): + "Validates if these coordinates are inside the map" + x, y = coordinates + try: + lines = map_str.split("\n") + # The reverse is needed because otherwise the pyramid will be + # upside down + lines.reverse() + line = lines[y] + column = line[x] + return column == "." + except IndexError: + return False + + def get_location_name(self, coordinates): + "Set the location name" + x, y = coordinates + if y == 3: + return "Atop the pyramid." + else: + return "Inside a pyramid." + + def at_prepare_room(self, coordinates, caller, room): + "Any other changes done to the room before showing it" + x, y = coordinates + desc = "This is a room in the pyramid." + if y == 3 : + desc = "You can see far and wide from the top of the pyramid." + room.db.desc = desc +``` + +Now we can use our new pyramid-shaped wilderness map. From inside Evennia we +create a new wilderness (with the name "default") but using our new map provider: + + py from world import pyramid as p; p.wilderness.create_wilderness(mapprovider=p.PyramidMapProvider()) + py from evennia.contrib import wilderness; wilderness.enter_wilderness(me, coordinates=(4, 1)) + +## Implementation details + +When a character moves into the wilderness, they get their own room. If they +move, instead of moving the character, the room changes to match the new +coordinates. If a character meets another character in the wilderness, then +their room merges. When one of the character leaves again, they each get their +own separate rooms. Rooms are created as needed. Unneeded rooms are stored away +to avoid the overhead cost of creating new rooms again in the future. diff --git a/evennia/contrib/grid/wilderness/__init__.py b/evennia/contrib/grid/wilderness/__init__.py new file mode 100644 index 0000000000..8e1c9490a1 --- /dev/null +++ b/evennia/contrib/grid/wilderness/__init__.py @@ -0,0 +1,14 @@ +""" +Wilderness contrib - titeuf87, 2017 + +""" + +from .wilderness import create_wilderness # noqa +from .wilderness import enter_wilderness # noqa +from .wilderness import get_new_coordinates # noqa +from .wilderness import WildernessScript # noqa +from .wilderness import WildernessExit # noqa +from .wilderness import WildernessRoom # noqa +from .wilderness import WildernessMapProvider # noqa +from .wilderness import Wilderness # noqa +from .wilderness import WildernessMap # noqa diff --git a/evennia/contrib/grid/wilderness.py b/evennia/contrib/grid/wilderness/wilderness.py similarity index 88% rename from evennia/contrib/grid/wilderness.py rename to evennia/contrib/grid/wilderness/wilderness.py index 689eec074b..7d35d43986 100644 --- a/evennia/contrib/grid/wilderness.py +++ b/evennia/contrib/grid/wilderness/wilderness.py @@ -7,43 +7,43 @@ This contrib provides a wilderness map. This is an area that can be huge where the rooms are mostly similar, except for some small cosmetic changes like the room name. -Usage: +## Usage - This contrib does not provide any commands. Instead the @py command can be - used. +This contrib does not provide any new commands. Instead the default `py` command +is used. - A wilderness map needs to created first. There can be different maps, all - with their own name. If no name is provided, then a default one is used. Internally, - the wilderness is stored as a Script with the name you specify. If you don't - specify the name, a script named "default" will be created and used. +A wilderness map needs to created first. There can be different maps, all +with their own name. If no name is provided, then a default one is used. Internally, +the wilderness is stored as a Script with the name you specify. If you don't +specify the name, a script named "default" will be created and used. - @py from evennia.contrib import wilderness; wilderness.create_wilderness() + @py from evennia.contrib.grid import wilderness; wilderness.create_wilderness() - Once created, it is possible to move into that wilderness map: +Once created, it is possible to move into that wilderness map: - @py from evennia.contrib import wilderness; wilderness.enter_wilderness(me) + @py from evennia.contrib.grid import wilderness; wilderness.enter_wilderness(me) - All coordinates used by the wilderness map are in the format of `(x, y)` - tuples. x goes from left to right and y goes from bottom to top. So `(0, 0)` - is the bottom left corner of the map. +All coordinates used by the wilderness map are in the format of `(x, y)` +tuples. x goes from left to right and y goes from bottom to top. So `(0, 0)` +is the bottom left corner of the map. -Customisation: +## Customisation - The defaults, while useable, are meant to be customised. When creating a - new wilderness map it is possible to give a "map provider": this is a - python object that is smart enough to create the map. +The defaults, while useable, are meant to be customised. When creating a +new wilderness map it is possible to give a "map provider": this is a +python object that is smart enough to create the map. - The default provider, WildernessMapProvider, just creates a grid area that - is unlimited in size. - This WildernessMapProvider can be subclassed to create more interesting - maps and also to customize the room/exit typeclass used. +The default provider, `WildernessMapProvider`, just creates a grid area that +is unlimited in size. +This `WildernessMapProvider` can be subclassed to create more interesting +maps and also to customize the room/exit typeclass used. - There is also no command that allows players to enter the wilderness. This - still needs to be added: it can be a command or an exit, depending on your - needs. +There is also no command that allows players to enter the wilderness. This +still needs to be added: it can be a command or an exit, depending on your +needs. -Customisation example: +## Example To give an example of how to customize, we will create a very simple (and small) wilderness map that is shaped like a pyramid. The map will be @@ -51,59 +51,56 @@ Customisation example: Let's create a file world/pyramid.py: - ```python - map_str = \"\"\" - . - ... - ..... - ....... - \"\"\" +```python +map_str = ''' + . + ... + ..... + ....... +''' - from evennia.contrib import wilderness +from evennia.contrib.grid import wilderness - class PyramidMapProvider(wilderness.WildernessMapProvider): +class PyramidMapProvider(wilderness.WildernessMapProvider): - def is_valid_coordinates(self, wilderness, coordinates): - "Validates if these coordinates are inside the map" - x, y = coordinates - try: - lines = map_str.split("\n") - # The reverse is needed because otherwise the pyramid will be - # upside down - lines.reverse() - line = lines[y] - column = line[x] - return column == "." - except IndexError: - return False + def is_valid_coordinates(self, wilderness, coordinates): + "Validates if these coordinates are inside the map" + x, y = coordinates + try: + lines = map_str.split("\n") + # The reverse is needed because otherwise the pyramid will be + # upside down + lines.reverse() + line = lines[y] + column = line[x] + return column == "." + except IndexError: + return False - def get_location_name(self, coordinates): - "Set the location name" - x, y = coordinates - if y == 3: - return "Atop the pyramid." - else: - return "Inside a pyramid." + def get_location_name(self, coordinates): + "Set the location name" + x, y = coordinates + if y == 3: + return "Atop the pyramid." + else: + return "Inside a pyramid." - def at_prepare_room(self, coordinates, caller, room): - "Any other changes done to the room before showing it" - x, y = coordinates - desc = "This is a room in the pyramid." - if y == 3 : - desc = "You can see far and wide from the top of the pyramid." - room.db.desc = desc - ``` + def at_prepare_room(self, coordinates, caller, room): + "Any other changes done to the room before showing it" + x, y = coordinates + desc = "This is a room in the pyramid." + if y == 3 : + desc = "You can see far and wide from the top of the pyramid." + room.db.desc = desc +``` - Now we can use our new pyramid-shaped wilderness map. From inside Evennia we - create a new wilderness (with the name "default") but using our new map provider: +Now we can use our new pyramid-shaped wilderness map. From inside Evennia we +create a new wilderness (with the name "default") but using our new map provider: - ``` - @py from world import pyramid as p; p.wilderness.create_wilderness(mapprovider=p.PyramidMapProvider()) + py from world import pyramid as p; p.wilderness.create_wilderness(mapprovider=p.PyramidMapProvider()) + py from evennia.contrib import wilderness; wilderness.enter_wilderness(me, coordinates=(4, 1)) - @py from evennia.contrib import wilderness; wilderness.enter_wilderness(me, coordinates=(4, 1)) - - ``` -Implementation details: +## Implementation details When a character moves into the wilderness, they get their own room. If they move, instead of moving the character, the room changes to match the diff --git a/evennia/contrib/grid/xyzgrid/__init__.py b/evennia/contrib/grid/xyzgrid/__init__.py index e69de29bb2..5e3c1299c9 100644 --- a/evennia/contrib/grid/xyzgrid/__init__.py +++ b/evennia/contrib/grid/xyzgrid/__init__.py @@ -0,0 +1,15 @@ +""" +XYZGrid - Evennia 2021 + +""" + +from . import commands # noqa +from . import example # noqa +from . import launchcmd # noqa +from . import prototypes # noqa +from . import tests # noqa +from . import utils # noqa +from . import xymap # noqa +from . import xymap_legend # noqa +from . import xyzgrid # noqa +from . import xyzroom # noqa diff --git a/evennia/contrib/grid/xyzgrid/commands.py b/evennia/contrib/grid/xyzgrid/commands.py index ab38e54060..7a23231076 100644 --- a/evennia/contrib/grid/xyzgrid/commands.py +++ b/evennia/contrib/grid/xyzgrid/commands.py @@ -12,8 +12,8 @@ from django.conf import settings from evennia import InterruptCommand from evennia import default_cmds, CmdSet from evennia.commands.default import building -from evennia.contrib.xyzgrid.xyzroom import XYZRoom -from evennia.contrib.xyzgrid.xyzgrid import get_xyzgrid +from evennia.contrib.grid.xyzgrid.xyzroom import XYZRoom +from evennia.contrib.grid.xyzgrid.xyzgrid import get_xyzgrid from evennia.utils import ansi from evennia.utils.utils import list_to_string, class_from_module, delay diff --git a/evennia/contrib/grid/xyzgrid/example.py b/evennia/contrib/grid/xyzgrid/example.py index bd2bc74502..42bc39ae8a 100644 --- a/evennia/contrib/grid/xyzgrid/example.py +++ b/evennia/contrib/grid/xyzgrid/example.py @@ -17,7 +17,7 @@ Then """ -from evennia.contrib.xyzgrid import xymap_legend +from evennia.contrib.grid.xyzgrid import xymap_legend # default prototype parent. It's important that # the typeclass inherits from the XYZRoom (or XYZExit) diff --git a/evennia/contrib/grid/xyzgrid/launchcmd.py b/evennia/contrib/grid/xyzgrid/launchcmd.py index b84380920d..b06ec03a3f 100644 --- a/evennia/contrib/grid/xyzgrid/launchcmd.py +++ b/evennia/contrib/grid/xyzgrid/launchcmd.py @@ -20,7 +20,7 @@ from os.path import join as pathjoin from django.conf import settings import evennia from evennia.utils import ansi -from evennia.contrib.xyzgrid.xyzgrid import get_xyzgrid +from evennia.contrib.grid.xyzgrid.xyzgrid import get_xyzgrid _HELP_SHORT = """ diff --git a/evennia/contrib/grid/xyzgrid/tests.py b/evennia/contrib/grid/xyzgrid/tests.py index cf739c0e98..be7a81456e 100644 --- a/evennia/contrib/grid/xyzgrid/tests.py +++ b/evennia/contrib/grid/xyzgrid/tests.py @@ -4,7 +4,6 @@ Tests for the XYZgrid system. """ -from time import time from random import randint from parameterized import parameterized from django.test import TestCase diff --git a/evennia/contrib/grid/xyzgrid/xyzgrid.py b/evennia/contrib/grid/xyzgrid/xyzgrid.py index 24f1781146..77eb797bf7 100644 --- a/evennia/contrib/grid/xyzgrid/xyzgrid.py +++ b/evennia/contrib/grid/xyzgrid/xyzgrid.py @@ -18,7 +18,7 @@ The grid has three main functions: """ from evennia.scripts.scripts import DefaultScript from evennia.utils import logger -from evennia.utils.utils import variable_from_module, make_iter +from evennia.utils.utils import variable_from_module from .xymap import XYMap from .xyzroom import XYZRoom, XYZExit diff --git a/evennia/contrib/grid/xyzgrid/xyzroom.py b/evennia/contrib/grid/xyzgrid/xyzroom.py index 22c9e0f34a..e69f2bb4b1 100644 --- a/evennia/contrib/grid/xyzgrid/xyzroom.py +++ b/evennia/contrib/grid/xyzgrid/xyzroom.py @@ -10,7 +10,6 @@ used as stand-alone XYZ-coordinate-aware rooms. from django.db.models import Q from evennia.objects.objects import DefaultRoom, DefaultExit from evennia.objects.manager import ObjectManager -from evennia.utils.utils import make_iter # name of all tag categories. Note that the Z-coordinate is # the `map_name` of the XYZgrid diff --git a/evennia/contrib/rpg/chargen.py b/evennia/contrib/rpg/chargen.py deleted file mode 100644 index 06ee4e68bf..0000000000 --- a/evennia/contrib/rpg/chargen.py +++ /dev/null @@ -1,194 +0,0 @@ -""" - -Contribution - Griatch 2011 - -> Note - with the advent of MULTISESSION_MODE=2, this is not really as -necessary anymore - the ooclook and @charcreate commands in that mode -replaces this module with better functionality. This remains here for -inspiration. - -This is a simple character creation commandset for the Account level. -It shows some more info and gives the Account the option to create a -character without any more customizations than their name (further -options are unique for each game anyway). - -In MULTISESSION_MODEs 0 and 1, you will automatically log into an -existing Character. When using `@ooc` you will then end up in this -cmdset. - -Installation: - -Import this module to `mygame/commands/default_cmdsets.py` and -add `chargen.OOCCMdSetCharGen` to the `AccountCmdSet` class -(it says where to add it). Reload. - -""" - -from django.conf import settings -from evennia import Command, create_object, utils -from evennia import default_cmds, managers - -CHARACTER_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS - - -class CmdOOCLook(default_cmds.CmdLook): - """ - ooc look - - Usage: - look - look - - This is an OOC version of the look command. Since an Account doesn't - have an in-game existence, there is no concept of location or - "self". - - If any characters are available for you to control, you may look - at them with this command. - """ - - key = "look" - aliases = ["l", "ls"] - locks = "cmd:all()" - help_category = "General" - - def func(self): - """ - Implements the ooc look command - - We use an attribute _character_dbrefs on the account in order - to figure out which characters are "theirs". A drawback of this - is that only the CmdCharacterCreate command adds this attribute, - and thus e.g. account #1 will not be listed (although it will work). - Existence in this list does not depend on puppeting rights though, - that is checked by the @ic command directly. - """ - - # making sure caller is really an account - self.character = None - if utils.inherits_from(self.caller, "evennia.objects.objects.Object"): - # An object of some type is calling. Convert to account. - self.character = self.caller - if hasattr(self.caller, "account"): - self.caller = self.caller.account - - if not self.character: - # ooc mode, we are accounts - - avail_chars = self.caller.db._character_dbrefs - if self.args: - # Maybe the caller wants to look at a character - if not avail_chars: - self.caller.msg("You have no characters to look at. Why not create one?") - return - objs = managers.objects.get_objs_with_key_and_typeclass( - self.args.strip(), CHARACTER_TYPECLASS - ) - objs = [obj for obj in objs if obj.id in avail_chars] - if not objs: - self.caller.msg("You cannot see this Character.") - return - self.caller.msg(objs[0].return_appearance(self.caller)) - return - - # not inspecting a character. Show the OOC info. - charnames = [] - if self.caller.db._character_dbrefs: - dbrefs = self.caller.db._character_dbrefs - charobjs = [managers.objects.get_id(dbref) for dbref in dbrefs] - charnames = [charobj.key for charobj in charobjs if charobj] - if charnames: - charlist = "The following Character(s) are available:\n\n" - charlist += "\n\r".join(["|w %s|n" % charname for charname in charnames]) - charlist += "\n\n Use |w@ic |n to switch to that Character." - else: - charlist = "You have no Characters." - string = """ You, %s, are an |wOOC ghost|n without form. The world is hidden - from you and besides chatting on channels your options are limited. - You need to have a Character in order to interact with the world. - - %s - - Use |wcreate |n to create a new character and |whelp|n for a - list of available commands.""" % ( - self.caller.key, - charlist, - ) - self.caller.msg(string) - - else: - # not ooc mode - leave back to normal look - # we have to put this back for normal look to work. - self.caller = self.character - super().func() - - -class CmdOOCCharacterCreate(Command): - """ - creates a character - - Usage: - create - - This will create a new character, assuming - the given character name does not already exist. - """ - - key = "create" - locks = "cmd:all()" - - def func(self): - """ - Tries to create the Character object. We also put an - attribute on ourselves to remember it. - """ - - # making sure caller is really an account - self.character = None - if utils.inherits_from(self.caller, "evennia.objects.objects.Object"): - # An object of some type is calling. Convert to account. - self.character = self.caller - if hasattr(self.caller, "account"): - self.caller = self.caller.account - - if not self.args: - self.caller.msg("Usage: create ") - return - charname = self.args.strip() - old_char = managers.objects.get_objs_with_key_and_typeclass(charname, CHARACTER_TYPECLASS) - if old_char: - self.caller.msg("Character |c%s|n already exists." % charname) - return - # create the character - - new_character = create_object(CHARACTER_TYPECLASS, key=charname) - if not new_character: - self.caller.msg( - "|rThe Character couldn't be created. This is a bug. Please contact an admin." - ) - return - # make sure to lock the character to only be puppeted by this account - new_character.locks.add( - "puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" - % (new_character.id, self.caller.id) - ) - - # save dbref - avail_chars = self.caller.db._character_dbrefs - if avail_chars: - avail_chars.append(new_character.id) - else: - avail_chars = [new_character.id] - self.caller.db._character_dbrefs = avail_chars - self.caller.msg("|gThe character |c%s|g was successfully created!" % charname) - - -class OOCCmdSetCharGen(default_cmds.AccountCmdSet): - """ - Extends the default OOC cmdset. - """ - - def at_cmdset_creation(self): - """Install everything from the default set, then overload""" - self.add(CmdOOCLook()) - self.add(CmdOOCCharacterCreate()) diff --git a/evennia/contrib/rpg/dice/README.md b/evennia/contrib/rpg/dice/README.md new file mode 100644 index 0000000000..6b39d200a9 --- /dev/null +++ b/evennia/contrib/rpg/dice/README.md @@ -0,0 +1,61 @@ +# Dice + +Rolls dice for roleplaying, in-game gambling or GM:ing + +Evennia contribution - Griatch 2012 + +# Installation: + + +Add the `CmdDice` command from this module to your character's cmdset +(and then restart the server): + +```python +# in mygame/commands/default_cmdsets.py + +# ... +from evennia.contrib.rpg import dice <--- + +class CharacterCmdSet(default_cmds.CharacterCmdSet): + # ... + def at_object_creation(self): + # ... + self.add(dice.CmdDice()) # <--- + +``` + +# Usage: + + > roll 1d100 + 2 + > roll 1d20 + > roll 1d20 - 4 + +The result of the roll will be echoed to the room + +One can also specify a standard Python operator in order to specify +eventual target numbers and get results in a fair and guaranteed +unbiased way. For example: + + > roll 2d6 + 2 < 8 + +Rolling this will inform all parties if roll was indeed below 8 or not. + + > roll/hidden + +Informs the room that the roll is being made without telling what the result +was. + + > roll/secret + +Is a hidden roll that does not inform the room it happened. + +## Rolling dice from code + +To roll dice in code, use the `roll` function from this module: + +```python + +from evennia.contrib.rpg import dice +dice.roll(3, 10, ("+", 2)) # 3d10 + 2 + +``` diff --git a/evennia/contrib/rpg/dice/__init__.py b/evennia/contrib/rpg/dice/__init__.py new file mode 100644 index 0000000000..1c196bb27f --- /dev/null +++ b/evennia/contrib/rpg/dice/__init__.py @@ -0,0 +1,9 @@ +""" +Rolling dice - Griatch, 2012 + +""" + +from .dice import roll # noqa +from .dice import roll_dice # noqa +from .dice import CmdDice # noqa +from .dice import DiceCmdSet # noqa diff --git a/evennia/contrib/rpg/dice.py b/evennia/contrib/rpg/dice/dice.py similarity index 91% rename from evennia/contrib/rpg/dice.py rename to evennia/contrib/rpg/dice/dice.py index 73285e60fc..7b7078684d 100644 --- a/evennia/contrib/rpg/dice.py +++ b/evennia/contrib/rpg/dice/dice.py @@ -1,13 +1,16 @@ """ -Dice - rolls dice for roleplaying, in-game gambling or GM:ing +# Dice + +Rolls dice for roleplaying, in-game gambling or GM:ing Evennia contribution - Griatch 2012 - -This module implements a full-fledged dice-roller and a 'dice' command +This module implements a a dice-roller and a `dice`/`roll` command to go with it. It uses standard RPG 'd'-syntax (e.g. 2d6 to roll two six-sided die) and also supports modifiers such as 3d6 + 5. + > roll 1d20 + 2 + One can also specify a standard Python operator in order to specify eventual target numbers and get results in a fair and guaranteed unbiased way. For example a GM could (using the dice command) from @@ -16,17 +19,38 @@ required to succeed. The command will normally echo this result to all parties (although it also has options for hidden and secret rolls). -Installation: +# Installation: -To use in your code, just import the roll_dice function from this module. -To use the dice/roll command, just import this module in your custom -cmdset module and add the following line to the end of DefaultCmdSet's -at_cmdset_creation(): +Add the `CmdDice` command from this module to your character's cmdset +(and then restart the server): - self.add(dice.CmdDice()) +```python +# in mygame/commands/default_cmdsets.py -After a reload the dice (or roll) command will be available in-game. +# ... +from evennia.contrib.rpg import dice <--- + +class CharacterCmdSet(default_cmds.CharacterCmdSet): + # ... + def at_object_creation(self): + # ... + self.add(dice.CmdDice()) # <--- + +``` + +# Usage: + + roll 1d100 + 10 + +To roll dice in code, use the `roll` function from this module: + +```python + +from evennia.contrib.rpg import dice +dice.roll_dice(3, 10, ("+", 2)) # 3d10 + 2 + +``` """ import re @@ -34,7 +58,8 @@ from random import randint from evennia import default_cmds, CmdSet -def roll_dice(dicenum, dicetype, modifier=None, conditional=None, return_tuple=False): +def roll(dicenum, dicetype, modifier=None, + conditional=None, return_tuple=False): """ This is a standard dice roller. @@ -116,6 +141,9 @@ def roll_dice(dicenum, dicetype, modifier=None, conditional=None, return_tuple=F else: return result +# legacy alias +roll_dice = roll + RE_PARTS = re.compile(r"(d|\+|-|/|\*|<|>|<=|>=|!=|==)") RE_MOD = re.compile(r"(\+|-|/|\*)") diff --git a/evennia/contrib/rpg/health_bar/README.md b/evennia/contrib/rpg/health_bar/README.md new file mode 100644 index 0000000000..4c16cf2f38 --- /dev/null +++ b/evennia/contrib/rpg/health_bar/README.md @@ -0,0 +1,36 @@ +# Health Bar + +Contrib - Tim Ashley Jenkins 2017 + +The function provided in this module lets you easily display visual +bars or meters - "health bar" is merely the most obvious use for this, +though these bars are highly customizable and can be used for any sort +of appropriate data besides player health. + +Today's players may be more used to seeing statistics like health, +stamina, magic, and etc. displayed as bars rather than bare numerical +values, so using this module to present this data this way may make it +more accessible. Keep in mind, however, that players may also be using +a screen reader to connect to your game, which will not be able to +represent the colors of the bar in any way. By default, the values +represented are rendered as text inside the bar which can be read by +screen readers. + +## Usage + +No installation, just import and use `display_meter` from this +module: + +```python + from evennia.contrib.rpg.health_bar import display_meter + + # health is 23/100 + health_bar = display_meter(23, 100) + caller.msg(prompt=health_bar) + +``` + +The health bar will account for current values above the maximum or +below 0, rendering them as a completely full or empty bar with the +values displayed within. + diff --git a/evennia/contrib/rpg/health_bar/__init__.py b/evennia/contrib/rpg/health_bar/__init__.py new file mode 100644 index 0000000000..f98069157d --- /dev/null +++ b/evennia/contrib/rpg/health_bar/__init__.py @@ -0,0 +1,6 @@ +""" +Health bar - Tim Ashley Jenkins, 2017 + +""" + +from .health_bar import display_meter # noqa diff --git a/evennia/contrib/rpg/health_bar.py b/evennia/contrib/rpg/health_bar/health_bar.py similarity index 93% rename from evennia/contrib/rpg/health_bar.py rename to evennia/contrib/rpg/health_bar/health_bar.py index db6bd2212b..df6f6c86e1 100644 --- a/evennia/contrib/rpg/health_bar.py +++ b/evennia/contrib/rpg/health_bar/health_bar.py @@ -20,6 +20,17 @@ screen readers. The health bar will account for current values above the maximum or below 0, rendering them as a completely full or empty bar with the values displayed within. + +No installation, just import and use `display_meter` from this +module: + +```python + from evennia.contrib.rpg.health_bar import display_meter + + health_bar = display_meter(23, 100) + caller.msg(prompt=health_bar) +``` + """ @@ -38,12 +49,12 @@ def display_meter( """ Represents a current and maximum value given as a "bar" rendered with ANSI or xterm256 background colors. - + Args: cur_value (int): Current value to display max_value (int): Maximum value to display - - Options: + + Keyword Arguments: length (int): Length of meter returned, in characters fill_color (list): List of color codes for the full portion of the bar, sans any sort of prefix - both ANSI and xterm256 @@ -62,6 +73,10 @@ def display_meter( the bar. It's highly recommended you keep this on, especially if there's no info given in pre_text or post_text, as players on screen readers will be unable to read the graphical aspect of the bar. + + Returns: + str: The display bar to show. + """ # Start by building the base string. num_text = "" diff --git a/evennia/contrib/rpg/rpsystem/README.md b/evennia/contrib/rpg/rpsystem/README.md new file mode 100644 index 0000000000..8870d3096a --- /dev/null +++ b/evennia/contrib/rpg/rpsystem/README.md @@ -0,0 +1,257 @@ +# Roleplaying base system for Evennia + +Roleplaying emotes/sdescs - Griatch, 2015 +Language/whisper emotes - Griatch, 2015 + +## 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. diff --git a/evennia/contrib/rpg/rpsystem/__init__.py b/evennia/contrib/rpg/rpsystem/__init__.py new file mode 100644 index 0000000000..d78217ea7f --- /dev/null +++ b/evennia/contrib/rpg/rpsystem/__init__.py @@ -0,0 +1,19 @@ +""" +Roleplaying emotes and language - Griatch, 2015 + +""" + +from .rpsystem import EmoteError, SdescError, RecogError, LanguageError # noqa +from .rpsystem import ordered_permutation_regex, regex_tuple_from_key_alias # noqa +from .rpsystem import parse_language, parse_sdescs_and_recogs, send_emote # noqa +from .rpsystem import SdescHandler, RecogHandler # noqa +from .rpsystem import RPCommand, CmdEmote, CmdSay, CmdSdesc, CmdPose, CmdRecog, CmdMask # noqa +from .rpsystem import RPSystemCmdSet # noqa +from .rpsystem import ContribRPObject # noqa +from .rpsystem import ContribRPRoom # noqa +from .rpsystem import ContribRPCharacter # noqa + +from .rplanguage import LanguageError, LanguageExistsError # noqa +from .rplanguage import LanguageHandler # noqa +from .rplanguage import obfuscate_language, obfuscate_whisper # noqa +from .rplanguage import add_language, available_languages # noqa diff --git a/evennia/contrib/rpg/rplanguage.py b/evennia/contrib/rpg/rpsystem/rplanguage.py similarity index 99% rename from evennia/contrib/rpg/rplanguage.py rename to evennia/contrib/rpg/rpsystem/rplanguage.py index 307b7ff4e0..d3d561c85e 100644 --- a/evennia/contrib/rpg/rplanguage.py +++ b/evennia/contrib/rpg/rpsystem/rplanguage.py @@ -3,7 +3,6 @@ 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: diff --git a/evennia/contrib/rpg/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py similarity index 93% rename from evennia/contrib/rpg/rpsystem.py rename to evennia/contrib/rpg/rpsystem/rpsystem.py index cad9236ff7..68b902a0a5 100644 --- a/evennia/contrib/rpg/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -8,44 +8,93 @@ 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. - -To use, simply import the typclasses you want from this module and use -them to create your objects, or set them to default. +- 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) +- 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): + # ... + +``` Examples: @@ -72,31 +121,31 @@ 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. -Verbose Installation Instructions: +Extra Installation Instructions: - 1. In typeclasses/character.py: - Import the `ContribRPCharacter` class: - `from evennia.contrib.rpsystem import ContribRPCharacter` - Inherit ContribRPCharacter: - Change "class Character(DefaultCharacter):" to - `class Character(ContribRPCharacter):` - If you have any overriden calls in `at_object_creation(self)`: - Add `super().at_object_creation()` as the top line. - 2. In `typeclasses/rooms.py`: - Import the `ContribRPRoom` class: - `from evennia.contrib.rpsystem import ContribRPRoom` - Inherit `ContribRPRoom`: - Change `class Room(DefaultRoom):` to - `class Room(ContribRPRoom):` - 3. In `typeclasses/objects.py` - Import the `ContribRPObject` class: - `from evennia.contrib.rpsystem import ContribRPObject` - Inherit `ContribRPObject`: - Change `class Object(DefaultObject):` to - `class Object(ContribRPObject):` - 4. Reload the server (`reload` or from console: "evennia reload") - 5. Force typeclass updates as required. Example for your character: - `type/reset/force me = typeclasses.characters.Character` +1. In typeclasses/character.py: + Import the `ContribRPCharacter` class: + `from evennia.contrib.rpsystem import ContribRPCharacter` + Inherit ContribRPCharacter: + Change "class Character(DefaultCharacter):" to + `class Character(ContribRPCharacter):` + If you have any overriden calls in `at_object_creation(self)`: + Add `super().at_object_creation()` as the top line. +2. In `typeclasses/rooms.py`: + Import the `ContribRPRoom` class: + `from evennia.contrib.rpsystem import ContribRPRoom` + Inherit `ContribRPRoom`: + Change `class Room(DefaultRoom):` to + `class Room(ContribRPRoom):` +3. In `typeclasses/objects.py` + Import the `ContribRPObject` class: + `from evennia.contrib.rpsystem import ContribRPObject` + Inherit `ContribRPObject`: + Change `class Object(DefaultObject):` to + `class Object(ContribRPObject):` +4. Reload the server (`reload` or from console: "evennia reload") +5. Force typeclass updates as required. Example for your character: + `type/reset/force me = typeclasses.characters.Character` """ import re diff --git a/evennia/contrib/rpg/traits/README.md b/evennia/contrib/rpg/traits/README.md new file mode 100644 index 0000000000..2889633f12 --- /dev/null +++ b/evennia/contrib/rpg/traits/README.md @@ -0,0 +1,443 @@ +# Traits + +Whitenoise 2014, Ainneve contributors, +Griatch 2020 + +A `Trait` represents a modifiable property on (usually) a Character. They can +be used to represent everything from attributes (str, agi etc) to skills +(hunting 10, swords 14 etc) and dynamically changing things like HP, XP etc. + +Traits differ from normal Attributes in that they track their changes and limit +themselves to particular value-ranges. One can add/subtract from them easily and +they can even change dynamically at a particular rate (like you being poisoned or +healed). + +Traits use Evennia Attributes under the hood, making them persistent (they survive +a server reload/reboot). + +## Installation + +Traits are always added to a typeclass, such as the Character class. + +There are two ways to set up Traits on a typeclass. The first sets up the `TraitHandler` +as a property `.traits` on your class and you then access traits as e.g. `.traits.strength`. +The other alternative uses a `TraitProperty`, which makes the trait available directly +as e.g. `.strength`. This solution also uses the `TraitHandler`, but you don't need to +define it explicitly. You can combine both styles if you like. + +### Traits with TraitHandler + +Here's an example for adding the TraitHandler to the Character class: + +```python +# mygame/typeclasses/objects.py + +from evennia import DefaultCharacter +from evennia.utils import lazy_property +from evennia.contrib.traits import TraitHandler + +# ... + +class Character(DefaultCharacter): + ... + @lazy_property + def traits(self): + # this adds the handler as .traits + return TraitHandler(self) + + + def at_object_creation(self): + # (or wherever you want) + self.traits.add("str", "Strength", trait_type="static", base=10, mod=2) + self.traits.add("hp", "Health", trait_type="gauge", min=0, max=100) + self.traits.add("hunting", "Hunting Skill", trait_type="counter", + base=10, mod=1, min=0, max=100) + + +``` +When adding the trait, you supply the name of the property (`hunting`) along +with a more human-friendly name ("Hunting Skill"). The latter will show if you +print the trait etc. The `trait_type` is important, this specifies which type +of trait this is (see below). + +### TraitProperties + +Using `TraitProperties` makes the trait available directly on the class, much like Django model +fields. The drawback is that you must make sure that the name of your Traits don't collide with any +other properties/methods on your class. + +```python +# mygame/typeclasses/objects.py + +from evennia import DefaultObject +from evennia.utils import lazy_property +from evennia.contrib.traits import TraitProperty + +# ... + +class Object(DefaultObject): + ... + strength = TraitProperty("Strength", trait_type="static", base=10, mod=2) + health = TraitProperty("Health", trait_type="gauge", min=0, base=100, mod=2) + hunting = TraitProperty("Hunting Skill", trait_type="counter", base=10, mod=1, min=0, max=100) + +``` + +> Note that the property-name will become the name of the trait and you don't supply `trait_key` +> separately. + +> The `.traits` TraitHandler will still be created (it's used under the +> hood. But it will only be created when the TraitProperty has been accessed at least once, +> so be careful if mixing the two styles. If you want to make sure `.traits` is always available, +> add the `TraitHandler` manually like shown earlier - the `TraitProperty` will by default use +> the same handler (`.traits`). + +## Using traits + +A trait is added to the traithandler (if you use `TraitProperty` the handler is just created under +the hood) after which one can access it as a property on the handler (similarly to how you can do +.db.attrname for Attributes in Evennia). + +All traits have a _read-only_ field `.value`. This is only used to read out results, you never +manipulate it directly (if you try, it will just remain unchanged). The `.value` is calculated based +on combining fields, like `.base` and `.mod` - which fields are available and how they relate to +each other depends on the trait type. + +```python +> obj.traits.strength.value +12 # base + mod + +> obj.traits.strength.base += 5 +obj.traits.strength.value +17 + +> obj.traits.hp.value +102 # base + mod + +> obj.traits.hp.base -= 200 +> obj.traits.hp.value +0 # min of 0 + +> obj.traits.hp.reset() +> obj.traits.hp.value +100 + +# you can also access properties like a dict +> obj.traits.hp["value"] +100 + +# you can store arbitrary data persistently for easy reference +> obj.traits.hp.effect = "poisoned!" +> obj.traits.hp.effect +"poisoned!" + +# with TraitProperties: + +> obj.hunting.value +12 + +> obj.strength.value += 5 +> obj.strength.value +17 + +``` + +## Trait types + +All default traits have a read-only `.value` property that shows the relevant or +'current' value of the trait. Exactly what this means depends on the type of trait. + +Traits can also be combined to do arithmetic with their .value, if both have a +compatible type. + +```python +> trait1 + trait2 +54 + +> trait1.value +3 + +> trait1 + 2 +> trait1.value +5 + +``` + +Two numerical traits can also be compared (bigger-than etc), which is useful in +all sorts of rule-resolution. + +```python + +if trait1 > trait2: + # do stuff + +``` + +## Static trait + +`value = base + mod` + +The static trait has a `base` value and an optional `mod`-ifier. A typical use +of a static trait would be a Strength stat or Skill value. That is, something +that varies slowly or not at all, and which may be modified in-place. + +```python +> obj.traits.add("str", "Strength", trait_type="static", base=10, mod=2) +> obj.traits.mytrait.value + +12 # base + mod +> obj.traits.mytrait.base += 2 +> obj.traits.mytrait.mod += 1 +> obj.traits.mytrait.value +15 + +> obj.traits.mytrait.mod = 0 +> obj.traits.mytrait.value +12 + +``` + +### Counter + + + min/unset base base+mod max/unset + |--------------|--------|---------X--------X------------| + current value + = current + + mod + +A counter describes a value that can move from a base. The `.current` property +is the thing usually modified. It starts at the `.base`. One can also add a +modifier, which will both be added to the base and to current (forming +`.value`). The min/max of the range are optional, a boundary set to None will +remove it. A suggested use for a Counter Trait would be to track skill values. + +```python +> obj.traits.add("hunting", "Hunting Skill", trait_type="counter", + base=10, mod=1, min=0, max=100) +> obj.traits.hunting.value +11 # current starts at base + mod + +> obj.traits.hunting.current += 10 +> obj.traits.hunting.value +21 + +# reset back to base+mod by deleting current +> del obj.traits.hunting.current +> obj.traits.hunting.value +11 +> obj.traits.hunting.max = None # removing upper bound + +# for TraitProperties, pass the args/kwargs of traits.add() to the +# TraitProperty constructor instead. + + +``` + +Counters have some extra properties: + +#### .descs + +The `descs` property is a dict `{upper_bound:text_description}`. This allows for easily +storing a more human-friendly description of the current value in the +interval. Here is an example for skill values between 0 and 10: + + {0: "unskilled", 1: "neophyte", 5: "trained", 7: "expert", 9: "master"} + +The keys must be supplied from smallest to largest. Any values below the lowest and above the +highest description will be considered to be included in the closest description slot. +By calling `.desc()` on the Counter, you will get the text matching the current `value`. + +```python +# (could also have passed descs= to traits.add()) +> obj.traits.hunting.descs = { + 0: "unskilled", 10: "neophyte", 50: "trained", 70: "expert", 90: "master"} +> obj.traits.hunting.value +11 + +> obj.traits.hunting.desc() +"neophyte" +> obj.traits.hunting.current += 60 +> obj.traits.hunting.value +71 + +> obj.traits.hunting.desc() +"expert" + +``` + +#### .rate + +The `rate` property defaults to 0. If set to a value different from 0, it +allows the trait to change value dynamically. This could be used for example +for an attribute that was temporarily lowered but will gradually (or abruptly) +recover after a certain time. The rate is given as change of the current +`.value` per-second, and this will still be restrained by min/max boundaries, +if those are set. + +It is also possible to set a `.ratetarget`, for the auto-change to stop at +(rather than at the min/max boundaries). This allows the value to return to +a previous value. + +```python + +> obj.traits.hunting.value +71 + +> obj.traits.hunting.ratetarget = 71 +# debuff hunting for some reason +> obj.traits.hunting.current -= 30 +> obj.traits.hunting.value +41 + +> obj.traits.hunting.rate = 1 # 1/s increase +# Waiting 5s +> obj.traits.hunting.value +46 + +# Waiting 8s +> obj.traits.hunting.value +54 + +# Waiting 100s +> obj.traits.hunting.value +71 # we have stopped at the ratetarget + +> obj.traits.hunting.rate = 0 # disable auto-change + + +``` +Note that when retrieving the `current`, the result will always be of the same +type as the `.base` even `rate` is a non-integer value. So if `base` is an `int` +(default)`, the `current` value will also be rounded the closest full integer. +If you want to see the exact `current` value, set `base` to a float - you +will then need to use `round()` yourself on the result if you want integers. + +#### .percent() + +If both min and max are defined, the `.percent()` method of the trait will +return the value as a percentage. + +```python +> obj.traits.hunting.percent() +"71.0%" + +> obj.traits.hunting.percent(formatting=None) +71.0 + +``` + +### Gauge + +This emulates a [fuel-] gauge that empties from a base+mod value. + + min/0 max=base+mod + |-----------------------X---------------------------| + value + = current + +The `.current` value will start from a full gauge. The .max property is +read-only and is set by `.base` + `.mod`. So contrary to a `Counter`, the +`.mod` modifier only applies to the max value of the gauge and not the current +value. The minimum bound defaults to 0 if not set explicitly. + +This trait is useful for showing commonly depletable resources like health, +stamina and the like. + +```python +> obj.traits.add("hp", "Health", trait_type="gauge", base=100) +> obj.traits.hp.value # (or .current) +100 + +> obj.traits.hp.mod = 10 +> obj.traits.hp.value +110 + +> obj.traits.hp.current -= 30 +> obj.traits.hp.value +80 + +``` + +The Gauge trait is subclass of the Counter, so you have access to the same +methods and properties where they make sense. So gauges can also have a +`.descs` dict to describe the intervals in text, and can use `.percent()` to +get how filled it is as a percentage etc. + +The `.rate` is particularly relevant for gauges - useful for everything +from poison slowly draining your health, to resting gradually increasing it. + +### Trait + +A single value of any type. + +This is the 'base' Trait, meant to inherit from if you want to invent +trait-types from scratch (most of the time you'll probably inherit from some of +the more advanced trait-type classes though). + +Unlike other Trait-types, the single `.value` property of the base `Trait` can +be editied. The value can hold any data that can be stored in an Attribute. If +it's an integer/float you can do arithmetic with it, but otherwise this acts just +like a glorified Attribute. + + +```python +> obj.traits.add("mytrait", "My Trait", trait_type="trait", value=30) +> obj.traits.mytrait.value +30 + +> obj.traits.mytrait.value = "stringvalue" +> obj.traits.mytrait.value +"stringvalue" + +``` + +## Expanding with your own Traits + +A Trait is a class inhering from `evennia.contrib.traits.Trait` (or from one of +the existing Trait classes). + +```python +# in a file, say, 'mygame/world/traits.py' + +from evennia.contrib.traits import StaticTrait + +class RageTrait(StaticTrait): + + trait_type = "rage" + default_keys = { + "rage": 0 + } + + def berserk(self): + self.mod = 100 + + def sedate(self): + self.mod = 0 + + +``` + +Above is an example custom-trait-class "rage" that stores a property "rage" on +itself, with a default value of 0. This has all the functionality of a Trait - +for example, if you do del on the `rage` property, it will be set back to its +default (0). Above we also added some helper methods. + +To add your custom RageTrait to Evennia, add the following to your settings file +(assuming your class is in mygame/world/traits.py): + + TRAIT_CLASS_PATHS = ["world.traits.RageTrait"] + +Reload the server and you should now be able to use your trait: + +```python +> obj.traits.add("mood", "A dark mood", rage=30, trait_type='rage') +> obj.traits.mood.rage +30 + +# as TraitProperty + +class Character(DefaultCharacter): + rage = TraitProperty("A dark mood", rage=30, trait_type='rage') + +``` diff --git a/evennia/contrib/rpg/traits/__init__.py b/evennia/contrib/rpg/traits/__init__.py new file mode 100644 index 0000000000..64f32d0017 --- /dev/null +++ b/evennia/contrib/rpg/traits/__init__.py @@ -0,0 +1,10 @@ +""" +Traits - Whitenoise 2014, Griatch 2020 + +""" + +from .traits import TraitException # noqa +from .traits import MandatoryTraitKey # noqa +from .traits import TraitHandler # noqa +from .traits import TraitProperty # noqa +from .traits import Trait, StaticTrait, CounterTrait, GaugeTrait # noqa diff --git a/evennia/contrib/rpg/test_traits.py b/evennia/contrib/rpg/traits/tests.py similarity index 99% rename from evennia/contrib/rpg/test_traits.py rename to evennia/contrib/rpg/traits/tests.py index 73c95ea625..8fe6975e62 100644 --- a/evennia/contrib/rpg/test_traits.py +++ b/evennia/contrib/rpg/traits/tests.py @@ -10,8 +10,7 @@ from copy import copy from anything import Something from mock import MagicMock, patch from django.test import TestCase -from evennia.utils.utils import lazy_property -from evennia.contrib import traits +from . import traits class _MockObj: diff --git a/evennia/contrib/rpg/traits.py b/evennia/contrib/rpg/traits/traits.py similarity index 99% rename from evennia/contrib/rpg/traits.py rename to evennia/contrib/rpg/traits/traits.py index 189bd2bd57..140f00c535 100644 --- a/evennia/contrib/rpg/traits.py +++ b/evennia/contrib/rpg/traits/traits.py @@ -12,7 +12,9 @@ be used to represent everything from attributes (str, agi etc) to skills Traits use Evennia Attributes under the hood, making them persistent (they survive a server reload/reboot). -## Adding Traits to a typeclass +## Installation + +Traits are always added to a typeclass, such as the Character class. There are two ways to set up Traits on a typeclass. The first sets up the `TraitHandler` as a property `.traits` on your class and you then access traits as e.g. `.traits.strength`. diff --git a/evennia/contrib/tutorials/talking_npc/__init__.py b/evennia/contrib/tutorials/talking_npc/__init__.py new file mode 100644 index 0000000000..b5e025e90a --- /dev/null +++ b/evennia/contrib/tutorials/talking_npc/__init__.py @@ -0,0 +1,6 @@ +""" +Talking NPC - Griatch 2011, grungies1138 2016 + +""" + +from .talking_npc import CmdTalk, TalkingCmdSet, TalkingNPC # noqa diff --git a/evennia/contrib/rpg/talking_npc.py b/evennia/contrib/tutorials/talking_npc/talking_npc.py similarity index 91% rename from evennia/contrib/rpg/talking_npc.py rename to evennia/contrib/tutorials/talking_npc/talking_npc.py index 31872a170a..1384514a47 100644 --- a/evennia/contrib/rpg/talking_npc.py +++ b/evennia/contrib/tutorials/talking_npc/talking_npc.py @@ -4,13 +4,17 @@ Evennia Talkative NPC Contribution - Griatch 2011, grungies1138, 2016 This is a static NPC object capable of holding a simple menu-driven -conversation. It's just meant as an example. Create it by creating an -object of typeclass contrib.talking_npc.TalkingNPC, For example using -@create: +conversation. It's just meant as an example. - @create/drop John : contrib.talking_npc.TalkingNPC +Installation: + +Create the NPC by creating an object of typeclass `contrib.tutorials.talking_npc.TalkingNPC`, +For example: + + create/drop John : contrib.tutorials.talking_npc.TalkingNPC + +Use `talk` in the same room as the NPC to start a conversation. -Walk up to it and give the talk command to strike up a conversation. If there are many talkative npcs in the same room you will get to choose which one's talk command to call (Evennia handles this automatically). This use of EvMenu is very simplistic; See EvMenu for