diff options
Diffstat (limited to 'formation_spawner.py')
-rw-r--r-- | formation_spawner.py | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/formation_spawner.py b/formation_spawner.py new file mode 100644 index 0000000..1f3f8d1 --- /dev/null +++ b/formation_spawner.py @@ -0,0 +1,341 @@ +import math +from random import choice, randint, random + +from boss import CircleBossFormation, SnakeBossFormation +from enemy import EnemyAttributes +from formation import ( + CircleFormation, + CircleFormationAttributes, + EnemyFormation, + FormationAttributes, + LemniscateFormation, + RectangleFormation, + RectangleFormationAttributes, + TriangleFormation, + TriangleFormationAttributes, +) + + +def wobble_pattern(formation): + """A sinusoidal movement pattern + + :param formation: Formation to move + """ + x = (1+math.sin(formation.alpha/80)) * formation.game.w/2 + y = formation.y + (1 if formation.alpha % 4 == 0 else 0) + formation.set_pos((x, y)) + + +def speed_pattern(formation): + """Quickly move the formation downwards + + :param formation: Formation to move + """ + x = formation.x + y = formation.y + 2 + formation.set_pos((x, y)) + + +def slow_pattern(formation): + """Slowly move the formation downwards + + :param formation: Formation to move + """ + x = formation.x + y = formation.y + (1 if formation.alpha % 8 == 0 else 0) + formation.set_pos((x, y)) + + +def slide_in_pattern(formation): + """Slowly move into the center of the screen and then remain there + + :param formation: Formation to move + """ + cy = formation.game.h//3 + + if formation.alpha < 400: + x = formation.x + y = (formation.alpha/400) * (cy*1.5) - (cy*0.5) + else: + x = formation.x + y = formation.y + + formation.set_pos((int(x), int(y))) + + +def no_pattern(formation): + """No movement, stay in the center + + :param formation: Formation to move + """ + formation.set_pos(( + formation.game.w // 2, + formation.game.h // 3 + )) + + +def figure_of_eight_pattern(formation): + """Move the formation in a figure of eight + + :param formation: Formation to move + """ + period = 600 + edge = 8 + radius = formation.game.h//3 - edge + cx, cy = formation.game.w//2, formation.game.h//3 + + if formation.alpha < 200: + x = formation.x + y = (formation.alpha/200) * (cy*1.5) - (cy*0.5) + else: + a = formation.alpha - 200 + t = (a/period)*2*math.pi - math.pi/2 + + y = cy + (radius * math.cos(t)) / (1 + math.sin(t)**2) + x = cx + (radius * math.sin(t) * math.cos(t)) / (1 + math.sin(t)**2) + + formation.set_pos((int(x), int(y))) + + +class FormationSpawner(): + """Object to manage spawning of enemies and phases""" + + def __init__(self, game): + """Initialise the formation spawner + + :param game: The game which this belongs to + """ + self.game = game + self.formations = [] + self.difficulty_multiplier = 0.5 + + self.next_formation = 0 + + self.phase = -1 + self.phases = [ + Phase("Phase:1", [ + self.spawn_fleet, + self.spawn_loop, + self.spawn_orbital], 10), + Phase("Boss:1", [self.spawn_circle_boss], 1), + Phase("Phase:2", [ + self.spawn_fleet, + self.spawn_loop, + self.spawn_orbital], 10, max_wave=3), + Phase("Boss:2", [self.spawn_snake_boss], 1), + ] + + self.to_spawn = 0 + self.current_reward = 1 + + def tick(self): + """Update all formations""" + for formation, update in self.formations: + formation.tick(self.game.player) + update(formation) + if formation.y > self.game.h: + formation.destroy() + self.formations = list( + filter(lambda s: not s[0].destroyed, self.formations)) + + self.spawn_next() + + def spawn_random(self): + """Spawn a random formation""" + options = [ + self.spawn_fleet, + self.spawn_loop, + self.spawn_orbital, + self.spawn_rectangle + ] + choice(options)() + + def spawn_formation(self, formation: EnemyFormation, update): + """Add a formation to the list of formations + + :param formation: Formation to add + :type formation: EnemyFormation + :param update: movement function to use for this formation + """ + update(formation) + formation.show() + self.formations.append((formation, update)) + + def spawn_circle_boss(self): + """Spawn the circle boss""" + attributes = EnemyAttributes( + hp=int(15*self.difficulty_multiplier), + reward=self.current_reward, + cooldown=50 + ) + formation = CircleBossFormation(self.game, attributes) + formation.set_pos((self.game.w//2, 0)) + update = figure_of_eight_pattern + self.spawn_formation(formation, update) + + def spawn_snake_boss(self): + """Spawn the snake boss""" + attributes = FormationAttributes( + hp=int(10*self.difficulty_multiplier), + reward=self.current_reward, + cooldown=160 + ) + + formation = SnakeBossFormation(self.game, attributes) + formation.set_pos((self.game.w//2, 0)) + update = slide_in_pattern + self.spawn_formation(formation, update) + + def spawn_fleet(self): + """Spawn the fleet formation""" + sprite = randint(6, 7) + + position = (random()*self.game.w, -32) + attributes = TriangleFormationAttributes( + hp=int(self.difficulty_multiplier), + cooldown=-1, + reward=self.current_reward, + count=randint(1, 3)*2 + 1, + spacing=8 + ) + formation = TriangleFormation( + self.game, f"smallenemy{sprite}", attributes) + formation.set_pos(position) + + update = speed_pattern + self.spawn_formation(formation, update) + + def spawn_orbital(self): + """Spawn the orbital formation""" + position = (random()*self.game.w, -32) + sprite = choice((1, 3)) + + attributes = CircleFormationAttributes( + hp=int(self.difficulty_multiplier * 2), + count=randint(3, 4)*2, + radius=randint(10, 20), + period=randint(100//int(self.difficulty_multiplier), 400), + cooldown=80, + reward=self.current_reward + + ) + + formation = CircleFormation( + self.game, f"smallenemy{sprite}", attributes) + formation.set_pos(position) + + update = wobble_pattern + formation.alpha = randint(1, 1000) + self.spawn_formation(formation, update) + + def spawn_rectangle(self): + """Spawn the rectangle formation""" + sprite = choice((0, 2)) + position = (random() * self.game.w, -32) + + attributes = RectangleFormationAttributes( + hp=int(self.difficulty_multiplier * 2), + width=randint(4, 6), + height=randint(2, 3), + cooldown=80, + reward=self.current_reward, + ) + + formation = RectangleFormation( + self.game, f"smallenemy{sprite}", attributes + ) + formation.set_pos(position) + + update = wobble_pattern + formation.alpha = randint(1, 1000) + self.spawn_formation(formation, update) + + def spawn_loop(self): + """Spawn the loop formation""" + sprite = choice((4, 5)) + position = (random()*self.game.w, -32) + attributes = CircleFormationAttributes( + count=randint(4, 8), + radius=randint(self.game.w//2, self.game.w), + period=randint(200, 300), + hp=int(self.difficulty_multiplier), + reward=self.current_reward, + cooldown=160, + ) + + formation = LemniscateFormation( + self.game, f"smallenemy{sprite}", attributes) + formation.set_pos(position) + + update = slow_pattern + self.spawn_formation(formation, update) + + def spawn_next(self): + """Spawn the next formation to be spawned""" + if self.to_spawn > 0: + if len(self.formations) < self.current_phase().max_wave: + if self.game.alpha > self.next_formation \ + and self.next_formation != -1: + self.next_formation = self.game.alpha \ + + 100 / self.difficulty_multiplier + + self.current_phase().get_spawn_function()() + self.to_spawn -= 1 + else: + if len(self.formations) == 0: + self.next_phase() + + def next_phase(self): + """Increment the phase by 1 and start the next phase""" + self.phase += 1 + self.game.save_game() + self.start_phase() + + def start_phase(self): + """Start the next phase""" + self.to_spawn = self.current_phase().duration + + self.difficulty_multiplier = (self.phase+2) * 0.5 + self.current_reward = int(2**self.difficulty_multiplier) + + self.next_formation = self.game.alpha + 100 + if self.current_phase().name: + self.game.effect_player.splash_text(self.current_phase().name) + + def current_phase(self): + """Return the current phase""" + if self.phase < len(self.phases): + return self.phases[self.phase] + + return Phase(f"Phase:{self.phase-1}", [ + self.spawn_random + ], 10 * self.difficulty_multiplier, + max_wave=int(self.difficulty_multiplier) + ) + + def clear_all(self): + """Remove all formation objects""" + for f, _ in self.formations: + f.destroy() + + +class Phase: + """Rules for which formation will be spawned""" + + def __init__(self, name, spawn_functions, duration, max_wave=2): + """__init__. + + :param name: The name of the phase + :param spawn_functions: A list of functions to use to spawn enemies + :param duration: The number of formations to spawn + before the phase is over + :param max_wave: The maximum number of formations to spawn at a time + """ + self.spawn_functions = spawn_functions + self.duration = duration + self.name = name + self.max_wave = max_wave + + def get_spawn_function(self): + """Return a random spawn function""" + return choice(self.spawn_functions) |