2022-08-06 22:50:53 +02:00
|
|
|
# Handling Equipment
|
|
|
|
|
|
|
|
|
|
In _Knave_, you have a certain number of inventory "slots". The amount of slots is given by `CON + 10`.
|
|
|
|
|
All items (except coins) have a `size`, indicating how many slots it uses. You can't carry more items
|
|
|
|
|
than you have slot-space for. Also items wielded or worn count towards the slots.
|
|
|
|
|
|
|
|
|
|
We still need to track what the character is using however: What weapon they have readied affects the damage
|
|
|
|
|
they can do. The shield, helmet and armor they use affects their defense.
|
|
|
|
|
|
|
|
|
|
We have already set up the possible 'wear/wield locations' when we defined our Objects
|
2022-08-07 00:11:38 +02:00
|
|
|
[in the previous lesson](./Beginner-Tutorial-Objects.md). This is what we have in `enums.py`:
|
2022-08-06 22:50:53 +02:00
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
# mygame/evadventure/enums.py
|
|
|
|
|
|
|
|
|
|
# ...
|
|
|
|
|
|
|
|
|
|
class WieldLocation(Enum):
|
|
|
|
|
|
|
|
|
|
BACKPACK = "backpack"
|
|
|
|
|
WEAPON_HAND = "weapon_hand"
|
|
|
|
|
SHIELD_HAND = "shield_hand"
|
|
|
|
|
TWO_HANDS = "two_handed_weapons"
|
|
|
|
|
BODY = "body" # armor
|
|
|
|
|
HEAD = "head" # helmets
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Basically, all the weapon/armor locations are exclusive - you can only have one item in each (or none).
|
|
|
|
|
The BACKPACK is special - it contains any number of items (up to the maximum slot usage).
|
|
|
|
|
|
2022-08-11 09:05:29 +02:00
|
|
|
## EquipmentHandler that saves
|
2022-08-06 22:50:53 +02:00
|
|
|
|
|
|
|
|
> Create a new module `mygame/evadventure/equipment.py`.
|
|
|
|
|
|
|
|
|
|
In default Evennia, everything you pick up will end up "inside" your character object (that is, have
|
|
|
|
|
you as its `.location`). This is called your _inventory_ and has no limit. We will keep 'moving items into us'
|
|
|
|
|
when we pick them up, but we will add more functionality using an _Equipment handler_.
|
|
|
|
|
|
|
|
|
|
```{sidebar}
|
|
|
|
|
If you want to understand more about behind how Evennia uses handlers, there is a
|
2022-08-07 00:11:38 +02:00
|
|
|
[dedicated tutorial](../../Tutorial-Persistent-Handler.md) talking about the principle.
|
2022-08-06 22:50:53 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
A handler is (for our purposes) an object that sits "on" another entity, containing functionality
|
|
|
|
|
for doing one specific thing (managing equipment, in our case).
|
|
|
|
|
|
|
|
|
|
This is the start of our handler:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
# in mygame/evadventure/equipment.py
|
|
|
|
|
|
|
|
|
|
from .enums import WieldLocation
|
|
|
|
|
|
|
|
|
|
class EquipmentHandler:
|
|
|
|
|
save_attribute = "inventory_slots"
|
|
|
|
|
|
|
|
|
|
def __init__(self, obj):
|
|
|
|
|
# here obj is the character we store the handler on
|
|
|
|
|
self.obj = obj
|
|
|
|
|
self._load()
|
|
|
|
|
|
|
|
|
|
def _load(self):
|
|
|
|
|
"""Load our data from an Attribute on `self.obj`"""
|
|
|
|
|
self.slots = self.obj.attributes.get(
|
|
|
|
|
self.save_attribute,
|
|
|
|
|
category="inventory",
|
|
|
|
|
default={
|
|
|
|
|
WieldLocation.WEAPON_HAND: None,
|
|
|
|
|
WieldLocation.SHIELD_HAND: None,
|
|
|
|
|
WieldLocation.TWO_HANDS: None,
|
|
|
|
|
WieldLocation.BODY: None,
|
|
|
|
|
WieldLocation.HEAD: None,
|
|
|
|
|
WieldLocation.BACKPACK: []
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _save(self):
|
|
|
|
|
"""Save our data back to the same Attribute"""
|
|
|
|
|
self.obj.attributes.add(self.save_attribute, self.slots, category="inventory")
|
|
|
|
|
```
|
|
|
|
|
|
2022-08-11 09:05:29 +02:00
|
|
|
This is a compact and functional little handler. Before analyzing how it works, this is how
|
2022-08-06 22:50:53 +02:00
|
|
|
we will add it to the Character:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
# mygame/evadventure/characters.py
|
|
|
|
|
|
|
|
|
|
# ...
|
|
|
|
|
|
|
|
|
|
from evennia.utils.utils import lazy_property
|
|
|
|
|
from .equipment import EquipmentHandler
|
|
|
|
|
|
|
|
|
|
# ...
|
|
|
|
|
|
|
|
|
|
class EvAdventureCharacter(LivingMixin, DefaultCharacter):
|
|
|
|
|
|
|
|
|
|
# ...
|
|
|
|
|
|
|
|
|
|
@lazy_property
|
|
|
|
|
def equipment(self):
|
|
|
|
|
return EquipmentHandler(self)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
After reloading the server, the equipment-handler will now be accessible on the character as
|
|
|
|
|
|
|
|
|
|
character.equipment
|
|
|
|
|
|
|
|
|
|
The `@lazy_property` works such that it will not load the handler until it is first accessed. When that
|
|
|
|
|
happens, we start up the handler and feed it `self` (the Character itself). This is what enters `__init__`
|
|
|
|
|
as `.obj` in the `EquipmentHandler` code above.
|
|
|
|
|
|
|
|
|
|
So we now have a handler on the character, and the handler has a back-reference to the character it sits
|
|
|
|
|
on.
|
|
|
|
|
|
|
|
|
|
Since the handler itself is just a regular Python object, we need to use the `Character` to store
|
2022-08-11 09:05:29 +02:00
|
|
|
our data - our _Knave_ "slots". We must save them to the database, because we want the server to remember
|
2022-08-06 22:50:53 +02:00
|
|
|
them even after reloading.
|
|
|
|
|
|
|
|
|
|
Using `self.obj.attributes.add()` and `.get()` we save the data to the Character in a specially named
|
2022-08-11 09:05:29 +02:00
|
|
|
[Attribute](../../../Components/Attributes.md). Since we use a `category`, we are unlikely to collide with
|
|
|
|
|
other Attributes.
|
|
|
|
|
|
|
|
|
|
Our storage structure is a `dict` with keys after our available `WieldLocation` enums. Each can only
|
|
|
|
|
have one item except `WieldLocation.BACKPACK`, which is a list.
|
|
|
|
|
|
|
|
|
|
## Connecting the EquipmentHandler
|
|
|
|
|
|
|
|
|
|
We already made `EquipmentHandler` available on the Character as `.equipment`. Now we want it to come into
|
|
|
|
|
play automatically whenever we pick up or drop something. To do this we need to override two hooks
|
|
|
|
|
on the Character class:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
# mygame/evadventure/character.py
|
|
|
|
|
|
|
|
|
|
# ...
|
|
|
|
|
|
|
|
|
|
class EvAdventureCharacter(LivingMixin, DefaultCharacter):
|
|
|
|
|
|
|
|
|
|
# ...
|
|
|
|
|
|
|
|
|
|
def at_pre_object_receive(self, moved_object, source_location, **kwargs):
|
|
|
|
|
"""Called by Evennia before object arrives 'in' this character (that is,
|
|
|
|
|
if they pick up something). If it returns False, move is aborted.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
# we haven't written this yet!
|
|
|
|
|
return self.equipment.validate_slot_usage(moved_object)
|
|
|
|
|
|
|
|
|
|
def at_object_receive(self, moved_object, source_location, **kwargs):
|
|
|
|
|
"""
|
|
|
|
|
Called by Evennia when an object arrives 'in' the character.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
self.equipment.add(moved_object)
|
|
|
|
|
|
|
|
|
|
def at_object_leave(self, moved_object, destination, **kwargs):
|
|
|
|
|
"""
|
|
|
|
|
Called by Evennia when object leaves the Character.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
self.equipment.remove(moved_object)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|