Base Weapons

Place in world, pick up and drop working on multiplayer
This commit is contained in:
Twirpytherobot
2025-11-15 16:03:38 +00:00
parent 791b1557c7
commit 86db6865b7
144 changed files with 3582 additions and 2 deletions
+192 -1
View File
@@ -13,6 +13,7 @@ enum SkinColor { BLUE, YELLOW, GREEN, RED }
@export_category("Objects")
@export var _body: Node3D = null
@export var _spring_arm_offset: Node3D = null
@export var _weapon_attachment: BoneAttachment3D = null # WeaponPoint bone attachment
@export_category("Skin Colors")
@export var blue_texture : CompressedTexture2D
@@ -28,6 +29,10 @@ enum SkinColor { BLUE, YELLOW, GREEN, RED }
var _current_speed: float
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
# Weapon system
var equipped_weapon: BaseWeapon = null
var _nearby_weapons: Array[WorldWeapon] = []
# Attack system
@export var attack_damage: float = 10.0
@export var attack_range: float = 3.0
@@ -67,6 +72,18 @@ func _ready():
if is_multiplayer_authority():
_create_health_ui()
# Setup weapon pickup detection area
_setup_weapon_pickup_area()
# Auto-find weapon attachment if not set
if _weapon_attachment == null:
var bone_attach = get_node_or_null("3DGodotRobot/RobotArmature/Skeleton3D/BoneAttachment3D")
if bone_attach:
_weapon_attachment = bone_attach
print("Auto-found weapon attachment point")
else:
push_warning("Could not find BoneAttachment3D for weapons!")
func _physics_process(delta):
# Check if multiplayer is ready
if multiplayer.multiplayer_peer == null:
@@ -122,9 +139,23 @@ func _process(delta):
_perform_dash()
# Handle attack input
if Input.is_action_just_pressed("attack") and _attack_timer <= 0 and not is_dead and not _is_dashing:
if Input.is_action_just_pressed("attack") and not is_dead and not _is_dashing:
_perform_attack()
# Handle weapon pickup/drop
if Input.is_action_just_pressed("pickup") and not is_dead:
print("Pickup pressed! Equipped: ", equipped_weapon != null, " Nearby: ", _nearby_weapons.size())
if equipped_weapon:
# Tell server to drop (server will handle spawning)
if multiplayer.is_server():
drop_weapon()
else:
rpc_id(1, "drop_weapon") # Only send to server
elif _nearby_weapons.size() > 0:
_pickup_nearest_weapon()
else:
print("No weapons nearby to pick up")
func freeze():
velocity.x = 0
velocity.z = 0
@@ -208,10 +239,19 @@ func _perform_attack():
if not is_multiplayer_authority() or is_dead:
return
# Use equipped weapon if available
if equipped_weapon and equipped_weapon.can_attack():
equipped_weapon.perform_attack()
return
# Fallback to default unarmed attack
# Don't attack if already attacking
if _body and _body.animation_player and _body.animation_player.current_animation == "Attack1":
return
if _attack_timer > 0:
return
_attack_timer = attack_cooldown
# Play attack animation once
@@ -399,3 +439,154 @@ func _perform_dash():
func _reset_dash_rotation(rotation_value: float):
if _body:
_body.rotation.x = rotation_value
## Weapon System
func _setup_weapon_pickup_area():
# Create an Area3D to detect nearby weapons
var pickup_area = Area3D.new()
pickup_area.name = "WeaponPickupArea"
pickup_area.collision_layer = 0
pickup_area.collision_mask = 4 # Will detect WorldWeapons (we'll use layer 3)
add_child(pickup_area)
# Create collision shape for pickup range
var pickup_collision = CollisionShape3D.new()
var sphere = SphereShape3D.new()
sphere.radius = 2.0 # Pickup range
pickup_collision.shape = sphere
pickup_area.add_child(pickup_collision)
# Connect signals to track nearby weapons
pickup_area.area_entered.connect(_on_weapon_area_entered)
pickup_area.area_exited.connect(_on_weapon_area_exited)
func _on_weapon_area_entered(area: Area3D):
# Check if the area belongs to a WorldWeapon
var weapon = area.get_parent()
if weapon is WorldWeapon and weapon not in _nearby_weapons:
_nearby_weapons.append(weapon)
if is_multiplayer_authority():
print("Weapon nearby: ", weapon.weapon_data.weapon_name if weapon.weapon_data else "Unknown")
func _on_weapon_area_exited(area: Area3D):
# Remove weapon from nearby list
var weapon = area.get_parent()
if weapon is WorldWeapon and weapon in _nearby_weapons:
_nearby_weapons.erase(weapon)
if is_multiplayer_authority():
print("Weapon left range: ", weapon.weapon_data.weapon_name if weapon.weapon_data else "Unknown")
## Equip a weapon from WorldWeapon data (receives resource path)
@rpc("any_peer", "call_local", "reliable")
func equip_weapon_from_world(weapon_data_path: String):
var data = load(weapon_data_path) as WeaponData
if data:
equip_weapon(data)
else:
push_error("Failed to load weapon data from: " + weapon_data_path)
## Equip a weapon with given data
func equip_weapon(data: WeaponData):
# Unequip current weapon first
if equipped_weapon:
unequip_weapon()
if not _weapon_attachment:
push_error("No weapon attachment point found!")
return
# Create new weapon instance
var weapon = BaseWeapon.new()
weapon.weapon_data = data
weapon.name = "EquippedWeapon"
weapon.set_owner_character(self)
# Attach to bone
_weapon_attachment.add_child(weapon)
equipped_weapon = weapon
if is_multiplayer_authority():
print("Equipped: ", data.weapon_name)
## Unequip current weapon (local only)
func unequip_weapon():
if equipped_weapon:
equipped_weapon.queue_free()
equipped_weapon = null
## Sync unequip across all clients
@rpc("any_peer", "call_local", "reliable")
func _unequip_weapon_sync():
unequip_weapon()
## Drop currently equipped weapon
@rpc("any_peer", "reliable")
func drop_weapon():
print("drop_weapon called on peer ", multiplayer.get_unique_id(), " is_server: ", multiplayer.is_server(), " has weapon: ", equipped_weapon != null)
if not equipped_weapon:
print("No weapon equipped, cannot drop")
return
# Only server spawns the world weapon
if multiplayer.is_server():
print("Server spawning dropped weapon")
_spawn_world_weapon(equipped_weapon.weapon_data)
# Tell all clients to unequip
rpc("_unequip_weapon_sync")
# Unequip locally
unequip_weapon()
if is_multiplayer_authority():
print("Dropped weapon")
## Spawn a weapon in the world (server only)
func _spawn_world_weapon(data: WeaponData):
if not multiplayer.is_server():
return
# Get the resource path
var resource_path = data.resource_path
if resource_path == "":
push_error("WeaponData has no resource path! Make sure to save it as a .tres file")
return
# Position in front of player
var spawn_pos = global_position + (-transform.basis.z * 2.0)
spawn_pos.y += 1.0 # Spawn at chest height
# Calculate forward velocity
var velocity = -transform.basis.z * 3.0
# Tell level to spawn weapon on all clients
var level = get_tree().get_current_scene()
if level and level.has_method("spawn_world_weapon"):
# Increment the level's weapon counter
level._weapon_spawn_counter += 1
level.rpc("spawn_world_weapon", resource_path, spawn_pos, velocity, level._weapon_spawn_counter)
## Pick up nearest weapon
func _pickup_nearest_weapon():
if _nearby_weapons.size() == 0:
return
# Find closest weapon
var closest_weapon: WorldWeapon = null
var closest_distance: float = INF
for weapon in _nearby_weapons:
if not is_instance_valid(weapon):
continue
var distance = global_position.distance_to(weapon.global_position)
if distance < closest_distance:
closest_distance = distance
closest_weapon = weapon
if closest_weapon:
# Request server to pickup (server validates)
if multiplayer.is_server():
closest_weapon.try_pickup(multiplayer.get_unique_id())
else:
closest_weapon.rpc_id(1, "try_pickup", multiplayer.get_unique_id())