data/states/battle.py (view raw)
1"""This is the state that handles battles against
2monsters"""
3import random, copy
4import pygame as pg
5from .. import tools, battlegui, observer
6from .. components import person, attack, attackitems
7from .. import constants as c
8
9
10class Battle(tools._State):
11 def __init__(self):
12 super(Battle, self).__init__()
13 self.game_data = {}
14 self.current_time = 0.0
15 self.timer = 0.0
16 self.allow_input = False
17 self.allow_info_box_change = False
18 self.name = 'battle'
19 self.state = None
20
21 self.player = None
22 self.attack_animations = None
23 self.sword = None
24 self.enemy_index = 0
25 self.attacked_enemy = None
26 self.attacking_enemy = None
27 self.enemy_group = None
28 self.enemy_pos_list = []
29 self.enemy_list = []
30
31 self.background = None
32 self.info_box = None
33 self.arrow = None
34 self.select_box = None
35 self.player_health_box = None
36 self.select_action_state_dict = {}
37 self.next = None
38 self.observers = []
39 self.damage_points = pg.sprite.Group()
40
41 def startup(self, current_time, game_data):
42 """Initialize state attributes"""
43 self.current_time = current_time
44 self.timer = current_time
45 self.game_data = game_data
46 self.inventory = game_data['player inventory']
47 self.state = c.SELECT_ACTION
48 self.next = game_data['last state']
49 self.run_away = False
50
51 self.player = self.make_player()
52 self.attack_animations = pg.sprite.Group()
53 self.sword = attackitems.Sword(self.player)
54 self.enemy_group, self.enemy_pos_list, self.enemy_list = self.make_enemies()
55 self.background = self.make_background()
56 self.info_box = battlegui.InfoBox(game_data)
57 self.arrow = battlegui.SelectArrow(self.enemy_pos_list,
58 self.info_box)
59 self.select_box = battlegui.SelectBox()
60 self.player_health_box = battlegui.PlayerHealth(self.select_box.rect,
61 self.game_data)
62
63 self.select_action_state_dict = self.make_selection_state_dict()
64 self.observers = [observer.Battle(self)]
65 self.player.observers.extend(self.observers)
66 self.damage_points = pg.sprite.Group()
67
68 @staticmethod
69 def make_background():
70 """Make the blue/black background"""
71 background = pg.sprite.Sprite()
72 surface = pg.Surface(c.SCREEN_SIZE).convert()
73 surface.fill(c.BLACK_BLUE)
74 background.image = surface
75 background.rect = background.image.get_rect()
76 background_group = pg.sprite.Group(background)
77
78 return background_group
79
80 @staticmethod
81 def make_enemies():
82 """Make the enemies for the battle. Return sprite group"""
83 pos_list = []
84
85 for column in range(3):
86 for row in range(3):
87 x = (column * 100) + 100
88 y = (row * 100) + 100
89 pos_list.append([x, y])
90
91 enemy_group = pg.sprite.Group()
92
93 for enemy in range(random.randint(1, 6)):
94 enemy_group.add(person.Person('devil', 0, 0,
95 'down', 'battle resting'))
96
97 for i, enemy in enumerate(enemy_group):
98 enemy.rect.topleft = pos_list[i]
99 enemy.image = pg.transform.scale2x(enemy.image)
100 enemy.index = i
101 enemy.level = 1
102 enemy.health = enemy.level * 7
103
104 enemy_list = [enemy for enemy in enemy_group]
105
106 return enemy_group, pos_list[0:len(enemy_group)], enemy_list
107
108 @staticmethod
109 def make_player():
110 """Make the sprite for the player's character"""
111 player = person.Player('left', 630, 220, 'battle resting', 1)
112 player.image = pg.transform.scale2x(player.image)
113 return player
114
115 def make_selection_state_dict(self):
116 """
117 Make a dictionary of states with arrow coordinates as keys.
118 """
119 pos_list = self.arrow.make_select_action_pos_list()
120 state_list = [c.SELECT_ENEMY, c.SELECT_ITEM, c.SELECT_MAGIC, c.RUN_AWAY]
121 return dict(zip(pos_list, state_list))
122
123 def update(self, surface, keys, current_time):
124 """Update the battle state"""
125 self.current_time = current_time
126 self.check_input(keys)
127 self.check_timed_events()
128 self.check_if_battle_won()
129 self.enemy_group.update(current_time)
130 self.player.update(keys, current_time)
131 self.attack_animations.update()
132 self.info_box.update()
133 self.arrow.update(keys)
134 self.sword.update(current_time)
135 self.damage_points.update()
136
137 self.draw_battle(surface)
138
139 def check_input(self, keys):
140 """
141 Check user input to navigate GUI.
142 """
143 if self.allow_input:
144 if keys[pg.K_RETURN]:
145 self.notify(c.END_BATTLE)
146
147 elif keys[pg.K_SPACE]:
148 if self.state == c.SELECT_ACTION:
149 self.state = self.select_action_state_dict[
150 self.arrow.rect.topleft]
151 self.notify(self.state)
152
153 elif self.state == c.SELECT_ENEMY:
154 self.state = c.PLAYER_ATTACK
155 self.notify(self.state)
156
157 elif self.state == c.SELECT_ITEM:
158 if self.arrow.index == (len(self.arrow.pos_list) - 1):
159 self.state = c.SELECT_ACTION
160 self.notify(self.state)
161 elif self.info_box.item_text_list[self.arrow.index][:14] == 'Healing Potion':
162 self.state = c.DRINK_HEALING_POTION
163 self.notify(self.state)
164
165 elif self.state == c.SELECT_MAGIC:
166 if self.arrow.index == (len(self.arrow.pos_list) - 1):
167 self.state = c.SELECT_ACTION
168 self.notify(self.state)
169 elif self.info_box.magic_text_list[self.arrow.index] == 'Cure':
170 if self.game_data['player stats']['magic points']['current'] >= 25:
171 self.state = c.CURE_SPELL
172 self.notify(self.state)
173 elif self.info_box.magic_text_list[self.arrow.index] == 'Fire Blast':
174 if self.game_data['player stats']['magic points']['current'] >= 75:
175 self.state = c.FIRE_SPELL
176 self.notify(self.state)
177
178 self.allow_input = False
179
180 if keys[pg.K_RETURN] == False and keys[pg.K_SPACE] == False:
181 self.allow_input = True
182
183 def check_timed_events(self):
184 """
185 Check if amount of time has passed for timed events.
186 """
187 timed_states = [c.DISPLAY_ENEMY_ATTACK_DAMAGE,
188 c.ENEMY_HIT,
189 c.ENEMY_DEAD,
190 c.DRINK_HEALING_POTION]
191 long_delay = timed_states[1:]
192
193 if self.state in long_delay:
194 if (self.current_time - self.timer) > 800:
195 if self.state == c.ENEMY_HIT:
196 if len(self.enemy_list):
197 self.state = c.ENEMY_ATTACK
198 else:
199 self.state = c.BATTLE_WON
200 elif (self.state == c.DRINK_HEALING_POTION or
201 self.state == c.CURE_SPELL):
202 if len(self.enemy_list):
203 self.state = c.ENEMY_ATTACK
204 else:
205 self.state = c.BATTLE_WON
206 self.timer = self.current_time
207 self.notify(self.state)
208
209 elif self.state == c.FIRE_SPELL or self.state == c.CURE_SPELL:
210 if (self.current_time - self.timer) > 1500:
211 if len(self.enemy_list):
212 self.state = c.ENEMY_ATTACK
213 else:
214 self.state = c.BATTLE_WON
215 self.timer = self.current_time
216 self.notify(self.state)
217
218 elif self.state == c.FLEE or self.state == c.BATTLE_WON:
219 if (self.current_time - self.timer) > 1500:
220 self.end_battle()
221
222 elif self.state == c.DISPLAY_ENEMY_ATTACK_DAMAGE:
223 if (self.current_time - self.timer) > 600:
224 if self.enemy_index == (len(self.enemy_list) - 1):
225 if self.run_away:
226 self.state = c.FLEE
227 else:
228 self.state = c.SELECT_ACTION
229 else:
230 self.state = c.SWITCH_ENEMY
231 self.timer = self.current_time
232 self.notify(self.state)
233
234 def check_if_battle_won(self):
235 """
236 Check if state is SELECT_ACTION and there are no enemies left.
237 """
238 if self.state == c.SELECT_ACTION:
239 if len(self.enemy_group) == 0:
240 self.notify(c.BATTLE_WON)
241
242 def notify(self, event):
243 """
244 Notify observer of event.
245 """
246 for new_observer in self.observers:
247 new_observer.on_notify(event)
248
249 def end_battle(self):
250 """
251 End battle and flip back to previous state.
252 """
253 self.game_data['last state'] = self.name
254 self.done = True
255
256 def attack_enemy(self, enemy_damage):
257 enemy = self.player.attacked_enemy
258 enemy.health -= enemy_damage
259 self.set_enemy_indices()
260
261 if enemy:
262 enemy.enter_knock_back_state()
263 if enemy.health <= 0:
264 enemy.kill()
265 self.enemy_list.pop(enemy.index)
266 self.arrow.remove_pos(self.player.attacked_enemy)
267 posx = enemy.rect.x - 32
268 posy = enemy.rect.y - 64
269 fire_sprite = attack.Fire(posx, posy)
270 self.attack_animations.add(fire_sprite)
271 self.enemy_index = 0
272 self.player.attacked_enemy = None
273
274 def set_enemy_indices(self):
275 for i, enemy in enumerate(self.enemy_list):
276 enemy.index = i
277
278 def draw_battle(self, surface):
279 """Draw all elements of battle state"""
280 self.background.draw(surface)
281 self.enemy_group.draw(surface)
282 self.attack_animations.draw(surface)
283 self.sword.draw(surface)
284 surface.blit(self.player.image, self.player.rect)
285 surface.blit(self.info_box.image, self.info_box.rect)
286 surface.blit(self.select_box.image, self.select_box.rect)
287 surface.blit(self.arrow.image, self.arrow.rect)
288 self.player_health_box.draw(surface)
289 self.damage_points.draw(surface)
290
291
292 def player_damaged(self, damage):
293 self.game_data['player stats']['health']['current'] -= damage
294
295 def player_healed(self, heal, magic_points=0):
296 health = self.game_data['player stats']['health']
297
298 health['current'] += heal
299 if health['current'] > health['maximum']:
300 health['current'] = health['maximum']
301
302 if self.state == c.DRINK_HEALING_POTION:
303 self.game_data['player inventory']['Healing Potion']['quantity'] -= 1
304 if self.game_data['player inventory']['Healing Potion']['quantity'] == 0:
305 del self.game_data['player inventory']['Healing Potion']
306 elif self.state == c.CURE_SPELL:
307 self.game_data['player stats']['magic points']['current'] -= magic_points
308
309 def set_timer_to_current_time(self):
310 """Set the timer to the current time."""
311 self.timer = self.current_time
312
313 def cast_fire_blast(self):
314 """
315 Cast fire blast on all enemies.
316 """
317 POWER = self.inventory['Fire Blast']['power']
318 MAGIC_POINTS = self.inventory['Fire Blast']['magic points']
319 self.game_data['player stats']['magic points']['current'] -= MAGIC_POINTS
320 for enemy in self.enemy_list:
321 DAMAGE = random.randint(POWER//2, POWER)
322 self.damage_points.add(
323 attackitems.HealthPoints(DAMAGE, enemy.rect.topright))
324 enemy.health -= DAMAGE
325 posx = enemy.rect.x - 32
326 posy = enemy.rect.y - 64
327 fire_sprite = attack.Fire(posx, posy)
328 self.attack_animations.add(fire_sprite)
329 if enemy.health <= 0:
330 enemy.kill()
331 self.arrow.remove_pos(enemy)
332 else:
333 enemy.enter_knock_back_state()
334 self.enemy_list = [enemy for enemy in self.enemy_list if enemy.health > 0]
335 self.enemy_index = 0
336 self.arrow.index = 0
337 self.arrow.become_invisible_surface()
338 self.arrow.state = c.SELECT_ACTION
339 self.state = c.FIRE_SPELL
340 self.set_timer_to_current_time()
341 self.info_box.state = c.FIRE_SPELL
342
343 def cast_cure(self):
344 """
345 Cast cure spell on player.
346 """
347 HEAL_AMOUNT = self.inventory['Cure']['power']
348 MAGIC_POINTS = self.inventory['Cure']['magic points']
349 self.player.healing = True
350 self.set_timer_to_current_time()
351 self.state = c.CURE_SPELL
352 self.arrow.become_invisible_surface()
353 self.enemy_index = 0
354 self.damage_points.add(
355 attackitems.HealthPoints(HEAL_AMOUNT, self.player.rect.topright, False))
356 self.player_healed(HEAL_AMOUNT, MAGIC_POINTS)
357 self.info_box.state = c.DRINK_HEALING_POTION
358
359
360
361
362