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"]