Skip to content

Commit ae51338

Browse files
committed
Part 13: Gearing Up - Equipment System
1 parent 413d827 commit ae51338

File tree

11 files changed

+286
-13
lines changed

11 files changed

+286
-13
lines changed

game/actions.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,15 @@ def perform(self) -> None:
185185
self.engine.message_log.add_message("You descend the staircase.", descend)
186186
else:
187187
raise game.exceptions.Impossible("There are no stairs here.")
188+
189+
190+
class EquipAction(Action):
191+
def __init__(self, entity: game.entity.Actor, item: game.entity.Item):
192+
super().__init__(entity)
193+
194+
self.item = item
195+
196+
def perform(self) -> None:
197+
# Type check to ensure entity is an Actor with equipment
198+
assert isinstance(self.entity, game.entity.Actor), "Entity must be an Actor for equipment access"
199+
self.entity.equipment.toggle_equip(self.item)

game/components/equipment.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING, Optional
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(f"You remove the {item_name}.")
48+
49+
def equip_message(self, item_name: str) -> None:
50+
self.parent.parent.engine.message_log.add_message(f"You equip the {item_name}.")
51+
52+
def equip_to_slot(self, slot: str, item: game.entity.Item, add_message: bool) -> None:
53+
current_item = getattr(self, slot)
54+
55+
if current_item is not None:
56+
self.unequip_from_slot(slot, add_message)
57+
58+
setattr(self, slot, item)
59+
60+
if add_message:
61+
self.equip_message(item.name)
62+
63+
def unequip_from_slot(self, slot: str, add_message: bool) -> None:
64+
current_item = getattr(self, slot)
65+
66+
if add_message:
67+
self.unequip_message(current_item.name)
68+
69+
setattr(self, slot, None)
70+
71+
def toggle_equip(self, equippable_item: game.entity.Item, add_message: bool = True) -> None:
72+
if (
73+
equippable_item.equippable
74+
and equippable_item.equippable.equipment_type == game.equipment_types.EquipmentType.WEAPON
75+
):
76+
slot = "weapon"
77+
else:
78+
slot = "armor"
79+
80+
if getattr(self, slot) == equippable_item:
81+
self.unequip_from_slot(slot, add_message)
82+
else:
83+
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(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
@@ -54,14 +54,14 @@ def increase_max_hp(self, amount: int = 20) -> None:
5454
self.increase_level()
5555

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

5959
self.engine.message_log.add_message("You feel stronger!")
6060

6161
self.increase_level()
6262

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

6666
self.engine.message_log.add_message("Your movements are getting swifter!")
6767

game/entity.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
if TYPE_CHECKING:
99
import game.components.ai
1010
import game.components.consumable
11+
import game.components.equipment
12+
import game.components.equippable
1113
import game.components.fighter
1214
import game.components.inventory
1315
import game.components.level
@@ -84,6 +86,7 @@ def __init__(
8486
color: Tuple[int, int, int] = (255, 255, 255),
8587
name: str = "<Unnamed>",
8688
ai_cls: Type[game.components.ai.BaseAI],
89+
equipment: game.components.equipment.Equipment,
8790
fighter: game.components.fighter.Fighter,
8891
inventory: game.components.inventory.Inventory,
8992
level: game.components.level.Level,
@@ -100,6 +103,9 @@ def __init__(
100103

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

106+
self.equipment = equipment
107+
self.equipment.parent = self
108+
103109
self.fighter = fighter
104110
self.fighter.parent = self
105111

@@ -127,6 +133,7 @@ def __init__(
127133
color: Tuple[int, int, int] = (255, 255, 255),
128134
name: str = "<Unnamed>",
129135
consumable: Optional[game.components.consumable.Consumable] = None,
136+
equippable: Optional[game.components.equippable.Equippable] = None,
130137
):
131138
super().__init__(
132139
parent=None,
@@ -139,8 +146,11 @@ def __init__(
139146
)
140147

141148
self.consumable = consumable
142-
143149
if self.consumable:
144150
self.consumable.parent = self
145151

152+
self.equippable = equippable
153+
if self.equippable:
154+
self.equippable.parent = self
155+
146156
self.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
)
@@ -67,3 +72,31 @@
6772
name="Fireball Scroll",
6873
consumable=FireballDamageConsumable(damage=12, radius=3),
6974
)
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(),
102+
)

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 Enum, auto
2+
3+
4+
class EquipmentType(Enum):
5+
WEAPON = auto()
6+
ARMOR = auto()

0 commit comments

Comments
 (0)