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.

96 lines
2.6 KiB

2 weeks ago
extends Area3D
class_name HitBox
## A component that deals damage to HurtBoxes
## Attach to weapons or attack effects
## Uses direct physics queries for reliable hit detection
signal hit_landed(target: Node, damage: float, knockback: float, attacker_pos: Vector3)
## Damage dealt on hit
@export var damage: float = 10.0
## Knockback force applied
@export var knockback: float = 5.0
## Owner entity (used to prevent self-damage and identify attacker)
@export var owner_entity: Node = null
## Whether hitbox is currently active (only deals damage when active)
var is_active: bool = false
## Tracks entities hit this attack (prevents multi-hit)
var _hits_this_attack: Array[Node] = []
## Shape for queries (extracted from child CollisionShape3D)
var _query_shape: Shape3D = null
func _ready():
# Find the collision shape for queries
for child in get_children():
if child is CollisionShape3D and child.shape:
_query_shape = child.shape
break
func _physics_process(_delta):
if not is_active:
return
_check_hits()
func _check_hits():
if not _query_shape:
# Fallback: create a default sphere
var sphere = SphereShape3D.new()
sphere.radius = 2.0
_query_shape = sphere
# Use physics server for reliable queries
var space_state = get_world_3d().direct_space_state
var query = PhysicsShapeQueryParameters3D.new()
query.shape = _query_shape
query.transform = global_transform
query.collision_mask = 16 # Layer 5 (hurtbox)
query.collide_with_areas = true
query.collide_with_bodies = false
var results = space_state.intersect_shape(query, 32)
for result in results:
var collider = result["collider"]
if collider is HurtBox:
_process_hit(collider)
func _process_hit(hurtbox: HurtBox):
# Don't hit our own hurtbox
if hurtbox.owner_entity == owner_entity:
return
# Don't hit same entity twice in one attack
if hurtbox.owner_entity in _hits_this_attack:
return
# Register this hit
var target = hurtbox.owner_entity
if target:
_hits_this_attack.append(target)
# Get attacker position for knockback direction
var attacker_pos = global_position
if owner_entity and owner_entity is Node3D:
attacker_pos = owner_entity.global_position
# Emit signal - let the weapon/owner handle damage routing to server
hit_landed.emit(target, damage, knockback, attacker_pos)
## Activate hitbox (call when attack starts)
func activate():
is_active = true
_hits_this_attack.clear()
## Deactivate hitbox (call when attack ends)
func deactivate():
is_active = false
_hits_this_attack.clear()
## Set damage stats (usually from weapon data)
func set_stats(new_damage: float, new_knockback: float):
damage = new_damage
knockback = new_knockback