44
55from game .actions import Action , ItemAction
66from game .color import health_recovered
7+ from game .components .ai import ConfusedEnemy
78from game .components .base_component import BaseComponent
9+ from game .components .inventory import Inventory
10+ from game .entity import Actor , Item
811from game .exceptions import Impossible
9- import game .entity
12+ from game .input_handlers import ActionOrHandler , AreaRangedAttackHandler , SingleRangedAttackHandler
13+
14+ if TYPE_CHECKING :
15+ import game .actions
1016
1117
1218class Consumable (BaseComponent ):
13- parent : game . entity . Item
19+ parent : Item
1420
15- def get_action (self , consumer : game . entity . Actor ) -> Optional [Action ]:
21+ def get_action (self , consumer : Actor ) -> Optional [Action ]:
1622 """Try to return the action for this item."""
1723 return ItemAction (consumer , self .parent )
1824
@@ -27,7 +33,7 @@ def consume(self) -> None:
2733 """Remove the consumed item from its containing inventory."""
2834 entity = self .parent
2935 inventory = entity .parent
30- if isinstance (inventory , game . components . inventory . Inventory ):
36+ if isinstance (inventory , Inventory ):
3137 inventory .items .remove (entity )
3238
3339
@@ -37,7 +43,7 @@ def __init__(self, amount: int):
3743
3844 def activate (self , action : ItemAction ) -> None :
3945 # Type check to ensure consumer is an Actor
40- assert isinstance (action .entity , game . entity . Actor ), "Consumer must be an Actor"
46+ assert isinstance (action .entity , Actor ), "Consumer must be an Actor"
4147 consumer = action .entity
4248 amount_recovered = consumer .fighter .heal (self .amount )
4349
@@ -49,3 +55,98 @@ def activate(self, action: ItemAction) -> None:
4955 self .consume ()
5056 else :
5157 raise Impossible ("Your health is already full." )
58+
59+
60+ class LightningDamageConsumable (Consumable ):
61+ def __init__ (self , damage : int , maximum_range : int ):
62+ self .damage = damage
63+ self .maximum_range = maximum_range
64+
65+ def activate (self , action : game .actions .ItemAction ) -> None :
66+ consumer = action .entity
67+ target = None
68+ closest_distance = self .maximum_range + 1.0
69+
70+ for actor in self .engine .game_map .actors :
71+ if actor is not consumer and self .parent .gamemap .visible [actor .x , actor .y ]:
72+ distance = consumer .distance (actor .x , actor .y )
73+
74+ if distance < closest_distance :
75+ target = actor
76+ closest_distance = distance
77+
78+ if target :
79+ self .engine .message_log .add_message (
80+ f"A lighting bolt strikes the { target .name } with a loud thunder, for { self .damage } damage!"
81+ )
82+ target .fighter .take_damage (self .damage )
83+ self .consume ()
84+ else :
85+ raise game .exceptions .Impossible ("No enemy is close enough to strike." )
86+
87+
88+ class ConfusionConsumable (Consumable ):
89+ def __init__ (self , number_of_turns : int ):
90+ self .number_of_turns = number_of_turns
91+
92+ def get_action (self , consumer : Actor ) -> Optional [ActionOrHandler ]:
93+ self .engine .message_log .add_message ("Select a target location." , game .color .needs_target )
94+ return SingleRangedAttackHandler (
95+ self .engine ,
96+ callback = lambda xy : game .actions .ItemAction (consumer , self .parent , xy ),
97+ )
98+
99+ def activate (self , action : game .actions .ItemAction ) -> None :
100+ consumer = action .entity
101+ target = action .target_actor
102+
103+ if not self .engine .game_map .visible [action .target_xy ]:
104+ raise game .exceptions .Impossible ("You cannot target an area that you cannot see." )
105+ if not target :
106+ raise game .exceptions .Impossible ("You must select an enemy to target." )
107+ if target is consumer :
108+ raise game .exceptions .Impossible ("You cannot confuse yourself!" )
109+
110+ self .engine .message_log .add_message (
111+ f"The eyes of the { target .name } look vacant, as it starts to stumble around!" ,
112+ game .color .status_effect_applied ,
113+ )
114+ target .ai = ConfusedEnemy (
115+ entity = target ,
116+ previous_ai = target .ai ,
117+ turns_remaining = self .number_of_turns ,
118+ )
119+ self .consume ()
120+
121+
122+ class FireballDamageConsumable (Consumable ):
123+ def __init__ (self , damage : int , radius : int ):
124+ self .damage = damage
125+ self .radius = radius
126+
127+ def get_action (self , consumer : Actor ) -> Optional [ActionOrHandler ]:
128+ self .engine .message_log .add_message ("Select a target location." , game .color .needs_target )
129+ return AreaRangedAttackHandler (
130+ self .engine ,
131+ radius = self .radius ,
132+ callback = lambda xy : game .actions .ItemAction (consumer , self .parent , xy ),
133+ )
134+
135+ def activate (self , action : game .actions .ItemAction ) -> None :
136+ target_xy = action .target_xy
137+
138+ if not self .engine .game_map .visible [target_xy ]:
139+ raise game .exceptions .Impossible ("You cannot target an area that you cannot see." )
140+
141+ targets_hit = False
142+ for actor in self .engine .game_map .actors :
143+ if actor .distance (* target_xy ) <= self .radius :
144+ self .engine .message_log .add_message (
145+ f"The { actor .name } is engulfed in a fiery explosion, taking { self .damage } damage!"
146+ )
147+ actor .fighter .take_damage (self .damage )
148+ targets_hit = True
149+
150+ if not targets_hit :
151+ raise game .exceptions .Impossible ("There are no targets in the radius." )
152+ self .consume ()
0 commit comments