extends CharacterBody3D enum State { WANDER, RAGDOLL } var _state: State = State.WANDER var _skeleton: Skeleton3D = null var _sim: PhysicalBoneSimulator3D = null var _wander_target: Vector3 = Vector3.ZERO var _idle_timer: float = 0.0 @onready var _mesh: Node3D = $matador_v02 @onready var _hit_area: Area3D = $HitArea func _ready() -> void: add_to_group(&"matador") _skeleton = _find_skeleton(_mesh) if _skeleton: _sim = _setup_physical_bones() _hit_area.body_entered.connect(_on_body_entered) _pick_wander_target() func _physics_process(delta: float) -> void: if not is_on_floor(): velocity += get_gravity() * delta match _state: State.WANDER: _tick_wander(delta) State.RAGDOLL: _tick_ragdoll(delta) func _tick_wander(delta: float) -> void: if _idle_timer > 0.0: _idle_timer -= delta velocity.x = move_toward(velocity.x, 0.0, 10.0 * delta) velocity.z = move_toward(velocity.z, 0.0, 10.0 * delta) move_and_slide() return var dx: float = _wander_target.x - global_position.x var dz: float = _wander_target.z - global_position.z if Vector2(dx, dz).length() < 0.8: _idle_timer = randf_range(DP.f("mat_idle_min"), DP.f("mat_idle_max")) _pick_wander_target() else: var dir := Vector3(dx, 0.0, dz).normalized() var spd := DP.f("mat_walk_speed") velocity.x = dir.x * spd velocity.z = dir.z * spd _mesh.rotation.y = lerp_angle( _mesh.rotation.y, atan2(velocity.x, velocity.z), delta * 8.0) move_and_slide() func _tick_ragdoll(delta: float) -> void: velocity.x = move_toward(velocity.x, 0.0, 5.0 * delta) velocity.z = move_toward(velocity.z, 0.0, 5.0 * delta) move_and_slide() func _on_body_entered(body: Node3D) -> void: if _state == State.RAGDOLL: return if not body.is_in_group(&"player"): return var player := body as CharacterBody3D if player.velocity.length() < DP.f("mat_hit_threshold"): return var hit_dir := (global_position - player.global_position).normalized() _enter_ragdoll(hit_dir, player.velocity.length()) func _enter_ragdoll(hit_dir: Vector3, bull_speed: float) -> void: _state = State.RAGDOLL hit_dir.y = 0.0 hit_dir = hit_dir.normalized() velocity = Vector3(hit_dir.x, 0.5, hit_dir.z) * bull_speed * 0.4 if not _sim: return _sim.active = true var strength := DP.f("mat_ragdoll_impulse") for child: Node in _sim.get_children(): if child is PhysicalBone3D: var scatter := Vector3( randf_range(-0.3, 0.3), randf_range(0.2, 0.8), randf_range(-0.3, 0.3)) (child as PhysicalBone3D).apply_central_impulse( (hit_dir + scatter).normalized() * strength) func _pick_wander_target() -> void: var radius := DP.f("mat_wander_radius") var angle := randf() * TAU var dist := randf_range(2.0, radius) _wander_target = Vector3(cos(angle) * dist, 0.0, sin(angle) * dist) # ── Ragdoll setup ───────────────────────────────────────────────────────────── func _setup_physical_bones() -> PhysicalBoneSimulator3D: for child: Node in _skeleton.get_children(): if child is PhysicalBoneSimulator3D: (child as PhysicalBoneSimulator3D).active = false return child as PhysicalBoneSimulator3D var sim := PhysicalBoneSimulator3D.new() sim.active = false _skeleton.add_child(sim) # [bone_name, sphere_radius] or [bone_name, capsule_radius, capsule_height] const BONES: Array = [ ["matador", 0.18], ["chest", 0.13, 0.30], ["head", 0.14], ["arm_L", 0.07, 0.22], ["arm_R", 0.07, 0.22], ["forearm_L", 0.06, 0.22], ["forearm_R", 0.06, 0.22], ["leg_L", 0.10, 0.32], ["leg_R", 0.10, 0.32], ["shin_L", 0.08, 0.28], ["shin_R", 0.08, 0.28], ] for entry: Array in BONES: var bname: String = entry[0] if _skeleton.find_bone(bname) == -1: continue var pb := PhysicalBone3D.new() pb.bone_name = bname pb.linear_damp = 0.5 pb.angular_damp = 0.5 var cs := CollisionShape3D.new() var use_capsule: bool = entry.size() >= 3 if use_capsule: var cap := CapsuleShape3D.new() cap.radius = entry[1] cap.height = entry[2] cs.shape = cap else: var sph := SphereShape3D.new() sph.radius = entry[1] cs.shape = sph pb.add_child(cs) sim.add_child(pb) return sim # ── Utility ─────────────────────────────────────────────────────────────────── func _find_skeleton(node: Node) -> Skeleton3D: if node is Skeleton3D: return node as Skeleton3D for child: Node in node.get_children(): var r := _find_skeleton(child) if r: return r return null