extends BaseProjectileWeapon class_name RocketSwarm var is_targeting: bool = false var homing_target_pos: Vector3 = Vector3.ZERO var targeting_laser: MeshInstance3D func _init() -> void: weapon_name = "Rocket Swarm" fire_rate = 1.0 # 1 click fires all 8 max_ammo = 4 # 4 swarms reload_time = 3.0 base_damage = 35.0 # Per mini rocket min_damage = 20.0 falloff_start = 0.0 max_range = 300.0 automatic = false spread_angle = 0.15 # Wide spread out of the barrel projectile_speed = 25.0 # Slower so they have time to home func _build_model() -> void: position = Vector3(0.4, -0.4, -0.656) var model_scene = load("res://assets/weapons/swarm launcher/source/LOD_0.fbx") if model_scene: var model = model_scene.instantiate() model_root.add_child(model) model.scale = Vector3(1.0, 1.0, 1.0) model.rotation_degrees.y = -90 model.position.y -= 0.6 # Move model down without moving arms # Strip lights for child in model.find_children("*", "Light3D", true, false): child.queue_free() # Laser targeting_laser = MeshInstance3D.new() var l_mesh = CylinderMesh.new() l_mesh.top_radius = 0.02 # Make it thicker and more visible l_mesh.bottom_radius = 0.02 l_mesh.height = 300.0 var l_mat = StandardMaterial3D.new() l_mat.albedo_color = Color(1.0, 0.0, 0.0, 1.0) l_mat.emission_enabled = true l_mat.emission = Color(2.0, 0.0, 0.0) # Bright red l_mesh.material = l_mat targeting_laser.mesh = l_mesh targeting_laser.rotation.x = deg_to_rad(90) targeting_laser.position = Vector3(0, 0, -234.375) targeting_laser.visible = false model_root.add_child(targeting_laser) # Audio fire_sound = AudioStreamPlayer3D.new() fire_sound.bus = "SFX" fire_sound.stream = AudioManager.stream_for("swarm_fire", "res://assets/sounds/swarm_fire.wav") model_root.add_child(fire_sound) reload_sound = AudioStreamPlayer3D.new() reload_sound.bus = "SFX" reload_sound.stream = AudioManager.stream_for("shotgun_reload", "res://assets/sounds/shotgun_reload.wav") model_root.add_child(reload_sound) func _process(delta: float) -> void: super._process(delta) if not visible: return if camera: is_targeting = Input.is_action_pressed("fire_alt") targeting_laser.visible = is_targeting if is_targeting: var space_state = get_world_3d().direct_space_state var origin = camera.global_position var target = origin - camera.global_transform.basis.z * 300.0 var query = PhysicsRayQueryParameters3D.create(origin, target) if player: query.exclude = [player.get_rid()] var result = space_state.intersect_ray(query) if result: homing_target_pos = result.position # Shrink laser to hit point var dist = origin.distance_to(result.position) (targeting_laser.mesh as CylinderMesh).height = dist targeting_laser.position.z = -dist / 2.0 else: homing_target_pos = target (targeting_laser.mesh as CylinderMesh).height = 300.0 targeting_laser.position.z = -150.0 if player and player.is_multiplayer_authority(): player.synced_is_targeting = true player.synced_homing_target_pos = homing_target_pos else: if player and player.is_multiplayer_authority(): player.synced_is_targeting = false func unequip() -> void: is_targeting = false targeting_laser.visible = false if player and player.is_multiplayer_authority(): player.synced_is_targeting = false # Override fire to shoot 8 rockets at once func _fire() -> void: if current_ammo <= 0 and not reloading: _start_reload() return if not camera: return current_ammo -= 1 fire_cooldown = fire_rate if fire_sound: fire_sound.play() if muzzle_flash: muzzle_flash.light_energy = 2.0 var tween = create_tween() tween.tween_property(muzzle_flash, "light_energy", 0.0, 0.1) var _origin = camera.global_position + camera.global_transform.basis * Vector3(0.4, -0.2, -0.938) var base_dir = -camera.global_transform.basis.z _current_aim_dir = base_dir for i in range(8): var right = camera.global_transform.basis.x var up = camera.global_transform.basis.y var fraction = float(i) / 7.0 # 0.0 to 1.0 (left to right) # Offset from left (-0.1) to right (0.1) on the top edge of the launcher var local_x = lerpf(-0.1, 0.1, fraction) var local_offset = Vector3(local_x, 0.1, 0.0) var launcher_center_local = Vector3(0.4, -0.4, -0.6) var proj_origin = camera.global_position + camera.global_transform.basis * (launcher_center_local + local_offset) # Fire direction: radial fan out # They shoot mostly forward, but fan out horizontally, and slightly arch upwards radially var angle = lerpf(-PI/3.0, PI/3.0, fraction) # -60 deg to +60 deg fan var x_dir = sin(angle) * 0.6 var y_dir = cos(angle) * 0.8 # Reduced upward angle so it's not mostly straight up var fire_dir = (base_dir + right * x_dir + up * y_dir).normalized() _spawn_custom_projectile(proj_origin, fire_dir) if multiplayer.has_multiplayer_peer() and not(multiplayer.multiplayer_peer is OfflineMultiplayerPeer): player.server_play_fire_effects.rpc_id(1, proj_origin, fire_dir, weapon_name, false) var _current_aim_dir: Vector3 = Vector3.ZERO func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void: var proj = load("res://weapons/homing_projectile.gd").new() # Mesh var mesh_instance = MeshInstance3D.new() var p_mesh = CylinderMesh.new() p_mesh.top_radius = 0.0 p_mesh.bottom_radius = 0.02 p_mesh.height = 0.1 var mat = StandardMaterial3D.new() mat.albedo_color = Color(0.8, 0.8, 0.1) # Yellow mini rocket p_mesh.material = mat mesh_instance.mesh = p_mesh mesh_instance.rotation.x = deg_to_rad(-90) proj.add_child(mesh_instance) # Rotation var up_vec = Vector3.UP if abs(fire_dir.dot(up_vec)) > 0.99: up_vec = Vector3.RIGHT proj.transform.basis = Basis.looking_at(fire_dir, up_vec) # Logic proj.weapon_source = self proj.turn_speed = 6.5 # Slower turn radius so they sweep nicely proj.position = origin # Start at origin to spread outward around camera proj.logical_source = origin # Initial pop-up speed proj.velocity = fire_dir * 5.0 if player: proj.velocity += fire_dir * max(0.0, player.velocity.dot(fire_dir)) var aim_dir = _current_aim_dir # If remote player, use the synced head rotation to determine forward direction if player and not player.is_multiplayer_authority(): var head = player.get_node_or_null("HeadPivot") if head: aim_dir = -head.global_transform.basis.z.normalized() else: aim_dir = fire_dir # Fallback proj.initial_aim_dir = aim_dir proj.cruise_speed = 65.0 proj.ignition_timer = randf_range(0.3, 0.6) # Staggered ignition proj.damage = base_damage proj.min_damage = min_damage proj.falloff_start = falloff_start proj.max_range = max_range proj.drop_gravity = 5.0 # Slight arc before ignition proj.owner_player = player # Explosive stats proj.explosion_radius = 1.5 # Tiny AoE effect proj.explosion_knockback = 5.0 proj.can_self_damage = false proj.explosion_pitch = 1.2 # Less deep but still explosion get_tree().current_scene.add_child(proj)