extends Node3D class_name BaseWeapon ## Base class for equipped weapons ## Attached to player's hand via BoneAttachment3D ## Provides common weapon functionality and stats signal attack_performed() @export var weapon_data: WeaponData # Runtime references var owner_character: Character = null var _mesh_instance: Node3D = null var _attack_timer: float = 0.0 func _ready(): if weapon_data and weapon_data.mesh_scene: _spawn_mesh() func _process(delta): if _attack_timer > 0: _attack_timer -= delta ## Spawn the visual mesh for this weapon func _spawn_mesh(): # Remove old mesh if exists if _mesh_instance: _mesh_instance.queue_free() # Instantiate new mesh _mesh_instance = weapon_data.mesh_scene.instantiate() add_child(_mesh_instance) ## Perform an attack with this weapon ## Called by the character who owns this weapon func perform_attack() -> bool: if not weapon_data or not owner_character: return false # Check cooldown if _attack_timer > 0: return false _attack_timer = weapon_data.attack_cooldown # Play attack animation on owner if owner_character._body: owner_character._body.play_attack() # Find targets in range _find_and_damage_targets() attack_performed.emit() return true ## Find targets in range and apply damage func _find_and_damage_targets(): if not owner_character: return # Check if the owner character has authority (not this node) if not owner_character.is_multiplayer_authority(): return var space_state = get_world_3d().direct_space_state var query = PhysicsShapeQueryParameters3D.new() var sphere = SphereShape3D.new() sphere.radius = weapon_data.attack_range query.shape = sphere query.transform = global_transform query.collision_mask = 1 # Player layer var results = space_state.intersect_shape(query) for result in results: var hit_body = result["collider"] if hit_body != owner_character and hit_body is BaseUnit: var attacker_id = multiplayer.get_unique_id() # If we're the server, apply damage directly if multiplayer.is_server(): owner_character._server_apply_damage(hit_body.name, weapon_data.damage, attacker_id) else: # Otherwise, request server to apply damage owner_character.rpc_id(1, "_server_apply_damage", hit_body.name, weapon_data.damage, attacker_id) break # Only hit one target per attack ## Check if weapon can attack func can_attack() -> bool: return _attack_timer <= 0 ## Set the character who owns this weapon func set_owner_character(character: Character): owner_character = character ## Get weapon stats func get_damage() -> float: return weapon_data.damage if weapon_data else 0.0 func get_range() -> float: return weapon_data.attack_range if weapon_data else 0.0 func get_cooldown() -> float: return weapon_data.attack_cooldown if weapon_data else 0.0