class_name WallGapEmitter extends BulletEmitter ## A curtain of bullets sweeping across the arena with a survivable gap. The gap ## moves by [member gap_step] every shot so players cannot camp one lane. ## 0 = travels down, 1 = up, 2 = right, 3 = left. @export_enum("Down", "Up", "Right", "Left") var direction: int = 0 @export var count: int = 18 ## Slots left empty. Width 2-3 is tight but fair at default bullet radius. @export var gap_width: int = 3 @export var gap_index: int = 6 ## How far the gap slides each shot. Coprime-ish values feel least predictable. @export var gap_step: int = 5 ## Randomise the gap instead of stepping it. @export var randomize_gap: bool = false ## Direction of travel, the axis the curtain is strung along, and how far the ## curtain reaches -- all measured from the room the pattern is filling rather ## than from a fixed arena. func _axis(bounds: Rect2) -> Dictionary: var half := bounds.size * 0.5 match direction: 1: return {"dir": Vector2.UP, "along": Vector2.RIGHT, "extent": half.x, "edge": half.y} 2: return {"dir": Vector2.RIGHT, "along": Vector2.DOWN, "extent": half.y, "edge": half.x} 3: return {"dir": Vector2.LEFT, "along": Vector2.DOWN, "extent": half.y, "edge": half.x} _: return {"dir": Vector2.DOWN, "along": Vector2.RIGHT, "extent": half.x, "edge": half.y} func fire(ctx: EmitContext) -> void: if count <= 0: return var ax := _axis(ctx.bounds) var dir: Vector2 = ax["dir"] var along: Vector2 = ax["along"] var extent: float = ax["extent"] var edge: float = ax["edge"] var gap := gap_index if randomize_gap and ctx.rng != null: gap = ctx.rng.randi_range(0, maxi(count - gap_width, 0)) else: gap = posmod(gap_index + gap_step * ctx.shot_index, maxi(count - gap_width + 1, 1)) var centre := ctx.bounds.get_center() var start := centre - dir * (edge + 8.0) var angle := dir.angle() for i in count: if i >= gap and i < gap + gap_width: continue var t := (float(i) / float(maxi(count - 1, 1))) * 2.0 - 1.0 emit_shot(ctx, angle, start + along * (t * extent))