Implements timed mines for warden
This commit is contained in:
+78
-34
@@ -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()
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user