From b809fc070176cfc196db22e07cf8104636749b3a Mon Sep 17 00:00:00 2001 From: chazmm Date: Tue, 8 Sep 2026 18:46:21 +0100 Subject: [PATCH] Replace examples with native trainer and expose enabled settings beside pause menu --- Build-NativeTrainer.ps1 | 44 +++ PLAY-NATIVE-TRAINER.bat | 23 ++ crates/skate-core/src/riding/anti_flip.rs | 1 + crates/skate-core/src/riding/ground_force.rs | 1 + crates/skate-core/src/riding/heading.rs | 1 + .../skate-core/src/riding/slide_friction.rs | 1 + crates/skate-core/src/riding/speed_model.rs | 1 + crates/skate-core/src/riding/straighten.rs | 1 + crates/skate-game/src/graphics_menu.rs | 6 + crates/skate-game/src/modding/menu.rs | 63 ++-- crates/skate-game/src/modding/mod.rs | 25 ++ crates/skate-game/src/modding/panel.rs | 271 ++++++++++++++++++ crates/skate-game/src/physics.rs | 2 + crates/skate-game/src/physics/frame.rs | 3 + crates/skate-game/src/physics/grind.rs | 2 +- .../src/physics/ground_animation/board.rs | 5 +- .../src/physics/ground_runtime/input.rs | 2 +- .../src/physics/ground_runtime/settings.rs | 14 + crates/skate-mods/src/api.lua | 3 + crates/skate-mods/src/schema.rs | 42 +++ crates/skate-mods/src/vm.rs | 4 + crates/skate-mods/tests/runtime.rs | 28 +- docs/lua-modding.md | 30 +- mods/native-trainer/main.lua | 10 + mods/native-trainer/mod.json | 74 +++++ mods/route-trainer/main.lua | 45 --- mods/route-trainer/mod.json | 11 - mods/speedometer/main.lua | 16 -- mods/speedometer/mod.json | 14 - sdk/skate.lua | 12 + 30 files changed, 626 insertions(+), 129 deletions(-) create mode 100644 Build-NativeTrainer.ps1 create mode 100644 PLAY-NATIVE-TRAINER.bat create mode 100644 crates/skate-game/src/modding/panel.rs create mode 100644 mods/native-trainer/main.lua create mode 100644 mods/native-trainer/mod.json delete mode 100644 mods/route-trainer/main.lua delete mode 100644 mods/route-trainer/mod.json delete mode 100644 mods/speedometer/main.lua delete mode 100644 mods/speedometer/mod.json diff --git a/Build-NativeTrainer.ps1 b/Build-NativeTrainer.ps1 new file mode 100644 index 00000000..201caf7a --- /dev/null +++ b/Build-NativeTrainer.ps1 @@ -0,0 +1,44 @@ +param([switch]$StageOnly, [string]$DependencyTargetDirectory = (Join-Path $PSScriptRoot 'target')) +$ErrorActionPreference = 'Stop' +$privateDirectory = Join-Path $PSScriptRoot '.local/native-trainer' +$buildDirectory = Join-Path $privateDirectory 'build' +$artifact = Join-Path $buildDirectory 'skate3rust-native-trainer.exe' +$destination = Join-Path $privateDirectory 'skate3-native-trainer.exe' +Push-Location $PSScriptRoot +$previousFlags = $env:CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS +try { + New-Item -ItemType Directory -Force -Path $buildDirectory | Out-Null + if (-not $StageOnly) { + $env:CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS = '-C target-feature=+crt-static' + # Dependencies may use a shared cache. The linker writes this task's + # artifact directly, never staging the cache's generic skate3rust.exe. + # Shared target fingerprints can refer to another worktree's local crates. + # Refresh only our local source timestamps; never clean the shared cache. + foreach ($crate in @('skate-core', 'skate-data', 'skate-mods')) { + (Get-Item -LiteralPath (Join-Path $PSScriptRoot "crates/$crate/src/lib.rs")).LastWriteTime = Get-Date + } + & cargo rustc --release --locked --target x86_64-pc-windows-msvc ` + --target-dir $DependencyTargetDirectory -p skate-game --no-default-features ` + --bin skate3rust -- -C extra-filename=-native-trainer ` + -o (Join-Path $buildDirectory 'skate3rust.exe') + if ($LASTEXITCODE -ne 0) { throw 'Lua SDK build failed.' } + } + $bytes = [System.IO.File]::ReadAllBytes($artifact) + $text = [System.Text.Encoding]::ASCII.GetString($bytes) + foreach ($required in @('--start-paused', 'ENABLED MOD SETTINGS', 'Native trainer controls are already owned by another mod')) { + if (-not $text.Contains($required)) { throw "Wrong native-trainer artifact: missing $required" } + } + Copy-Item -LiteralPath $artifact -Destination $destination -Force + $hash = (Get-FileHash -LiteralPath $destination -Algorithm SHA256).Hash + if ($hash -ne (Get-FileHash -LiteralPath $artifact -Algorithm SHA256).Hash) { + throw 'Staged executable hash mismatch.' + } + @{ executable = $destination; sha256 = $hash; source = $PSScriptRoot; + revision = (& git rev-parse HEAD); staticChecks = 'CLI, Mods UI, Lua quota diagnostics' } | + ConvertTo-Json | Set-Content -LiteralPath (Join-Path $privateDirectory 'build-manifest.json') -Encoding UTF8 + Write-Host "Ready for manual launch: $destination" + Write-Host "SHA256: $hash" +} finally { + $env:CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS = $previousFlags + Pop-Location +} diff --git a/PLAY-NATIVE-TRAINER.bat b/PLAY-NATIVE-TRAINER.bat new file mode 100644 index 00000000..5fd3589d --- /dev/null +++ b/PLAY-NATIVE-TRAINER.bat @@ -0,0 +1,23 @@ +@echo off +setlocal +cd /d "%~dp0" +if not defined SKATE3_ASSETS ( + if exist "%~dp0.local\native-trainer\assets.path" set /p "SKATE3_ASSETS="<"%~dp0.local\native-trainer\assets.path" +) +if not defined SKATE3_ASSETS ( + echo Set SKATE3_ASSETS to your prepared assets directory. + pause + exit /b 1 +) +if not exist "%~dp0.local\native-trainer\skate3-native-trainer.exe" ( + echo Build missing. Run Build-NativeTrainer.ps1 first. + pause + exit /b 1 +) +set "SKATE3_MODS=%~dp0mods" +set "SKATE3_MOD_SETTINGS=%~dp0.local\native-trainer\settings" +if exist "%~dp0.local\native-trainer\overlay.path" set /p "SKATE3_SESSION_MARKER_OVERLAY="<"%~dp0.local\native-trainer\overlay.path" +echo Open Mods and ENABLE Native Trainer. Escape shows its settings on the left. +echo Trainer log: %~dp0.local\native-trainer\session.log +"%~dp0.local\native-trainer\skate3-native-trainer.exe" --assets "%SKATE3_ASSETS%" --start-paused %* > "%~dp0.local\native-trainer\session.log" 2>&1 +if errorlevel 1 pause diff --git a/crates/skate-core/src/riding/anti_flip.rs b/crates/skate-core/src/riding/anti_flip.rs index 708124a4..5a422dfc 100644 --- a/crates/skate-core/src/riding/anti_flip.rs +++ b/crates/skate-core/src/riding/anti_flip.rs @@ -1,6 +1,7 @@ //! Complete TU3 Toolkit_AntiFlipTorque 82D94190, distinct from heading/manual. use super::vector::{cross, dot3, normalize}; use crate::{point_graph::PointGraph, trigonometry::acos}; +#[derive(Clone)] pub struct AntiFlipSettings { pub axis_96_response: PointGraph<8>, // +1832/+1864 pub axis_64_response: PointGraph<8>, // +1912/+1944 diff --git a/crates/skate-core/src/riding/ground_force.rs b/crates/skate-core/src/riding/ground_force.rs index ab6ea08c..140dc0ca 100644 --- a/crates/skate-core/src/riding/ground_force.rs +++ b/crates/skate-core/src/riding/ground_force.rs @@ -2,6 +2,7 @@ //! The research index has not recovered its original name. Keep its four //! caller-supplied scalar arguments explicit instead of inventing units. use super::vector::{clamp, dot3, normalize}; +#[derive(Clone)] pub struct GroundForceSettings { pub range_1220: f32, pub speed_scale_1224: f32, diff --git a/crates/skate-core/src/riding/heading.rs b/crates/skate-core/src/riding/heading.rs index 4aceec8d..75d5957e 100644 --- a/crates/skate-core/src/riding/heading.rs +++ b/crates/skate-core/src/riding/heading.rs @@ -2,6 +2,7 @@ //! AntiFlipTorque; the two are distinct. The caller owns board+284 history. use crate::{physics::reciprocal_sqrt::estimate, point_graph::PointGraph, trigonometry::acos}; +#[derive(Clone)] pub struct HeadingSettings { pub manual_wrong_wheel_scalar: f32, // +904 pub manual_damping: f32, // +908 diff --git a/crates/skate-core/src/riding/slide_friction.rs b/crates/skate-core/src/riding/slide_friction.rs index 257df578..efa60fb2 100644 --- a/crates/skate-core/src/riding/slide_friction.rs +++ b/crates/skate-core/src/riding/slide_friction.rs @@ -2,6 +2,7 @@ use super::vector::{clamp, dot3}; use crate::{point_graph::PointGraph, trigonometry::acos}; +#[derive(Clone)] pub struct SlideFrictionSettings { pub angle_response: PointGraph<16>, // +196/+260 pub speed_response: PointGraph<8>, // +324/+356 diff --git a/crates/skate-core/src/riding/speed_model.rs b/crates/skate-core/src/riding/speed_model.rs index c3bbd1c2..998a0aae 100644 --- a/crates/skate-core/src/riding/speed_model.rs +++ b/crates/skate-core/src/riding/speed_model.rs @@ -4,6 +4,7 @@ use super::vector::{clamp, dot3, normalize}; use crate::point_graph::PointGraph; +#[derive(Clone)] pub struct SpeedModelSettings { /// physics_speedmodel layout +0,+8,+16,+20,+24. pub negative_gain: f32, diff --git a/crates/skate-core/src/riding/straighten.rs b/crates/skate-core/src/riding/straighten.rs index 6a4b9a55..2f78539a 100644 --- a/crates/skate-core/src/riding/straighten.rs +++ b/crates/skate-core/src/riding/straighten.rs @@ -1,6 +1,7 @@ //! Complete TU3 Toolkit_CalcStraightenOutTorque 82D92B60. use super::vector::{clamp, cross, dot3}; use crate::point_graph::PointGraph; +#[derive(Clone)] pub struct StraightenSettings { pub time_response: PointGraph<16>, // settings +768/+832 pub opposite_turn_limit: f32, // +896 diff --git a/crates/skate-game/src/graphics_menu.rs b/crates/skate-game/src/graphics_menu.rs index 9b82a897..13f63503 100644 --- a/crates/skate-game/src/graphics_menu.rs +++ b/crates/skate-game/src/graphics_menu.rs @@ -252,6 +252,7 @@ pub(crate) fn interact( mut transition: ResMut, mut customiser: ResMut, mut mods: ResMut, + panel: Res, nav: Res, mut physics: ResMut, keys: Res>, @@ -269,6 +270,7 @@ pub(crate) fn interact( if keys.just_pressed(KeyCode::Escape) || nav.pressed & 0x10 != 0 { menu.open = !menu.open; } + if panel.focused && menu.open { return; } let mut action = None; if menu.open { if keys.just_pressed(KeyCode::ArrowUp) || nav.pressed & 1 != 0 { @@ -407,12 +409,16 @@ fn labels( time: Res>, customiser: Res, mods: Res, + enabled_mods: Res, window: Single<&Window, With>, mut root: Single<&mut Node, With>, mut labels: Query<(&MenuLabel, &mut Text), Without>, mut status: Single<&mut Text, With>, mut buttons: Query<(&MenuRow, &Interaction, &mut BackgroundColor)>, ) { + let has_enabled=enabled_mods.manager.packages.values().any(|p|p.running()); + root.justify_content=if has_enabled {JustifyContent::FlexEnd}else{JustifyContent::Center}; + root.padding.right=if has_enabled {percent(3)}else{px(0.)}; root.display = if menu.open && !customiser.open && !mods.open { Display::Flex } else { diff --git a/crates/skate-game/src/modding/menu.rs b/crates/skate-game/src/modding/menu.rs index 17befc24..5e18f246 100644 --- a/crates/skate-game/src/modding/menu.rs +++ b/crates/skate-game/src/modding/menu.rs @@ -10,6 +10,10 @@ pub(crate) struct ModMenu { status: String, } impl ModMenu { + pub fn configure(&mut self, id: String) { + self.begin(); + self.id = Some(id); + } pub fn begin(&mut self) { self.open = true; self.just_opened = true; @@ -24,6 +28,8 @@ struct Row(usize); #[derive(Component)] struct Label(usize); #[derive(Component)] +struct Badge(usize); +#[derive(Component)] struct Detail; #[derive(Clone)] enum Action { @@ -41,17 +47,11 @@ fn rows(menu: &ModMenu, mods: &Mods) -> Vec<(String, Action)> { if let Some(p) = manager.packages.get(id) { let mut rows = vec![ ( - format!( - "{} — {}", - if p.enabled { "Disable" } else { "Enable" }, - if p.running() { - "Running" - } else if p.error.is_some() { - "Error" - } else { - "Disabled" - } - ), + if p.running() { + "DISABLE MOD".into() + } else { + "ENABLE MOD".into() + }, Action::Enable(id.clone()), ), ("Reload from disk".into(), Action::Reload(id.clone())), @@ -73,22 +73,7 @@ fn rows(menu: &ModMenu, mods: &Mods) -> Vec<(String, Action)> { let mut rows: Vec<_> = manager .packages .iter() - .map(|(id, p)| { - ( - format!( - "{} [{}]", - p.manifest.name, - if p.running() { - "Running" - } else if p.error.is_some() { - "Error" - } else { - "Disabled" - } - ), - Action::Select(id.clone()), - ) - }) + .map(|(id, p)| (p.manifest.name.clone(), Action::Select(id.clone()))) .collect(); rows.push(("Rescan packages".into(), Action::Scan)); rows.push(("Back to pause menu".into(), Action::Back)); @@ -108,7 +93,7 @@ fn setup(mut commands: Commands) { commands.spawn((Root,GlobalZIndex(20),Node{display:Display::None,width:percent(100),height:percent(100),position_type:PositionType::Absolute,align_items:AlignItems::Center,justify_content:JustifyContent::Center,..default()},BackgroundColor(Color::srgba(0.015,0.025,0.04,0.98)))).with_children(|root| { root.spawn((Node{width:px(760),max_width:percent(95),padding:UiRect::all(px(18)),flex_direction:FlexDirection::Column,row_gap:px(5),..default()},BackgroundColor(Color::srgb(0.035,0.055,0.08)))).with_children(|panel| { panel.spawn((Text::new("MODS — Lua SDK 1"),TextFont{font_size:28.,..default()},TextColor(Color::WHITE))); - for i in 0..8 { panel.spawn((Button,Row(i),Node{min_height:px(32),padding:UiRect::all(px(6)),..default()},BackgroundColor(Color::srgb(0.08,0.11,0.15)))).with_children(|row| {row.spawn((Label(i),Text::new(""),TextFont{font_size:17.,..default()},TextColor(Color::WHITE)));}); } + for i in 0..8 { panel.spawn((Button,Row(i),Node{min_height:px(32),padding:UiRect::all(px(6)),justify_content:JustifyContent::SpaceBetween,..default()},BackgroundColor(Color::srgb(0.08,0.11,0.15)))).with_children(|row| {row.spawn((Label(i),Text::new(""),TextFont{font_size:17.,..default()},TextColor(Color::WHITE)));row.spawn((Badge(i),Text::new(""),TextFont{font_size:16.,..default()},TextColor(Color::WHITE)));}); } panel.spawn((Detail,Text::new(""),TextFont{font_size:15.,..default()},TextColor(Color::srgb(0.65,0.85,0.85)))); panel.spawn((Text::new("Arrows / D-pad: select & adjust | Enter / A: choose | Esc / B: back\nStrings: Enter then type; Enter saves, Esc cancels. More rows scroll automatically."),TextFont{font_size:14.,..default()},TextColor(Color::WHITE))); }); @@ -220,7 +205,7 @@ fn input( Ok(()) } Action::Enable(id) => { - let enabled = !mods.manager.packages[&id].enabled; + let enabled = !mods.manager.packages[&id].running(); mods.manager.enable(&id, enabled) } Action::Reload(id) => { @@ -269,9 +254,10 @@ fn draw( menu: Res, mods: Res, mut root: Single<&mut Node, With>, - mut labels: Query<(&Label, &mut Text)>, + mut labels: Query<(&Label, &mut Text), Without>, + mut badges: Query<(&Badge, &mut Text, &mut TextColor), Without