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'] >= 25:
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.game_data['battle counter'] = random.randint(50, 255)
255 self.done = True
256
257 def attack_enemy(self, enemy_damage):
258 enemy = self.player.attacked_enemy
259 enemy.health -= enemy_damage
260 self.set_enemy_indices()
261
262 if enemy:
263 enemy.enter_knock_back_state()
264 if enemy.health <= 0:
265 enemy.kill()
266 self.enemy_list.pop(enemy.index)
267 self.arrow.remove_pos(self.player.attacked_enemy)
268 posx = enemy.rect.x - 32
269 posy = enemy.rect.y - 64
270 fire_sprite = attack.Fire(posx, posy)
271 self.attack_animations.add(fire_sprite)
272 self.enemy_index = 0
273 self.player.attacked_enemy = None
274
275 def set_enemy_indices(self):
276 for i, enemy in enumerate(self.enemy_list):
277 enemy.index = i
278
279 def draw_battle(self, surface):
280 """Draw all elements of battle state"""
281 self.background.draw(surface)
282 self.enemy_group.draw(surface)
283 self.attack_animations.draw(surface)
284 self.sword.draw(surface)
285 surface.blit(self.player.image, self.player.rect)
286 surface.blit(self.info_box.image, self.info_box.rect)
287 surface.blit(self.select_box.image, self.select_box.rect)
288 surface.blit(self.arrow.image, self.arrow.rect)
289 self.player_health_box.draw(surface)
290 self.damage_points.draw(surface)
291
292
293 def player_damaged(self, damage):
294 self.game_data['player stats']['health']['current'] -= damage
295
296 def player_healed(self, heal, magic_points=0):
297 health = self.game_data['player stats']['health']
298
299 health['current'] += heal
300 if health['current'] > health['maximum']:
301 health['current'] = health['maximum']
302
303 if self.state == c.DRINK_HEALING_POTION:
304 self.game_data['player inventory']['Healing Potion']['quantity'] -= 1
305 if self.game_data['player inventory']['Healing Potion']['quantity'] == 0:
306 del self.game_data['player inventory']['Healing Potion']
307 elif self.state == c.CURE_SPELL:
308 self.game_data['player stats']['magic points']['current'] -= magic_points
309
310 def set_timer_to_current_time(self):
311 """Set the timer to the current time."""
312 self.timer = self.current_time
313
314 def cast_fire_blast(self):
315 """
316 Cast fire blast on all enemies.
317 """
318 POWER = self.inventory['Fire Blast']['power']
319 MAGIC_POINTS = self.inventory['Fire Blast']['magic points']
320 self.game_data['player stats']['magic points']['current'] -= MAGIC_POINTS
321 for enemy in self.enemy_list:
322 DAMAGE = random.randint(POWER//2, POWER)
323 self.damage_points.add(
324 attackitems.HealthPoints(DAMAGE, enemy.rect.topright))
325 enemy.health -= DAMAGE
326 posx = enemy.rect.x - 32
327 posy = enemy.rect.y - 64
328 fire_sprite = attack.Fire(posx, posy)
329 self.attack_animations.add(fire_sprite)
330 if enemy.health <= 0:
331 enemy.kill()
332 self.arrow.remove_pos(enemy)
333 else:
334 enemy.enter_knock_back_state()
335 self.enemy_list = [enemy for enemy in self.enemy_list if enemy.health > 0]
336 self.enemy_index = 0
337 self.arrow.index = 0
338 self.arrow.become_invisible_surface()
339 self.arrow.state = c.SELECT_ACTION
340 self.state = c.FIRE_SPELL
341 self.set_timer_to_current_time()
342 self.info_box.state = c.FIRE_SPELL
343
344 def cast_cure(self):
345 """
346 Cast cure spell on player.
347 """
348 HEAL_AMOUNT = self.inventory['Cure']['power']
349 MAGIC_POINTS = self.inventory['Cure']['magic points']
350 self.player.healing = True
351 self.set_timer_to_current_time()
352 self.state = c.CURE_SPELL
353 self.arrow.become_invisible_surface()
354 self.enemy_index = 0
355 self.damage_points.add(
356 attackitems.HealthPoints(HEAL_AMOUNT, self.player.rect.topright, False))
357 self.player_healed(HEAL_AMOUNT, MAGIC_POINTS)
358 self.info_box.state = c.DRINK_HEALING_POTION
359
360
361
362
363