Files
Bullosseum/debug_params.gd
T
2026-08-28 21:43:59 +03:00

511 lines
31 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
extends Node
## Central registry for all runtime-tunable parameters.
## Add a new param with one _reg_f() call in _register_all() — the menu picks it up automatically.
## Access: DP.f("key") • Persist: DP.save() / DP.load_saved() • Reset: DP.reset_all()
signal any_changed(key: String, value: Variant)
const SAVE_PATH := "user://debug_params.cfg"
# _params holds the full metadata (min/max/step/default/section/type) and is the
# authority the debug menu reads. _values is a flat key→value mirror so the hot
# f()/b() reads — called dozens of times per matador per physics frame — cost a
# single hash lookup instead of a nested dict access. Every write updates both.
var _params: Dictionary = {}
var _values: Dictionary = {}
func _ready() -> void:
_register_all()
func _register_all() -> void:
# ── Movement ──────────────────────────────────────────────────────────────
# WASD/arrows drive the bull: move_accel ramps flat velocity toward max_speed in
# the input direction; move_friction bleeds it off when no direction is held.
_reg_f("Movement", "max_speed", 10.0, 2.0, 1000.0)
_reg_f("Movement", "move_accel", 120.0, 5.0, 600.0)
_reg_f("Movement", "move_friction", 90.0, 5.0, 600.0)
_reg_f("Movement", "jump_velocity", 4.5, 1.0, 200.0)
_reg_f("Movement", "visual_turn_speed", 12.0, 1.0, 200.0)
_reg_f("Movement", "bounce_restitution", 0.55, 0.0, 2.0, 0.05)
_reg_f("Movement", "bounce_min_impact", 1.0, 0.0, 20.0)
_reg_f("Movement", "wall_bounce_restitution", 0.6, 0.0, 1.5, 0.05)
_reg_f("Movement", "wall_min_bounce_speed", 6.0, 0.0, 30.0)
# ── Bull health ───────────────────────────────────────────────────────────
# The bull soaks bull_max_hp clean hits before it dies; each hit grants
# bull_hit_iframes seconds of invulnerability so a single pass can't drain it.
_reg_f("Bull", "bull_max_hp", 10.0, 1.0, 100.0, 1.0)
_reg_f("Bull", "bull_hit_iframes", 0.8, 0.0, 3.0, 0.1)
# ── Dust speed thresholds ─────────────────────────────────────────────────
_reg_f("Dust", "dust_walk_spd", 1.5, 0.1, 10.0)
_reg_f("Dust", "dust_charge_spd", 7.0, 1.0, 20.0)
# ── Dust ──────────────────────────────────────────────────────────────────
_reg_f("Dust", "dust_lifetime", 0.9, 0.1, 4.0)
_reg_f("Dust", "dust_explosiveness", 0.35, 0.0, 1.0)
_reg_f("Dust", "dust_spread", 45.0, 5.0, 90.0)
_reg_f("Dust", "dust_gravity_y", -1.5,-15.0, 0.0)
_reg_f("Dust", "walk_scale_min", 0.20, 0.05, 3.0)
_reg_f("Dust", "walk_scale_max", 0.40, 0.05, 3.0)
_reg_f("Dust", "walk_vel_min", 0.8, 0.1, 12.0)
_reg_f("Dust", "walk_vel_max", 1.8, 0.1, 12.0)
_reg_f("Dust", "charge_scale_min", 0.35, 0.05, 6.0)
_reg_f("Dust", "charge_scale_max", 0.70, 0.05, 6.0)
_reg_f("Dust", "charge_vel_min", 2.8, 0.1, 20.0)
_reg_f("Dust", "charge_vel_max", 5.5, 0.1, 20.0)
# ── Tail ──────────────────────────────────────────────────────────────────
_reg_f("Tail", "tail_gravity", 9.8, 0.0, 30.0)
_reg_f("Tail", "tail_damping", 0.08, 0.0, 0.5, 0.01)
_reg_f("Tail", "tail_body_radius", 0.35, 0.05, 2.0, 0.05)
_reg_f("Tail", "tail_wag_freq", 4.0, 0.5, 15.0)
_reg_f("Tail", "tail_wag_idle", 0.3, 0.0, 2.0, 0.05)
# ── Charge pose (horns-down lean during dash / roll) ─────────────────────
_reg_f("Charge", "charge_head_pitch", 0.3, 0.0, 1.2, 0.01)
_reg_f("Charge", "charge_head_speed", 5.0, 0.5, 20.0)
# ── Body ──────────────────────────────────────────────────────────────────
_reg_f("Body", "body_tilt_fwd", 0.015, 0.0, 0.1, 0.001)
_reg_f("Body", "body_tilt_side", 0.01, 0.0, 0.1, 0.001)
_reg_f("Body", "aerial_pitch_scale", 0.6, 0.0, 2.0)
_reg_f("Body", "aerial_pitch_speed", 5.0, 0.5, 20.0)
# ── Legs ──────────────────────────────────────────────────────────────────
_reg_f("Legs", "leg_step_threshold", 0.15, 0.01, 1.0, 0.01)
_reg_f("Legs", "leg_step_duration", 0.12, 0.02, 0.5, 0.01)
_reg_f("Legs", "leg_step_height", 0.15, 0.0, 0.5)
_reg_f("Legs", "leg_step_overshoot", 0.3, 0.0, 1.0)
_reg_f("Legs", "gait_freq", 1.5, 0.1, 5.0)
_reg_f("Legs", "launch_hold_time", 0.15, 0.0, 1.0, 0.01)
_reg_f("Legs", "ragdoll_wobble_amp", 0.15, 0.0, 1.0)
_reg_f("Legs", "tuck_apex_threshold", 0.5, -5.0, 5.0)
_reg_f("Legs", "jump_tuck_speed", 8.0, 1.0, 30.0)
_reg_f("Legs", "jump_tuck_up", 0.2, 0.0, 1.0)
_reg_f("Legs", "jump_tuck_fwd", -0.1, -0.5, 0.5)
_reg_f("Legs", "jump_land_threshold", 1.5, 0.5, 5.0)
# ── Camera ────────────────────────────────────────────────────────────────
_reg_f("Camera", "charge_zoom_factor", 0.65, 0.3, 1.0, 0.05)
_reg_f("Camera", "cam_lerp_power", 6.0, 0.5, 30.0)
_reg_f("Camera", "cam_sensitivity", 0.005, 0.001, 0.05, 0.001)
_reg_f("Camera", "cam_min_zoom", 3.5, 0.5, 20.0)
_reg_f("Camera", "cam_max_zoom", 40.0, 2.0, 80.0)
_reg_f("Camera", "cam_spring_length", 7.0, 1.0, 30.0)
_reg_f("Camera", "cam_min_vert_angle", -60.0, -90.0, 0.0, 1.0)
_reg_f("Camera", "cam_max_vert_angle", 45.0, 0.0, 90.0, 1.0)
# Trailing "drag" camera: the focus point lags the bull and slides opposite to
# its velocity, so the camera visibly drags behind during a fast charge.
# cam_drag = metres of trail per m/s of bull speed (0 = rigid follow)
# cam_drag_speed = how fast the trail offset eases toward its target
# cam_follow_lerp = how tightly the focus point chases the bull (higher = snappier)
_reg_f("Camera", "cam_drag", 0.12, 0.0, 1.0, 0.01)
_reg_f("Camera", "cam_drag_speed", 3.5, 0.5, 20.0)
_reg_f("Camera", "cam_follow_lerp", 10.0, 1.0, 40.0)
# ── Matador ───────────────────────────────────────────────────────────────
# Wave system: matadors enter the arena in escalating waves (1 → 2 → … → N where N
# is the number of placed spawn points). mat_spawn_stagger is the delay between each
# matador leaping in within a wave; mat_wave_pause is the breather after a wave clears.
_reg_f("Matador", "mat_wave_size", 8.0, 1, 50)
_reg_f("Matador", "mat_spawn_stagger", 0.4, 0.0, 3.0, 0.05)
_reg_f("Matador", "mat_wave_pause", 2.0, 0.0, 8.0, 0.25)
_reg_f("Matador", "mat_walk_speed", 5.5, 0.5, 10.0)
_reg_f("Matador", "mat_wander_radius", 22.0, 5.0, 50.0)
# Radius of the inner fighting floor: bull + matadors spawn on this ring (so
# they stay off the benches). Tune in the console to hug the bench edge.
_reg_f("Matador", "arena_spawn_radius", 10.0, 2.0, 22.0, 0.5)
_reg_f("Matador", "mat_idle_min", 1.2, 0.0, 5.0, 0.1)
_reg_f("Matador", "mat_idle_max", 4.0, 0.5, 10.0, 0.1)
_reg_f("Matador", "mat_hit_threshold", 4.0, 1.0, 30.0)
_reg_f("Matador", "mat_ragdoll_impulse", 6.0, 0.5, 20.0)
# Ragdoll joint limits — lower swing/twist = stiffer corpse that keeps its
# shape; higher angular damp settles the flail instead of flopping flat.
_reg_f("Matador", "mat_joint_swing", 35.0, 0.0, 180.0, 1.0)
_reg_f("Matador", "mat_joint_twist", 40.0, 0.0, 180.0, 1.0)
_reg_f("Matador", "mat_ragdoll_damp", 1.5, 0.0, 10.0, 0.1)
_reg_f("Matador", "mat_flee_range", 3.5, 2.0, 40.0)
_reg_f("Matador", "mat_flee_speed", 7.0, 0.5, 12.0)
_reg_f("Matador", "mat_charge_speed", 10.0, 1.0, 30.0)
_reg_f("Matador", "mat_brace_range", 5.0, 2.0, 20.0)
_reg_f("Matador", "mat_brace_duration", 0.7, 0.05, 1.5, 0.05)
_reg_f("Matador", "mat_commit_dist", 2.5, 0.5, 6.0, 0.1)
_reg_f("Matador", "mat_step_speed", 6.5, 2.0, 20.0)
_reg_f("Matador", "mat_step_duration", 0.35, 0.1, 1.0, 0.05)
# How strongly the sidestep biases toward the bull so the blade sweeps through
# its path during the pass (0 = pure lateral dodge, 1 = straight at the bull).
_reg_f("Matador", "mat_pass_lunge", 0.35, 0.0, 1.0, 0.05)
_reg_f("Matador", "mat_dodge_cooldown", 2.5, 0.5, 12.0, 0.25)
_reg_f("Matador", "mat_dodge_chance", 0.72, 0.0, 1.0, 0.05)
_reg_f("Matador", "mat_attack_range", 8.0, 2.0, 20.0)
_reg_f("Matador", "mat_attack_speed", 7.5, 1.0, 12.0)
_reg_f("Matador", "mat_attack_duration", 1.8, 0.5, 5.0, 0.1)
_reg_f("Matador", "mat_attack_min_dist", 4.2, 0.5, 6.0, 0.1)
# Forward drive during the opening of a melee swing — turns the stationary stab
# into an estocada lunge so the blade covers ground toward the bull.
_reg_f("Matador", "mat_lunge_speed", 13.0, 0.0, 20.0)
_reg_f("Matador", "mat_lunge_time", 0.22, 0.0, 0.6, 0.01)
# Reach of the reach-based stab (the reliable melee hit) and its re-strike gap.
# The bull dies if it comes inside mat_stab_reach of a matador that's attacking,
# bracing, or passing with a drawn blade — closing in is genuinely deadly.
_reg_f("Matador", "mat_stab_reach", 1.7, 0.5, 4.0, 0.1)
_reg_f("Matador", "mat_stab_cooldown", 0.6, 0.1, 3.0, 0.1)
# Half-angle (deg) the matador must be facing the bull within for a stab to land.
# Stops "sword's off to the side but I still died" — you're only gored when the
# blade is actually presented at you.
_reg_f("Matador", "mat_stab_arc", 55.0, 5.0, 180.0, 5.0)
_reg_f("Matador", "mat_roll_chance", 0.8, 0.0, 1.0, 0.05)
_reg_f("Matador", "mat_roll_duration", 0.45, 0.1, 1.5, 0.05)
# Playback rate of the Draw_weapon clip used for both drawing and sheathing —
# higher = snappier unholster/holster.
_reg_f("Matador", "mat_draw_speed", 1.9, 0.25, 3.0, 0.05)
# 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)
# ── Matador heuristics (utility AI) ───────────────────────────────────────
# Every mat_decide_interval seconds the matador scores its options — dodge the
# pass, close for a strike, retreat, throw the blade, or strut/taunt — and picks
# the highest. These weights bias the personality: crank aggression for a reckless
# bullfighter, caution for a slippery one. mat_w_commit is a stickiness bonus for
# the current choice so it doesn't dither between two near-tied options.
_reg_f("Matador", "mat_decide_interval", 0.2, 0.05, 1.0, 0.05)
_reg_f("Matador", "mat_w_aggression", 1.1, 0.0, 3.0, 0.05)
_reg_f("Matador", "mat_w_caution", 1.0, 0.0, 3.0, 0.05)
_reg_f("Matador", "mat_w_flee", 0.8, 0.0, 3.0, 0.05)
# Showmanship (strut, circle the bull, taunt) — the supporting matadors lean on
# this to spread out and give the lead room, so a whole crowd reads as a bullring,
# not a swarm. Crank it for more posturing, drop it for a relentless mob.
_reg_f("Matador", "mat_w_showmanship", 1.6, 0.0, 3.0, 0.05)
# Weight on hurling the blade. Kept low — a thrown sword is an occasional flourish,
# not a barrage; raising this brings back the pelting.
_reg_f("Matador", "mat_w_throw", 0.35, 0.0, 3.0, 0.05)
# How many matadors press the bull at once. The nearest this-many engage (close,
# throw); everyone else keeps their distance and taunts, taking turns as the bull
# moves. 1 = strict one-on-one, higher = a bolder pack.
_reg_f("Matador", "mat_engage_slots", 2.0, 1.0, 8.0, 1.0)
# Preferred distance (m) a taunting matador circles the bull at while waiting its
# turn — the showmanship ring the crowd spreads out onto.
_reg_f("Matador", "mat_taunt_ring", 7.0, 3.0, 15.0, 0.5)
# How long (s) a matador safely out of the action holds a taunt before drifting to
# a new spot — much longer than the between-passes idle, so bystanders mostly pose
# for the crowd instead of milling about. Actual hold is randomised ±30%.
_reg_f("Matador", "mat_taunt_hold", 8.0, 1.0, 20.0, 0.5)
# Small so it smooths dithering without locking the matador into idling.
_reg_f("Matador", "mat_w_commit", 0.15, 0.0, 2.0, 0.05)
# ── Bear (boss) ─────────────────────────────────────────────────────────────
# A Dark-Souls-style boss with a learnable moveset. It soaks bear_max_hp clean hits
# (ram or ability), staggering on each, and only topples when worn down. From NEUTRAL
# it weighs its options by the range band to the bull and commits to one telegraphed
# routine (wind-up → active → recovery); each routine plays to the end (the recovery
# is the player's punish window). Below bear_enrage_frac HP it enters phase 2: faster
# tells and recovery (× bear_enrage_scale) and longer combo chains.
_reg_f("Bear", "bear_max_hp", 30.0, 1.0, 100.0, 1.0)
# HP fraction at which phase 2 ("Fury") triggers, and how much it multiplies every
# wind-up / recovery timer (lower = snappier, more dangerous).
_reg_f("Bear", "bear_enrage_frac", 0.4, 0.0, 1.0, 0.05)
_reg_f("Bear", "bear_enrage_scale", 0.7, 0.3, 1.0, 0.05)
# Range bands (m) that pick the moveset: ≤close it strikes (swipe / smash), ≤mid it
# walks in to close the gap (stalk) or leaps, beyond it lopes in (prowl) or leaps.
# close is deliberately generous — the bear commits to melee "from a bit further" and
# steps in during the swing (see bear_lunge_speed) rather than needing to be on top.
_reg_f("Bear", "bear_close_range", 5.0, 1.0, 12.0, 0.1)
_reg_f("Bear", "bear_mid_range", 10.0, 3.0, 20.0, 0.5)
# Beat spent in NEUTRAL sizing up the bull between routines — kept short so the bear
# flows straight from one action into the next instead of pausing (raise for a more
# hesitant, readable boss; near-0 for relentless pressure). Shrinks again in phase 2.
_reg_f("Bear", "bear_think_time", 0.06, 0.0, 1.0, 0.01)
# Locomotion: the all-fours prowl (close a big gap), the menacing 2-leg walk-in
# (stalk), the facing rate while positioning/winding up, and the softer tracking rate
# a swing keeps on the bull mid-attack so it's harder to just walk out of.
_reg_f("Bear", "bear_run_speed", 11.0, 1.0, 20.0, 0.1)
_reg_f("Bear", "bear_stalk_speed", 5.5, 0.5, 12.0, 0.1)
_reg_f("Bear", "bear_turn_rate", 7.0, 1.0, 20.0, 0.5)
_reg_f("Bear", "bear_track_active", 3.5, 0.0, 12.0, 0.1)
# Forward step-in during a swing so a strike lands even when the bull is a touch out of
# reach (also carries the tracking); 0 = a purely stationary swing.
_reg_f("Bear", "bear_lunge_speed", 7.0, 0.0, 16.0, 0.5)
# Playback multiplier for the swipe/smash clips — >1 makes the punches snappier.
_reg_f("Bear", "bear_attack_anim_speed", 1.35, 0.5, 3.0, 0.05)
# Half-arc (deg) the bear must be facing the bull within for a directional swing to
# connect; slams use their own radius instead of an arc.
_reg_f("Bear", "bear_hit_arc", 70.0, 5.0, 180.0, 5.0)
# Double Swipe (ONE_TWO_HIT) — bread-and-butter close combo, two quick hits.
_reg_f("Bear", "bear_swipe_windup", 0.3, 0.05, 2.0, 0.05)
_reg_f("Bear", "bear_swipe_recover", 0.35, 0.05, 3.0, 0.05)
_reg_f("Bear", "bear_swipe_reach", 4.2, 1.0, 8.0, 0.1)
# Overhead Smash (SMASH_HIT) — slower tell, biggest punish window, AoE on impact.
_reg_f("Bear", "bear_smash_windup", 0.6, 0.1, 3.0, 0.05)
_reg_f("Bear", "bear_smash_recover", 0.75, 0.1, 4.0, 0.05)
_reg_f("Bear", "bear_smash_radius", 4.2, 1.0, 8.0, 0.1)
# Leap Slam (JUMP_SMASH, 47 frames @30fps) — timed to the clip by FRAME: it plays the
# crouch grounded, springs at the takeoff frame, and the arc is solved so the paws hit
# ground exactly on the impact frame (25), leaving the tail frames to settle on the
# ground — no finishing the anim in mid-air. The arc uses its own gravity so peak height
# and airtime are independent: bear_leap_height is the peak reached in that fixed airtime.
_reg_f("Bear", "bear_leap_takeoff_frame", 6.0, 0.0, 46.0, 1.0)
_reg_f("Bear", "bear_leap_impact_frame", 25.0, 1.0, 47.0, 1.0)
_reg_f("Bear", "bear_leap_height", 2.6, 0.5, 8.0, 0.1)
_reg_f("Bear", "bear_leap_recover", 0.35, 0.0, 4.0, 0.05)
_reg_f("Bear", "bear_leap_speed", 14.0, 4.0, 30.0, 0.5)
_reg_f("Bear", "bear_slam_radius", 4.0, 1.0, 8.0, 0.1)
# Stalk (WALK_ON2LEGS) — how long it will keep walking the bull down before re-deciding.
_reg_f("Bear", "bear_stalk_time", 2.5, 0.0, 6.0, 0.1)
# Odds a close combo chains a follow-up smash after the swipe (phase 2 always chains).
_reg_f("Bear", "bear_combo_chance", 0.7, 0.0, 1.0, 0.05)
# Minimum bull speed (ram) that counts as a hit on the bear — a gentle nudge is
# shrugged off, only a real charge or an ability wounds it. (The bear itself never
# damages the bull by colliding — only its swings and slams hurt.)
_reg_f("Bear", "bear_hit_threshold", 6.0, 1.0, 30.0, 0.5)
# How long (s) the bear is rocked back and can't act after taking a hit.
_reg_f("Bear", "bear_stagger_time", 0.5, 0.0, 2.0, 0.05)
# Poise: after a stagger the bear shrugs off staggers for this long (hits still wound
# it), so it can't be perma-stunlocked — it gets a window to commit an attack that then
# rides its hyper-armour. 0 = no poise (stun-locks under sustained fire).
_reg_f("Bear", "bear_stagger_cd", 1.0, 0.0, 4.0, 0.05)
# ── Sword ─────────────────────────────────────────────────────────────────
# Local seating of the sword in the right hand (drawn / fighting).
# The blade model runs along its local +Z, but weapon_bone (like every rig bone)
# points down its local +Y — so identity leaves the blade jutting out sideways.
# rot_x = -90 maps the blade's +Z onto the bone's +Y so it thrusts straight along
# the bone; rot_y is then the free roll (crossguard tilt) about the blade axis.
_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_z", 0.0, -1.0, 1.0, 0.01)
_reg_f("Sword", "sword_rot_x", -90.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)
# 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. weapon_rest_bone sits on the left hip; these angle the blade down and
# out along the OUTSIDE of the left thigh (rot_x/rot_y aim the blade, rot_z rolls
# it about its own axis) so it trails back beside the leg instead of clipping it.
_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", -110.0, -180.0, 180.0, 1.0)
_reg_f("Sword", "sword_rest_rot_y", -133.0, -180.0, 180.0, 1.0)
_reg_f("Sword", "sword_rest_rot_z", 0.0, -180.0, 180.0, 1.0)
# ── Spear throw (matador ranged attack) ──────────────────────────────────
# Local seating of the spear in the throwing hand during the wind-up. Tune to
# seat the shaft in the grip without re-exporting; flight orientation is derived
# from the aim direction at release, not from this pose.
_reg_f("Sword", "spear_pos_x", 0.0, -1.0, 1.0, 0.01)
_reg_f("Sword", "spear_pos_y", 0.0, -1.0, 1.0, 0.01)
_reg_f("Sword", "spear_pos_z", 0.0, -1.0, 1.0, 0.01)
_reg_f("Sword", "spear_rot_x", 0.0, -180.0, 180.0, 1.0)
_reg_f("Sword", "spear_rot_y", 0.0, -180.0, 180.0, 1.0)
_reg_f("Sword", "spear_rot_z", 0.0, -180.0, 180.0, 1.0)
# Delay before a fresh sword reappears in the holster if one is ever lost.
_reg_f("Sword", "mat_resword_delay", 2.0, 0.0, 10.0, 0.5)
_reg_f("Sword", "mat_throw_range", 18.0, 5.0, 40.0)
_reg_f("Sword", "mat_throw_min_dist", 5.0, 2.0, 15.0, 0.1)
_reg_f("Sword", "mat_throw_windup", 0.5, 0.2, 2.0, 0.05)
_reg_f("Sword", "mat_throw_release", 0.55, 0.1, 0.95, 0.01)
# Horizontal speed of the thrown spear; the vertical launch is solved from this
# and the gravity below so the arc lands on the bull. Lower = a higher, slower lob.
_reg_f("Sword", "mat_throw_speed", 11.0, 5.0, 60.0)
# Gravity scale on the spear (higher = a more pronounced arc) and the vertical aim
# offset from the bull origin down onto its low collision body.
_reg_f("Sword", "mat_throw_gravity", 1.0, 0.1, 3.0, 0.05)
_reg_f("Sword", "mat_throw_aim_y", -0.45,-1.5, 0.5, 0.05)
# Aim scatter (± degrees) so thrown blades can be read and dodged on the move.
_reg_f("Sword", "mat_throw_spread", 11.0, 0.0, 30.0, 0.5)
_reg_f("Sword", "mat_throw_arm_cock", 70.0,-180.0, 180.0, 1.0)
_reg_f("Sword", "mat_throw_arm_release", -90.0,-180.0, 180.0, 1.0)
_reg_f("Sword", "mat_throw_elbow", 80.0,-180.0, 180.0, 1.0)
# ── Abilities ─────────────────────────────────────────────────────────────
_reg_f("Abilities", "kick_cooldown", 2, 0.2, 600, 0.1)
_reg_f("Abilities", "kick_range", 6.0, 0.5, 8.0, 0.1)
_reg_f("Abilities", "kick_strength", 12.0, 1.0, 30.0)
_reg_f("Abilities", "slam_cooldown", 12, 0.5, 600, 0.1)
_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_strength", 14.0, 1.0, 30.0)
_reg_f("Abilities", "dash_cooldown", 3, 0.1, 600.0, 0.1)
_reg_f("Abilities", "dash_speed", 66.0, 5.0, 200.0)
# Exponential rate the dash bleeds from dash_speed back to cruise (max_speed).
# Higher = a snappier bullet that dies off quicker; lower = a longer glide.
_reg_f("Abilities", "dash_decel", 7.0, 0.5, 30.0, 0.5)
# ── Roll (homing chain ball) ──────────────────────────────────────────────
# The bull curls into a ball and ACCELERATES the longer it rolls, from
# roll_base_speed up to roll_max_speed over roll_rampup_time. It HOMES on the
# closest matador; on slamming one the ball POPS it (launched skyward via
# roll_knockup) then CHAINS to the next closest, up to roll_chain_count times.
# roll_turn is the homing turn rate. When no chain targets remain, the roll ends.
_reg_f("Roll", "roll_cooldown", 20, 0.5, 600, 0.1)
_reg_f("Roll", "roll_duration", 2.5, 0.5, 6.0, 0.1) # max roll time
_reg_f("Roll", "roll_chain_count", 2.0, 0.0, 8.0, 1.0) # extra matadors chained after the first
_reg_f("Roll", "roll_base_speed", 28.0, 5.0, 120.0) # speed at the start of the roll
_reg_f("Roll", "roll_max_speed", 95.0, 10.0, 200.0) # top speed once fully ramped
_reg_f("Roll", "roll_rampup_time", 2.0, 0.2, 6.0, 0.1) # time to reach top speed
_reg_f("Roll", "roll_turn", 7.0, 0.2, 20.0) # homing turn rate (higher = tighter tracking)
_reg_f("Roll", "roll_spin_radius", 1.4, 0.3, 4.0, 0.1) # ball radius for tumble spin (smaller = faster)
_reg_f("Roll", "roll_hit_radius", 2.4, 0.5, 6.0, 0.1) # contact radius that pops the ball
_reg_f("Roll", "roll_hit_strength", 22.0, 1.0, 40.0) # horizontal launch on the pop
_reg_f("Roll", "roll_knockup", 11.0, 0.0, 25.0) # vertical knock-up on the pop
# ── Debug overlays (see debug_draw.gd) ───────────────────────────────────
_reg_b("Debug", "show_collisions", false)
# PS1 post-process filter (see ps1_filter.gd on the fullscreen ColorRect):
# screen-covering CRT/PS1 shader — pixelation, colour-banding + dither, and a
# rolling scanline. Keys map 1:1 onto the shader uniforms; show_ps1 toggles the
# whole overlay. Ranges mirror the shader's hint_range so the console can't push
# a uniform out of bounds.
_reg_b("PS1", "show_ps1", true)
_reg_f("PS1", "ps1_pixel_scale", 4.0, 1.0, 8.0, 1.0)
_reg_f("PS1", "ps1_color_depth", 4.5, 2.0, 8.0, 1.0)
_reg_f("PS1", "ps1_dither_strength", 1, 0.0, 2.0, 0.05)
_reg_f("PS1", "ps1_scanline_strength", 0.2, 0.0, 1.0, 0.05)
_reg_f("PS1", "ps1_roll_speed", 3.0, 0.0, 10.0, 0.1)
_reg_f("PS1", "ps1_roll_strength", 1.0, 0.0, 1.0, 0.05)
_reg_b("Debug", "show_hitboxes", false)
_reg_b("Debug", "show_bones", false)
_reg_b("Debug", "show_raycasts", false)
_reg_b("Debug", "show_tail", false)
_reg_b("Debug", "show_stats", false)
_reg_b("Debug", "show_states", false)
_reg_b("Debug", "show_animation_name", false)
_reg_b("Debug", "show_wireframe", false)
_reg_b("Debug", "show_grid", false)
_reg_b("Debug", "show_axes", false)
# Force the on-screen touch controls (joystick + ability buttons) on even on a
# desktop build — otherwise they appear only when a touchscreen is detected.
_reg_b("Touch", "force_touch_controls", false)
# On-screen readout of live touch state (event count, last position, active zone) so
# touch problems can be diagnosed on the actual device, where no console is reachable.
_reg_b("Touch", "touch_debug", false)
func _reg_f(section: String, key: String, default: float,
min_val: float, max_val: float, step: float = 0.05) -> void:
_params[key] = {
"value": default,
"default": default,
"section": section,
"type": TYPE_FLOAT,
"min": min_val,
"max": max_val,
"step": step,
}
_values[key] = default
func _reg_b(section: String, key: String, default: bool) -> void:
_params[key] = {
"value": default,
"default": default,
"section": section,
"type": TYPE_BOOL,
}
_values[key] = default
## Read a float param. Panics on unknown key — intentional: typos should be loud.
func f(key: String) -> float:
return _values[key] as float
## Read a bool param.
func b(key: String) -> bool:
return _values[key] as bool
func set_value(key: String, value: Variant) -> void:
if not _params.has(key):
return
_params[key]["value"] = value
_values[key] = value
any_changed.emit(key, value)
## Returns the full metadata dict — used by Console to build UI.
func get_all() -> Dictionary:
return _params
func save() -> void:
var path := "res://debug_params.gd"
var file := FileAccess.open(path, FileAccess.READ)
if file == null:
push_error("DP.save: cannot open " + path)
return
var src := file.get_as_text()
file.close()
for key: String in _params:
var p: Dictionary = _params[key]
if p["value"] == p["default"]:
continue
var re := RegEx.new()
var literal := ""
if p["type"] == TYPE_FLOAT:
re.compile('(_reg_f\\("[^"]+",\\s*"%s",\\s*)([-\\d.e]+)' % key)
literal = _fmt_float(p["value"] as float)
else:
re.compile('(_reg_b\\("[^"]+",\\s*"%s",\\s*)(true|false)' % key)
literal = "true" if bool(p["value"]) else "false"
var m := re.search(src)
if m == null:
push_warning("DP.save: no _reg_ line found for '%s' — skipped" % key)
continue
# Splice the value in by index rather than RegEx.sub(): sub()'s replacement
# string parses "$n" as a backreference, so "$1"+"12.5" becomes "$112.5"
# (group 112 → PCRE2 error → mangled output). Indexed splice inserts the
# literal verbatim, exactly the value the user entered.
src = src.substr(0, m.get_start(2)) + literal + src.substr(m.get_end(2))
p["default"] = p["value"]
var wfile := FileAccess.open(path, FileAccess.WRITE)
if wfile == null:
push_error("DP.save: cannot write " + path)
return
wfile.store_string(src)
wfile.close()
# Godot's String % operator has no "g" conversion — "%.6g" % v slips past the
# compiler (v is not const) but emits the literal "%.6g" at runtime, which would be
# written straight into the source. "%s" gives the shortest round-tripping form,
# matching the value the user entered; the guard keeps it a float literal.
func _fmt_float(v: float) -> String:
var s := "%s" % v
if not ("." in s or "e" in s):
s += ".0"
return s
func load_saved() -> void:
var cfg := ConfigFile.new()
if cfg.load(SAVE_PATH) != OK:
return
for section: String in cfg.get_sections():
for key: String in cfg.get_section_keys(section):
if not _params.has(key):
continue
var p: Dictionary = _params[key]
var val: Variant = cfg.get_value(section, key)
if p["type"] == TYPE_FLOAT:
val = clampf(val as float, p["min"] as float, p["max"] as float)
_params[key]["value"] = val
_values[key] = val
func reset_all() -> void:
for key: String in _params:
var default: Variant = _params[key]["default"]
_params[key]["value"] = default
_values[key] = default
any_changed.emit("__all__", null)
## Reset a single param to its default. No-op on unknown key.
func reset(key: String) -> void:
if not _params.has(key):
return
var default: Variant = _params[key]["default"]
_params[key]["value"] = default
_values[key] = default
any_changed.emit(key, default)
## True when the param currently differs from its registered default.
func is_modified(key: String) -> bool:
return _params.has(key) and _params[key]["value"] != _params[key]["default"]