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