extends CharacterBody3D signal destroyed enum State { IDLE, FLYING } const AIR_FRICTION := 0.86 const MIN_SPEED := 0.5 const WALL_BOUNCE := 0.45 const WALL_SELF_DMG := 0.4 var kickable_type: String = "metal_plate" var kick_tier: int = 0 var toughness_tier: int = 2 var state: State = State.IDLE var fly_vel: Vector3 = Vector3.ZERO var health: float = 1200.0 var dead: bool = false var damage_modifier: float = 1.2 @onready var mesh_node: MeshInstance3D = $PlateMesh @onready var tooltip: Label3D = $Tooltip var plate_mat: StandardMaterial3D const COLOR_IDLE := Color(0.6, 0.65, 0.72) const COLOR_IMPACT := Color(1.0, 1.0, 1.0) func _ready() -> void: add_to_group("kickable") add_to_group("interactable") plate_mat = mesh_node.material_override.duplicate() as StandardMaterial3D mesh_node.material_override = plate_mat tooltip.visible = false func _process(_delta: float) -> void: if dead: tooltip.visible = false return var players := get_tree().get_nodes_in_group("player") if players.is_empty(): tooltip.visible = false return var p := players[0] as Node3D tooltip.visible = p != null and global_position.distance_to(p.global_position) < 2.5 func interact(player: Node) -> void: if dead: return player.call("apply_upgrade_armor") dead = true set_physics_process(false) tooltip.visible = false remove_from_group("interactable") remove_from_group("kickable") var tw := create_tween() tw.tween_property(self, "scale", Vector3(1.4, 0.1, 1.4), 0.15) tw.tween_property(self, "scale", Vector3(0.0, 0.0, 0.0), 0.1) tw.tween_callback(queue_free) 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 state = State.FLYING func _physics_process(delta: float) -> void: if state == State.IDLE: return _fly(delta) func _fly(delta: float) -> void: var speed_now := Vector2(fly_vel.x, fly_vel.z).length() velocity = fly_vel velocity.y = 0.0 move_and_slide() var handled := false for i in get_slide_collision_count(): var col := get_slide_collision(i) var col3d := col.get_collider() as Node3D if col3d == null: continue if col3d.has_meta("is_wall"): var normal := col.get_normal() normal.y = 0.0 if normal.length() > 0.01: fly_vel = fly_vel.bounce(normal.normalized()) * WALL_BOUNCE else: fly_vel = Vector3.ZERO _take_damage(speed_now * WALL_SELF_DMG) handled = true break elif col3d.is_in_group("player"): col3d.call("take_damage", int(speed_now * damage_modifier), toughness_tier) fly_vel *= 0.3 handled = true break elif col3d.is_in_group("enemies") or col3d.is_in_group("kickable"): 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.5) fly_vel *= 0.45 handled = true break if not handled: fly_vel = velocity fly_vel.y = 0.0 fly_vel *= pow(AIR_FRICTION, delta * 60.0) if Vector2(fly_vel.x, fly_vel.z).length() < MIN_SPEED: fly_vel = Vector3.ZERO velocity = Vector3.ZERO state = State.IDLE mesh_node.rotation.y += delta * speed_now * 0.25 func _take_damage(dmg: float) -> void: if dead: return health -= dmg _flash() if health <= 0.0: _die() func _die() -> void: dead = true state = State.IDLE set_physics_process(false) emit_signal("destroyed") var tw := create_tween() tw.tween_property(self, "scale", Vector3(1.8, 0.1, 1.8), 0.12) tw.tween_property(self, "scale", Vector3(0.0, 0.0, 0.0), 0.1) tw.tween_callback(queue_free) func _flash() -> void: var tw := create_tween() tw.tween_property(plate_mat, "albedo_color", COLOR_IMPACT, 0.04) var target_color := COLOR_IDLE.lerp(Color.RED, clampf(1.0 - health / 120.0, 0.0, 0.6)) tw.tween_property(plate_mat, "albedo_color", target_color, 0.18)