From e854432cc901adb638a5d17ea0c197a33a00633f Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 5 Aug 2022 20:39:45 +0200 Subject: [PATCH] Update doc sources with new contribs --- docs/source/Coding/Changelog.md | 16 + docs/source/Contribs/Contrib-Buffs.md | 371 ++++++++++++++++++ .../source/Contribs/Contrib-Name-Generator.md | 282 +++++++++++++ docs/source/Contribs/Contribs-Overview.md | 26 ++ docs/source/Setup/Settings-Default.md | 14 +- .../api/evennia.contrib.rpg.buffs.buff.md | 10 + docs/source/api/evennia.contrib.rpg.buffs.md | 19 + .../evennia.contrib.rpg.buffs.samplebuffs.md | 10 + .../api/evennia.contrib.rpg.buffs.tests.md | 10 + docs/source/api/evennia.contrib.rpg.md | 1 + 10 files changed, 753 insertions(+), 6 deletions(-) create mode 100644 docs/source/Contribs/Contrib-Buffs.md create mode 100644 docs/source/Contribs/Contrib-Name-Generator.md create mode 100644 docs/source/api/evennia.contrib.rpg.buffs.buff.md create mode 100644 docs/source/api/evennia.contrib.rpg.buffs.md create mode 100644 docs/source/api/evennia.contrib.rpg.buffs.samplebuffs.md create mode 100644 docs/source/api/evennia.contrib.rpg.buffs.tests.md diff --git a/docs/source/Coding/Changelog.md b/docs/source/Coding/Changelog.md index be0527668a..2d15be0167 100644 --- a/docs/source/Coding/Changelog.md +++ b/docs/source/Coding/Changelog.md @@ -168,6 +168,22 @@ Up requirements to Django 4.0+, Twisted 22+, Python 3.9 or 3.10 now return `None` instead of `.db.desc` if no sdesc is set; fallback in hook (inspectorCaracal) - Reworked text2html parser to avoid problems with stateful color tags (inspectorCaracal) - Simplified `EvMenu.options_formatter` hook to use `EvColumn` and f-strings (inspectorcaracal) +- Added `move_type` str kwarg to `move_to()` calls, optionally identifying the type of + move being done ('teleport', 'disembark', 'give' etc). (volund) +- Made RPSystem contrib msg calls pass `pose` or `say` as msg-`type` for use in + e.g. webclient pane filtering where desired. (volund) +- Fixed bug in `cmdset.remove()` where a command could not be deleted by `key`, + even though doc suggested one could (ChrisLR) +- New contrib `name_generator` for building random real-world based or fantasy-names + based on phonetic rules. +- Enable proper serialization of dict subclasses in Attributes (aogier) +- `object.search` fuzzy-matching now uses `icontains` instead of `istartswith` + to better match how search works elsewhere (volund) +- The `.at_traverse` hook now receives a `exit_obj` kwarg, linking back to the + exit triggering the hook (volund) +- Contrib `buffs` for managing temporary and permanent RPG status buffs effects (tegiminis) +- New `at_server_init()` hook called before all other startup hooks for all + startup modes. Used for more generic overriding (volund) ## Evennia 0.9.5 diff --git a/docs/source/Contribs/Contrib-Buffs.md b/docs/source/Contribs/Contrib-Buffs.md new file mode 100644 index 0000000000..1b09d19dd5 --- /dev/null +++ b/docs/source/Contribs/Contrib-Buffs.md @@ -0,0 +1,371 @@ +# Buffs + +Contribution by Tegiminis 2022 + +A buff is a timed object, attached to a game entity. It is capable of modifying values, triggering code, or both. +It is a common design pattern in RPGs, particularly action games. + +Features: + +- `BuffHandler`: A buff handler to apply to your objects. +- `BaseBuff`: A buff class to extend from to create your own buffs. +- `BuffableProperty`: A sample property class to show how to automatically check modifiers. +- `CmdBuff`: A command which applies buffs. +- `samplebuffs.py`: Some sample buffs to learn from. + +## Quick Start +Assign the handler to a property on the object, like so. + +```python +@lazy_property +def buffs(self) -> BuffHandler: + return BuffHandler(self) +``` + +You may then call the handler to add or manipulate buffs like so: `object.buffs`. See **Using the Handler**. + +### Customization + +If you want to customize the handler, you can feed the constructor two arguments: +- `dbkey`: The string you wish to use as the attribute key for the buff database. Defaults to "buffs". This allows you to keep separate buff pools - for example, "buffs" and "perks". +- `autopause`: If you want this handler to automatically pause playtime buffs when its owning object is unpuppeted. + +> **Note**: If you enable autopausing, you MUST initialize the property in your owning object's +> `at_init` hook. Otherwise, a hot reload can cause playtime buffs to not update properly +> on puppet/unpuppet. You have been warned! + +Let's say you want another handler for an object, `perks`, which has a separate database and +respects playtime buffs. You'd assign this new property as so: + +```python +class BuffableObject(Object): + @lazy_property + def perks(self) -> BuffHandler: + return BuffHandler(self, dbkey='perks', autopause=True) + + def at_init(self): + self.perks +``` + +## Using the Handler + +Here's how to make use of your new handler. + +### Apply a Buff + +Call the handler's `add` method. This requires a class reference, and also contains a number of +optional arguments to customize the buff's duration, stacks, and so on. You can also store any arbitrary value +in the buff's cache by passing a dictionary through the `to_cache` optional argument. This will not overwrite the normal +values on the cache. + +```python +self.buffs.add(StrengthBuff) # A single stack of StrengthBuff with normal duration +self.buffs.add(DexBuff, stacks=3, duration=60) # Three stacks of DexBuff, with a duration of 60 seconds +self.buffs.add(ReflectBuff, to_cache={'reflect': 0.5}) # A single stack of ReflectBuff, with an extra cache value +``` + +Two important attributes on the buff are checked when the buff is applied: `refresh` and `unique`. +- `refresh` (default: True) determines if a buff's timer is refreshed when it is reapplied. +- `unique` (default: True) determines if this buff is unique; that is, only one of it exists on the object. + +The combination of these two booleans creates one of three kinds of keys: +- `Unique is True, Refresh is True/False`: The buff's default key. +- `Unique is False, Refresh is True`: The default key mixed with the applier's dbref. This makes the buff "unique-per-player", so you can refresh through reapplication. +- `Unique is False, Refresh is False`: The default key mixed with a randomized number. + +### Get Buffs + +The handler has several getter methods which return instanced buffs. You won't need to use these for basic functionality, but if you want to manipulate +buffs after application, they are very useful. The handler's `check`/`trigger` methods utilize some of these getters, while others are just for developer convenience. + +`get(key)` is the most basic getter. It returns a single buff instance, or `None` if the buff doesn't exist on the handler. It is also the only getter +that returns a single buff instance, rather than a dictionary. + +Group getters, listed below, return a dictionary of values in the format `{buffkey: instance}`. If you want to iterate over all of these buffs, +you should do so via the `dict.values()` method. + +- `get_all()` returns all buffs on this handler. You can also use the `handler.all` property. +- `get_by_type(BuffClass)` returns buffs of the specified type. +- `get_by_stat(stat)` returns buffs with a `Mod` object of the specified `stat` string in their `mods` list. +- `get_by_trigger(string)` returns buffs with the specified string in their `triggers` list. +- `get_by_source(Object)` returns buffs applied by the specified `source` object. +- `get_by_cachevalue(key, value)` returns buffs with the matching `key: value` pair in their cache. `value` is optional. + +All group getters besides `get_all()` can "slice" an existing dictionary through the optional `to_filter` argument. + +```python +dict1 = handler.get_by_type(Burned) # This finds all "Burned" buffs on the handler +dict2 = handler.get_by_source(self, to_filter=dict1) # This filters dict1 to find buffs with the matching source +``` + +> **Note**: Most of these getters also have an associated handler property. For example, `handler.effects` returns all buffs that can be triggered, which +> is then iterated over by the `get_by_trigger` method. + +### Remove Buffs + +There are also a number of remover methods. Generally speaking, these follow the same format as the getters. + +- `remove(key)` removes the buff with the specified key. +- `clear()` removes all buffs. +- `remove_by_type(BuffClass)` removes buffs of the specified type. +- `remove_by_stat(stat)` removes buffs with a `Mod` object of the specified `stat` string in their `mods` list. +- `remove_by_trigger(string)` removes buffs with the specified string in their `triggers` list. +- `remove_by_source(Object)` removes buffs applied by the specified source +- `remove_by_cachevalue(key, value)` removes buffs with the matching `key: value` pair in their cache. `value` is optional. + +You can also remove a buff by calling the instance's `remove` helper method. You can do this on the dictionaries returned by the +getters listed above. + +```python +to_remove = handler.get_by_trigger(trigger) # Finds all buffs with the specified trigger +for buff in to_remove.values(): # Removes all buffs in the to_remove dictionary via helper methods + buff.remove() +``` + +### Check Modifiers + +Call the handler `check(value, stat)` method when you want to see the modified value. +This will return the `value`, modified by any relevant buffs on the handler's owner (identified by +the `stat` string). + +For example, let's say you want to modify how much damage you take. That might look something like this: + +```python +# The method we call to damage ourselves +def take_damage(self, source, damage): + _damage = self.buffs.check(damage, 'taken_damage') + self.db.health -= _damage +``` + +This method calls the `at_pre_check` and `at_post_check` methods at the relevant points in the process. You can use to this make +buffs that are reactive to being checked; for example, removing themselves, altering their values, or interacting with the game state. + +> **Note**: You can also trigger relevant buffs at the same time as you check them by ensuring the optional argument `trigger` is True in the `check` method. + +### Trigger Buffs + +Call the handler's `trigger(string)` method when you want an event call. This will call the `at_trigger` hook method on all buffs with the relevant trigger `string`. + +For example, let's say you want to trigger a buff to "detonate" when you hit your target with an attack. +You'd write a buff that might look like this: + +```python +class Detonate(BaseBuff): + ... + triggers = ['take_damage'] + def at_trigger(self, trigger, *args, **kwargs) + self.owner.take_damage(100) + self.remove() +``` + +And then call `handler.trigger('take_damage')` in the method you use to take damage. + +> **Note** You could also do this through mods and `at_post_check` if you like, depending on how to want to add the damage. + +### Ticking + +Ticking buffs are slightly special. They are similar to trigger buffs in that they run code, but instead of +doing so on an event trigger, they do so on a periodic tick. A common use case for a buff like this is a poison, +or a heal over time. + +```python +class Poison(BaseBuff): + ... + tickrate = 5 + def at_tick(self, initial=True, *args, **kwargs): + _dmg = self.dmg * self.stacks + if not initial: + self.owner.location.msg_contents( + "Poison courses through {actor}'s body, dealing {damage} damage.".format( + actor=self.owner.named, damage=_dmg + ) + ) +``` + +To make a buff ticking, ensure the `tickrate` is 1 or higher, and it has code in its `at_tick` +method. Once you add it to the handler, it starts ticking! + +> **Note**: Ticking buffs always tick on initial application, when `initial` is `True`. If you don't want your hook to fire at that time, +> make sure to check the value of `initial` in your `at_tick` method. + +### Context + +Every important handler method optionally accepts a `context` dictionary. + +Context is an important concept for this handler. Every method which checks, triggers, or ticks a buff passes this +dictionary (default: empty) to the buff hook methods as keyword arguments (`**kwargs`). It is used for nothing else. This allows you to make those +methods "event-aware" by storing relevant data in the dictionary you feed to the method. + +For example, let's say you want a "thorns" buff which damages enemies that attack you. Let's take our `take_damage` method +and add a context to the mix. + +```python +def take_damage(attacker, damage): + context = {'attacker': attacker, 'damage': damage} + _damage = self.buffs.check(damage, 'taken_damage', context=context) + self.buffs.trigger('taken_damage', context=context) + self.db.health -= _damage +``` +Now we use the values that context passes to the buff kwargs to customize our logic. +```python +class ThornsBuff(BaseBuff): + ... + triggers = ['taken_damage'] + # This is the hook method on our thorns buff + def at_trigger(self, trigger, attacker=None, damage=0, **kwargs): + if not attacker: + return + attacker.db.health -= damage * 0.2 +``` +Apply the buff, take damage, and watch the thorns buff do its work! + +## Creating New Buffs + +Creating a new buff is very easy: extend `BaseBuff` into a new class, and fill in all the relevant buff details. +However, there are a lot of individual moving parts to a buff. Here's a step-through of the important stuff. + +### Basics + +Regardless of any other functionality, all buffs have the following class attributes: + +- They have customizable `key`, `name`, and `flavor` strings. +- They have a `duration` (float), and automatically clean-up at the end. Use -1 for infinite duration, and 0 to clean-up immediately. (default: -1) +- They can stack, if `maxstacks` (int) is not equal to 1. If it's 0, the buff stacks forever. (default: 1) +- They can be `unique` (bool), which determines if they have a unique namespace or not. (default: True) +- They can `refresh` (bool), which resets the duration when stacked or reapplied. (default: True) +- They can be `playtime` (bool) buffs, where duration only counts down during active play. (default: False) + +They also always store some useful mutable information about themselves in the cache: + +- `ref` (class): The buff class path we use to construct the buff. +- `start` (float): The timestamp of when the buff was applied. +- `source` (Object): If specified; this allows you to track who or what applied the buff. +- `prevtick` (float): The timestamp of the previous tick. +- `duration` (float): The cached duration. This can vary from the class duration, depending on if the duration has been modified (paused, extended, shortened, etc). +- `stacks` (int): How many stacks they have. +- `paused` (bool): Paused buffs do not clean up, modify values, tick, or fire any hook methods. + +You can always access the raw cache dictionary through the `cache` attribute on an instanced buff. This is grabbed when you get the buff through +a handler method, so it may not always reflect recent changes you've made, depending on how you structure your buff calls. All of the above +mutable information can be found in this cache, as well as any arbitrary information you pass through the handler `add` method (via `to_cache`). + +### Modifiers + +Mods are stored in the `mods` list attribute. Buffs which have one or more Mod objects in them can modify stats. You can use the handler method to check all +mods of a specific stat string and apply their modifications to the value; however, you are encouraged to use `check` in a getter/setter, for easy access. + +Mod objects consist of only four values, assigned by the constructor in this order: + +- `stat`: The stat you want to modify. When `check` is called, this string is used to find all the mods that are to be collected. +- `mod`: The modifier. Defaults are 'add' and 'mult'. Modifiers are calculated additively, and in standard arithmetic order (see `_calculate_mods` for more) +- `value`: How much value the modifier gives regardless of stacks +- `perstack`: How much value the modifier grants per stack, INCLUDING the first. (default: 0) + +The most basic way to add a Mod to a buff is to do so in the buff class definition, like this: + +```python +class DamageBuff(BaseBuff): + mods = [Mod('damage', 'add', 10)] +``` + +No mods applied to the value are permanent in any way. All calculations are done at runtime, and the mod values are never stored +anywhere except on the buff in question. In other words: you don't need to track the origin of particular stat mods, and you will +never permanently change a stat modified by a buff. To remove the modification, simply remove the buff from the object. + +> **Note**: You can add your own modifier types by overloading the `_calculate_mods` method, which contains the basic modifier application logic. + +#### Generating Mods (Advanced) + +An advanced way to do mods is to generate them when the buff is initialized. This lets you create mods on the fly that are reactive to the game state. + +```python +class GeneratedStatBuff(BaseBuff): + ... + def __init__(self, handler, buffkey, cache={}) -> None: + super().__init__(handler, buffkey, cache) + # Finds our "modgen" cache value, and generates a mod from it + modgen = list(self.cache.get("modgen")) + if modgen: + self.mods = [Mod(*modgen)] +``` + +### Triggers + +Buffs which have one or more strings in the `triggers` attribute can be triggered by events. + +When the handler's `trigger` method is called, it searches all buffs on the handler for any with a matchingtrigger, +then calls their `at_trigger` hooks. Buffs can have multiple triggers, and you can tell which trigger was used by +the `trigger` argument in the hook. + +```python +class AmplifyBuff(BaseBuff): + triggers = ['damage', 'heal'] + + def at_trigger(self, trigger, **kwargs): + if trigger == 'damage': print('Damage trigger called!') + if trigger == 'heal': print('Heal trigger called!') +``` + +### Ticking + +A buff which ticks isn't much different than one which triggers. You're still executing arbitrary hooks on +the buff class. To tick, the buff must have a `tickrate` of 1 or higher. + +```python +class Poison(BaseBuff): + ... + # this buff will tick 6 times between application and cleanup. + duration = 30 + tickrate = 5 + def at_tick(self, initial, **kwargs): + self.owner.take_damage(10) +``` +> **Note**: The buff always ticks once when applied. For this **first tick only**, `initial` will be True in the `at_tick` hook method. `initial` will be False on subsequent ticks. + +Ticks utilize a persistent delay, so they should be pickleable. As long as you are not adding new properties to your buff class, this shouldn't be a concern. +If you **are** adding new properties, try to ensure they do not end up with a circular code path to their object or handler, as this will cause pickling errors. + +### Extras + +Buffs have a grab-bag of extra functionality to let you add complexity to your designs. + +#### Conditionals + +You can restrict whether or not the buff will `check`, `trigger`, or `tick` through defining the `conditional` hook. As long +as it returns a "truthy" value, the buff will apply itself. This is useful for making buffs dependent on game state - for +example, if you want a buff that makes the player take more damage when they are on fire: + +```python +class FireSick(BaseBuff): + ... + def conditional(self, *args, **kwargs): + if self.owner.buffs.get_by_type(FireBuff): + return True + return False +``` + +Conditionals for `check`/`trigger` are checked when the buffs are gathered by the handler methods for the respective operations. `Tick` +conditionals are checked each tick. + +#### Helper Methods + +Buff instances have a number of helper methods. + +- `remove`/`dispel`: Allows you to remove or dispel the buff. Calls `at_remove`/`at_dispel`, depending on optional arguments. +- `pause`/`unpause`: Pauses and unpauses the buff. Calls `at_pause`/`at_unpause`. +- `reset`: Resets the buff's start to the current time; same as "refreshing" it. + +#### Playtime Duration + +If your handler has `autopause` enabled, any buffs with truthy `playtime` value will automatically pause +and unpause when the object the handler is attached to is puppetted or unpuppetted. This even works with ticking buffs, +although if you have less than 1 second of tick duration remaining, it will round up to 1s. + +> **Note**: If you want more control over this process, you can comment out the signal subscriptions on the handler and move the autopause logic +> to your object's `at_pre/post_puppet/unpuppet` hooks. + + +---- + +This document page is generated from `evennia/contrib/rpg/buffs/README.md`. Changes to this +file will be overwritten, so edit that file rather than this one. diff --git a/docs/source/Contribs/Contrib-Name-Generator.md b/docs/source/Contribs/Contrib-Name-Generator.md new file mode 100644 index 0000000000..8772cdd537 --- /dev/null +++ b/docs/source/Contribs/Contrib-Name-Generator.md @@ -0,0 +1,282 @@ +# Random Name Generator + +Contribution by InspectorCaracal (2022) + +A module for generating random names, both real-world and fantasy. Real-world +names can be generated either as first (personal) names, family (last) names, or +full names (first, optional middles, and last). The name data is from [Behind the Name](https://www.behindthename.com/) +and used under the [CC BY-SA 4.0 license](https://creativecommons.org/licenses/by-sa/4.0/). + +Fantasy names are generated from basic phonetic rules, using CVC syllable syntax. + +Both real-world and fantasy name generation can be extended to include additional +information via your game's `settings.py` + +## Installation + +This is a stand-alone utility. Just import this module (`from evennia.contrib.utils import name_generator`) and use its functions wherever you like. + +## Usage + +Import the module where you need it with the following: +```py +from evennia.contrib.utils.name_generator import namegen +``` + +By default, all of the functions will return a string with one generated name. +If you specify more than one, or pass `return_list=True` as a keyword argument, the returned value will be a list of strings. + +The module is especially useful for naming newly-created NPCs, like so: +```py +npc_name = namegen.full_name() +npc_obj = create_object(key=npc_name, typeclass="typeclasses.characters.NPC") +``` + +## Available Settings + +These settings can all be defined in your game's `server/conf/settings.py` file. + +- `NAMEGEN_FIRST_NAMES` adds a new list of first (personal) names. +- `NAMEGEN_LAST_NAMES` adds a new list of last (family) names. +- `NAMEGEN_REPLACE_LISTS` - set to `True` if you want to use only the names defined in your settings. +- `NAMEGEN_FANTASY_RULES` lets you add new phonetic rules for generating entirely made-up names. See the section "Custom Fantasy Name style rules" for details on how this should look. + +Examples: +```py +NAMEGEN_FIRST_NAMES = [ + ("Evennia", 'mf'), + ("Green Tea", 'f'), + ] + +NAMEGEN_LAST_NAMES = [ "Beeblebrox", "Son of Odin" ] + +NAMEGEN_FANTASY_RULES = { + "example_style": { + "syllable": "(C)VC", + "consonants": [ 'z','z','ph','sh','r','n' ], + "start": ['m'], + "end": ['x','n'], + "vowels": [ "e","e","e","a","i","i","u","o", ], + "length": (2,4), + } +} +``` + + +## Generating Real Names + +The contrib offers three functions for generating random real-world names: +`first_name()`, `last_name()`, and `full_name()`. If you want more than one name +generated at once, you can use the `num` keyword argument to specify how many. + +Example: +``` +>>> namegen.first_name(num=5) +['Genesis', 'Tali', 'Budur', 'Dominykas', 'Kamau'] +>>> namegen.first_name(gender='m') +'Blanchard' +``` + +The `first_name` function also takes a `gender` keyword argument to filter names +by gender association. 'f' for feminine, 'm' for masculine, 'mf' for feminine +_and_ masculine, or the default `None` to match any gendering. + +The `full_name` function also takes the `gender` keyword, as well as `parts` which +defines how many names make up the full name. The minimum is two: a first name and +a last name. You can also generate names with the family name first by setting +the keyword arg `surname_first` to `True` + +Example: +``` +>>> namegen.full_name() +'Keeva Bernat' +>>> namegen.full_name(parts=4) +'Suzu Shabnam Kafka Baier' +>>> namegen.full_name(parts=3, surname_first=True) +'Ó Muircheartach Torunn Dyson' +>>> namegen.full_name(gender='f') +'Wikolia Ó Deasmhumhnaigh' +``` + +### Adding your own names + +You can add additional names with the settings `NAMEGEN_FIRST_NAMES` and +`NAMEGEN_LAST_NAMES` + +`NAMEGEN_FIRST_NAMES` should be a list of tuples, where the first value is the name +and then second value is the gender flag - 'm' for masculine-only, 'f' for feminine- +only, and 'mf' for either one. + +`NAMEGEN_LAST_NAMES` should be a list of strings, where each item is an available +surname. + +Examples: +```py +NAMEGEN_FIRST_NAMES = [ + ("Evennia", 'mf'), + ("Green Tea", 'f'), + ] + +NAMEGEN_LAST_NAMES = [ "Beeblebrox", "Son of Odin" ] +``` + +Set `NAMEGEN_REPLACE_LISTS = True` if you want your custom lists above to entirely replace the built-in lists rather than extend them. + +## Generating Fantasy Names + +Generating completely made-up names is done with the `fantasy_name` function. The +contrib comes with three built-in styles of names which you can use, or you can +put a dictionary of custom name rules into `settings.py` + +Generating a fantasy name takes the ruleset key as the "style" keyword, and can +return either a single name or multiple names. By default, it will return a +single name in the built-in "harsh" style. The contrib also comes with "fluid" and "alien" styles. + +```py +>>> namegen.fantasy_name() +'Vhon' +>>> namegen.fantasy_name(num=3, style="harsh") +['Kha', 'Kizdhu', 'Godögäk'] +>>> namegen.fantasy_name(num=3, style="fluid") +['Aewalisash', 'Ayi', 'Iaa'] +>>> namegen.fantasy_name(num=5, style="alien") +["Qz'vko'", "Xv'w'hk'hxyxyz", "Wxqv'hv'k", "Wh'k", "Xbx'qk'vz"] +``` + +### Multi-Word Fantasy Names + +The `fantasy_name` function will only generate one name-word at a time, so for multi-word names +you'll need to combine pieces together. Depending on what kind of end result you want, there are +several approaches. + + +#### The simple approach + +If all you need is for it to have multiple parts, you can generate multiple names at once and `join` them. + +```py +>>> name = " ".join(namegen.fantasy_name(num=2)) +>>> name +'Dezhvözh Khäk' +``` + +If you want a little more variation between first/last names, you can also generate names for +different styles and then combine them. + +```py +>>> first = namegen.fantasy_name(style="fluid") +>>> last = namegen.fantasy_name(style="harsh") +>>> name = f"{first} {last}" +>>> name +'Ofasa Käkudhu' +``` + +#### "Nakku Silversmith" + +One common fantasy name practice is profession- or title-based surnames. To achieve this effect, +you can use the `last_name` function with a custom list of last names and combine it with your generated +fantasy name. + +Example: +```py +NAMEGEN_LAST_NAMES = [ "Silversmith", "the Traveller", "Destroyer of Worlds" ] +NAMEGEN_REPLACE_LISTS = True + +>>> first = namegen.fantasy_name() +>>> last = namegen.last_name() +>>> name = f"{first} {last}" +>>> name +'Tözhkheko the Traveller' +``` + +#### Elarion d'Yrinea, Thror Obinson + +Another common flavor of fantasy names is to use a surname suffix or prefix. For that, you'll +need to add in the extra bit yourself. + +Examples: +```py +>>> names = namegen.fantasy_name(num=2) +>>> name = f"{names[0]} za'{names[1]}" +>>> name +"Tithe za'Dhudozkok" + +>>> names = namegen.fantasy_name(num=2) +>>> name = f"{names[0]} {names[1]}son" +>>> name +'Kön Ködhöddoson' +``` + + +### Custom Fantasy Name style rules + +The style rules are contained in a dictionary of dictionaries, where the style name +is the key and the style rules are the dictionary value. + +The following is how you would add a custom style to `settings.py`: +```py +NAMEGEN_FANTASY_RULES = { + "example_style": { + "syllable": "(C)VC", + "consonants": [ 'z','z','ph','sh','r','n' ], + "start": ['m'], + "end": ['x','n'], + "vowels": [ "e","e","e","a","i","i","u","o", ], + "length": (2,4), + } +} +``` + +Then you could generate names following that ruleset with `namegen.fantasy_name(style="example_style")`. + +The keys `syllable`, `consonants`, `vowels`, and `length` must be present, and `length` must be the minimum and maximum syllable counts. `start` and `end` are optional. + + +#### syllable +The "syllable" field defines the structure of each syllable. C is consonant, V is vowel, +and parentheses mean it's optional. So, the example `(C)VC` means that every syllable +will always have a vowel followed by a consonant, and will *sometimes* have another +consonant at the beginning. e.g. `en`, `bak` + +*Note:* While it's not standard, the contrib lets you nest parentheses, with each layer +being less likely to show up. Additionally, any other characters put into the syllable +structure - e.g. an apostrophe - will be read and inserted as written. The +"alien" style rules in the module gives an example of both: the syllable structure is `C(C(V))(')(C)` +which results in syllables such as `khq`, `xho'q`, and `q'` with a much lower frequency of vowels than +`C(C)(V)(')(C)` would have given. + +#### consonants +A simple list of consonant phonemes that can be chosen from. Multi-character strings are +perfectly acceptable, such as "th", but each one will be treated as a single consonant. + +The function uses a naive form of weighting, where you make a phoneme more likely to +occur by putting more copies of it into the list. + +#### start and end +These are **optional** lists for the first and last letters of a syllable, if they're +a consonant. You can add on additional consonants which can only occur at the beginning +or end of a syllable, or you can add extra copies of already-defined consonants to +increase the frequency of them at the start/end of syllables. + +For example, in the `example_style` above, we have a `start` of m, and `end` of x and n. +Taken with the rest of the consonants/vowels, this means you can have the syllables of `mez` +but not `zem`, and you can have `phex` or `phen` but not `xeph` or `neph`. + +They can be left out of custom rulesets entirely. + +#### vowels +Vowels is a simple list of vowel phonemes - exactly like consonants, but instead used for the +vowel selection. Single-or multi-character strings are equally fine. It uses the same naive weighting system +as consonants - you can increase the frequency of any given vowel by putting it into the list multiple times. + +#### length +A tuple with the minimum and maximum number of syllables a name can have. + +When setting this, keep in mind how long your syllables can get! 4 syllables might +not seem like very many, but if you have a (C)(V)VC structure with one- and +two-letter phonemes, you can get up to eight characters per syllable. + +---- + +This document page is generated from `evennia/contrib/utils/name_generator/README.md`. Changes to this +file will be overwritten, so edit that file rather than this one. diff --git a/docs/source/Contribs/Contribs-Overview.md b/docs/source/Contribs/Contribs-Overview.md index a8138acfd3..343129062e 100644 --- a/docs/source/Contribs/Contribs-Overview.md +++ b/docs/source/Contribs/Contribs-Overview.md @@ -458,6 +458,7 @@ and rule implementation like character traits, dice rolling and emoting._ ```{toctree} :maxdepth: 1 +Contrib-Buffs.md Contrib-Dice.md Contrib-Health-Bar.md Contrib-RPSystem.md @@ -465,6 +466,17 @@ Contrib-Traits.md ``` +### Contrib: `buffs` + +_Contribution by Tegiminis 2022_ + +A buff is a timed object, attached to a game entity. It is capable of modifying values, triggering code, or both. +It is a common design pattern in RPGs, particularly action games. + +[Read the documentation](./Contrib-Buffs.md) - [Browse the Code](evennia.contrib.rpg.buffs) + + + ### Contrib: `dice` _Contribution by Griatch, 2012_ @@ -628,6 +640,7 @@ and more._ Contrib-Auditing.md Contrib-Fieldfill.md +Contrib-Name-Generator.md Contrib-Random-String-Generator.md Contrib-Tree-Select.md ``` @@ -661,6 +674,19 @@ to any callable of your choice. +### Contrib: `name_generator` + +_Contribution by InspectorCaracal (2022)_ + +A module for generating random names, both real-world and fantasy. Real-world +names can be generated either as first (personal) names, family (last) names, or +full names (first, optional middles, and last). The name data is from [Behind the Name](https://www.behindthename.com/) +and used under the [CC BY-SA 4.0 license](https://creativecommons.org/licenses/by-sa/4.0/). + +[Read the documentation](./Contrib-Name-Generator.md) - [Browse the Code](evennia.contrib.utils.name_generator) + + + ### Contrib: `random_string_generator` _Contribution by Vincent Le Goff (vlgeoff), 2017_ diff --git a/docs/source/Setup/Settings-Default.md b/docs/source/Setup/Settings-Default.md index 5567ce2052..baa2ee3c9e 100644 --- a/docs/source/Setup/Settings-Default.md +++ b/docs/source/Setup/Settings-Default.md @@ -31,12 +31,12 @@ value - which may change as Evennia is developed. This way you can always be sure of what you have changed and what is default behaviour. """ -from django.contrib.messages import constants as messages -from django.urls import reverse_lazy - import os import sys +from django.contrib.messages import constants as messages +from django.urls import reverse_lazy + ###################################################################### # Evennia base server config ###################################################################### @@ -405,9 +405,11 @@ INITIAL_SETUP_MODULE = "evennia.server.initial_setup" # the server's initial setup sequence (the very first startup of the system). # The check will fail quietly if module doesn't exist or fails to load. AT_INITIAL_SETUP_HOOK_MODULE = "server.conf.at_initial_setup" -# Module containing your custom at_server_start(), at_server_reload() and -# at_server_stop() methods. These methods will be called every time -# the server starts, reloads and resets/stops respectively. +# Module(s) containing custom at_server_init(), at_server_start(), +# at_server_reload() and at_server_stop() methods. These methods will be called +# every time the server starts, reloads and resets/stops +# respectively. Can be given as a single path or a list of paths. If a list, +# each module's hooks will be called in list order. AT_SERVER_STARTSTOP_MODULE = "server.conf.at_server_startstop" # List of one or more module paths to modules containing a function start_ # plugin_services(application). This module will be called with the main diff --git a/docs/source/api/evennia.contrib.rpg.buffs.buff.md b/docs/source/api/evennia.contrib.rpg.buffs.buff.md new file mode 100644 index 0000000000..826198c22e --- /dev/null +++ b/docs/source/api/evennia.contrib.rpg.buffs.buff.md @@ -0,0 +1,10 @@ +```{eval-rst} +evennia.contrib.rpg.buffs.buff +===================================== + +.. automodule:: evennia.contrib.rpg.buffs.buff + :members: + :undoc-members: + :show-inheritance: + +``` \ No newline at end of file diff --git a/docs/source/api/evennia.contrib.rpg.buffs.md b/docs/source/api/evennia.contrib.rpg.buffs.md new file mode 100644 index 0000000000..114ecd8af4 --- /dev/null +++ b/docs/source/api/evennia.contrib.rpg.buffs.md @@ -0,0 +1,19 @@ +```{eval-rst} +evennia.contrib.rpg.buffs +================================= + +.. automodule:: evennia.contrib.rpg.buffs + :members: + :undoc-members: + :show-inheritance: + + + +.. toctree:: + :maxdepth: 6 + + evennia.contrib.rpg.buffs.buff + evennia.contrib.rpg.buffs.samplebuffs + evennia.contrib.rpg.buffs.tests + +``` \ No newline at end of file diff --git a/docs/source/api/evennia.contrib.rpg.buffs.samplebuffs.md b/docs/source/api/evennia.contrib.rpg.buffs.samplebuffs.md new file mode 100644 index 0000000000..ecad1d62b3 --- /dev/null +++ b/docs/source/api/evennia.contrib.rpg.buffs.samplebuffs.md @@ -0,0 +1,10 @@ +```{eval-rst} +evennia.contrib.rpg.buffs.samplebuffs +============================================ + +.. automodule:: evennia.contrib.rpg.buffs.samplebuffs + :members: + :undoc-members: + :show-inheritance: + +``` \ No newline at end of file diff --git a/docs/source/api/evennia.contrib.rpg.buffs.tests.md b/docs/source/api/evennia.contrib.rpg.buffs.tests.md new file mode 100644 index 0000000000..84b3076dfb --- /dev/null +++ b/docs/source/api/evennia.contrib.rpg.buffs.tests.md @@ -0,0 +1,10 @@ +```{eval-rst} +evennia.contrib.rpg.buffs.tests +====================================== + +.. automodule:: evennia.contrib.rpg.buffs.tests + :members: + :undoc-members: + :show-inheritance: + +``` \ No newline at end of file diff --git a/docs/source/api/evennia.contrib.rpg.md b/docs/source/api/evennia.contrib.rpg.md index ea6607f239..f4ad7822e4 100644 --- a/docs/source/api/evennia.contrib.rpg.md +++ b/docs/source/api/evennia.contrib.rpg.md @@ -11,6 +11,7 @@ evennia.contrib.rpg .. toctree:: :maxdepth: 6 + evennia.contrib.rpg.buffs evennia.contrib.rpg.dice evennia.contrib.rpg.health_bar evennia.contrib.rpg.rpsystem