Weapon and shield slots
This commit is contained in:
+114
-33
@@ -13,7 +13,10 @@ 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 var _weapon_attachment: BoneAttachment3D = null # WeaponPoint bone attachment (main hand)
|
||||
@export var _weapon_container: Node3D = null # Container node for main hand weapons
|
||||
@export var _offhand_attachment: BoneAttachment3D = null # OffHandPoint bone attachment
|
||||
@export var _offhand_container: Node3D = null # Container node for off-hand weapons
|
||||
|
||||
@export_category("Skin Colors")
|
||||
@export var blue_texture : CompressedTexture2D
|
||||
@@ -30,7 +33,8 @@ var _current_speed: float
|
||||
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
|
||||
|
||||
# Weapon system
|
||||
var equipped_weapon: BaseWeapon = null
|
||||
var equipped_weapon: BaseWeapon = null # Main hand weapon
|
||||
var equipped_offhand: BaseWeapon = null # Off-hand weapon
|
||||
var _nearby_weapons: Array[WorldWeapon] = []
|
||||
|
||||
# Attack system
|
||||
@@ -84,6 +88,29 @@ func _ready():
|
||||
else:
|
||||
push_warning("Could not find BoneAttachment3D for weapons!")
|
||||
|
||||
# Auto-find weapon container if not set
|
||||
if _weapon_container == null and _weapon_attachment:
|
||||
var container = _weapon_attachment.get_node_or_null("WeaponContainer")
|
||||
if container:
|
||||
_weapon_container = container
|
||||
print("Auto-found main hand weapon container")
|
||||
else:
|
||||
push_warning("Could not find WeaponContainer! Weapons will attach directly to BoneAttachment3D.")
|
||||
|
||||
# Auto-find off-hand attachment if not set
|
||||
if _offhand_attachment == null:
|
||||
var offhand_attach = get_node_or_null("3DGodotRobot/RobotArmature/Skeleton3D/OffHandPoint")
|
||||
if offhand_attach:
|
||||
_offhand_attachment = offhand_attach
|
||||
print("Auto-found off-hand attachment point")
|
||||
|
||||
# Auto-find off-hand container if not set
|
||||
if _offhand_container == null and _offhand_attachment:
|
||||
var container = _offhand_attachment.get_node_or_null("OffHandContainer")
|
||||
if container:
|
||||
_offhand_container = container
|
||||
print("Auto-found off-hand weapon container")
|
||||
|
||||
func _physics_process(delta):
|
||||
# Check if multiplayer is ready
|
||||
if multiplayer.multiplayer_peer == null:
|
||||
@@ -144,15 +171,24 @@ func _process(delta):
|
||||
|
||||
# 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:
|
||||
print("Pickup pressed! Main: ", equipped_weapon != null, " OffHand: ", equipped_offhand != null, " Nearby: ", _nearby_weapons.size())
|
||||
|
||||
# Try to pickup nearby weapon
|
||||
if _nearby_weapons.size() > 0:
|
||||
_pickup_nearest_weapon()
|
||||
# If no nearby weapons, drop what we're holding (main hand first, then off-hand)
|
||||
elif equipped_weapon:
|
||||
# Tell server to drop (server will handle spawning)
|
||||
if multiplayer.is_server():
|
||||
drop_weapon()
|
||||
drop_weapon(false) # main hand
|
||||
else:
|
||||
rpc_id(1, "drop_weapon") # Only send to server
|
||||
elif _nearby_weapons.size() > 0:
|
||||
_pickup_nearest_weapon()
|
||||
rpc_id(1, "drop_weapon", false) # Only send to server
|
||||
elif equipped_offhand:
|
||||
# Drop off-hand
|
||||
if multiplayer.is_server():
|
||||
drop_weapon(true) # off-hand
|
||||
else:
|
||||
rpc_id(1, "drop_weapon", true)
|
||||
else:
|
||||
print("No weapons nearby to pick up")
|
||||
|
||||
@@ -239,11 +275,16 @@ func _perform_attack():
|
||||
if not is_multiplayer_authority() or is_dead:
|
||||
return
|
||||
|
||||
# Use equipped weapon if available
|
||||
# Use main hand weapon if available
|
||||
if equipped_weapon and equipped_weapon.can_attack():
|
||||
equipped_weapon.perform_attack()
|
||||
return
|
||||
|
||||
# Or use off-hand weapon if available
|
||||
if equipped_offhand and equipped_offhand.can_attack():
|
||||
equipped_offhand.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":
|
||||
@@ -487,59 +528,84 @@ func equip_weapon_from_world(weapon_data_path: String):
|
||||
|
||||
## Equip a weapon with given data
|
||||
func equip_weapon(data: WeaponData):
|
||||
# Unequip current weapon first
|
||||
if equipped_weapon:
|
||||
unequip_weapon()
|
||||
# Determine which hand based on weapon type
|
||||
var is_offhand = (data.hand_type == WeaponData.Hand.OFF_HAND)
|
||||
|
||||
if not _weapon_attachment:
|
||||
push_error("No weapon attachment point found!")
|
||||
# Unequip current weapon in that hand first
|
||||
if is_offhand:
|
||||
if equipped_offhand:
|
||||
unequip_weapon(true)
|
||||
else:
|
||||
if equipped_weapon:
|
||||
unequip_weapon(false)
|
||||
|
||||
# Determine where to attach the weapon
|
||||
var attach_point: Node3D
|
||||
if is_offhand:
|
||||
attach_point = _offhand_container if _offhand_container else _offhand_attachment
|
||||
else:
|
||||
attach_point = _weapon_container if _weapon_container else _weapon_attachment
|
||||
|
||||
if not attach_point:
|
||||
push_error("No weapon attachment point found for ", "off-hand" if is_offhand else "main hand")
|
||||
return
|
||||
|
||||
# Create new weapon instance
|
||||
var weapon = BaseWeapon.new()
|
||||
weapon.weapon_data = data
|
||||
weapon.name = "EquippedWeapon"
|
||||
weapon.name = "EquippedOffHand" if is_offhand else "EquippedWeapon"
|
||||
weapon.set_owner_character(self)
|
||||
|
||||
# Attach to bone
|
||||
_weapon_attachment.add_child(weapon)
|
||||
equipped_weapon = weapon
|
||||
# Attach to weapon container (or bone attachment if no container)
|
||||
attach_point.add_child(weapon)
|
||||
|
||||
# Store reference
|
||||
if is_offhand:
|
||||
equipped_offhand = weapon
|
||||
else:
|
||||
equipped_weapon = weapon
|
||||
|
||||
if is_multiplayer_authority():
|
||||
print("Equipped: ", data.weapon_name)
|
||||
print("Equipped: ", data.weapon_name, " to ", attach_point.name)
|
||||
|
||||
## Unequip current weapon (local only)
|
||||
func unequip_weapon():
|
||||
if equipped_weapon:
|
||||
equipped_weapon.queue_free()
|
||||
equipped_weapon = null
|
||||
func unequip_weapon(is_offhand: bool = false):
|
||||
if is_offhand:
|
||||
if equipped_offhand:
|
||||
equipped_offhand.queue_free()
|
||||
equipped_offhand = null
|
||||
else:
|
||||
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()
|
||||
func _unequip_weapon_sync(is_offhand: bool = false):
|
||||
unequip_weapon(is_offhand)
|
||||
|
||||
## 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)
|
||||
func drop_weapon(is_offhand: bool = false):
|
||||
var has_weapon = equipped_offhand if is_offhand else equipped_weapon
|
||||
print("drop_weapon called on peer ", multiplayer.get_unique_id(), " is_server: ", multiplayer.is_server(), " has weapon: ", has_weapon != null, " offhand: ", is_offhand)
|
||||
|
||||
if not equipped_weapon:
|
||||
print("No weapon equipped, cannot drop")
|
||||
if not has_weapon:
|
||||
print("No weapon equipped in ", "off-hand" if is_offhand else "main hand", ", 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)
|
||||
_spawn_world_weapon(has_weapon.weapon_data)
|
||||
# Tell all clients to unequip
|
||||
rpc("_unequip_weapon_sync")
|
||||
rpc("_unequip_weapon_sync", is_offhand)
|
||||
|
||||
# Unequip locally
|
||||
unequip_weapon()
|
||||
unequip_weapon(is_offhand)
|
||||
|
||||
if is_multiplayer_authority():
|
||||
print("Dropped weapon")
|
||||
print("Dropped weapon from ", "off-hand" if is_offhand else "main hand")
|
||||
|
||||
## Spawn a weapon in the world (server only)
|
||||
func _spawn_world_weapon(data: WeaponData):
|
||||
@@ -585,6 +651,21 @@ func _pickup_nearest_weapon():
|
||||
closest_weapon = weapon
|
||||
|
||||
if closest_weapon:
|
||||
# Check what hand this weapon goes in
|
||||
var weapon_hand_type = closest_weapon.weapon_data.hand_type if closest_weapon.weapon_data else WeaponData.Hand.MAIN_HAND
|
||||
var is_offhand = (weapon_hand_type == WeaponData.Hand.OFF_HAND)
|
||||
var slot_occupied = equipped_offhand if is_offhand else equipped_weapon
|
||||
|
||||
# If the slot is occupied, drop it first
|
||||
if slot_occupied:
|
||||
print("Slot occupied, dropping current weapon before picking up")
|
||||
if multiplayer.is_server():
|
||||
drop_weapon(is_offhand)
|
||||
else:
|
||||
rpc_id(1, "drop_weapon", is_offhand)
|
||||
# Wait a tiny bit for the drop to complete
|
||||
await get_tree().create_timer(0.1).timeout
|
||||
|
||||
# Request server to pickup (server validates)
|
||||
if multiplayer.is_server():
|
||||
closest_weapon.try_pickup(multiplayer.get_unique_id())
|
||||
|
||||
@@ -4,9 +4,12 @@ class_name WeaponData
|
||||
## Resource that stores weapon statistics and properties
|
||||
## Can be reused for both equipped weapons and world pickups
|
||||
|
||||
enum Hand { MAIN_HAND, OFF_HAND, TWO_HAND }
|
||||
|
||||
@export_category("Weapon Info")
|
||||
@export var weapon_name: String = "Weapon"
|
||||
@export_multiline var description: String = ""
|
||||
@export var hand_type: Hand = Hand.MAIN_HAND
|
||||
|
||||
@export_category("Combat Stats")
|
||||
@export var damage: float = 10.0
|
||||
|
||||
Reference in New Issue
Block a user