From 8a6ecf3f45dcd6af0bf890bd7639e0ac741ba12a Mon Sep 17 00:00:00 2001 From: Nikolai Fedorov Date: Wed, 22 Apr 2026 17:44:54 +0300 Subject: [PATCH] add kick system --- scripts/Enemy.gd | 26 +++++++++++++++++++++---- scripts/KickSystem.gd | 41 +++++++++++++++++++++++++++++++++++++++ scripts/KickSystem.gd.uid | 1 + scripts/Rock.gd | 25 +++++++++++++++--------- 4 files changed, 80 insertions(+), 13 deletions(-) create mode 100644 scripts/KickSystem.gd create mode 100644 scripts/KickSystem.gd.uid diff --git a/scripts/Enemy.gd b/scripts/Enemy.gd index 3029173..a6143b9 100644 --- a/scripts/Enemy.gd +++ b/scripts/Enemy.gd @@ -18,6 +18,7 @@ var chain_factor: float = 0.65 var stun_time: float = 0.5 var base_scale: float = 1.0 var wave_num: int = 1 +var damage_modifier: float = 0.75 var state: State = State.CHASING var fly_vel: Vector3 = Vector3.ZERO @@ -140,14 +141,18 @@ func _fly(delta: float) -> void: hit_wall = true break elif col3d.is_in_group("enemies") and col3d != self: - var other: Node = col3d - if speed_now >= 3.0 and other.get("enemy_level") == enemy_level and other.get("is_upgrading") == false and is_upgrading == false: - _start_merge(other) - else: + var merged := KickSystem.resolve(self, col3d, fly_vel) + if not merged and is_instance_valid(col3d): var chain_dir := col3d.global_position - global_position chain_dir.y = 0.0 if chain_dir.length() > 0.01: col3d.call("receive_kick", chain_dir.normalized(), speed_now * chain_factor) + elif col3d.is_in_group("rocks"): + KickSystem.resolve(self, col3d, fly_vel) + var rock_dir := col3d.global_position - global_position + rock_dir.y = 0.0 + if rock_dir.length() > 0.01: + col3d.call("receive_kick", rock_dir.normalized(), speed_now * 0.5) elif col3d.is_in_group("player"): col3d.call("take_damage", int(speed_now * 0.6)) @@ -166,6 +171,19 @@ func _stun_tick(delta: float) -> void: if stun_timer <= 0.0: _enter_chase() +func can_merge_with(other: Node3D, collision_speed: float) -> bool: + return (collision_speed >= 3.0 + and other.get("enemy_type") == enemy_type + and other.get("enemy_level") == enemy_level + and not is_upgrading + and not other.get("is_upgrading")) + +func do_merge_with(other: Node3D) -> void: + _start_merge(other) + +func apply_collision_damage(dmg: float) -> void: + _take_hit(int(dmg)) + func receive_kick(direction: Vector3, force: float) -> void: if state == State.DEAD: return diff --git a/scripts/KickSystem.gd b/scripts/KickSystem.gd new file mode 100644 index 0000000..d22567b --- /dev/null +++ b/scripts/KickSystem.gd @@ -0,0 +1,41 @@ +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. Merge ────────────────────────────────────────────────────────────── + if owner.has_method("can_merge_with") and owner.call("can_merge_with", other, collision_speed): + owner.call("do_merge_with", other) + return true + + # ── 2. 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 diff --git a/scripts/KickSystem.gd.uid b/scripts/KickSystem.gd.uid new file mode 100644 index 0000000..f350d93 --- /dev/null +++ b/scripts/KickSystem.gd.uid @@ -0,0 +1 @@ +uid://ddke1n1a07f8x diff --git a/scripts/Rock.gd b/scripts/Rock.gd index 727beaa..aaea4f0 100644 --- a/scripts/Rock.gd +++ b/scripts/Rock.gd @@ -4,15 +4,14 @@ enum State { IDLE, FLYING } const AIR_FRICTION := 0.84 const MIN_SPEED := 0.5 -const DAMAGE_MULT := 0.9 const WALL_BOUNCE := 0.5 const WALL_SELF_DMG := 0.6 -const HIT_SELF_DMG := 0.4 var state: State = State.IDLE var fly_vel: Vector3 = Vector3.ZERO var health: float = 60.0 var dead: bool = false +var damage_modifier: float = 1.25 @onready var mesh_node: MeshInstance3D = $RockMesh var rock_mat: StandardMaterial3D @@ -25,6 +24,12 @@ func _ready() -> void: rock_mat = mesh_node.material_override.duplicate() as StandardMaterial3D mesh_node.material_override = rock_mat +func can_merge_with(_other: Node3D, _speed: float) -> bool: + return false + +func apply_collision_damage(dmg: float) -> void: + _take_damage(dmg) + func receive_kick(direction: Vector3, force: float) -> void: fly_vel = direction * force fly_vel.y = 0.0 @@ -57,14 +62,16 @@ func _fly(delta: float) -> void: _take_damage(speed_now * WALL_SELF_DMG) handled = true break - elif col3d.is_in_group("enemies"): - var to_enemy := col3d.global_position - global_position - to_enemy.y = 0.0 - var dir := to_enemy.normalized() if to_enemy.length() > 0.01 else (fly_vel.normalized() if fly_vel.length() > 0.01 else Vector3.FORWARD) - col3d.call("_take_hit", int(speed_now * DAMAGE_MULT)) - col3d.call("receive_kick", dir, speed_now * 0.65) + elif col3d.is_in_group("enemies") or col3d.is_in_group("rocks"): + if col3d == self: + continue + KickSystem.resolve(self, col3d, fly_vel) + if not dead and is_instance_valid(col3d): + var kick_dir := col3d.global_position - global_position + kick_dir.y = 0.0 + if kick_dir.length() > 0.01: + col3d.call("receive_kick", kick_dir.normalized(), speed_now * 0.65) fly_vel *= 0.45 - _take_damage(speed_now * HIT_SELF_DMG) handled = true break