extends RigidBody3D class_name WorldWeapon ## Weapon as a physics object in the world ## Can be picked up by players ## Spawned when a weapon is dropped or placed in the level @export var weapon_data: WeaponData var weapon_id: int = -1 # Set by Level when spawned var _mesh_instance: Node3D = null var _collision_shape: CollisionShape3D = null var _pickup_area: Area3D = null var _is_being_picked_up: bool = false func _ready(): # Set collision layer to "weapon" (layer 3 = bit 4) collision_layer = 4 collision_mask = 2 # Collide with world # Set up physics if weapon_data: mass = weapon_data.weight # Spawn mesh if weapon_data and weapon_data.mesh_scene: _spawn_mesh() # Create collision shape if not exists _setup_collision() # Create pickup area _setup_pickup_area() # Only server manages pickup logic if multiplayer.is_server(): _pickup_area.body_entered.connect(_on_body_entered_pickup_area) func _spawn_mesh(): # Remove old mesh if exists if _mesh_instance: _mesh_instance.queue_free() # Instantiate mesh _mesh_instance = weapon_data.mesh_scene.instantiate() add_child(_mesh_instance) func _setup_collision(): # Check if we already have a collision shape for child in get_children(): if child is CollisionShape3D: _collision_shape = child return # Create a basic box collision if none exists _collision_shape = CollisionShape3D.new() var box_shape = BoxShape3D.new() box_shape.size = Vector3(0.5, 0.5, 1.5) # Approximate weapon size _collision_shape.shape = box_shape add_child(_collision_shape) func _setup_pickup_area(): # Create Area3D for pickup detection _pickup_area = Area3D.new() _pickup_area.name = "PickupArea" _pickup_area.collision_layer = 4 # Layer 3 (weapon layer) so player can detect it _pickup_area.collision_mask = 1 # Detect players add_child(_pickup_area) # Create sphere collision for pickup range var pickup_collision = CollisionShape3D.new() var sphere = SphereShape3D.new() sphere.radius = weapon_data.pickup_radius if weapon_data else 1.5 pickup_collision.shape = sphere _pickup_area.add_child(pickup_collision) func _on_body_entered_pickup_area(body: Node3D): if _is_being_picked_up: return # Check if it's a player trying to pick up if body is Character and body.is_multiplayer_authority(): # Let the player handle the pickup # The player will call try_pickup() via RPC pass ## Called by player to attempt pickup @rpc("any_peer", "reliable") func try_pickup(player_id: int): # Only server validates pickup if not multiplayer.is_server(): return if _is_being_picked_up: return # Find the player var level = get_tree().get_current_scene() if not level or not level.has_node("PlayersContainer"): return var players_container = level.get_node("PlayersContainer") if not players_container.has_node(str(player_id)): return var player = players_container.get_node(str(player_id)) if not player is Character: return # Check distance var distance = global_position.distance_to(player.global_position) if distance > (weapon_data.pickup_radius if weapon_data else 1.5): return _is_being_picked_up = true # Get the resource path var resource_path = weapon_data.resource_path if resource_path == "": push_error("WeaponData has no resource path!") return # Check weapon_id is valid if weapon_id == -1: push_error("WorldWeapon.try_pickup: weapon_id is -1! This weapon was not spawned correctly.") print(" Weapon name: ", name) print(" Weapon data: ", weapon_data.resource_path if weapon_data else "null") return # Tell the player to equip this weapon (on all clients) print("[Server] Telling player ", player_id, " to equip weapon via RPC") player.rpc("equip_weapon_from_world", resource_path) # Remove this world weapon from all clients using level's centralized system print("[Server] Removing world weapon ", name, " (ID: ", weapon_id, ") via level.remove_world_weapon") if level.has_method("remove_world_weapon"): level.remove_world_weapon(weapon_id) else: push_error("Level doesn't have remove_world_weapon method!") ## Remove weapon from all clients @rpc("any_peer", "call_local", "reliable") func _remove_from_all_clients(): print("[Client ", multiplayer.get_unique_id(), "] Removing weapon ", name) # Delay removal to ensure RPC is fully processed await get_tree().process_frame queue_free() ## Set weapon data and refresh func set_weapon_data(data: WeaponData): weapon_data = data if is_inside_tree(): _spawn_mesh() if weapon_data: mass = weapon_data.weight