mirror of
https://github.com/evennia/evennia.git
synced 2026-03-20 14:56:30 +01:00
Update docs with contrib auto-generated content
This commit is contained in:
parent
fc7a49f152
commit
a18e5d0a81
11 changed files with 281 additions and 4 deletions
198
docs/source/Contribs/Contrib-Components.md
Normal file
198
docs/source/Contribs/Contrib-Components.md
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
# Components
|
||||
|
||||
_Contrib by ChrisLR 2021_
|
||||
|
||||
# The Components Contrib
|
||||
|
||||
This contrib introduces Components and Composition to Evennia.
|
||||
Each 'Component' class represents a feature that will be 'enabled' on a typeclass instance.
|
||||
You can register these components on an entire typeclass or a single object at runtime.
|
||||
It supports both persisted attributes and in-memory attributes by using Evennia's AttributeHandler.
|
||||
|
||||
# Pros
|
||||
- You can reuse a feature across multiple typeclasses without inheritance
|
||||
- You can cleanly organize each feature into a self-contained class.
|
||||
- You can check if your object supports a feature without checking its instance.
|
||||
|
||||
# Cons
|
||||
- It introduces additional complexity.
|
||||
- A host typeclass instance is required.
|
||||
|
||||
# How to install
|
||||
|
||||
To enable component support for a typeclass,
|
||||
import and inherit the ComponentHolderMixin, similar to this
|
||||
```python
|
||||
from evennia.contrib.base_systems.components import ComponentHolderMixin
|
||||
class Character(ComponentHolderMixin, DefaultCharacter):
|
||||
# ...
|
||||
```
|
||||
|
||||
Components need to inherit the Component class directly and require a name.
|
||||
```python
|
||||
from evennia.contrib.components import Component
|
||||
|
||||
class Health(Component):
|
||||
name = "health"
|
||||
```
|
||||
|
||||
Components may define DBFields or NDBFields at the class level.
|
||||
DBField will store its values in the host's DB with a prefixed key.
|
||||
NDBField will store its values in the host's NDB and will not persist.
|
||||
The key used will be 'component_name::field_name'.
|
||||
They use AttributeProperty under the hood.
|
||||
|
||||
Example:
|
||||
```python
|
||||
from evennia.contrib.base_systems.components import Component, DBField
|
||||
|
||||
class Health(Component):
|
||||
health = DBField(default=1)
|
||||
```
|
||||
|
||||
Note that default is optional and will default to None.
|
||||
|
||||
Adding a component to a host will also a similarly named tag with 'components' as category.
|
||||
A Component named health will appear as key="health, category="components".
|
||||
This allows you to retrieve objects with specific components by searching with the tag.
|
||||
|
||||
It is also possible to add Component Tags the same way, using TagField.
|
||||
TagField accepts a default value and can be used to store a single or multiple tags.
|
||||
Default values are automatically added when the component is added.
|
||||
Component Tags are cleared from the host if the component is removed.
|
||||
|
||||
Example:
|
||||
```python
|
||||
from evennia.contrib.base_systems.components import Component, TagField
|
||||
|
||||
class Health(Component):
|
||||
resistances = TagField()
|
||||
vulnerability = TagField(default="fire", enforce_single=True)
|
||||
```
|
||||
|
||||
The 'resistances' field in this example can be set to multiple times and it will keep the added tags.
|
||||
The 'vulnerability' field in this example will override the previous tag with the new one.
|
||||
|
||||
|
||||
|
||||
Each typeclass using the ComponentHolderMixin can declare its components
|
||||
in the class via the ComponentProperty.
|
||||
These are components that will always be present in a typeclass.
|
||||
You can also pass kwargs to override the default values
|
||||
Example
|
||||
```python
|
||||
from evennia.contrib.base_systems.components import ComponentHolderMixin
|
||||
class Character(ComponentHolderMixin, DefaultCharacter):
|
||||
health = ComponentProperty("health", hp=10, max_hp=50)
|
||||
```
|
||||
|
||||
You can then use character.components.health to access it.
|
||||
The shorter form character.cmp.health also exists.
|
||||
character.health would also be accessible but only for typeclasses that have
|
||||
this component defined on the class.
|
||||
|
||||
Alternatively you can add those components at runtime.
|
||||
You will have to access those via the component handler.
|
||||
Example
|
||||
```python
|
||||
character = self
|
||||
vampirism = components.Vampirism.create(character)
|
||||
character.components.add(vampirism)
|
||||
|
||||
...
|
||||
|
||||
vampirism_from_elsewhere = character.components.get("vampirism")
|
||||
```
|
||||
|
||||
Keep in mind that all components must be imported to be visible in the listing.
|
||||
As such, I recommend regrouping them in a package.
|
||||
You can then import all your components in that package's __init__
|
||||
|
||||
Because of how Evennia import typeclasses and the behavior of python imports
|
||||
I recommend placing the components package inside the typeclass package.
|
||||
In other words, create a folder named components inside your typeclass folder.
|
||||
Then, inside the 'typeclasses/__init__.py' file add the import to the folder, like
|
||||
```python
|
||||
from typeclasses import components
|
||||
```
|
||||
This ensures that the components package will be imported when the typeclasses are imported.
|
||||
You will also need to import each components inside the package's own 'typeclasses/components/__init__.py' file.
|
||||
You only need to import each module/file from there but importing the right class is a good practice.
|
||||
```python
|
||||
from typeclasses.components.health import Health
|
||||
```
|
||||
```python
|
||||
from typeclasses.components import health
|
||||
```
|
||||
Both of the above examples will work.
|
||||
|
||||
# Full Example
|
||||
```python
|
||||
from evennia.contrib.base_systems import components
|
||||
|
||||
|
||||
# This is the Component class
|
||||
class Health(components.Component):
|
||||
name = "health"
|
||||
|
||||
# Stores the current and max values as Attributes on the host, defaulting to 100
|
||||
current = components.DBField(default=100)
|
||||
max = components.DBField(default=100)
|
||||
|
||||
def damage(self, value):
|
||||
if self.current <= 0:
|
||||
return
|
||||
|
||||
self.current -= value
|
||||
if self.current > 0:
|
||||
return
|
||||
|
||||
self.current = 0
|
||||
self.on_death()
|
||||
|
||||
def heal(self, value):
|
||||
hp = self.current
|
||||
hp += value
|
||||
if hp >= self.max_hp:
|
||||
hp = self.max_hp
|
||||
|
||||
self.current = hp
|
||||
|
||||
@property
|
||||
def is_dead(self):
|
||||
return self.current <= 0
|
||||
|
||||
def on_death(self):
|
||||
# Behavior is defined on the typeclass
|
||||
self.host.on_death()
|
||||
|
||||
|
||||
# This is how the Character inherits the mixin and registers the component 'health'
|
||||
class Character(ComponentHolderMixin, DefaultCharacter):
|
||||
health = ComponentProperty("health")
|
||||
|
||||
|
||||
# This is an example of a command that checks for the component
|
||||
class Attack(Command):
|
||||
key = "attack"
|
||||
aliases = ('melee', 'hit')
|
||||
|
||||
def at_pre_cmd(self):
|
||||
caller = self.caller
|
||||
targets = self.caller.search(args, quiet=True)
|
||||
valid_target = None
|
||||
for target in targets:
|
||||
# Attempt to retrieve the component, None is obtained if it does not exist.
|
||||
if target.components.health:
|
||||
valid_target = target
|
||||
|
||||
if not valid_target:
|
||||
caller.msg("You can't attack that!")
|
||||
return True
|
||||
```
|
||||
|
||||
|
||||
----
|
||||
|
||||
<small>This document page is generated from `evennia/contrib/base_systems/components/README.md`. Changes to this
|
||||
file will be overwritten, so edit that file rather than this one.</small>
|
||||
Loading…
Add table
Add a link
Reference in a new issue