class_name Weapon extends Node3D @export var def: WeaponDef var shooter: PhysicsBody3D # Resolved in _ready(): nearest ancestor body var _cooldown := 0.0 @onready var _muzzle: Marker3D = %Muzzle @onready var _yaw: Node3D = get_node_or_null("%Yaw") @onready var _pitch: Node3D = get_node_or_null("%Pitch") @onready var hitbox: PhysicsBody3D = get_node_or_null("%Hitbox") func _ready() -> void: # Fallback: nearest ancestor body is the shooter (hand-placed weapons). var n := get_parent() while shooter == null and n != null: shooter = n as PhysicsBody3D n = n.get_parent() func _physics_process(delta: float) -> void: _cooldown = maxf(_cooldown - delta, 0.0) func try_fire() -> bool: if _cooldown > 0.0 or def == null or def.ammo == null: return false _cooldown = def.fire_interval var p: Projectile = def.ammo.projectile_scene.instantiate() get_tree().current_scene.add_child(p) if shooter != null: p.add_collision_exception_with(shooter) if hitbox != null: p.add_collision_exception_with(hitbox) p.launch(def.ammo, _muzzle.global_transform, def.muzzle_speed_scale) return true func aim_at(point, delta: float) -> void: if _yaw == null or def == null: return var yaw_step := deg_to_rad(def.traverse_speed_deg) * delta var pitch_step := deg_to_rad(def.elevation_speed_deg) * delta if point == null: # Nobody aiming: slew back to the stowed position. _yaw.rotation.y = rotate_toward(_yaw.rotation.y, 0.0, yaw_step) if _pitch != null: _pitch.rotation.x = rotate_toward(_pitch.rotation.x, 0.0, pitch_step) return # Yaw: desired angle lives in the space of the node we rotate *within*, # i.e. the Yaw node's parent. var local: Vector3 = _yaw.get_parent_node_3d().to_local(point) var desired_yaw := atan2(-local.x, -local.z) _yaw.rotation.y = rotate_toward(_yaw.rotation.y, desired_yaw, yaw_step) if _pitch == null: return # Pitch: same idea, in the Pitch node's parent space (the freshly-yawed Yaw node). var lp: Vector3 = _pitch.get_parent_node_3d().to_local(point) var desired_pitch := clampf( atan2(lp.y, Vector2(lp.x, lp.z).length()), deg_to_rad(def.elevation_min_deg), deg_to_rad(def.elevation_max_deg) ) _pitch.rotation.x = rotate_toward(_pitch.rotation.x, desired_pitch, pitch_step)