extends BaseProjectileWeapon class_name NailGun func _init() -> void: weapon_name = "Nail Gun" reload_style = "cell" fire_rate = 0.06 # Very fast (1000 RPM) max_ammo = 60 reload_time = 2.0 base_damage = 18.0 min_damage = 10.0 falloff_start = 20.0 max_range = 100.0 automatic = true spread_angle = 0.02 projectile_speed = 90.0 # Extremely fast, less lead and drop projectile_gravity = 5.0 # Slight drop func _build_model() -> void: position = Vector3(0.3, -0.3, -0.604) var model_scene = load("res://assets/weapons/nailgun/source/Sopra.fbx") if model_scene: var model = model_scene.instantiate() model_root.add_child(model) model.scale = Vector3(0.06, 0.06, 0.06) model.rotation_degrees.y = 180 for light in model.find_children("*", "Light3D", true, false): light.queue_free() var mat = StandardMaterial3D.new() mat.albedo_texture = load("res://assets/weapons/nailgun/textures/Base_texture.png") mat.normal_enabled = true mat.normal_texture = load("res://assets/weapons/nailgun/textures/Normal_texture.png") mat.metallic = 1.0 mat.metallic_texture = load("res://assets/weapons/nailgun/textures/Metallic_texture.png") mat.roughness_texture = load("res://assets/weapons/nailgun/textures/Roughness_texture.png") mat.ao_enabled = true mat.ao_texture = load("res://assets/weapons/nailgun/textures/internal_ground_ao_texture.jpeg") for child in model.find_children("*", "MeshInstance3D", true, false): child.set_surface_override_material(0, mat) # Muzzle Flash (Sparks) muzzle_flash = OmniLight3D.new() muzzle_flash.light_color = Color(1.0, 0.9, 0.6) muzzle_flash.light_energy = 0.0 muzzle_flash.omni_range = 3.0 muzzle_flash.position = Vector3(0, 0.05, -0.3) model_root.add_child(muzzle_flash) # Audio fire_sound = AudioStreamPlayer3D.new() fire_sound.bus = "SFX" fire_sound.stream = AudioManager.stream_for("nailgun_fire", "res://assets/sounds/nailgun_fire.wav") fire_sound.position = muzzle_flash.position fire_sound.volume_db = -5.0 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 _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void: var proj = load("res://weapons/projectile.gd").new() # Give it a nail/spike mesh var mesh_instance = MeshInstance3D.new() var p_mesh = CylinderMesh.new() p_mesh.top_radius = 0.0 p_mesh.bottom_radius = 0.01 p_mesh.height = 0.1 var mat = StandardMaterial3D.new() mat.albedo_color = Color(0.7, 0.7, 0.7) # Shiny metal mat.metallic = 1.0 mat.roughness = 0.3 p_mesh.material = mat mesh_instance.mesh = p_mesh # Rotate it so it points forward mesh_instance.rotation.x = deg_to_rad(-90) proj.add_child(mesh_instance) # Tracer streak var tracer = MeshInstance3D.new() var t_mesh = CylinderMesh.new() t_mesh.top_radius = 0.02 t_mesh.bottom_radius = 0.0 t_mesh.height = 1.5 var t_mat = StandardMaterial3D.new() t_mat.albedo_color = Color(1.0, 0.8, 0.1) # Bright yellow/orange t_mat.emission_enabled = true t_mat.emission = Color(1.0, 0.8, 0.1) t_mat.emission_energy_multiplier = 3.0 t_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA t_mat.albedo_color.a = 0.6 t_mesh.material = t_mat tracer.mesh = t_mesh tracer.rotation.x = deg_to_rad(-90) tracer.position.z = 0.75 # Trail behind the nail proj.add_child(tracer) var spawn_pos = origin var actual_fire_dir = fire_dir if muzzle_flash and camera: spawn_pos = muzzle_flash.global_position # Map the position through the viewmodel camera to fix FOV mismatch var wman = get_parent() if wman and "vm_camera" in wman and is_instance_valid(wman.vm_camera): if wman.vm_camera.is_position_behind(muzzle_flash.global_position) == false: var screen_pos = wman.vm_camera.unproject_position(muzzle_flash.global_position) var dist = camera.global_position.distance_to(muzzle_flash.global_position) spawn_pos = camera.project_position(screen_pos, dist) var target_pos = origin + fire_dir * max_range var space_state = camera.get_world_3d().direct_space_state var query = PhysicsRayQueryParameters3D.create(origin, target_pos) if player: query.exclude = [player.get_rid()] var result = space_state.intersect_ray(query) if result: target_pos = result.position actual_fire_dir = (target_pos - spawn_pos).normalized() # Projectile rotation so it faces velocity var up_vec = Vector3.UP if abs(actual_fire_dir.dot(up_vec)) > 0.99: up_vec = Vector3.RIGHT proj.transform.basis = Basis.looking_at(actual_fire_dir, up_vec) # Push the projectile forward slightly so the 1.5m long tracer tail # doesn't clip backward past the gun and look like it's coming from beside the player's head. proj.position = spawn_pos + actual_fire_dir * 1.5 proj.logical_source = origin proj.velocity = actual_fire_dir * projectile_speed if player: proj.velocity += actual_fire_dir * max(0.0, player.velocity.dot(actual_fire_dir)) proj.damage = base_damage proj.min_damage = min_damage proj.falloff_start = falloff_start proj.max_range = max_range proj.drop_gravity = projectile_gravity proj.owner_player = player # Spawn in the world get_tree().current_scene.add_child(proj)