summaryrefslogtreecommitdiff
path: root/formation_spawner.py
diff options
context:
space:
mode:
authordavidovski <david@sendula.com>2023-01-05 11:35:28 +0000
committerdavidovski <david@sendula.com>2023-01-05 11:35:28 +0000
commitb99dbb396c313f4b3130b566c0df42c10eec6084 (patch)
treeffd2bbebe47f66fd1c21000b371ebc897ef84e34 /formation_spawner.py
Initial CommitHEADmain
Diffstat (limited to 'formation_spawner.py')
-rw-r--r--formation_spawner.py341
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)