extends Node2D const PROJECTILE := preload("res://scenes/game/weapon_projectile.tscn") const ARTWORK := preload("res://assets/Ships/external_turret.png") const AUDIO := preload("res://scripts/game/weapon_audio.gd") static var _sprite: Texture2D var mother: CharacterBody2D var weapon: ItemInstance var ammunition := 50 var _fire_timer := 0.35 var _age := 0.0 var _flash := 0.0 func _ready() -> void: if _sprite == null: var image := ARTWORK.get_image() image.resize(256,256,Image.INTERPOLATE_LANCZOS) image.generate_mipmaps() _sprite = ImageTexture.create_from_image(image) add_to_group("player_external_turrets") z_index = 5 texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS func _physics_process(delta: float) -> void: if not is_instance_valid(mother) or bool(mother.get("_is_destroyed")) or bool(mother.get("is_docked")) or ammunition <= 0: queue_free() return _age += delta _flash = maxf(0.0,_flash-delta) _fire_timer = maxf(0.0,_fire_timer-delta) var target := _find_target() if target != null: var direction := target.global_position-global_position rotation = rotate_toward(rotation,direction.angle()+PI*0.5,8.0*delta) if _fire_timer <= 0.0 and absf(angle_difference(rotation,direction.angle()+PI*0.5)) < 0.12: _fire(target) queue_redraw() func _find_target() -> Node2D: var closest: Node2D var distance := weapon.definition.weapon_range for enemy in get_tree().get_nodes_in_group("enemies"): if enemy.is_queued_for_deletion() or float(enemy.get("hull")) <= 0.0 or bool(enemy.get("virus_controlled")): continue var candidate := global_position.distance_to(enemy.global_position) if candidate < distance and _clear_shot(enemy): distance = candidate closest = enemy return closest func _clear_shot(target: Node2D) -> bool: var query := PhysicsRayQueryParameters2D.create(global_position,target.global_position,77,[mother.get_rid()]) var hit := get_world_2d().direct_space_state.intersect_ray(query) return hit.is_empty() or hit.get("collider") == target func _fire(target: Node2D) -> void: if ammunition <= 0 or is_queued_for_deletion() or not is_instance_valid(target): return var direction := (target.global_position-global_position).normalized() var hit: Dictionary = mother.call("roll_weapon_hit",weapon) var bullet := PROJECTILE.instantiate() bullet.configure(global_position+direction*25.0,direction,ItemDefinition.WeaponKind.MACHINE_GUN,weapon.definition.projectile_speed,float(hit.damage),weapon.definition.weapon_range,null,bool(hit.critical)) bullet.shield_piercing = weapon.shield_piercing() bullet.damage_type_override = weapon.get_damage_type() bullet.source_ship = mother get_parent().add_child(bullet) ammunition -= 1 _fire_timer = weapon.get_turret_fire_interval() _flash = 0.05 AUDIO.play(get_parent(),global_position,ItemDefinition.WeaponKind.MACHINE_GUN,-8.0) mother.get_node("WeaponController").call("_break_cloak_on_fire") if ammunition == 0: queue_free() func _draw() -> void: var bob := Vector2(0,sin(_age*2.5)*2.0) var appear := smoothstep(0.0,0.3,_age) draw_circle(Vector2.ZERO,19.0,Color(0.1,0.7,1.0,0.08)) draw_arc(Vector2.ZERO,30.0,-PI*0.5,-PI*0.5+TAU*float(ammunition)/50.0,48,Color("70dfff"),1.6,true) draw_set_transform(bob,0.0,Vector2.ONE*lerpf(0.4,1.0,appear)) draw_texture_rect(_sprite,Rect2(-30,-30,60,60),false) if _flash > 0.0: for x in [-7.0,7.0]: draw_line(Vector2(x,-24),Vector2(x,-38),Color("ffdb86"),3.0,true) draw_circle(Vector2(x,-28),5.0,Color(1.0,0.65,0.2,0.45)) draw_set_transform(Vector2.ZERO,-rotation) draw_string(ThemeDB.fallback_font,Vector2(-11,43),str(ammunition),HORIZONTAL_ALIGNMENT_CENTER,22,11,Color("b9e7f5"))