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 self.experience_points = 0
31
32 self.background = None
33 self.info_box = None
34 self.arrow = None
35 self.select_box = None
36 self.player_health_box = None
37 self.select_action_state_dict = {}
38 self.next = None
39 self.observers = []
40 self.damage_points = pg.sprite.Group()
41
42 def startup(self, current_time, game_data):
43 """Initialize state attributes"""
44 self.current_time = current_time
45 self.timer = current_time
46 self.allow_input = False
47 self.game_data = game_data
48 self.inventory = game_data['player inventory']
49 self.state = c.SELECT_ACTION
50 self.next = game_data['last state']
51 self.run_away = False
52
53 self.player = self.make_player()
54 self.attack_animations = pg.sprite.Group()
55 self.sword = attackitems.Sword(self.player)
56 self.enemy_group, self.enemy_pos_list, self.enemy_list = self.make_enemies()
57 self.experience_points = self.get_experience_points()
58 self.background = self.make_background()
59 self.info_box = battlegui.InfoBox(game_data, self.experience_points)
60 self.arrow = battlegui.SelectArrow(self.enemy_pos_list,
61 self.info_box)
62 self.select_box = battlegui.SelectBox()
63 self.player_health_box = battlegui.PlayerHealth(self.select_box.rect,
64 self.game_data)
65
66 self.select_action_state_dict = self.make_selection_state_dict()
67 self.observers = [observer.Battle(self)]
68 self.player.observers.extend(self.observers)
69 self.damage_points = pg.sprite.Group()
70
71 def make_enemy_level_dict(self):
72 new_dict = {c.OVERWORLD: 1,
73 c.DUNGEON: 2,
74 c.DUNGEON2: 2,
75 c.DUNGEON3: 3,
76 c.DUNGEON4: 2}
77
78 return new_dict
79
80 def set_enemy_level(self, enemy_list):
81 dungeon_level_dict = self.make_enemy_level_dict()
82
83 for enemy in enemy_list:
84 enemy.level = dungeon_level_dict[self.previous]
85
86 def get_experience_points(self):
87 """
88 Calculate experience points based on number of enemies
89 and their levels.
90 """
91 experience_total = 0
92
93 for enemy in self.enemy_list:
94 experience_total += (random.randint(5,10)*enemy.level)
95
96 return experience_total
97
98 @staticmethod
99 def make_background():
100 """Make the blue/black background"""
101 background = pg.sprite.Sprite()
102 surface = pg.Surface(c.SCREEN_SIZE).convert()
103 surface.fill(c.BLACK_BLUE)
104 background.image = surface
105 background.rect = background.image.get_rect()
106 background_group = pg.sprite.Group(background)
107
108 return background_group
109
110 def make_enemies(self):
111 """Make the enemies for the battle. Return sprite group"""
112 pos_list = []
113
114 for column in range(3):
115 for row in range(3):
116 x = (column * 100) + 100
117 y = (row * 100) + 100
118 pos_list.append([x, y])
119
120 enemy_group = pg.sprite.Group()
121
122 if self.game_data['battle type']:
123 enemy = person.Enemy('evilwizard', 0, 0,
124 'down', 'battle resting')
125 enemy_group.add(enemy)
126 else:
127 for enemy in range(random.randint(1, 6)):
128 enemy_group.add(person.Enemy('devil', 0, 0,
129 'down', 'battle resting'))
130
131 for i, enemy in enumerate(enemy_group):
132 enemy.rect.topleft = pos_list[i]
133 enemy.image = pg.transform.scale2x(enemy.image)
134 enemy.index = i
135 enemy.level = self.make_enemy_level_dict()[self.previous]
136 enemy.health = enemy.level * 7
137
138 enemy_list = [enemy for enemy in enemy_group]
139
140 return enemy_group, pos_list[0:len(enemy_group)], enemy_list
141
142 def make_player(self):
143 """Make the sprite for the player's character"""
144 player = person.Player('left', self.game_data, 630, 220, 'battle resting', 1)
145 player.image = pg.transform.scale2x(player.image)
146 return player
147
148 def make_selection_state_dict(self):
149 """
150 Make a dictionary of states with arrow coordinates as keys.
151 """
152 pos_list = self.arrow.make_select_action_pos_list()
153 state_list = [c.SELECT_ENEMY, c.SELECT_ITEM, c.SELECT_MAGIC, c.RUN_AWAY]
154 return dict(zip(pos_list, state_list))
155
156 def update(self, surface, keys, current_time):
157 """Update the battle state"""
158 self.current_time = current_time
159 self.check_input(keys)
160 self.check_timed_events()
161 self.check_if_battle_won()
162 self.enemy_group.update(current_time)
163 self.player.update(keys, current_time)
164 self.attack_animations.update()
165 self.info_box.update()
166 self.arrow.update(keys)
167 self.sword.update(current_time)
168 self.damage_points.update()
169
170 self.draw_battle(surface)
171
172 def check_input(self, keys):
173 """
174 Check user input to navigate GUI.
175 """
176 if self.allow_input:
177 if keys[pg.K_RETURN]:
178 self.notify(c.END_BATTLE)
179
180 elif keys[pg.K_SPACE]:
181 if self.state == c.SELECT_ACTION:
182 self.state = self.select_action_state_dict[
183 self.arrow.rect.topleft]
184 self.notify(self.state)
185
186 elif self.state == c.SELECT_ENEMY:
187 self.state = c.PLAYER_ATTACK
188 self.notify(self.state)
189
190 elif self.state == c.SELECT_ITEM:
191 if self.arrow.index == (len(self.arrow.pos_list) - 1):
192 self.state = c.SELECT_ACTION
193 self.notify(self.state)
194 elif self.info_box.item_text_list[self.arrow.index][:14] == 'Healing Potion':
195 self.state = c.DRINK_HEALING_POTION
196 self.notify(self.state)
197 elif self.info_box.item_text_list[self.arrow.index][:5] == 'Ether':
198 self.state = c.DRINK_ETHER_POTION
199 self.notify(self.state)
200 elif self.state == c.SELECT_MAGIC:
201 if self.arrow.index == (len(self.arrow.pos_list) - 1):
202 self.state = c.SELECT_ACTION
203 self.notify(self.state)
204 elif self.info_box.magic_text_list[self.arrow.index] == 'Cure':
205 if self.game_data['player stats']['magic points']['current'] >= 25:
206 self.state = c.CURE_SPELL
207 self.notify(self.state)
208 elif self.info_box.magic_text_list[self.arrow.index] == 'Fire Blast':
209 if self.game_data['player stats']['magic points']['current'] >= 25:
210 self.state = c.FIRE_SPELL
211 self.notify(self.state)
212
213 self.allow_input = False
214
215 if keys[pg.K_RETURN] == False and keys[pg.K_SPACE] == False:
216 self.allow_input = True
217
218 def check_timed_events(self):
219 """
220 Check if amount of time has passed for timed events.
221 """
222 timed_states = [c.DISPLAY_ENEMY_ATTACK_DAMAGE,
223 c.ENEMY_HIT,
224 c.ENEMY_DEAD,
225 c.DRINK_HEALING_POTION,
226 c.DRINK_ETHER_POTION]
227 long_delay = timed_states[1:]
228
229 if self.state in long_delay:
230 if (self.current_time - self.timer) > 1000:
231 if self.state == c.ENEMY_HIT:
232 if len(self.enemy_list):
233 self.state = c.ENEMY_ATTACK
234 else:
235 self.state = c.BATTLE_WON
236 elif (self.state == c.DRINK_HEALING_POTION or
237 self.state == c.CURE_SPELL or
238 self.state == c.DRINK_ETHER_POTION):
239 if len(self.enemy_list):
240 self.state = c.ENEMY_ATTACK
241 else:
242 self.state = c.BATTLE_WON
243 self.timer = self.current_time
244 self.notify(self.state)
245
246 elif self.state == c.FIRE_SPELL or self.state == c.CURE_SPELL:
247 if (self.current_time - self.timer) > 1500:
248 if len(self.enemy_list):
249 self.state = c.ENEMY_ATTACK
250 else:
251 self.state = c.BATTLE_WON
252 self.timer = self.current_time
253 self.notify(self.state)
254
255 elif self.state == c.FLEE:
256 if (self.current_time - self.timer) > 1500:
257 self.end_battle()
258
259 elif self.state == c.BATTLE_WON:
260 if (self.current_time - self.timer) > 1800:
261 self.state = c.SHOW_EXPERIENCE
262 self.notify(self.state)
263
264 elif self.state == c.SHOW_EXPERIENCE:
265 if (self.current_time - self.timer) > 2200:
266 player_stats = self.game_data['player stats']
267 player_stats['experience to next level'] -= self.experience_points
268 if player_stats['experience to next level'] <= 0:
269 player_stats['Level'] += 1
270 player_stats['health']['maximum'] += int(player_stats['health']['maximum']*.25)
271 player_stats['magic points']['maximum'] += int(player_stats['magic points']['maximum']*.20)
272 new_experience = int((player_stats['Level'] * 100) * .75)
273 player_stats['experience to next level'] = new_experience
274 self.notify(c.LEVEL_UP)
275 else:
276 self.end_battle()
277
278 elif self.state == c.DISPLAY_ENEMY_ATTACK_DAMAGE:
279 if (self.current_time - self.timer) > 600:
280 if self.enemy_index == (len(self.enemy_list) - 1):
281 if self.run_away:
282 self.state = c.FLEE
283 else:
284 self.state = c.SELECT_ACTION
285 else:
286 self.state = c.SWITCH_ENEMY
287 self.timer = self.current_time
288 self.notify(self.state)
289
290 def check_if_battle_won(self):
291 """
292 Check if state is SELECT_ACTION and there are no enemies left.
293 """
294 if self.state == c.SELECT_ACTION:
295 if len(self.enemy_group) == 0:
296 self.notify(c.BATTLE_WON)
297
298 def notify(self, event):
299 """
300 Notify observer of event.
301 """
302 for new_observer in self.observers:
303 new_observer.on_notify(event)
304
305 def end_battle(self):
306 """
307 End battle and flip back to previous state.
308 """
309 if self.game_data['battle type'] == 'evilwizard':
310 self.game_data['crown quest'] = True
311 self.game_data['last state'] = self.name
312 self.game_data['battle counter'] = random.randint(50, 255)
313 self.game_data['battle type'] = None
314 self.done = True
315
316 def attack_enemy(self, enemy_damage):
317 enemy = self.player.attacked_enemy
318 enemy.health -= enemy_damage
319 self.set_enemy_indices()
320
321 if enemy:
322 enemy.enter_knock_back_state()
323 if enemy.health <= 0:
324 self.enemy_list.pop(enemy.index)
325 enemy.state = c.FADE_DEATH
326 self.notify(c.FADE_DEATH)
327 self.arrow.remove_pos(self.player.attacked_enemy)
328 self.enemy_index = 0
329
330 def set_enemy_indices(self):
331 for i, enemy in enumerate(self.enemy_list):
332 enemy.index = i
333
334 def draw_battle(self, surface):
335 """Draw all elements of battle state"""
336 self.background.draw(surface)
337 self.enemy_group.draw(surface)
338 self.attack_animations.draw(surface)
339 self.sword.draw(surface)
340 surface.blit(self.player.image, self.player.rect)
341 surface.blit(self.info_box.image, self.info_box.rect)
342 surface.blit(self.select_box.image, self.select_box.rect)
343 surface.blit(self.arrow.image, self.arrow.rect)
344 self.player_health_box.draw(surface)
345 self.damage_points.draw(surface)
346
347
348 def player_damaged(self, damage):
349 self.game_data['player stats']['health']['current'] -= damage
350
351 def player_healed(self, heal, magic_points=0):
352 """
353 Add health from potion to game data.
354 """
355 health = self.game_data['player stats']['health']
356
357 health['current'] += heal
358 if health['current'] > health['maximum']:
359 health['current'] = health['maximum']
360
361 if self.state == c.DRINK_HEALING_POTION:
362 self.game_data['player inventory']['Healing Potion']['quantity'] -= 1
363 if self.game_data['player inventory']['Healing Potion']['quantity'] == 0:
364 del self.game_data['player inventory']['Healing Potion']
365 elif self.state == c.CURE_SPELL:
366 self.game_data['player stats']['magic points']['current'] -= magic_points
367
368 def magic_boost(self, magic_points):
369 """
370 Add magic from ether to game data.
371 """
372 magic = self.game_data['player stats']['magic points']
373 magic['current'] += magic_points
374 if magic['current'] > magic['maximum']:
375 magic['current'] = magic['maximum']
376
377 self.game_data['player inventory']['Ether Potion']['quantity'] -= 1
378 if not self.game_data['player inventory']['Ether Potion']['quantity']:
379 del self.game_data['player inventory']['Ether Potion']
380
381 def set_timer_to_current_time(self):
382 """Set the timer to the current time."""
383 self.timer = self.current_time
384
385 def cast_fire_blast(self):
386 """
387 Cast fire blast on all enemies.
388 """
389 POWER = self.inventory['Fire Blast']['power']
390 MAGIC_POINTS = self.inventory['Fire Blast']['magic points']
391 self.game_data['player stats']['magic points']['current'] -= MAGIC_POINTS
392 for enemy in self.enemy_list:
393 DAMAGE = random.randint(POWER//2, POWER)
394 self.damage_points.add(
395 attackitems.HealthPoints(DAMAGE, enemy.rect.topright))
396 enemy.health -= DAMAGE
397 posx = enemy.rect.x - 32
398 posy = enemy.rect.y - 64
399 fire_sprite = attack.Fire(posx, posy)
400 self.attack_animations.add(fire_sprite)
401 if enemy.health <= 0:
402 enemy.kill()
403 self.arrow.remove_pos(enemy)
404 else:
405 enemy.enter_knock_back_state()
406 self.enemy_list = [enemy for enemy in self.enemy_list if enemy.health > 0]
407 self.enemy_index = 0
408 self.arrow.index = 0
409 self.arrow.become_invisible_surface()
410 self.arrow.state = c.SELECT_ACTION
411 self.state = c.FIRE_SPELL
412 self.set_timer_to_current_time()
413 self.info_box.state = c.FIRE_SPELL
414
415 def cast_cure(self):
416 """
417 Cast cure spell on player.
418 """
419 HEAL_AMOUNT = self.inventory['Cure']['power']
420 MAGIC_POINTS = self.inventory['Cure']['magic points']
421 self.player.healing = True
422 self.set_timer_to_current_time()
423 self.state = c.CURE_SPELL
424 self.arrow.become_invisible_surface()
425 self.enemy_index = 0
426 self.damage_points.add(
427 attackitems.HealthPoints(HEAL_AMOUNT, self.player.rect.topright, False))
428 self.player_healed(HEAL_AMOUNT, MAGIC_POINTS)
429 self.info_box.state = c.DRINK_HEALING_POTION
430
431 def drink_ether(self):
432 """
433 Drink ether potion.
434 """
435 self.player.healing = True
436 self.set_timer_to_current_time()
437 self.state = c.DRINK_ETHER_POTION
438 self.arrow.become_invisible_surface()
439 self.enemy_index = 0
440 self.damage_points.add(
441 attackitems.HealthPoints(30,
442 self.player.rect.topright,
443 False,
444 True))
445 self.magic_boost(30)
446 self.info_box.state = c.DRINK_ETHER_POTION
447
448 def drink_healing_potion(self):
449 """
450 Drink Healing Potion.
451 """
452 self.player.healing = True
453 self.set_timer_to_current_time()
454 self.state = c.DRINK_HEALING_POTION
455 self.arrow.become_invisible_surface()
456 self.enemy_index = 0
457 self.damage_points.add(
458 attackitems.HealthPoints(30,
459 self.player.rect.topright,
460 False))
461 self.player_healed(30)
462 self.info_box.state = c.DRINK_HEALING_POTION
463
464
465
466
467