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: #Level Unlocking _reg_b("Gate", "gate_unlocked_bear", false) # ── 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) # Half-angle (deg) of the bull's horns-first cone: running into an enemy only wounds it when the # bull's velocity points within this of the enemy (not a sideways brush). 90 = any contact at speed. # (The dash ability bypasses this.) _reg_f("Bull", "bull_gore_arc", 75.0, 10.0, 180.0, 5.0) # ── 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) # Planar reach at which the bull's swept charge path gores a matador (~bull + HitArea radii). # Raise for a more forgiving charge, lower for a more precise one. _reg_f("Matador", "mat_charge_gore_radius", 1.2, 0.5, 3.0, 0.05) _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) # Reach of the reach-based melee hit and its re-strike gap. This now governs ONLY the sidestep # PASS (mat charges the bull past with the blade out): a fast lateral sweep where the thin blade # Area would tunnel, so a center-to-center distance check is the reliable gore. The stationary # ATTACK stab no longer uses this — it drives off the sword's BladeHitbox collider (see below). _reg_f("Matador", "mat_stab_reach", 2.0, 0.5, 4.0, 0.1) # Distance the matador plants at to swing. The ATTACK stab damages off the animated BladeHitbox, # whose thrust reaches ~3.75 m at full extension — so this MUST stay under that or the blade never # touches the bull and the stab whiffs. 3.5 m keeps the matador clear of the bull's ~1.8 m-long # body while the thrust still lands. Raise toward 3.75 for more gap, lower to stab from closer. _reg_f("Matador", "mat_stab_standoff", 3.5, 0.5, 4.0, 0.1) # Fraction through the Attack clip at which the blade's collider is armed. The clip is guard → # wind-up (blade pulled back, phase ~0.3-0.5) → thrust (blade drives out to ~3.5 m, phase ~0.55+). # Arming at the thrust keeps the bull from being gored during the wind-up (blade not yet extended) # or the guard pose. The BladeHitbox overlap still decides the exact hit; this just gates timing. _reg_f("Matador", "mat_stab_strike_at", 0.5, 0.0, 1.0, 0.05) # Chance, each time a stab finishes, that the matador breaks off into something else (give ground, # circle, or throw) instead of immediately stabbing again — keeps it from drilling the same thrust # on repeat. 0 = always re-stab if in range, 1 = always break off after every stab. _reg_f("Matador", "mat_stab_followup_chance", 0.6, 0.0, 1.0, 0.05) # Beat after every stab where the brain won't re-commit to ATTACK, so it can't machine-gun the # bull — it circles/gives ground instead. 0 = stab as fast as range allows. _reg_f("Matador", "mat_stab_recovery", 0.9, 0.0, 3.0, 0.1) # Chance, when a stab actually CONNECTS, that the matador breaks into a crowd-pleasing taunt and # gives ground instead of pressing — milking the hit rather than spamming. High by design. _reg_f("Matador", "mat_taunt_after_hit", 0.85, 0.0, 1.0, 0.05) _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) # Chance a matador rolls clear (survives) instead of ragdolling when the bull's charge connects. _reg_f("Matador", "mat_roll_chance", 0.8, 0.0, 1.0, 0.05) # How much a committed charge pierces the survival roll: roll chance fades toward # mat_roll_chance*(1-this) as bull speed climbs to mat_charge_speed. 1 = full-speed charge always # gores. (A dash connect ignores the roll entirely.) _reg_f("Matador", "mat_charge_pierce", 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) _reg_f("Bear", "bear_long_range", 15.0, 3.0, 100.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) # Which claw-slash FX plays on the first vs second swipe hit — the L/R clips are easy to # mix up, so 0 = first hit L then R, 1 = swap them. Purely cosmetic (no gameplay effect). _reg_f("Bear", "bear_fx_claw_swap", 0.0, 0.0, 1.0, 1.0) # Live yaw (deg) applied to the whole Bear_FX node before each effect fires, so the claw # rake / debris can be spun to line up with the bear's facing by eye. Bake the good value. _reg_f("Bear", "bear_fx_yaw", 0.0, -180.0, 180.0, 5.0) # The baked crater clip only pops up (no shrink), so hold it on the ground this long after a # leap slam before hiding it — otherwise it flashes for a single frame and reads as invisible. _reg_f("Bear", "bear_fx_crater_linger", 0.8, 0.0, 3.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) # The overhead smash's hitbox is a forward box lane whose size + travel are authored in # BearAttackHitboxes.tscn's AnimationPlayer ("smash_travel"). Here we only set the upward # pop the bull takes off its feet at the far end (it ramps 0→this with hit distance). _reg_f("Bear", "bear_smash_knockup", 6.0, 0.0, 16.0, 0.5) # 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) # Where the leap-slam launch zone starts, as a fraction of the circle radius. 0 = launch anywhere # inside; raise to carve a grounded inner zone that only mauls (0.75 = only the outer quarter ring). _reg_f("Bear", "bear_leap_ring_frac", 0.0, 0.0, 1.0, 0.05) _reg_f("Bear", "bear_leap_knockup", 7.0, 0.0, 16.0, 0.5) # 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) # Planar reach at which a horns-first bull charge wounds the bear (~bear body radius + bull reach). # The bull's swept path is tested against this, so a tunnelling or point-blank charge still lands. _reg_f("Bear", "bear_ram_reach", 2.1, 0.5, 5.0, 0.1) # 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) ─────────────────────────────────── # 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_attacks", 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"]