Skip to content

Commit aa41b63

Browse files
committed
Part 13: Gearing Up - Equipment System
1 parent d7f84d9 commit aa41b63

File tree

11 files changed

+300
-14
lines changed

11 files changed

+300
-14
lines changed

game/actions.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,14 @@ def perform(self) -> None:
178178
"You descend the staircase.", game.color.descend
179179
)
180180
else:
181-
raise game.exceptions.Impossible("There are no stairs here.")
181+
raise game.exceptions.Impossible("There are no stairs here.")
182+
183+
184+
class EquipAction(Action):
185+
def __init__(self, entity: game.entity.Actor, item: game.entity.Item):
186+
super().__init__(entity)
187+
188+
self.item = item
189+
190+
def perform(self) -> None:
191+
self.entity.equipment.toggle_equip(self.item)

game/components/equipment.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from __future__ import annotations
2+
3+
from typing import Optional, TYPE_CHECKING
4+
5+
import game.components.base_component
6+
import game.equipment_types
7+
8+
if TYPE_CHECKING:
9+
import game.entity
10+
11+
12+
class Equipment(game.components.base_component.BaseComponent):
13+
parent: game.entity.Actor
14+
15+
def __init__(self, weapon: Optional[game.entity.Item] = None, armor: Optional[game.entity.Item] = None):
16+
self.weapon = weapon
17+
self.armor = armor
18+
19+
@property
20+
def defense_bonus(self) -> int:
21+
bonus = 0
22+
23+
if self.weapon is not None and self.weapon.equippable is not None:
24+
bonus += self.weapon.equippable.defense_bonus
25+
26+
if self.armor is not None and self.armor.equippable is not None:
27+
bonus += self.armor.equippable.defense_bonus
28+
29+
return bonus
30+
31+
@property
32+
def power_bonus(self) -> int:
33+
bonus = 0
34+
35+
if self.weapon is not None and self.weapon.equippable is not None:
36+
bonus += self.weapon.equippable.power_bonus
37+
38+
if self.armor is not None and self.armor.equippable is not None:
39+
bonus += self.armor.equippable.power_bonus
40+
41+
return bonus
42+
43+
def item_is_equipped(self, item: game.entity.Item) -> bool:
44+
return self.weapon == item or self.armor == item
45+
46+
def unequip_message(self, item_name: str) -> None:
47+
self.parent.parent.engine.message_log.add_message(
48+
f"You remove the {item_name}."
49+
)
50+
51+
def equip_message(self, item_name: str) -> None:
52+
self.parent.parent.engine.message_log.add_message(
53+
f"You equip the {item_name}."
54+
)
55+
56+
def equip_to_slot(self, slot: str, item: game.entity.Item, add_message: bool) -> None:
57+
current_item = getattr(self, slot)
58+
59+
if current_item is not None:
60+
self.unequip_from_slot(slot, add_message)
61+
62+
setattr(self, slot, item)
63+
64+
if add_message:
65+
self.equip_message(item.name)
66+
67+
def unequip_from_slot(self, slot: str, add_message: bool) -> None:
68+
current_item = getattr(self, slot)
69+
70+
if add_message:
71+
self.unequip_message(current_item.name)
72+
73+
setattr(self, slot, None)
74+
75+
def toggle_equip(self, equippable_item: game.entity.Item, add_message: bool = True) -> None:
76+
if (
77+
equippable_item.equippable
78+
and equippable_item.equippable.equipment_type == game.equipment_types.EquipmentType.WEAPON
79+
):
80+
slot = "weapon"
81+
else:
82+
slot = "armor"
83+
84+
if getattr(self, slot) == equippable_item:
85+
self.unequip_from_slot(slot, add_message)
86+
else:
87+
self.equip_to_slot(slot, equippable_item, add_message)

game/components/equippable.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
import game.components.base_component
6+
import game.equipment_types
7+
8+
if TYPE_CHECKING:
9+
import game.entity
10+
11+
12+
class Equippable(game.components.base_component.BaseComponent):
13+
parent: game.entity.Item
14+
15+
def __init__(
16+
self,
17+
equipment_type: game.equipment_types.EquipmentType,
18+
power_bonus: int = 0,
19+
defense_bonus: int = 0,
20+
):
21+
self.equipment_type = equipment_type
22+
23+
self.power_bonus = power_bonus
24+
self.defense_bonus = defense_bonus
25+
26+
27+
class Dagger(Equippable):
28+
def __init__(self) -> None:
29+
super().__init__(equipment_type=game.equipment_types.EquipmentType.WEAPON, power_bonus=2)
30+
31+
32+
class Sword(Equippable):
33+
def __init__(self) -> None:
34+
super().__init__(equipment_type=game.equipment_types.EquipmentType.WEAPON, power_bonus=4)
35+
36+
37+
class LeatherArmor(Equippable):
38+
def __init__(self) -> None:
39+
super().__init__(equipment_type=game.equipment_types.EquipmentType.ARMOR, defense_bonus=1)
40+
41+
42+
class ChainMail(Equippable):
43+
def __init__(self) -> None:
44+
super().__init__(equipment_type=game.equipment_types.EquipmentType.ARMOR, defense_bonus=3)

game/components/fighter.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
class Fighter(game.components.base_component.BaseComponent):
1515
parent: game.entity.Actor
1616

17-
def __init__(self, hp: int, defense: int, power: int):
17+
def __init__(self, hp: int, base_defense: int, base_power: int):
1818
self.max_hp = hp
1919
self._hp = hp
20-
self.defense = defense
21-
self.power = power
20+
self.base_defense = base_defense
21+
self.base_power = base_power
2222

2323
@property
2424
def hp(self) -> int:
@@ -30,6 +30,28 @@ def hp(self, value: int) -> None:
3030
if self._hp == 0 and self.parent.ai:
3131
self.die()
3232

33+
@property
34+
def defense(self) -> int:
35+
return self.base_defense + self.defense_bonus
36+
37+
@property
38+
def power(self) -> int:
39+
return self.base_power + self.power_bonus
40+
41+
@property
42+
def defense_bonus(self) -> int:
43+
if self.parent.equipment:
44+
return self.parent.equipment.defense_bonus
45+
else:
46+
return 0
47+
48+
@property
49+
def power_bonus(self) -> int:
50+
if self.parent.equipment:
51+
return self.parent.equipment.power_bonus
52+
else:
53+
return 0
54+
3355
def die(self) -> None:
3456
if self.engine.player is self.parent:
3557
death_message = "You died!"

game/components/level.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,14 @@ def increase_max_hp(self, amount: int = 20) -> None:
5656
self.increase_level()
5757

5858
def increase_power(self, amount: int = 1) -> None:
59-
self.parent.fighter.power += amount
59+
self.parent.fighter.base_power += amount
6060

6161
self.engine.message_log.add_message("You feel stronger!")
6262

6363
self.increase_level()
6464

6565
def increase_defense(self, amount: int = 1) -> None:
66-
self.parent.fighter.defense += amount
66+
self.parent.fighter.base_defense += amount
6767

6868
self.engine.message_log.add_message("Your movements are getting swifter!")
6969

game/entity.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
if TYPE_CHECKING:
88
import game.components.ai
99
import game.components.consumable
10+
import game.components.equipment
11+
import game.components.equippable
1012
import game.components.fighter
1113
import game.components.inventory
1214
import game.components.level
@@ -78,6 +80,7 @@ def __init__(
7880
color: Tuple[int, int, int] = (255, 255, 255),
7981
name: str = "<Unnamed>",
8082
ai_cls: Type[game.components.ai.BaseAI],
83+
equipment: game.components.equipment.Equipment,
8184
fighter: game.components.fighter.Fighter,
8285
inventory: game.components.inventory.Inventory,
8386
level: game.components.level.Level,
@@ -94,6 +97,9 @@ def __init__(
9497

9598
self.ai: Optional[game.components.ai.BaseAI] = ai_cls(self) if ai_cls else None
9699

100+
self.equipment = equipment
101+
self.equipment.parent = self
102+
97103
self.fighter = fighter
98104
self.fighter.parent = self
99105

@@ -121,6 +127,7 @@ def __init__(
121127
color: Tuple[int, int, int] = (255, 255, 255),
122128
name: str = "<Unnamed>",
123129
consumable: Optional[game.components.consumable.Consumable] = None,
130+
equippable: Optional[game.components.equippable.Equippable] = None,
124131
):
125132
super().__init__(
126133
parent=None,
@@ -133,8 +140,11 @@ def __init__(
133140
)
134141

135142
self.consumable = consumable
136-
137143
if self.consumable:
138144
self.consumable.parent = self
139145

146+
self.equippable = equippable
147+
if self.equippable:
148+
self.equippable.parent = self
149+
140150
self.render_order = game.render_order.RenderOrder.ITEM

game/entity_factories.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
HealingConsumable,
66
LightningDamageConsumable,
77
)
8+
from game.components.equipment import Equipment
9+
from game.components.equippable import ChainMail, Dagger, LeatherArmor, Sword
810
from game.components.fighter import Fighter
911
from game.components.inventory import Inventory
1012
from game.components.level import Level
@@ -15,7 +17,8 @@
1517
color=(255, 255, 255),
1618
name="Player",
1719
ai_cls=HostileEnemy,
18-
fighter=Fighter(hp=30, defense=2, power=5),
20+
equipment=Equipment(),
21+
fighter=Fighter(hp=30, base_defense=2, base_power=5),
1922
inventory=Inventory(capacity=26),
2023
level=Level(level_up_base=200),
2124
)
@@ -25,7 +28,8 @@
2528
color=(63, 127, 63),
2629
name="Orc",
2730
ai_cls=HostileEnemy,
28-
fighter=Fighter(hp=10, defense=0, power=3),
31+
equipment=Equipment(),
32+
fighter=Fighter(hp=10, base_defense=0, base_power=3),
2933
inventory=Inventory(capacity=0),
3034
level=Level(xp_given=35),
3135
)
@@ -35,7 +39,8 @@
3539
color=(0, 127, 0),
3640
name="Troll",
3741
ai_cls=HostileEnemy,
38-
fighter=Fighter(hp=16, defense=1, power=4),
42+
equipment=Equipment(),
43+
fighter=Fighter(hp=16, base_defense=1, base_power=4),
3944
inventory=Inventory(capacity=0),
4045
level=Level(xp_given=100),
4146
)
@@ -66,4 +71,32 @@
6671
color=(255, 0, 0),
6772
name="Fireball Scroll",
6873
consumable=FireballDamageConsumable(damage=12, radius=3),
74+
)
75+
76+
dagger = Item(
77+
char="/",
78+
color=(0, 191, 255),
79+
name="Dagger",
80+
equippable=Dagger(),
81+
)
82+
83+
sword = Item(
84+
char="/",
85+
color=(0, 191, 255),
86+
name="Sword",
87+
equippable=Sword(),
88+
)
89+
90+
leather_armor = Item(
91+
char="[",
92+
color=(139, 69, 19),
93+
name="Leather Armor",
94+
equippable=LeatherArmor(),
95+
)
96+
97+
chain_mail = Item(
98+
char="[",
99+
color=(139, 69, 19),
100+
name="Chain Mail",
101+
equippable=ChainMail(),
69102
)

game/equipment_types.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from enum import auto, Enum
2+
3+
4+
class EquipmentType(Enum):
5+
WEAPON = auto()
6+
ARMOR = auto()

0 commit comments

Comments
 (0)