1+ from __future__ import annotations
2+
3+ from typing import Optional , TYPE_CHECKING
4+
5+ import actions
6+ import color
7+ import components .ai
8+ import components .inventory
9+ from components .base_component import BaseComponent
10+ from exceptions import Impossible
11+ from input_handlers import AreaRangedAttackHandler , SingleRangedAttackHandler
12+
13+ if TYPE_CHECKING :
14+ from entity import Actor , Item
15+
16+
17+ class Consumable (BaseComponent ):
18+ parent : Item
19+
20+ def get_action (self , consumer : Actor ) -> Optional [actions .Action ]:
21+ """Try to return the action for this item."""
22+ return actions .ItemAction (consumer , self .parent )
23+
24+ def activate (self , action : actions .ItemAction ) -> None :
25+ """Invoke this items ability.
26+
27+ `action` is the context for this activation.
28+ """
29+ raise NotImplementedError ()
30+
31+ def consume (self ) -> None :
32+ """Remove the consumed item from its containing inventory."""
33+ entity = self .parent
34+ inventory = entity .parent
35+ if isinstance (inventory , components .inventory .Inventory ):
36+ inventory .items .remove (entity )
37+
38+
39+
40+ class HealingConsumable (Consumable ):
41+ def __init__ (self , amount : int ):
42+ self .amount = amount
43+
44+ def activate (self , action : actions .ItemAction ) -> None :
45+ consumer = action .entity
46+ amount_recovered = consumer .fighter .heal (self .amount )
47+
48+ if amount_recovered > 0 :
49+ self .engine .message_log .add_message (
50+ f"You consume the { self .parent .name } , and recover { amount_recovered } HP!" ,
51+ color .health_recovered ,
52+ )
53+ self .consume ()
54+ else :
55+ raise Impossible (f"Your health is already full." )
56+
57+ class EletricDamageConsumable (Consumable ):
58+ def __init__ (self , damage : int , maximum_range : int ):
59+ self .damage = damage
60+ self .maximum_range = maximum_range
61+
62+ def activate (self , action : actions .ItemAction ) -> None :
63+ consumer = action .entity
64+ target = None
65+ closest_distance = self .maximum_range + 1.0
66+
67+ for actor in self .engine .game_map .actors :
68+ if actor is not consumer and self .parent .gamemap .visible [actor .x , actor .y ]:
69+ distance = consumer .distance (actor .x , actor .y )
70+
71+ if distance < closest_distance :
72+ target = actor
73+ closest_distance = distance
74+
75+ if target :
76+ self .engine .message_log .add_message (
77+ f"ZAP! A bright eletric bolt strikes the { target .name } for { self .damage } damage!"
78+ )
79+ target .fighter .take_damage (self .damage )
80+ self .consume ()
81+ else :
82+ raise Impossible ("No enemy is close enough to strike." )
83+
84+ class ConfusionConsumable (Consumable ):
85+ def __init__ (self , number_of_turns : int ):
86+ self .number_of_turns = number_of_turns
87+
88+ def get_action (self , consumer : Actor ) -> Optional [actions .Action ]:
89+ self .engine .message_log .add_message (
90+ "Select a target location." , color .needs_target
91+ )
92+ self .engine .event_handler = SingleRangedAttackHandler (
93+ self .engine ,
94+ callback = lambda xy : actions .ItemAction (consumer , self .parent , xy ),
95+ )
96+ return None
97+
98+ def activate (self , action : actions .ItemAction ) -> None :
99+ consumer = action .entity
100+ target = action .target_actor
101+
102+ if not self .engine .game_map .visible [action .target_xy ]:
103+ raise Impossible ("You cannot target an area that you cannot see." )
104+ if not target :
105+ raise Impossible ("You must select an enemy to target." )
106+ if target is consumer :
107+ raise Impossible ("You cannot confuse yourself!" )
108+
109+ self .engine .message_log .add_message (
110+ f"The eyes of the { target .name } look vacant, as it starts to stumble around!" ,
111+ color .status_effect_applied ,
112+ )
113+ target .ai = components .ai .ConfusedEnemy (
114+ entity = target , previous_ai = target .ai , turns_remaining = self .number_of_turns ,
115+ )
116+ self .consume ()
117+
118+ class FireballDamageConsumable (Consumable ):
119+ def __init__ (self , damage : int , radius : int ):
120+ self .damage = damage
121+ self .radius = radius
122+
123+ def get_action (self , consumer : Actor ) -> Optional [actions .Action ]:
124+ self .engine .message_log .add_message (
125+ "Select a target location." , color .needs_target
126+ )
127+ self .engine .event_handler = AreaRangedAttackHandler (
128+ self .engine ,
129+ radius = self .radius ,
130+ callback = lambda xy : actions .ItemAction (consumer , self .parent , xy ),
131+ )
132+ return None
133+
134+ def activate (self , action : actions .ItemAction ) -> None :
135+ target_xy = action .target_xy
136+
137+ if not self .engine .game_map .visible [target_xy ]:
138+ raise Impossible ("You cannot target an area that you cannot see." )
139+
140+ targets_hit = False
141+ for actor in self .engine .game_map .actors :
142+ if actor .distance (* target_xy ) <= self .radius :
143+ self .engine .message_log .add_message (
144+ f"The { actor .name } is engulfed in a fiery explosion, taking { self .damage } damage!"
145+ )
146+ actor .fighter .take_damage (self .damage )
147+ targets_hit = True
148+
149+ if not targets_hit :
150+ raise Impossible ("There are no targets in the radius." )
151+ self .consume ()
0 commit comments