Expand and stage boss encounter
This commit is contained in:
parent
c325501999
commit
66b314f51a
17 changed files with 485 additions and 64 deletions
|
|
@ -9,19 +9,22 @@ const EYE_SCENE: PackedScene = preload("res://boss/BossEye.tscn")
|
|||
const MINION_SCENE: PackedScene = preload("res://boss/BossMinion.tscn")
|
||||
const RETURN_SCENE: PackedScene = preload("res://boss/BossReturnSignal.tscn")
|
||||
const LASER_SCENE: PackedScene = preload("res://enemies/EnemyBigLaserBeam.tscn")
|
||||
const COMBAT_TARGET = preload("res://core/CombatTarget.gd")
|
||||
|
||||
enum Phase { MINION_WAVES, LASER_STORM, DEFEATED }
|
||||
enum Phase { MINION_WAVES, LASER_STORM, CORE_EXPOSED, DEFEATED }
|
||||
|
||||
const EYE_COUNT: int = 6
|
||||
const PHASE_TWO_EYES: int = 3
|
||||
const BODY_RADIUS: float = 86.0
|
||||
const TENTACLE_LENGTH: float = 235.0
|
||||
const MAX_ACTIVE_MINIONS: int = 8
|
||||
const MAX_ACTIVE_MINIONS: int = 16
|
||||
const GOLDEN_WAVE_PERIOD: int = 3
|
||||
const CORE_MAX_HEALTH: int = 36
|
||||
const REDRAW_INTERVAL: float = 1.0 / 12.0
|
||||
|
||||
@export var first_wave_delay: float = 1.5
|
||||
@export var wave_interval: float = 6.0
|
||||
@export var minions_per_wave: int = 5
|
||||
@export var minions_per_wave: int = 7
|
||||
@export var laser_interval: float = 3.5
|
||||
@export var laser_charge_time: float = 0.85
|
||||
|
||||
|
|
@ -36,7 +39,10 @@ var spawned_minions: Array = []
|
|||
var return_signals: Array = []
|
||||
var animation_phase: float = 0.0
|
||||
var redraw_left: float = 0.0
|
||||
var total_max_health: int = 0
|
||||
var total_max_health: int = CORE_MAX_HEALTH
|
||||
var core_health: int = CORE_MAX_HEALTH
|
||||
var core_exposed: bool = false
|
||||
var combat_active: bool = true
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -68,7 +74,7 @@ func eye_position(index: int) -> Vector2:
|
|||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if phase == Phase.DEFEATED:
|
||||
if phase == Phase.DEFEATED or not combat_active:
|
||||
return
|
||||
animation_phase += delta
|
||||
|
||||
|
|
@ -89,16 +95,25 @@ func _process(delta: float) -> void:
|
|||
|
||||
|
||||
func spawn_wave() -> void:
|
||||
if not combat_active or phase == Phase.CORE_EXPOSED or phase == Phase.DEFEATED:
|
||||
return
|
||||
var fleet: Node2D = get_tree().get_first_node_in_group("player_fleet")
|
||||
if fleet == null or fleet.ships.is_empty() or get_parent() == null:
|
||||
return
|
||||
_prune_invalid(spawned_minions)
|
||||
var wanted: int = maxi(3,
|
||||
var wanted: int = maxi(5,
|
||||
minions_per_wave - (2 if phase == Phase.LASER_STORM else 0))
|
||||
var count: int = mini(wanted, MAX_ACTIVE_MINIONS - spawned_minions.size())
|
||||
if count <= 0:
|
||||
return
|
||||
var marked_slot: int = wave_index % count
|
||||
# One useful return every third ACTUAL wave. The first contains one so the
|
||||
# mechanic teaches itself immediately; the two red-only waves afterwards
|
||||
# make the gold target an event rather than permanent scenery.
|
||||
var marked_slot: int = wave_index % GOLDEN_WAVE_PERIOD
|
||||
if marked_slot != 0:
|
||||
marked_slot = -1
|
||||
else:
|
||||
marked_slot = wave_index % count
|
||||
for i in range(count):
|
||||
var minion: Area2D = MINION_SCENE.instantiate()
|
||||
minion.marked = i == marked_slot
|
||||
|
|
@ -108,8 +123,10 @@ func spawn_wave() -> void:
|
|||
var angle: float = TAU * float(i) / float(count) + float(wave_index) * 0.47
|
||||
# Minions are siblings rather than boss children, so account for the
|
||||
# encounter scene's imposing boss scale when placing them off its body.
|
||||
var launch_radius: float = BODY_RADIUS * scale.x + 34.0
|
||||
minion.global_position = global_position + Vector2.from_angle(angle) * launch_radius
|
||||
var launch_direction := Vector2.from_angle(angle)
|
||||
var launch_radius: float = BODY_RADIUS * scale.x + 60.0
|
||||
minion.global_position = global_position + launch_direction * launch_radius
|
||||
minion.expel(launch_direction, (i + wave_index) % 3 == 0)
|
||||
spawned_minions.append(minion)
|
||||
wave_index += 1
|
||||
|
||||
|
|
@ -140,7 +157,7 @@ func _on_eye_destroyed(_index: int) -> void:
|
|||
eyes_destroyed += 1
|
||||
health_changed.emit(current_health(), max_health())
|
||||
if eyes_destroyed >= EYE_COUNT:
|
||||
_defeat()
|
||||
_expose_core()
|
||||
return
|
||||
if phase == Phase.MINION_WAVES and eyes_destroyed >= PHASE_TWO_EYES:
|
||||
phase = Phase.LASER_STORM
|
||||
|
|
@ -154,7 +171,7 @@ func _on_eye_health_changed(_current: int, _maximum: int) -> void:
|
|||
|
||||
|
||||
func current_health() -> int:
|
||||
var total: int = 0
|
||||
var total: int = core_health
|
||||
for eye in eyes:
|
||||
if is_instance_valid(eye):
|
||||
total += maxi(int(eye.hp), 0)
|
||||
|
|
@ -187,6 +204,36 @@ func fire_laser_volley() -> void:
|
|||
beam.launch(global_position, toward.rotated(float(spread)))
|
||||
|
||||
|
||||
func set_combat_active(active: bool) -> void:
|
||||
combat_active = active
|
||||
if active:
|
||||
wave_timer = first_wave_delay
|
||||
laser_timer = laser_interval
|
||||
|
||||
|
||||
func _expose_core() -> void:
|
||||
if core_exposed or phase == Phase.DEFEATED:
|
||||
return
|
||||
core_exposed = true
|
||||
phase = Phase.CORE_EXPOSED
|
||||
COMBAT_TARGET.register(self, COMBAT_TARGET.DANGER_PRIORITY)
|
||||
$CoreCollision.set_deferred("disabled", false)
|
||||
phase_changed.emit(phase)
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func take_damage(amount: int) -> bool:
|
||||
if not core_exposed or phase == Phase.DEFEATED:
|
||||
return false
|
||||
core_health = maxi(core_health - maxi(amount, 1), 0)
|
||||
health_changed.emit(current_health(), max_health())
|
||||
if core_health > 0:
|
||||
queue_redraw()
|
||||
return false
|
||||
_defeat()
|
||||
return true
|
||||
|
||||
|
||||
func _forget_minion(minion: Node) -> void:
|
||||
spawned_minions.erase(minion)
|
||||
|
||||
|
|
@ -205,6 +252,9 @@ func _defeat() -> void:
|
|||
if phase == Phase.DEFEATED:
|
||||
return
|
||||
phase = Phase.DEFEATED
|
||||
core_health = 0
|
||||
remove_from_group(COMBAT_TARGET.GROUP)
|
||||
$CoreCollision.set_deferred("disabled", true)
|
||||
for minion in spawned_minions:
|
||||
if is_instance_valid(minion):
|
||||
minion.retire()
|
||||
|
|
@ -220,6 +270,8 @@ func _defeat() -> void:
|
|||
|
||||
func suppress_as_defeated() -> void:
|
||||
phase = Phase.DEFEATED
|
||||
core_health = 0
|
||||
combat_active = false
|
||||
visible = false
|
||||
set_process(false)
|
||||
|
||||
|
|
@ -251,8 +303,10 @@ func _draw() -> void:
|
|||
# The central eye watches but is armoured; only tentacle eyes opened by a
|
||||
# returning marked minion join the combat-target group.
|
||||
draw_set_transform(Vector2.ZERO, 0.0, Vector2(0.72, 1.0))
|
||||
var core_color := Color(1.0, 0.78, 0.18) if core_exposed \
|
||||
else Color(0.28, 0.92, 1.0)
|
||||
NeonDraw.draw_glowing_circle(self, Vector2.ZERO, 31.0,
|
||||
Color(0.28, 0.92, 1.0), 0.8 + 0.2 * sin(animation_phase * 3.0), dark)
|
||||
core_color, 0.8 + 0.2 * sin(animation_phase * 3.0), dark)
|
||||
NeonDraw.draw_glowing_circle(self, Vector2.ZERO, 12.0,
|
||||
Color(1.0, 0.82, 0.2), 1.0, dark)
|
||||
draw_set_transform(Vector2.ZERO, 0.0, Vector2.ONE)
|
||||
|
|
|
|||
Loading…
Reference in a new issue