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

223 lines
6.2 KiB

extends Node3D
class_name BaseWeapon
## Base class for equipped weapons
## Attached to player's hand via BoneAttachment3D
2 weeks ago
## Uses HitBox/HurtBox system for damage detection
signal attack_performed()
2 weeks ago
signal hit_connected(target: Node)
@export var weapon_data: WeaponData
# Runtime references
var owner_character: Character = null
var _mesh_instance: Node3D = null
var _attack_timer: float = 0.0
2 weeks ago
var _hitbox: HitBox = null
7 days ago
var _is_attacking: bool = false # Prevents overlapping attacks
func _ready():
if weapon_data and weapon_data.mesh_scene:
_spawn_mesh()
2 weeks ago
_setup_hitbox()
func _process(delta):
if _attack_timer > 0:
_attack_timer -= delta
## Spawn the visual mesh for this weapon
func _spawn_mesh():
# Remove old mesh if exists
if _mesh_instance:
_mesh_instance.queue_free()
# Instantiate new mesh
_mesh_instance = weapon_data.mesh_scene.instantiate()
add_child(_mesh_instance)
2 weeks ago
# Check if mesh has a HitBox child, use it instead of auto-generated one
var mesh_hitbox = _mesh_instance.get_node_or_null("HitBox")
if mesh_hitbox and mesh_hitbox is HitBox:
# Use the hitbox from the mesh scene
print("[BaseWeapon] Found manual HitBox in mesh scene")
if _hitbox:
_hitbox.queue_free()
_hitbox = mesh_hitbox
_configure_hitbox()
else:
print("[BaseWeapon] No manual HitBox found, will auto-generate")
## Setup the hitbox for this weapon
func _setup_hitbox():
# Skip if we already have a hitbox (e.g., from mesh scene)
if _hitbox:
return
# Create hitbox dynamically based on weapon range
_hitbox = HitBox.new()
_hitbox.name = "HitBox"
add_child(_hitbox)
# Add collision shape based on attack range - use sphere for consistent detection
# regardless of weapon orientation during animations
var collision = CollisionShape3D.new()
var sphere = SphereShape3D.new()
var range_val = weapon_data.attack_range if weapon_data else 1.5
sphere.radius = range_val
collision.shape = sphere
_hitbox.add_child(collision)
_configure_hitbox()
## Configure hitbox with weapon stats and owner
func _configure_hitbox():
if not _hitbox:
return
# Set damage stats from weapon
if weapon_data:
_hitbox.set_stats(weapon_data.damage, weapon_data.knockback_force)
# Set owner to prevent self-damage
_hitbox.owner_entity = owner_character
# Connect to hit signal
if not _hitbox.hit_landed.is_connected(_on_hitbox_hit):
_hitbox.hit_landed.connect(_on_hitbox_hit)
## Called when hitbox connects with a hurtbox
func _on_hitbox_hit(target: Node, damage_amount: float, knockback_amount: float, attacker_pos: Vector3):
if not target or not owner_character:
return
1 week ago
# Flash the target's hurtbox red for visual feedback
if target is Node:
var hurtbox = target.find_child("HurtBox", true, false)
if hurtbox and hurtbox.has_method("flash_hit"):
hurtbox.flash_hit()
2 weeks ago
hit_connected.emit(target)
# Route damage through server
var attacker_id = multiplayer.get_unique_id()
if multiplayer.is_server():
# We are server, apply directly
owner_character._server_apply_damage(
target.name,
damage_amount,
attacker_id,
knockback_amount,
attacker_pos
)
else:
# Send to server
owner_character.rpc_id(1, "_server_apply_damage",
target.name,
damage_amount,
attacker_id,
knockback_amount,
attacker_pos
)
## Perform an attack with this weapon
## Called by the character who owns this weapon
func perform_attack() -> bool:
if not weapon_data or not owner_character:
return false
7 days ago
# Check cooldown and if already attacking
if _attack_timer > 0 or _is_attacking:
return false
7 days ago
# Calculate total attack duration (startup + active)
var startup = weapon_data.startup_time if weapon_data else 0.15
var active = weapon_data.active_time if weapon_data else 0.2
var total_duration = startup + active
# Set cooldown to at least cover the full attack duration
var cooldown = max(weapon_data.attack_cooldown, total_duration)
_attack_timer = cooldown
_is_attacking = true
# Notify owner character of attack cooldown (for UI)
if owner_character and owner_character.is_multiplayer_authority():
7 days ago
owner_character._attack_timer = cooldown
# Play attack animation on owner (use weapon's animation)
if owner_character._body:
var anim_name = weapon_data.attack_animation if weapon_data.attack_animation else "Attack_OneHand"
owner_character._body.play_attack(anim_name)
# Sync animation to other clients
owner_character._sync_attack_animation.rpc(anim_name)
2 weeks ago
# Activate hitbox for the attack duration
# Only activate on authority - they detect hits and send to server
if owner_character.is_multiplayer_authority():
_activate_hitbox()
attack_performed.emit()
return true
7 days ago
## Activate the hitbox for attack detection - waits for startup, then activates
2 weeks ago
func _activate_hitbox():
if not _hitbox:
7 days ago
_is_attacking = false
return
2 weeks ago
# Update owner reference in case it changed
_hitbox.owner_entity = owner_character
7 days ago
# STARTUP PHASE - Wait before activating (wind-up animation)
var startup = weapon_data.startup_time if weapon_data else 0.15
if startup > 0:
await get_tree().create_timer(startup).timeout
# Check if weapon/hitbox is still valid after await
if not _hitbox or not is_instance_valid(_hitbox):
_is_attacking = false
return
# ACTIVE PHASE - Hitbox on, can deal damage
2 weeks ago
_hitbox.activate()
7 days ago
# Wait for active duration
1 week ago
var active = weapon_data.active_time if weapon_data else 0.2
7 days ago
await get_tree().create_timer(active).timeout
1 week ago
7 days ago
# RECOVERY PHASE - Hitbox off
2 weeks ago
if _hitbox and is_instance_valid(_hitbox):
_hitbox.deactivate()
7 days ago
# Attack complete
_is_attacking = false
## Check if weapon can attack
func can_attack() -> bool:
7 days ago
return _attack_timer <= 0 and not _is_attacking
## Set the character who owns this weapon
func set_owner_character(character: Character):
owner_character = character
2 weeks ago
# Update hitbox owner
if _hitbox:
_hitbox.owner_entity = character
## Get weapon stats
func get_damage() -> float:
return weapon_data.damage if weapon_data else 0.0
func get_range() -> float:
return weapon_data.attack_range if weapon_data else 0.0
func get_cooldown() -> float:
return weapon_data.attack_cooldown if weapon_data else 0.0
3 weeks ago
## Blocking functionality
func can_block() -> bool:
return weapon_data.can_block if weapon_data else false
func get_block_reduction() -> float:
return weapon_data.block_reduction if weapon_data else 0.0