Implements timed mines for warden

This commit is contained in:
2026-03-06 17:29:12 -05:00
parent c02eca8de3
commit 4f858d9610
+77 -33
View File
@@ -28,10 +28,12 @@ const SWEEP_CD_P2 := 4.5
const PROJ_SPACING := 18.0 const PROJ_SPACING := 18.0
const GAP_W := 90.0 const GAP_W := 90.0
const MINE_CD_P1 := 5.5 const MINE_CD_P1 := 4.0
const MINE_CD_P2 := 3.5 const MINE_CD_P2 := 2.5
const MINE_RADIUS := 22.0 const MINE_RADIUS := 18.0
const MINE_CAP := 10 const MINE_ARM_TIME := 1.0
const MINE_LIFE_TIME := 3.0
const MINE_AOE := 50.0
const FREEZE_DURATION := 1.5 const FREEZE_DURATION := 1.5
@@ -53,9 +55,9 @@ var _pass_delay_timer: float = 0.0
var _sweep_dir: float = 1.0 # 1 = downward, -1 = upward var _sweep_dir: float = 1.0 # 1 = downward, -1 = upward
var _sweep_projs: Array = [] # [{node: Node2D, vel: Vector2}] var _sweep_projs: Array = [] # [{node: Node2D, vel: Vector2}]
# Mines # Mines — each entry: {node: Node2D, timer: float, armed: bool, poly: Polygon2D}
var _mine_cd_timer: float = MINE_CD_P1 var _mine_cd_timer: float = MINE_CD_P1
var _mines: Array = [] # [Node2D] var _mines: Array = []
# Climax # Climax
var _freeze_timer: float = 0.0 var _freeze_timer: float = 0.0
@@ -235,55 +237,97 @@ func _tick_sweep_projs(delta: float) -> void:
i -= 1 i -= 1
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Mine Drop — stationary proximity mine at current orbit position # Timed Mines — drop at orbit position, arm after 1s, explode at 3s with AOE
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
func _tick_mines(delta: float) -> void: func _tick_mines(delta: float) -> void:
# Prune freed references # Spawn new mines on cooldown
_mines = _mines.filter(func(m: Node) -> bool: return is_instance_valid(m))
_mine_cd_timer -= delta _mine_cd_timer -= delta
if _mine_cd_timer <= 0.0: if _mine_cd_timer <= 0.0:
_mine_cd_timer = MINE_CD_P2 if _phase == Phase.TWO else MINE_CD_P1 _mine_cd_timer = MINE_CD_P2 if _phase == Phase.TWO else MINE_CD_P1
if _mines.size() < MINE_CAP:
_spawn_mine(global_position) _spawn_mine(global_position)
# Update each mine's lifecycle
var i := _mines.size() - 1
while i >= 0:
var entry: Dictionary = _mines[i]
var node: Node2D = entry["node"]
if not is_instance_valid(node):
_mines.remove_at(i)
i -= 1
continue
entry["timer"] += delta
# Arm at 1 second — start pulsing red
if not entry["armed"] and entry["timer"] >= MINE_ARM_TIME:
entry["armed"] = true
# Armed visual pulse
if entry["armed"]:
var poly: Polygon2D = entry["poly"]
var pulse := fmod(entry["timer"], 0.25) < 0.125
poly.color = Color(1.0, 0.15, 0.1, 0.95) if pulse else Color(0.6, 0.08, 0.05, 0.9)
# Explode at 3 seconds
if entry["timer"] >= MINE_LIFE_TIME:
_explode_mine(entry)
_mines.remove_at(i)
i -= 1
func _spawn_mine(world_pos: Vector2) -> void: func _spawn_mine(world_pos: Vector2) -> void:
var mine := Node2D.new() var mine := Node2D.new()
# Visual — dark red polygon circle # Visual — dim dark red polygon circle (not armed yet)
var poly := Polygon2D.new() var poly := Polygon2D.new()
var pts := PackedVector2Array() var pts := PackedVector2Array()
for i in 12: for j in 12:
var a := TAU * i / 12.0 var a := TAU * j / 12.0
pts.append(Vector2(cos(a), sin(a)) * MINE_RADIUS) pts.append(Vector2(cos(a), sin(a)) * MINE_RADIUS)
poly.polygon = pts poly.polygon = pts
poly.color = Color(0.55, 0.05, 0.05, 0.9) poly.color = Color(0.35, 0.05, 0.05, 0.7) # dim until armed
mine.add_child(poly) mine.add_child(poly)
var area := Area2D.new()
area.collision_layer = 0
area.collision_mask = 2
var cs := CollisionShape2D.new()
var circle := CircleShape2D.new()
circle.radius = MINE_RADIUS
cs.shape = circle
area.add_child(cs)
area.body_entered.connect(func(body: Node) -> void:
if body.is_in_group("player"):
body.take_damage(1.0)
mine.queue_free())
mine.add_child(area)
get_parent().add_child(mine) get_parent().add_child(mine)
mine.global_position = world_pos mine.global_position = world_pos
_mines.append(mine) _mines.append({"node": mine, "timer": 0.0, "armed": false, "poly": poly})
func _explode_mine(entry: Dictionary) -> void:
var node: Node2D = entry["node"]
if not is_instance_valid(node):
return
# Check if player is within AOE
if _player != null and is_instance_valid(_player):
var dist := node.global_position.distance_to(_player.global_position)
if dist <= MINE_AOE:
_player.take_damage(1.0)
# Brief visual flash for explosion (optional: could add particle later)
var flash := Polygon2D.new()
var pts := PackedVector2Array()
for j in 16:
var a := TAU * j / 16.0
pts.append(Vector2(cos(a), sin(a)) * MINE_AOE)
flash.polygon = pts
flash.color = Color(1.0, 0.4, 0.1, 0.6)
flash.global_position = node.global_position
get_parent().add_child(flash)
# Remove flash after short delay
var tw := get_tree().create_tween()
tw.tween_property(flash, "modulate:a", 0.0, 0.15)
tw.tween_callback(flash.queue_free)
node.queue_free()
func _clear_mines() -> void: func _clear_mines() -> void:
for mine in _mines: for entry in _mines:
if is_instance_valid(mine): if is_instance_valid(entry["node"]):
mine.queue_free() entry["node"].queue_free()
_mines.clear() _mines.clear()
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------