tkinter-space-game/boss.py

204 lines
5.8 KiB
Python
Raw Normal View History

2023-01-05 11:35:28 +00:00
from dataclasses import replace
import math
from enemy import EnemyAttributes
from formation import (
CircleFormation,
CircleFormationAttributes,
EnemyFormation,
FormationAttributes,
FormationEnemy,
)
from game import Game
class CircleBossFormation(EnemyFormation):
"""Enemy Formation for the circular game boss"""
RADIUS = 10
CYCLE_PEROID = 500
COUNT = 6
def __init__(self, game: Game, attributes: EnemyAttributes):
"""Initialise the circle boss
:param game: The game which this boss belongs to
:type game: Game
:param attributes: The attributes on which to base spawned enemies
:type attributes: EnemyAttributes
"""
self.image_name = "enemy0"
self.minion_image_name = "smallenemy0"
self.alpha = 0
attributes = CircleFormationAttributes(
radius=40,
period=300,
count=CircleBossFormation.COUNT,
velocity=attributes.velocity,
cooldown=attributes.cooldown,
hp=attributes.hp//2,
reward=attributes.reward*10
)
self.circle_formation = CircleFormation(
game, self.minion_image_name, attributes)
super().__init__(game, self.image_name, attributes)
def create_enemies(self):
"""Spawn the boss"""
self.spawn_enemy((-4, -4, 0))
def tick(self, player):
"""Update the boss's position
:param player: The player which to check collision with
:type player: Player
"""
super().tick(player)
self.circle_formation.x = self.x
self.circle_formation.y = self.y
a = (self.alpha/CircleBossFormation.CYCLE_PEROID)*2*math.pi - math.pi
r = 50*math.sin(a) - 25
p = math.sin(a*2)*100
self.circle_formation.attributes.radius = math.floor(
CircleBossFormation.RADIUS + (r if r > 0 else 0)
)
self.circle_formation.attributes.period = math.floor(
400 + (p if p < 100 else 100)
)
self.circle_formation.tick(player)
# When the boss is dead, the minions will all die
if len(self.sprites) == 0:
if len(self.circle_formation.sprites) > 0:
self.circle_formation.sprites[0].damage()
else:
self.destroy()
def destroy(self):
"""Remove the circle boss"""
super().destroy()
self.circle_formation.destroy()
def hide(self):
"""Hide the circle boss"""
self.circle_formation.hide()
return super().hide()
def show(self):
"""Show the circle boss"""
self.circle_formation.show()
return super().show()
class SnakeBossFormation(EnemyFormation):
"""Enemy formation for the snake boss"""
LENGTH = 32
def __init__(self, game: Game, attributes: FormationAttributes):
"""Initialise the snake boss
:param game: The game which the boss belongs to
:type game: Game
:param attributes: The attributes of which to base spawned enemies on
:type attributes: FormationAttributes
"""
self.minion_name = "smallenemy1"
self.tail_name = "smallenemy1_evil"
self.head_name = "enemy2"
self.phase = 1
self.phase_timer = 0
super().__init__(game, self.minion_name, attributes)
def create_enemies(self):
"""Spawn the snake"""
head_attributes = replace(self.attributes)
head_attributes.hp *= 100
self.head = FormationEnemy(self.game, self.head_name,
(0, 0, 0), head_attributes)
self.sprites.append(self.head)
for i in range(SnakeBossFormation.LENGTH):
self.spawn_enemy((0, 0, i+1))
tail_attributes = replace(self.attributes)
head_attributes.hp //= 5
self.tail = FormationEnemy(self.game, self.tail_name,
(0, 0, SnakeBossFormation.LENGTH+1),
tail_attributes)
self.sprites.append(self.tail)
def spawn_enemy(self, offset):
"""Spawn one enemy unit of the snake
:param offset: The offset of the enemy
"""
attributes = replace(self.attributes)
if offset[2] % 6 == 0:
attributes.cooldown = 40
else:
attributes.cooldown = -1
enemy = FormationEnemy(self.game, self.image_name, offset, attributes)
self.sprites.append(enemy)
return enemy
def position_enemy(self, enemy: FormationEnemy):
"""Position the enemy on the game screen
:param enemy: The enemy to position
:type enemy: FormationEnemy
"""
if self.phase == 2:
p = 120 / (100 + math.cos(self.phase_timer / 400)*20) * 120
else:
p = 120
m = 4
t = ((-enemy.offset_a*m) + self.game.alpha) / p + math.pi
a = self.game.w // 2
b = self.game.h // 3
c = 0
if self.phase == 2:
n = 10 - (2000 / (self.phase_timer+2000))*5
else:
n = 5
enemy.set_pos((
int(self.x + a*math.sin(n*t+c)),
int(self.y + b*math.sin(t))
))
def tick(self, player):
"""Update the position of the enemies
:param player: The player which to check collision with
"""
super().tick(player)
if self.phase == 1:
self.head.hp = self.attributes.hp*100
if self.tail.destroyed:
if len(self.sprites) > 1:
self.sprites[-1].damage(amount=(self.attributes.hp//4))
else:
self.head.hp = self.attributes.hp * 3
self.phase = 2
elif self.phase == 2:
self.phase_timer += 1
self.head.attributes.cooldown = int(
20 + math.sin(self.phase_timer / 50)*10)