71 lines
3.2 KiB
GDScript
71 lines
3.2 KiB
GDScript
class_name KickSystem
|
|
|
|
# Deduplication: each (A,B) pair resolved once per physics frame
|
|
static var _frame: int = -1
|
|
static var _pairs: Dictionary = {}
|
|
|
|
# Called by a flying kickable when it detects another kickable.
|
|
# Returns true if a merge happened (both objects will despawn).
|
|
static func resolve(owner: Node3D, other: Node3D, owner_vel: Vector3) -> bool:
|
|
var f := Engine.get_physics_frames()
|
|
if f != _frame:
|
|
_frame = f
|
|
_pairs.clear()
|
|
|
|
var id_lo := mini(owner.get_instance_id(), other.get_instance_id())
|
|
var id_hi := maxi(owner.get_instance_id(), other.get_instance_id())
|
|
var key := str(id_lo) + "_" + str(id_hi)
|
|
if _pairs.has(key):
|
|
return false
|
|
_pairs[key] = true
|
|
|
|
var other_vel: Vector3 = other.get("fly_vel") if other.get("fly_vel") != null else Vector3.ZERO
|
|
var speed_a := Vector2(owner_vel.x, owner_vel.z).length()
|
|
var speed_b := Vector2(other_vel.x, other_vel.z).length()
|
|
var collision_speed := speed_a + speed_b
|
|
|
|
# ── 1. Recipe merge (MergeRecipes) ────────────────────────────────────────
|
|
var kt_a: String = owner.get("kickable_type") if owner.get("kickable_type") != null else ""
|
|
var kt_b: String = other.get("kickable_type") if other.get("kickable_type") != null else ""
|
|
if kt_a != "" and kt_b != "":
|
|
var recipe: Dictionary = MergeRecipes.find(kt_a, kt_b, collision_speed)
|
|
if not recipe.is_empty():
|
|
_execute_recipe(owner, other, recipe)
|
|
return true
|
|
|
|
# ── 2. Merge (enemy-to-enemy) ─────────────────────────────────────────────
|
|
if owner.has_method("can_merge_with") and owner.call("can_merge_with", other, collision_speed):
|
|
owner.call("do_merge_with", other)
|
|
return true
|
|
|
|
# ── 3. Damage ─────────────────────────────────────────────────────────────
|
|
var mod_a: float = owner.get("damage_modifier") if owner.get("damage_modifier") != null else 0.0
|
|
var mod_b: float = other.get("damage_modifier") if other.get("damage_modifier") != null else 0.0
|
|
|
|
if is_instance_valid(other) and other.has_method("apply_collision_damage") and mod_a > 0.0:
|
|
other.call("apply_collision_damage", collision_speed * mod_a)
|
|
if is_instance_valid(owner) and owner.has_method("apply_collision_damage") and mod_b > 0.0:
|
|
owner.call("apply_collision_damage", collision_speed * mod_b)
|
|
|
|
return false
|
|
|
|
static func _execute_recipe(a: Node3D, b: Node3D, recipe: Dictionary) -> void:
|
|
var pos := (a.global_position + b.global_position) * 0.5
|
|
var parent := a.get_parent()
|
|
# Emit before freeing so respawn counters in Main decrement correctly.
|
|
if a.has_signal("destroyed"):
|
|
a.emit_signal("destroyed")
|
|
if b.has_signal("destroyed"):
|
|
b.emit_signal("destroyed")
|
|
a.queue_free()
|
|
b.queue_free()
|
|
var scene: PackedScene = load(recipe["result_scene"])
|
|
if scene == null or parent == null:
|
|
return
|
|
var result := scene.instantiate() as Node3D
|
|
parent.add_child(result)
|
|
result.global_position = pos
|
|
FX.merge_smoke(pos + Vector3(0, 0.3, 0), parent)
|
|
SFX.merge(parent)
|
|
parent.get_tree().create_timer(30.0).connect("timeout", result.queue_free)
|