diff --git a/level/resources/weapon_testsword.tres b/level/resources/weapon_testsword.tres index f73452e..cc61405 100644 --- a/level/resources/weapon_testsword.tres +++ b/level/resources/weapon_testsword.tres @@ -11,7 +11,7 @@ damage = 20.0 attack_range = 3.5 attack_cooldown = 0.6 knockback_force = 12.0 -startup_time = 0.5 +startup_time = 0.1 active_time = 1.0 mesh_scene = ExtResource("1_gdc1w") weight = 2.0 diff --git a/level/scripts/base_weapon.gd b/level/scripts/base_weapon.gd index 43d2c2c..3044f4c 100644 --- a/level/scripts/base_weapon.gd +++ b/level/scripts/base_weapon.gd @@ -15,6 +15,7 @@ var owner_character: Character = null var _mesh_instance: Node3D = null var _attack_timer: float = 0.0 var _hitbox: HitBox = null +var _is_attacking: bool = false # Prevents overlapping attacks func _ready(): if weapon_data and weapon_data.mesh_scene: @@ -126,15 +127,23 @@ func perform_attack() -> bool: if not weapon_data or not owner_character: return false - # Check cooldown - if _attack_timer > 0: + # Check cooldown and if already attacking + if _attack_timer > 0 or _is_attacking: return false - _attack_timer = weapon_data.attack_cooldown + # 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(): - owner_character._attack_timer = weapon_data.attack_cooldown + owner_character._attack_timer = cooldown # Play attack animation on owner (use weapon's animation) if owner_character._body: @@ -151,31 +160,42 @@ func perform_attack() -> bool: attack_performed.emit() return true -## Activate the hitbox for attack detection - active for entire animation +## Activate the hitbox for attack detection - waits for startup, then activates func _activate_hitbox(): if not _hitbox: + _is_attacking = false return # Update owner reference in case it changed _hitbox.owner_entity = owner_character - # Activate hitbox immediately for the duration specified in weapon data + # 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 _hitbox.activate() - # Calculate total active duration from weapon resource (startup + active) - var startup = weapon_data.startup_time if weapon_data else 0.15 + # Wait for active duration var active = weapon_data.active_time if weapon_data else 0.2 - var duration = startup + active + await get_tree().create_timer(active).timeout - await get_tree().create_timer(duration).timeout - - # Deactivate when attack is complete + # RECOVERY PHASE - Hitbox off if _hitbox and is_instance_valid(_hitbox): _hitbox.deactivate() + # Attack complete + _is_attacking = false + ## Check if weapon can attack func can_attack() -> bool: - return _attack_timer <= 0 + return _attack_timer <= 0 and not _is_attacking ## Set the character who owns this weapon func set_owner_character(character: Character): diff --git a/level/scripts/player.gd b/level/scripts/player.gd index 8f7dbc7..835df10 100644 --- a/level/scripts/player.gd +++ b/level/scripts/player.gd @@ -48,6 +48,7 @@ var is_blocking: bool = false @export var unarmed_active: float = 0.15 # Hit window duration var _attack_timer: float = 0.0 var _unarmed_hitbox: HitBox = null +var _is_unarmed_attacking: bool = false # Prevents overlapping unarmed attacks # Dash system @export var dash_speed_multiplier: float = 2.0 @@ -430,10 +431,14 @@ func _perform_attack(): if _body and _body.animation_player and _body.animation_player.current_animation.begins_with("Attack"): return - if _attack_timer > 0: + if _attack_timer > 0 or _is_unarmed_attacking: return - _attack_timer = attack_cooldown + # Calculate total attack duration and ensure cooldown covers it + var total_duration = unarmed_startup + unarmed_active + var cooldown = max(attack_cooldown, total_duration) + _attack_timer = cooldown + _is_unarmed_attacking = true # Play attack animation once if _body: @@ -650,6 +655,7 @@ func _on_unarmed_hit(target: Node, damage_amount: float, knockback_amount: float func _activate_unarmed_hitbox(): if not _unarmed_hitbox: + _is_unarmed_attacking = false return # STARTUP PHASE - Wait before activating (wind-up animation) @@ -657,6 +663,7 @@ func _activate_unarmed_hitbox(): await get_tree().create_timer(unarmed_startup).timeout if not _unarmed_hitbox or not is_instance_valid(_unarmed_hitbox): + _is_unarmed_attacking = false return # ACTIVE PHASE - Hitbox on, can deal damage @@ -668,6 +675,9 @@ func _activate_unarmed_hitbox(): if _unarmed_hitbox and is_instance_valid(_unarmed_hitbox): _unarmed_hitbox.deactivate() + # Attack complete + _is_unarmed_attacking = false + func _on_weapon_area_entered(area: Area3D): # Check if the area belongs to a WorldWeapon var weapon = area.get_parent()