|
|
|
|
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
|