cooldowns, sword holstering, ability colors
This commit is contained in:
@@ -2,3 +2,4 @@ disable:
|
|||||||
- class-definitions-order
|
- class-definitions-order
|
||||||
- no-else-return
|
- no-else-return
|
||||||
max-line-length: 120
|
max-line-length: 120
|
||||||
|
max-file-lines: 1200
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
extends CPUParticles3D
|
||||||
|
## One-shot blood spray. Add as a child of whatever bleeds, then call burst().
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
var mat := StandardMaterial3D.new()
|
||||||
|
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||||
|
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||||
|
mat.vertex_color_use_as_albedo = true
|
||||||
|
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||||||
|
|
||||||
|
var sphere := SphereMesh.new()
|
||||||
|
sphere.radius = 0.07
|
||||||
|
sphere.height = 0.14
|
||||||
|
sphere.radial_segments = 4
|
||||||
|
sphere.rings = 2
|
||||||
|
sphere.material = mat
|
||||||
|
|
||||||
|
var ramp := Gradient.new()
|
||||||
|
ramp.set_color(0, Color(1.0, 0.02, 0.02, 1.0))
|
||||||
|
ramp.set_color(1, Color(0.25, 0.0, 0.0, 0.0))
|
||||||
|
|
||||||
|
var scale_curve := Curve.new()
|
||||||
|
scale_curve.add_point(Vector2(0.0, 1.0))
|
||||||
|
scale_curve.add_point(Vector2(0.6, 0.5))
|
||||||
|
scale_curve.add_point(Vector2(1.0, 0.0))
|
||||||
|
|
||||||
|
mesh = sphere
|
||||||
|
color_ramp = ramp
|
||||||
|
scale_amount_curve = scale_curve
|
||||||
|
one_shot = true
|
||||||
|
explosiveness = 0.9
|
||||||
|
amount = 25
|
||||||
|
lifetime = 1.1
|
||||||
|
randomness = 0.4
|
||||||
|
local_coords = false
|
||||||
|
spread = 80.0
|
||||||
|
gravity = Vector3(0.0, -7.0, 0.0)
|
||||||
|
initial_velocity_min = 4.0
|
||||||
|
initial_velocity_max = 11.0
|
||||||
|
scale_amount_min = 0.5
|
||||||
|
scale_amount_max = 1.6
|
||||||
|
emitting = false
|
||||||
|
|
||||||
|
|
||||||
|
# Fire a burst at a world position, sprayed along dir (biased upward).
|
||||||
|
func burst(at: Vector3, dir: Vector3) -> void:
|
||||||
|
global_position = at
|
||||||
|
direction = (dir * 0.6 + Vector3.UP * 0.4).normalized()
|
||||||
|
restart()
|
||||||
+19
-5
@@ -115,14 +115,14 @@ func _register_all() -> void:
|
|||||||
_reg_f("Matador", "mat_attack_min_dist", 2.5, 0.5, 6.0, 0.1)
|
_reg_f("Matador", "mat_attack_min_dist", 2.5, 0.5, 6.0, 0.1)
|
||||||
_reg_f("Matador", "mat_roll_chance", 0.35, 0.0, 1.0, 0.05)
|
_reg_f("Matador", "mat_roll_chance", 0.35, 0.0, 1.0, 0.05)
|
||||||
_reg_f("Matador", "mat_roll_duration", 0.45, 0.1, 1.5, 0.05)
|
_reg_f("Matador", "mat_roll_duration", 0.45, 0.1, 1.5, 0.05)
|
||||||
# Draw the sword from the holster once the bull closes to within this range;
|
# Playback rate of the Draw_weapon clip used for both drawing and sheathing —
|
||||||
# re-sheathe when it retreats well beyond it (hysteresis in _update_sword_carry).
|
# higher = snappier unholster/holster.
|
||||||
_reg_f("Matador", "mat_draw_range", 12.0, 2.0, 40.0)
|
_reg_f("Matador", "mat_draw_speed", 1.25, 0.25, 3.0, 0.05)
|
||||||
# Cross-fade between matador animation clips — smooths Run/Attack/Taunt cuts.
|
# Cross-fade between matador animation clips — smooths Run/Attack/Taunt cuts.
|
||||||
_reg_f("Matador", "mat_anim_blend", 0.12, 0.0, 0.5, 0.01)
|
_reg_f("Matador", "mat_anim_blend", 0.12, 0.0, 0.5, 0.01)
|
||||||
# ── Sword ─────────────────────────────────────────────────────────────────
|
# ── Sword ─────────────────────────────────────────────────────────────────
|
||||||
# Local seating of the sword on its weapon bones (hand grip + hip holster).
|
# Local seating of the sword in the right hand (drawn / fighting).
|
||||||
# Defaults are identity — the bones are oriented in Blender to seat the sword
|
# Defaults are identity — the bone is oriented in Blender to seat the sword
|
||||||
# directly; these exist to fine-tune in-game without re-exporting.
|
# directly; these exist to fine-tune in-game without re-exporting.
|
||||||
_reg_f("Sword", "sword_pos_x", 0.0, -1.0, 1.0, 0.01)
|
_reg_f("Sword", "sword_pos_x", 0.0, -1.0, 1.0, 0.01)
|
||||||
_reg_f("Sword", "sword_pos_y", 0.0, -1.0, 1.0, 0.01)
|
_reg_f("Sword", "sword_pos_y", 0.0, -1.0, 1.0, 0.01)
|
||||||
@@ -130,6 +130,15 @@ func _register_all() -> void:
|
|||||||
_reg_f("Sword", "sword_rot_x", 0.0, -180.0, 180.0, 1.0)
|
_reg_f("Sword", "sword_rot_x", 0.0, -180.0, 180.0, 1.0)
|
||||||
_reg_f("Sword", "sword_rot_y", 0.0, -180.0, 180.0, 1.0)
|
_reg_f("Sword", "sword_rot_y", 0.0, -180.0, 180.0, 1.0)
|
||||||
_reg_f("Sword", "sword_rot_z", 0.0, -180.0, 180.0, 1.0)
|
_reg_f("Sword", "sword_rot_z", 0.0, -180.0, 180.0, 1.0)
|
||||||
|
# Local seating of the sword in the hip holster (sheathed / wandering).
|
||||||
|
# Independent of the hand grip so each pose can be tuned without affecting the
|
||||||
|
# other. Defaults angle the blade down along the matador's side.
|
||||||
|
_reg_f("Sword", "sword_rest_pos_x", 0.0, -1.0, 1.0, 0.01)
|
||||||
|
_reg_f("Sword", "sword_rest_pos_y", 0.0, -1.0, 1.0, 0.01)
|
||||||
|
_reg_f("Sword", "sword_rest_pos_z", 0.0, -1.0, 1.0, 0.01)
|
||||||
|
_reg_f("Sword", "sword_rest_rot_x", -120.0, -180.0, 180.0, 1.0)
|
||||||
|
_reg_f("Sword", "sword_rest_rot_y", 0.0, -180.0, 180.0, 1.0)
|
||||||
|
_reg_f("Sword", "sword_rest_rot_z", 0.0, -180.0, 180.0, 1.0)
|
||||||
# ── Sword throw (matador ranged attack) ──────────────────────────────────
|
# ── Sword throw (matador ranged attack) ──────────────────────────────────
|
||||||
# Probability of committing to a throw each time the matador becomes eligible;
|
# Probability of committing to a throw each time the matador becomes eligible;
|
||||||
# kept low so they favour closing in for a melee Attack over throwing.
|
# kept low so they favour closing in for a melee Attack over throwing.
|
||||||
@@ -151,6 +160,11 @@ func _register_all() -> void:
|
|||||||
_reg_f("Abilities", "kick_strength", 12.0, 1.0, 30.0)
|
_reg_f("Abilities", "kick_strength", 12.0, 1.0, 30.0)
|
||||||
_reg_f("Abilities", "slam_cooldown", 4.0, 0.5, 15.0, 0.1)
|
_reg_f("Abilities", "slam_cooldown", 4.0, 0.5, 15.0, 0.1)
|
||||||
_reg_f("Abilities", "slam_jump_velocity", 7.0, 2.0, 20.0)
|
_reg_f("Abilities", "slam_jump_velocity", 7.0, 2.0, 20.0)
|
||||||
|
# Descent gravity multiplier — higher = faster crash, less airtime.
|
||||||
|
_reg_f("Abilities", "slam_fall_mult", 3.0, 1.0, 8.0, 0.5)
|
||||||
|
# Upward launch velocity (m/s) given to a slammed matador's ragdoll — pops them
|
||||||
|
# skyward. ~7 ≈ a 2.5 m hop; 0 disables the launch.
|
||||||
|
_reg_f("Abilities", "slam_launch_up", 7.0, 0.0, 15.0, 0.5)
|
||||||
_reg_f("Abilities", "slam_range", 4.0, 1.0, 10.0, 0.1)
|
_reg_f("Abilities", "slam_range", 4.0, 1.0, 10.0, 0.1)
|
||||||
_reg_f("Abilities", "slam_strength", 14.0, 1.0, 30.0)
|
_reg_f("Abilities", "slam_strength", 14.0, 1.0, 30.0)
|
||||||
_reg_f("Abilities", "dash_cooldown", 2.0, 0.5, 10.0, 0.1)
|
_reg_f("Abilities", "dash_cooldown", 2.0, 0.5, 10.0, 0.1)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ var _toggle_btn: Button
|
|||||||
var _spawner: Node
|
var _spawner: Node
|
||||||
var _player: Node = null
|
var _player: Node = null
|
||||||
var _cd_overlays: Array[ColorRect] = []
|
var _cd_overlays: Array[ColorRect] = []
|
||||||
|
var _cd_labels: Array[Label] = []
|
||||||
var _health_segments: Array[ColorRect] = []
|
var _health_segments: Array[ColorRect] = []
|
||||||
var _health_connected: bool = false
|
var _health_connected: bool = false
|
||||||
|
|
||||||
@@ -48,6 +49,13 @@ func _process(_delta: float) -> void:
|
|||||||
var ov := _cd_overlays[i]
|
var ov := _cd_overlays[i]
|
||||||
ov.anchor_top = 1.0 - fraction
|
ov.anchor_top = 1.0 - fraction
|
||||||
ov.anchor_bottom = 1.0
|
ov.anchor_bottom = 1.0
|
||||||
|
var remaining: float = _player.call(&"ability_cooldown_remaining", i)
|
||||||
|
var lbl := _cd_labels[i]
|
||||||
|
if remaining > 0.0:
|
||||||
|
lbl.text = "%.1fs" % remaining
|
||||||
|
lbl.visible = true
|
||||||
|
else:
|
||||||
|
lbl.visible = false
|
||||||
|
|
||||||
|
|
||||||
func _find_spawner() -> void:
|
func _find_spawner() -> void:
|
||||||
@@ -188,6 +196,17 @@ func _build_ability_slot(idx: int) -> Control:
|
|||||||
stack.add_child(cd_overlay)
|
stack.add_child(cd_overlay)
|
||||||
_cd_overlays.append(cd_overlay)
|
_cd_overlays.append(cd_overlay)
|
||||||
|
|
||||||
|
var cd_lbl := Label.new()
|
||||||
|
cd_lbl.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
|
cd_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
cd_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||||
|
cd_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
cd_lbl.visible = false
|
||||||
|
cd_lbl.add_theme_color_override("font_color", Color.WHITE)
|
||||||
|
cd_lbl.add_theme_font_size_override("font_size", 18)
|
||||||
|
stack.add_child(cd_lbl)
|
||||||
|
_cd_labels.append(cd_lbl)
|
||||||
|
|
||||||
var key_lbl := Label.new()
|
var key_lbl := Label.new()
|
||||||
key_lbl.text = _ABILITY_KEYS[idx]
|
key_lbl.text = _ABILITY_KEYS[idx]
|
||||||
key_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
key_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
|||||||
+142
-101
@@ -39,6 +39,7 @@ var _sword_rest_attach: BoneAttachment3D = null
|
|||||||
var _sword_node: Node3D = null
|
var _sword_node: Node3D = null
|
||||||
var _sword_in_hand: bool = false
|
var _sword_in_hand: bool = false
|
||||||
var _drawing: bool = false
|
var _drawing: bool = false
|
||||||
|
var _sheathing: bool = false
|
||||||
var _draw_timer: float = 0.0
|
var _draw_timer: float = 0.0
|
||||||
var _draw_len: float = 0.0
|
var _draw_len: float = 0.0
|
||||||
var _resword_timer: float = 0.0
|
var _resword_timer: float = 0.0
|
||||||
@@ -93,7 +94,8 @@ func _ready() -> void:
|
|||||||
_throw_aim_cd = randf_range(0.8, 2.5)
|
_throw_aim_cd = randf_range(0.8, 2.5)
|
||||||
_hit_area.body_entered.connect(_on_body_entered)
|
_hit_area.body_entered.connect(_on_body_entered)
|
||||||
_pick_wander_target()
|
_pick_wander_target()
|
||||||
_setup_blood_burst()
|
_blood_burst = preload("res://blood_burst.gd").new()
|
||||||
|
add_child(_blood_burst)
|
||||||
if ResourceLoader.exists("res://sounds/ole.ogg"):
|
if ResourceLoader.exists("res://sounds/ole.ogg"):
|
||||||
_ole_player = AudioStreamPlayer.new()
|
_ole_player = AudioStreamPlayer.new()
|
||||||
_ole_player.stream = load("res://sounds/ole.ogg")
|
_ole_player.stream = load("res://sounds/ole.ogg")
|
||||||
@@ -157,7 +159,7 @@ func _update_ai_state() -> void:
|
|||||||
return
|
return
|
||||||
var dist := global_position.distance_to(_bull.global_position)
|
var dist := global_position.distance_to(_bull.global_position)
|
||||||
if (_state == State.WANDER or _state == State.FLEE or _state == State.ATTACK) \
|
if (_state == State.WANDER or _state == State.FLEE or _state == State.ATTACK) \
|
||||||
and _throw_aim_cd <= 0.0 and _sword_in_hand and is_instance_valid(_sword_node) \
|
and _throw_aim_cd <= 0.0 and is_instance_valid(_sword_node) \
|
||||||
and dist >= DP.f("mat_throw_min_dist") and dist <= DP.f("mat_throw_range"):
|
and dist >= DP.f("mat_throw_min_dist") and dist <= DP.f("mat_throw_range"):
|
||||||
# Roll for a throw; on a miss, wait before becoming eligible again so the
|
# Roll for a throw; on a miss, wait before becoming eligible again so the
|
||||||
# matador commits to chasing for a melee Attack instead of throwing early.
|
# matador commits to chasing for a melee Attack instead of throwing early.
|
||||||
@@ -260,8 +262,8 @@ func _face_point(point: Vector3, delta: float, rate: float) -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _tick_wander(delta: float) -> void:
|
func _tick_wander(delta: float) -> void:
|
||||||
# Plant the feet while unsheathing instead of sliding through the draw clip.
|
# Plant the feet while drawing / sheathing instead of sliding through the clip.
|
||||||
if _drawing or _idle_timer > 0.0:
|
if _drawing or _sheathing or _idle_timer > 0.0:
|
||||||
_idle_timer = maxf(0.0, _idle_timer - delta)
|
_idle_timer = maxf(0.0, _idle_timer - delta)
|
||||||
_decelerate(10.0, delta)
|
_decelerate(10.0, delta)
|
||||||
_play_anim(_ANIM_IDLE)
|
_play_anim(_ANIM_IDLE)
|
||||||
@@ -282,6 +284,13 @@ func _tick_wander(delta: float) -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _tick_flee(delta: float) -> void:
|
func _tick_flee(delta: float) -> void:
|
||||||
|
# Plant the feet while sheathing instead of sliding away through the clip.
|
||||||
|
if _drawing or _sheathing:
|
||||||
|
_decelerate(18.0, delta)
|
||||||
|
_face_point(_bull.global_position, delta, _TURN_FACE)
|
||||||
|
move_and_slide()
|
||||||
|
return
|
||||||
|
|
||||||
var away := global_position - _bull.global_position
|
var away := global_position - _bull.global_position
|
||||||
away.y = 0.0
|
away.y = 0.0
|
||||||
if away.length() < 0.01:
|
if away.length() < 0.01:
|
||||||
@@ -355,6 +364,12 @@ func _start_attack() -> void:
|
|||||||
func _tick_attack(delta: float) -> void:
|
func _tick_attack(delta: float) -> void:
|
||||||
_attack_timer -= delta
|
_attack_timer -= delta
|
||||||
_swing_timer = maxf(0.0, _swing_timer - delta)
|
_swing_timer = maxf(0.0, _swing_timer - delta)
|
||||||
|
# Plant the feet while drawing instead of sliding toward the bull mid-clip.
|
||||||
|
if _drawing or _sheathing:
|
||||||
|
_decelerate(22.0, delta)
|
||||||
|
_face_point(_bull.global_position, delta, _TURN_FACE)
|
||||||
|
move_and_slide()
|
||||||
|
return
|
||||||
_face_point(_bull.global_position, delta, _TURN_FACE)
|
_face_point(_bull.global_position, delta, _TURN_FACE)
|
||||||
var to_bull := _bull.global_position - global_position
|
var to_bull := _bull.global_position - global_position
|
||||||
to_bull.y = 0.0
|
to_bull.y = 0.0
|
||||||
@@ -363,7 +378,7 @@ func _tick_attack(delta: float) -> void:
|
|||||||
# Commit to a full swing once in range instead of flipping Run/Attack at the
|
# Commit to a full swing once in range instead of flipping Run/Attack at the
|
||||||
# distance boundary every frame (the old foot-slide jitter). When the swing
|
# distance boundary every frame (the old foot-slide jitter). When the swing
|
||||||
# ends the matador re-closes if the bull slipped away, or stabs again if not.
|
# ends the matador re-closes if the bull slipped away, or stabs again if not.
|
||||||
if _swing_timer <= 0.0 and dist <= DP.f("mat_attack_min_dist"):
|
if _swing_timer <= 0.0 and dist <= DP.f("mat_attack_min_dist") and _sword_in_hand:
|
||||||
_swing_timer = _anim_length(_ANIM_ATTACK, 0.9)
|
_swing_timer = _anim_length(_ANIM_ATTACK, 0.9)
|
||||||
|
|
||||||
if _swing_timer > 0.0:
|
if _swing_timer > 0.0:
|
||||||
@@ -408,6 +423,8 @@ func _start_throw() -> void:
|
|||||||
_state = State.THROW
|
_state = State.THROW
|
||||||
_throw_timer = DP.f("mat_throw_windup")
|
_throw_timer = DP.f("mat_throw_windup")
|
||||||
velocity = Vector3.ZERO
|
velocity = Vector3.ZERO
|
||||||
|
_cancel_transitions()
|
||||||
|
_carry_sword(true) # blade must be in hand for the wind-up and release
|
||||||
if _anim_player:
|
if _anim_player:
|
||||||
_anim_player.pause()
|
_anim_player.pause()
|
||||||
var to_bull := _bull.global_position - global_position
|
var to_bull := _bull.global_position - global_position
|
||||||
@@ -542,11 +559,11 @@ func _tick_ragdoll(_delta: float) -> void:
|
|||||||
move_and_slide()
|
move_and_slide()
|
||||||
|
|
||||||
|
|
||||||
func apply_ability_hit(hit_dir: Vector3, strength: float) -> void:
|
func apply_ability_hit(hit_dir: Vector3, strength: float, up_boost: float = 0.0) -> void:
|
||||||
if _state == State.RAGDOLL or _state == State.ROLL:
|
if _state == State.RAGDOLL or _state == State.ROLL:
|
||||||
return
|
return
|
||||||
_acquire_bull()
|
_acquire_bull()
|
||||||
_enter_ragdoll(hit_dir, strength)
|
_enter_ragdoll(hit_dir, strength, up_boost)
|
||||||
|
|
||||||
|
|
||||||
func _on_body_entered(body: Node3D) -> void:
|
func _on_body_entered(body: Node3D) -> void:
|
||||||
@@ -578,7 +595,7 @@ func _on_blade_hit(body: Node3D) -> void:
|
|||||||
body.call(&"take_sword_hit")
|
body.call(&"take_sword_hit")
|
||||||
|
|
||||||
|
|
||||||
func _enter_ragdoll(hit_dir: Vector3, bull_speed: float) -> void:
|
func _enter_ragdoll(hit_dir: Vector3, bull_speed: float, up_boost: float = 0.0) -> void:
|
||||||
_state = State.RAGDOLL
|
_state = State.RAGDOLL
|
||||||
if _sword_blade_area:
|
if _sword_blade_area:
|
||||||
_sword_blade_area.monitoring = false
|
_sword_blade_area.monitoring = false
|
||||||
@@ -592,7 +609,7 @@ func _enter_ragdoll(hit_dir: Vector3, bull_speed: float) -> void:
|
|||||||
hit_dir = hit_dir.normalized()
|
hit_dir = hit_dir.normalized()
|
||||||
var throw_dir := (hit_dir + Vector3(0.0, 0.5, 0.0)).normalized()
|
var throw_dir := (hit_dir + Vector3(0.0, 0.5, 0.0)).normalized()
|
||||||
|
|
||||||
_spawn_blood_burst(hit_dir)
|
_blood_burst.burst(global_position + Vector3(0.0, 0.9, 0.0), hit_dir)
|
||||||
if _death_player:
|
if _death_player:
|
||||||
_death_player.pitch_scale = randf_range(0.9, 1.1)
|
_death_player.pitch_scale = randf_range(0.9, 1.1)
|
||||||
_death_player.play()
|
_death_player.play()
|
||||||
@@ -619,6 +636,7 @@ func _enter_ragdoll(hit_dir: Vector3, bull_speed: float) -> void:
|
|||||||
pb.global_transform = _skeleton.global_transform * _skeleton.get_bone_global_pose(bone_idx)
|
pb.global_transform = _skeleton.global_transform * _skeleton.get_bone_global_pose(bone_idx)
|
||||||
_sim.active = true
|
_sim.active = true
|
||||||
_sim.physical_bones_start_simulation()
|
_sim.physical_bones_start_simulation()
|
||||||
|
_pin_cape_to_body()
|
||||||
var strength := DP.f("mat_ragdoll_impulse") * clampf(bull_speed / 15.0, 0.4, 1.3)
|
var strength := DP.f("mat_ragdoll_impulse") * clampf(bull_speed / 15.0, 0.4, 1.3)
|
||||||
var right := throw_dir.cross(Vector3.UP).normalized()
|
var right := throw_dir.cross(Vector3.UP).normalized()
|
||||||
for child: Node in _sim.get_children():
|
for child: Node in _sim.get_children():
|
||||||
@@ -641,61 +659,46 @@ func _enter_ragdoll(hit_dir: Vector3, bull_speed: float) -> void:
|
|||||||
impulse = (throw_dir * 0.3 + right * 0.8 + Vector3.UP * 0.5).normalized() * strength * 0.5
|
impulse = (throw_dir * 0.3 + right * 0.8 + Vector3.UP * 0.5).normalized() * strength * 0.5
|
||||||
"arm_R":
|
"arm_R":
|
||||||
impulse = (throw_dir * 0.3 - right * 0.8 + Vector3.UP * 0.5).normalized() * strength * 0.5
|
impulse = (throw_dir * 0.3 - right * 0.8 + Vector3.UP * 0.5).normalized() * strength * 0.5
|
||||||
|
"Bone", "Bone.001", "Bone.002":
|
||||||
|
# Scale by mass so the light cape gets the same velocity as the body
|
||||||
|
# (Δv = impulse/mass) and flies along with it instead of rocketing off.
|
||||||
|
impulse = (throw_dir + Vector3.UP * 0.35).normalized() * strength * 2.0 * pb.mass
|
||||||
_:
|
_:
|
||||||
continue
|
continue
|
||||||
var noise := Vector3(randf_range(-0.15, 0.15), randf_range(-0.08, 0.15), randf_range(-0.15, 0.15))
|
var noise := Vector3(randf_range(-0.15, 0.15), randf_range(-0.08, 0.15), randf_range(-0.15, 0.15))
|
||||||
pb.apply_central_impulse(impulse + noise * strength * 0.2)
|
pb.apply_central_impulse(impulse + noise * strength * 0.2)
|
||||||
|
|
||||||
|
# A floor slam launches the whole ragdoll straight up: a uniform per-bone Δv
|
||||||
# ── Blood burst ───────────────────────────────────────────────────────────────
|
# (impulse = mass · speed) so the joints carry every bone up together.
|
||||||
|
if up_boost > 0.0:
|
||||||
func _setup_blood_burst() -> void:
|
for child: Node in _sim.get_children():
|
||||||
var mat := StandardMaterial3D.new()
|
if child is PhysicalBone3D:
|
||||||
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
var pbb := child as PhysicalBone3D
|
||||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
pbb.apply_central_impulse(Vector3.UP * up_boost * pbb.mass)
|
||||||
mat.vertex_color_use_as_albedo = true
|
|
||||||
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
|
|
||||||
|
|
||||||
var sphere := SphereMesh.new()
|
|
||||||
sphere.radius = 0.07
|
|
||||||
sphere.height = 0.14
|
|
||||||
sphere.radial_segments = 4
|
|
||||||
sphere.rings = 2
|
|
||||||
sphere.material = mat
|
|
||||||
|
|
||||||
var ramp := Gradient.new()
|
|
||||||
ramp.set_color(0, Color(1.0, 0.02, 0.02, 1.0))
|
|
||||||
ramp.set_color(1, Color(0.25, 0.0, 0.0, 0.0))
|
|
||||||
|
|
||||||
var scale_curve := Curve.new()
|
|
||||||
scale_curve.add_point(Vector2(0.0, 1.0))
|
|
||||||
scale_curve.add_point(Vector2(0.6, 0.5))
|
|
||||||
scale_curve.add_point(Vector2(1.0, 0.0))
|
|
||||||
|
|
||||||
_blood_burst = CPUParticles3D.new()
|
|
||||||
_blood_burst.mesh = sphere
|
|
||||||
_blood_burst.color_ramp = ramp
|
|
||||||
_blood_burst.scale_amount_curve = scale_curve
|
|
||||||
_blood_burst.one_shot = true
|
|
||||||
_blood_burst.explosiveness = 0.9
|
|
||||||
_blood_burst.amount = 25
|
|
||||||
_blood_burst.lifetime = 1.1
|
|
||||||
_blood_burst.randomness = 0.4
|
|
||||||
_blood_burst.local_coords = false
|
|
||||||
_blood_burst.spread = 80.0
|
|
||||||
_blood_burst.gravity = Vector3(0.0, -7.0, 0.0)
|
|
||||||
_blood_burst.initial_velocity_min = 4.0
|
|
||||||
_blood_burst.initial_velocity_max = 11.0
|
|
||||||
_blood_burst.scale_amount_min = 0.5
|
|
||||||
_blood_burst.scale_amount_max = 1.6
|
|
||||||
_blood_burst.emitting = false
|
|
||||||
add_child(_blood_burst)
|
|
||||||
|
|
||||||
|
|
||||||
func _spawn_blood_burst(hit_dir: Vector3) -> void:
|
# Pin the cape's root link to the chest so it stays attached at the shoulders
|
||||||
_blood_burst.global_position = global_position + Vector3(0.0, 0.9, 0.0)
|
# while ragdolling (its bone chain is a skeleton root, so it has no joint to the
|
||||||
_blood_burst.direction = (hit_dir * 0.6 + Vector3.UP * 0.4).normalized()
|
# body otherwise). The chain hangs and swings from this point.
|
||||||
_blood_burst.restart()
|
func _pin_cape_to_body() -> void:
|
||||||
|
var cape := _physical_bone(&"Bone")
|
||||||
|
var chest := _physical_bone(&"chest")
|
||||||
|
if cape == null or chest == null:
|
||||||
|
return
|
||||||
|
var joint := PinJoint3D.new()
|
||||||
|
_sim.add_child(joint)
|
||||||
|
joint.global_position = cape.global_position
|
||||||
|
joint.node_a = chest.get_path()
|
||||||
|
joint.node_b = cape.get_path()
|
||||||
|
|
||||||
|
|
||||||
|
func _physical_bone(bone: StringName) -> PhysicalBone3D:
|
||||||
|
if not _sim:
|
||||||
|
return null
|
||||||
|
for child: Node in _sim.get_children():
|
||||||
|
if child is PhysicalBone3D and (child as PhysicalBone3D).bone_name == bone:
|
||||||
|
return child as PhysicalBone3D
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
# ── Animation helpers ─────────────────────────────────────────────────────────
|
# ── Animation helpers ─────────────────────────────────────────────────────────
|
||||||
@@ -715,8 +718,8 @@ func _anim_length(anim_name: StringName, fallback: float) -> float:
|
|||||||
func _play_anim(anim_name: StringName) -> void:
|
func _play_anim(anim_name: StringName) -> void:
|
||||||
if _anim_player == null:
|
if _anim_player == null:
|
||||||
return
|
return
|
||||||
if _drawing:
|
if _drawing or _sheathing:
|
||||||
return # let the unholster clip play through uninterrupted
|
return # let the draw / sheathe clip play through uninterrupted
|
||||||
if _anim_player.current_animation == anim_name:
|
if _anim_player.current_animation == anim_name:
|
||||||
return
|
return
|
||||||
_anim_player.play(anim_name)
|
_anim_player.play(anim_name)
|
||||||
@@ -743,9 +746,9 @@ func _pick_wander_target() -> void:
|
|||||||
# weapon_bone — the grip pose in the right hand (drawn / fighting)
|
# weapon_bone — the grip pose in the right hand (drawn / fighting)
|
||||||
# weapon_rest_bone — the holstered pose at the hip (sheathed / wandering)
|
# weapon_rest_bone — the holstered pose at the hip (sheathed / wandering)
|
||||||
# The sword lives in the holster by default and is carried to the hand while the
|
# The sword lives in the holster by default and is carried to the hand while the
|
||||||
# matador is in a combat state (see _carry_sword). Both bones are oriented in
|
# matador is in a combat state (see _carry_sword). Each bone has its own
|
||||||
# Blender to seat the sword directly, so the local offset/rotation default to
|
# DP-tunable local offset/rotation (sword_* for the hand, sword_rest_* for the
|
||||||
# identity and stay DP-tunable for fine-tuning without re-exporting.
|
# holster) so the two poses can be seated independently without re-exporting.
|
||||||
func _setup_sword() -> void:
|
func _setup_sword() -> void:
|
||||||
if not _skeleton:
|
if not _skeleton:
|
||||||
return
|
return
|
||||||
@@ -799,63 +802,83 @@ func _spawn_sword() -> void:
|
|||||||
_sword_blade_area.body_entered.connect(_on_blade_hit)
|
_sword_blade_area.body_entered.connect(_on_blade_hit)
|
||||||
|
|
||||||
|
|
||||||
# Draw the sword as the bull approaches and re-sheathe it once the bull is far
|
# The sword is carried by ACTION, not distance: drawn only while striking
|
||||||
# again — so a matador wanders with the sword holstered and pulls it out to
|
# (ATTACK / THROW) and holstered while running, dodging, bracing or taunting.
|
||||||
# engage. Drawing plays the Draw_weapon clip; re-sheathing snaps back. Hysteresis
|
# THROW snaps the blade out (its arm cock-back covers the motion); everything
|
||||||
# (1.25× on the re-sheathe) avoids flicker at the threshold.
|
# else animates with the Draw_weapon clip — forward to draw, reversed to sheathe.
|
||||||
|
func _wants_sword_drawn() -> bool:
|
||||||
|
return _state == State.ATTACK or _state == State.THROW
|
||||||
|
|
||||||
|
|
||||||
func _update_sword_carry() -> void:
|
func _update_sword_carry() -> void:
|
||||||
if not is_instance_valid(_sword_node):
|
if not is_instance_valid(_sword_node) or _state == State.RAGDOLL:
|
||||||
return
|
return
|
||||||
if _state == State.RAGDOLL or _bull == null:
|
if _wants_sword_drawn():
|
||||||
_cancel_draw()
|
|
||||||
_carry_sword(false)
|
|
||||||
return
|
|
||||||
var dist := global_position.distance_to(_bull.global_position)
|
|
||||||
var draw_r := DP.f("mat_draw_range")
|
|
||||||
if dist <= draw_r:
|
|
||||||
if not _sword_in_hand and not _drawing:
|
if not _sword_in_hand and not _drawing:
|
||||||
_begin_draw()
|
_begin_draw()
|
||||||
elif dist > draw_r * 1.25:
|
elif _sword_in_hand or _drawing:
|
||||||
_cancel_draw()
|
# Animate the sheathe only into the unhurried locomotion states; snap for
|
||||||
|
# a dodge / roll / brace where the body clip can't spare the time.
|
||||||
|
if _state == State.WANDER or _state == State.FLEE:
|
||||||
|
if not _sheathing:
|
||||||
|
_begin_sheathe()
|
||||||
|
else:
|
||||||
|
_cancel_transitions()
|
||||||
_carry_sword(false)
|
_carry_sword(false)
|
||||||
|
|
||||||
|
|
||||||
func _begin_draw() -> void:
|
func _begin_draw() -> void:
|
||||||
if _anim_player and _anim_player.has_animation(_ANIM_DRAW):
|
if not (_anim_player and _anim_player.has_animation(_ANIM_DRAW)):
|
||||||
_drawing = true
|
|
||||||
_draw_len = _anim_length(_ANIM_DRAW, 1.0)
|
|
||||||
_draw_timer = 0.0
|
|
||||||
_anim_player.play(_ANIM_DRAW)
|
|
||||||
_anim_player.seek(0.0, true)
|
|
||||||
else:
|
|
||||||
_carry_sword(true) # no draw clip on this rig — snap to hand
|
_carry_sword(true) # no draw clip on this rig — snap to hand
|
||||||
|
|
||||||
|
|
||||||
func _advance_draw(delta: float) -> void:
|
|
||||||
if not _drawing:
|
|
||||||
return
|
return
|
||||||
# A combat state needs the body animations back at once — finish instantly.
|
_sheathing = false
|
||||||
if _state != State.WANDER and _state != State.FLEE:
|
_drawing = true
|
||||||
_finalize_draw()
|
_draw_timer = 0.0
|
||||||
|
_draw_len = _anim_length(_ANIM_DRAW, 1.0) / _draw_speed()
|
||||||
|
_anim_player.play(_ANIM_DRAW, -1.0, _draw_speed())
|
||||||
|
_anim_player.seek(0.0, true)
|
||||||
|
|
||||||
|
|
||||||
|
func _begin_sheathe() -> void:
|
||||||
|
if not (_anim_player and _anim_player.has_animation(_ANIM_DRAW)):
|
||||||
|
_carry_sword(false) # no clip — snap to holster
|
||||||
|
return
|
||||||
|
_drawing = false
|
||||||
|
_sheathing = true
|
||||||
|
_draw_timer = 0.0
|
||||||
|
_draw_len = _anim_length(_ANIM_DRAW, 1.0) / _draw_speed()
|
||||||
|
# Draw_weapon reversed from its end frame = a put-the-sword-away motion.
|
||||||
|
_anim_player.play(_ANIM_DRAW, -1.0, -_draw_speed(), true)
|
||||||
|
|
||||||
|
|
||||||
|
# Progress a draw or sheathe; the blade reparents at the grab point (mirrored for
|
||||||
|
# the reverse sheathe), and the transition ends when the clip's runtime elapses.
|
||||||
|
func _advance_draw(delta: float) -> void:
|
||||||
|
if not _drawing and not _sheathing:
|
||||||
return
|
return
|
||||||
_draw_timer += delta
|
_draw_timer += delta
|
||||||
if not _sword_in_hand and _draw_timer >= _draw_len * _DRAW_GRAB_AT:
|
var t := _draw_timer / maxf(_draw_len, 0.001)
|
||||||
|
if _drawing:
|
||||||
|
if not _sword_in_hand and t >= _DRAW_GRAB_AT:
|
||||||
_carry_sword(true)
|
_carry_sword(true)
|
||||||
if _draw_timer >= _draw_len:
|
if t >= 1.0:
|
||||||
_finalize_draw()
|
|
||||||
|
|
||||||
|
|
||||||
func _finalize_draw() -> void:
|
|
||||||
if not _drawing:
|
|
||||||
return
|
|
||||||
_drawing = false
|
_drawing = false
|
||||||
_carry_sword(true)
|
_carry_sword(true)
|
||||||
|
else:
|
||||||
|
if _sword_in_hand and t >= 1.0 - _DRAW_GRAB_AT:
|
||||||
|
_carry_sword(false)
|
||||||
|
if t >= 1.0:
|
||||||
|
_sheathing = false
|
||||||
|
_carry_sword(false)
|
||||||
|
|
||||||
|
|
||||||
# Abort an in-progress draw without forcing the sword into the hand, so the
|
func _cancel_transitions() -> void:
|
||||||
# caller can re-holster (the suppressed locomotion anim resumes next frame).
|
|
||||||
func _cancel_draw() -> void:
|
|
||||||
_drawing = false
|
_drawing = false
|
||||||
|
_sheathing = false
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_speed() -> float:
|
||||||
|
return maxf(DP.f("mat_draw_speed"), 0.1)
|
||||||
|
|
||||||
|
|
||||||
# Move the sword between the hand grip and the hip holster. Reparenting follows
|
# Move the sword between the hand grip and the hip holster. Reparenting follows
|
||||||
@@ -879,10 +902,16 @@ func _carry_sword(in_hand: bool) -> void:
|
|||||||
func _apply_sword_grip() -> void:
|
func _apply_sword_grip() -> void:
|
||||||
if not is_instance_valid(_sword_node):
|
if not is_instance_valid(_sword_node):
|
||||||
return
|
return
|
||||||
|
if _sword_in_hand:
|
||||||
_sword_node.position = Vector3(
|
_sword_node.position = Vector3(
|
||||||
DP.f("sword_pos_x"), DP.f("sword_pos_y"), DP.f("sword_pos_z"))
|
DP.f("sword_pos_x"), DP.f("sword_pos_y"), DP.f("sword_pos_z"))
|
||||||
_sword_node.rotation_degrees = Vector3(
|
_sword_node.rotation_degrees = Vector3(
|
||||||
DP.f("sword_rot_x"), DP.f("sword_rot_y"), DP.f("sword_rot_z"))
|
DP.f("sword_rot_x"), DP.f("sword_rot_y"), DP.f("sword_rot_z"))
|
||||||
|
else:
|
||||||
|
_sword_node.position = Vector3(
|
||||||
|
DP.f("sword_rest_pos_x"), DP.f("sword_rest_pos_y"), DP.f("sword_rest_pos_z"))
|
||||||
|
_sword_node.rotation_degrees = Vector3(
|
||||||
|
DP.f("sword_rest_rot_x"), DP.f("sword_rest_rot_y"), DP.f("sword_rest_rot_z"))
|
||||||
|
|
||||||
|
|
||||||
# ── Ragdoll setup ─────────────────────────────────────────────────────────────
|
# ── Ragdoll setup ─────────────────────────────────────────────────────────────
|
||||||
@@ -916,6 +945,11 @@ func _setup_physical_bones() -> PhysicalBoneSimulator3D:
|
|||||||
["shin_R", 0.08, 0.28],
|
["shin_R", 0.08, 0.28],
|
||||||
["foot_L", 0.07, 0.18],
|
["foot_L", 0.07, 0.18],
|
||||||
["foot_R", 0.07, 0.18],
|
["foot_R", 0.07, 0.18],
|
||||||
|
# Muleta (cape) spine — a free 3-link chain so the cape ragdolls with the
|
||||||
|
# body instead of hanging frozen in the air. See the cape tweak below.
|
||||||
|
["Bone", 0.04, 0.30],
|
||||||
|
["Bone.001", 0.04, 0.34],
|
||||||
|
["Bone.002", 0.04, 0.30],
|
||||||
]
|
]
|
||||||
|
|
||||||
for entry: Array in BONES:
|
for entry: Array in BONES:
|
||||||
@@ -930,6 +964,13 @@ func _setup_physical_bones() -> PhysicalBoneSimulator3D:
|
|||||||
pb.angular_damp = 0.2
|
pb.angular_damp = 0.2
|
||||||
pb.collision_layer = 1
|
pb.collision_layer = 1
|
||||||
pb.collision_mask = 1
|
pb.collision_mask = 1
|
||||||
|
if bname.begins_with("Bone"):
|
||||||
|
# Cape: light and draggy so it flutters like cloth; layer 0 makes its
|
||||||
|
# collision one-way (drapes onto body/floor without shoving the corpse).
|
||||||
|
pb.mass = 0.15
|
||||||
|
pb.linear_damp = 0.5
|
||||||
|
pb.angular_damp = 0.7
|
||||||
|
pb.collision_layer = 0
|
||||||
var cs := CollisionShape3D.new()
|
var cs := CollisionShape3D.new()
|
||||||
var use_capsule: bool = entry.size() >= 3
|
var use_capsule: bool = entry.size() >= 3
|
||||||
if use_capsule:
|
if use_capsule:
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ var _roll_damping: float = 0.15
|
|||||||
|
|
||||||
enum DustState { NONE, WALK, CHARGE }
|
enum DustState { NONE, WALK, CHARGE }
|
||||||
var _dust_state: DustState = DustState.NONE
|
var _dust_state: DustState = DustState.NONE
|
||||||
|
var _walk_dust_ramp: Gradient = null
|
||||||
|
var _charge_dust_ramp: Gradient = null
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@@ -79,9 +81,14 @@ func _setup_hoof_dust() -> void:
|
|||||||
sphere.rings = 2
|
sphere.rings = 2
|
||||||
sphere.material = mat
|
sphere.material = mat
|
||||||
|
|
||||||
var ramp := Gradient.new()
|
_walk_dust_ramp = Gradient.new()
|
||||||
ramp.set_color(0, Color(0.80, 0.69, 0.46, 1.0))
|
_walk_dust_ramp.set_color(0, Color(0.80, 0.69, 0.46, 1.0))
|
||||||
ramp.set_color(1, Color(0.80, 0.69, 0.46, 0.0))
|
_walk_dust_ramp.set_color(1, Color(0.80, 0.69, 0.46, 0.0))
|
||||||
|
|
||||||
|
# Light blue dust while charging
|
||||||
|
_charge_dust_ramp = Gradient.new()
|
||||||
|
_charge_dust_ramp.set_color(0, Color(0.20, 0.40, 0.85, 1.0))
|
||||||
|
_charge_dust_ramp.set_color(1, Color(0.20, 0.40, 0.85, 0.0))
|
||||||
|
|
||||||
var scale_curve := Curve.new()
|
var scale_curve := Curve.new()
|
||||||
scale_curve.add_point(Vector2(0.0, 0.2))
|
scale_curve.add_point(Vector2(0.0, 0.2))
|
||||||
@@ -105,7 +112,7 @@ func _setup_hoof_dust() -> void:
|
|||||||
p.scale_amount_min = DP.f("walk_scale_min")
|
p.scale_amount_min = DP.f("walk_scale_min")
|
||||||
p.scale_amount_max = DP.f("walk_scale_max")
|
p.scale_amount_max = DP.f("walk_scale_max")
|
||||||
p.mesh = sphere
|
p.mesh = sphere
|
||||||
p.color_ramp = ramp
|
p.color_ramp = _walk_dust_ramp
|
||||||
p.scale_amount_curve = scale_curve
|
p.scale_amount_curve = scale_curve
|
||||||
cube_guy.add_child(p)
|
cube_guy.add_child(p)
|
||||||
_hoof_emitters.append(p)
|
_hoof_emitters.append(p)
|
||||||
@@ -185,12 +192,14 @@ func _set_dust_state(new_state: DustState) -> void:
|
|||||||
DustState.NONE:
|
DustState.NONE:
|
||||||
p.emitting = false
|
p.emitting = false
|
||||||
DustState.WALK:
|
DustState.WALK:
|
||||||
|
p.color_ramp = _walk_dust_ramp
|
||||||
p.scale_amount_min = DP.f("walk_scale_min")
|
p.scale_amount_min = DP.f("walk_scale_min")
|
||||||
p.scale_amount_max = DP.f("walk_scale_max")
|
p.scale_amount_max = DP.f("walk_scale_max")
|
||||||
p.initial_velocity_min = DP.f("walk_vel_min")
|
p.initial_velocity_min = DP.f("walk_vel_min")
|
||||||
p.initial_velocity_max = DP.f("walk_vel_max")
|
p.initial_velocity_max = DP.f("walk_vel_max")
|
||||||
p.emitting = true
|
p.emitting = true
|
||||||
DustState.CHARGE:
|
DustState.CHARGE:
|
||||||
|
p.color_ramp = _charge_dust_ramp
|
||||||
p.scale_amount_min = DP.f("charge_scale_min")
|
p.scale_amount_min = DP.f("charge_scale_min")
|
||||||
p.scale_amount_max = DP.f("charge_scale_max")
|
p.scale_amount_max = DP.f("charge_scale_max")
|
||||||
p.initial_velocity_min = DP.f("charge_vel_min")
|
p.initial_velocity_min = DP.f("charge_vel_min")
|
||||||
@@ -237,6 +246,10 @@ func ability_cooldown_fraction(slot: int) -> float:
|
|||||||
return ability_cd[slot] / maxcd if maxcd > 0.0 else 0.0
|
return ability_cd[slot] / maxcd if maxcd > 0.0 else 0.0
|
||||||
|
|
||||||
|
|
||||||
|
func ability_cooldown_remaining(slot: int) -> float:
|
||||||
|
return ability_cd[slot]
|
||||||
|
|
||||||
|
|
||||||
func _activate_kick() -> void:
|
func _activate_kick() -> void:
|
||||||
ability_cd[0] = DP.f("kick_cooldown")
|
ability_cd[0] = DP.f("kick_cooldown")
|
||||||
_ability_active = true
|
_ability_active = true
|
||||||
@@ -261,8 +274,10 @@ func _activate_slam() -> void:
|
|||||||
_slam_pending = true
|
_slam_pending = true
|
||||||
var jump_vel := DP.f("slam_jump_velocity")
|
var jump_vel := DP.f("slam_jump_velocity")
|
||||||
velocity.y = jump_vel
|
velocity.y = jump_vel
|
||||||
var air_time := jump_vel / 9.8 * 2.0
|
# Airtime with an accelerated descent: rise (v/g) + fall (v / (g·√mult)).
|
||||||
_ability_timer = air_time + 0.5
|
var fall_mult := maxf(DP.f("slam_fall_mult"), 0.01)
|
||||||
|
var air_time := (jump_vel / 9.8) * (1.0 + 1.0 / sqrt(fall_mult))
|
||||||
|
_ability_timer = air_time + 0.3
|
||||||
if _bull_anim and _bull_anim.has_animation(&"Armature|SLAM"):
|
if _bull_anim and _bull_anim.has_animation(&"Armature|SLAM"):
|
||||||
var anim_len := _bull_anim.get_animation(&"Armature|SLAM").length
|
var anim_len := _bull_anim.get_animation(&"Armature|SLAM").length
|
||||||
_bull_anim.speed_scale = anim_len / maxf(air_time, 0.01)
|
_bull_anim.speed_scale = anim_len / maxf(air_time, 0.01)
|
||||||
@@ -301,7 +316,7 @@ func _hit_matadors_cone(range_m: float, half_angle_rad: float, strength: float,
|
|||||||
mat.call(&"apply_ability_hit", (forward + Vector3.UP * 0.4).normalized(), strength)
|
mat.call(&"apply_ability_hit", (forward + Vector3.UP * 0.4).normalized(), strength)
|
||||||
|
|
||||||
|
|
||||||
func _hit_matadors_radius(range_m: float, strength: float) -> void:
|
func _hit_matadors_radius(range_m: float, strength: float, up_boost: float = 0.0) -> void:
|
||||||
for mat: Node in get_tree().get_nodes_in_group(&"matador"):
|
for mat: Node in get_tree().get_nodes_in_group(&"matador"):
|
||||||
var to_mat: Vector3 = (mat as Node3D).global_position - global_position
|
var to_mat: Vector3 = (mat as Node3D).global_position - global_position
|
||||||
to_mat.y = 0.0
|
to_mat.y = 0.0
|
||||||
@@ -309,7 +324,7 @@ func _hit_matadors_radius(range_m: float, strength: float) -> void:
|
|||||||
continue
|
continue
|
||||||
var away := to_mat.normalized() if to_mat.length() > 0.01 \
|
var away := to_mat.normalized() if to_mat.length() > 0.01 \
|
||||||
else Vector3(randf() - 0.5, 0.0, randf() - 0.5).normalized()
|
else Vector3(randf() - 0.5, 0.0, randf() - 0.5).normalized()
|
||||||
mat.call(&"apply_ability_hit", (away + Vector3.UP * 0.6).normalized(), strength)
|
mat.call(&"apply_ability_hit", (away + Vector3.UP * 0.6).normalized(), strength, up_boost)
|
||||||
|
|
||||||
|
|
||||||
func _spawn_slam_fx() -> void:
|
func _spawn_slam_fx() -> void:
|
||||||
@@ -424,8 +439,8 @@ func _spawn_kick_dust(origin: Vector3, back_dir: Vector3) -> void:
|
|||||||
sphere.material = mat
|
sphere.material = mat
|
||||||
|
|
||||||
var ramp := Gradient.new()
|
var ramp := Gradient.new()
|
||||||
ramp.set_color(0, Color(0.80, 0.68, 0.42, 0.9))
|
ramp.set_color(0, Color(1.0, 0.78, 0.18, 0.95))
|
||||||
ramp.set_color(1, Color(0.80, 0.68, 0.42, 0.0))
|
ramp.set_color(1, Color(1.0, 0.45, 0.05, 0.0))
|
||||||
|
|
||||||
var scale_curve := Curve.new()
|
var scale_curve := Curve.new()
|
||||||
scale_curve.add_point(Vector2(0.0, 0.3))
|
scale_curve.add_point(Vector2(0.0, 0.3))
|
||||||
@@ -566,7 +581,12 @@ func _apply_kick(direction: Vector3) -> void:
|
|||||||
func _physics_process(delta: float) -> void:
|
func _physics_process(delta: float) -> void:
|
||||||
# ── Gravity ───────────────────────────────────────────────────────────────
|
# ── Gravity ───────────────────────────────────────────────────────────────
|
||||||
if not is_on_floor():
|
if not is_on_floor():
|
||||||
velocity += get_gravity() * delta
|
var gravity := get_gravity()
|
||||||
|
# Crash a slam down hard: full leap on the way up, accelerated descent so
|
||||||
|
# the bull spends less time floating before the impact.
|
||||||
|
if _slam_pending and velocity.y < 0.0:
|
||||||
|
gravity *= DP.f("slam_fall_mult")
|
||||||
|
velocity += gravity * delta
|
||||||
|
|
||||||
# ── Camera-relative input direction ───────────────────────────────────────
|
# ── Camera-relative input direction ───────────────────────────────────────
|
||||||
var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
|
var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
|
||||||
@@ -686,7 +706,7 @@ func _physics_process(delta: float) -> void:
|
|||||||
if _bull_anim:
|
if _bull_anim:
|
||||||
_bull_anim.speed_scale = 1.0
|
_bull_anim.speed_scale = 1.0
|
||||||
_spawn_slam_fx()
|
_spawn_slam_fx()
|
||||||
_hit_matadors_radius(DP.f("slam_range"), DP.f("slam_strength"))
|
_hit_matadors_radius(DP.f("slam_range"), DP.f("slam_strength"), DP.f("slam_launch_up"))
|
||||||
|
|
||||||
# ── Dust ──────────────────────────────────────────────────────────────────
|
# ── Dust ──────────────────────────────────────────────────────────────────
|
||||||
if flat_speed > DP.f("dust_charge_spd") and on_floor:
|
if flat_speed > DP.f("dust_charge_spd") and on_floor:
|
||||||
|
|||||||
Reference in New Issue
Block a user