class_name BossPhase extends Resource ## One stage of a boss fight: a looping timeline of emitters, entered when the ## boss drops below [member enter_at_hp_fraction]. @export var name: String = "Phase" ## The phase becomes active once hp/max_hp is at or below this. Phases are ## evaluated in array order, so list them from 1.0 downwards. @export_range(0.0, 1.0, 0.01) var enter_at_hp_fraction: float = 1.0 ## The phase timeline wraps at this many ticks. Emitter [member start_tick] and ## [member end_tick] are relative to the start of each loop. @export var loop_ticks: int = 600 ## Ticks of no fire after entering the phase, so the transition is readable. @export var telegraph_ticks: int = 45 ## Damage taken while in this phase is scaled by this. Use < 1.0 for armoured ## phases rather than adding hit points, so the fight length stays predictable. @export var damage_taken_mult: float = 1.0 @export var emitters: Array[BulletEmitter] = [] @export_group("Movement") ## How the boss moves during this phase. Movement is a property of the PHASE, ## not of the boss: a fight that stands still and then starts hunting you is ## one boss with two phases, and expressing it any other way would put a ## per-boss branch in the simulation. @export var move: Move = Move.STATIC ## Units per second. Zero is equivalent to STATIC. @export var move_speed: float = 0.0 ## ORBIT: radius around the arena's centre. CHASE: the distance it tries to ## hold from you -- closing all the way would mean a boss you cannot see past. @export var move_param: float = 120.0 ## WAYPOINTS: points in the arena as fractions of it, so one phase works in any ## room. (0,0) is the top-left corner of the fightable area, (1,1) the bottom ## right. @export var waypoints: Array[Vector2] = [] ## Ticks spent standing at each waypoint before moving on. @export var waypoint_dwell: int = 60 enum Move { ## Never moves. Every phase written before bosses could move. STATIC, ## Circles the centre of its arena at [member move_param] radius. ORBIT, ## Closes on the nearest player, holding [member move_param] distance. CHASE, ## Walks a fixed circuit of [member waypoints], pausing at each. WAYPOINTS, } ## Whether this phase actually moves the boss. Derived rather than stored, so a ## phase cannot claim to move and then sit still. func moves() -> bool: return move != Move.STATIC and move_speed > 0.0