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