You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
162 lines
4.9 KiB
162 lines
4.9 KiB
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(): |
|
print("[WorldWeapon ", name, "] _ready() called. weapon_id=", weapon_id, ", weapon_data=", weapon_data) |
|
|
|
# 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 |
|
print("[WorldWeapon ", name, "] Set mass to ", mass) |
|
|
|
# Spawn mesh |
|
if weapon_data and weapon_data.mesh_scene: |
|
print("[WorldWeapon ", name, "] Spawning mesh from ", weapon_data.mesh_scene) |
|
_spawn_mesh() |
|
else: |
|
print("[WorldWeapon ", name, "] ERROR: Cannot spawn mesh. weapon_data=", weapon_data, ", mesh_scene=", weapon_data.mesh_scene if weapon_data else "null") |
|
|
|
# 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) |
|
print("[WorldWeapon ", name, "] Mesh spawned successfully: ", _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
|
|
|