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.
95 lines
2.6 KiB
95 lines
2.6 KiB
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
|
|
|