extends CharacterBody3D signal destroyed enum State { IDLE, FLYING } const AIR_FRICTION := 0.88 const MIN_SPEED := 0.5 const WALL_BOUNCE := 0.4 const WALL_SELF_DMG := 0.4 var kickable_type: String = "boulder" var state: State = State.IDLE var fly_vel: Vector3 = Vector3.ZERO var health: float = 150.0 var dead: bool = false var damage_modifier: float = 1.8 @onready var mesh_node: MeshInstance3D = $BoulderMesh var boulder_mat: StandardMaterial3D const COLOR_IDLE := Color(0.32, 0.28, 0.22) const COLOR_IMPACT := Color(1.0, 1.0, 1.0) func _ready() -> void: add_to_group("kickable") boulder_mat = mesh_node.material_override.duplicate() as StandardMaterial3D mesh_node.material_override = boulder_mat func apply_collision_damage(dmg: float) -> void: _take_damage(dmg) func receive_kick(direction: Vector3, force: float) -> void: fly_vel = direction * (force * 0.6) 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("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.55 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.15 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(2.0, 0.1, 2.0), 0.15) tw.tween_property(self, "scale", Vector3(0.0, 0.0, 0.0), 0.12) tw.tween_callback(queue_free) func _flash() -> void: var tw := create_tween() tw.tween_property(boulder_mat, "albedo_color", COLOR_IMPACT, 0.04) var target_color := COLOR_IDLE.lerp(Color.RED, clampf(1.0 - health / 150.0, 0.0, 0.6)) tw.tween_property(boulder_mat, "albedo_color", target_color, 0.18)