From 2b0627613e7429b7be96ef60ea0ad81f49d8881e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=AA=20H=C3=A0n=20Minh=20Khang?= Date: Sun, 28 Jul 2024 11:02:43 -0400 Subject: [PATCH] Add Max Acceleration Yaw Offset (#101) * Add max accel yaw offset and goodies * update BXT exported function * update hltas --- Cargo.toml | 4 +- bxt-ipc-types/Cargo.toml | 2 +- bxt-strafe/Cargo.toml | 4 +- bxt-strafe/src/lib.rs | 12 + bxt-strafe/src/steps.rs | 55 ++- src/hooks/bxt.rs | 19 +- src/modules/tas_optimizer/optimizer.rs | 20 +- src/modules/tas_optimizer/simulator.rs | 242 ++++++++++- src/modules/tas_studio/editor/mod.rs | 450 ++++++++++++++++++++- src/modules/tas_studio/editor/operation.rs | 253 ++++++++++++ src/modules/tas_studio/editor/utils.rs | 88 ++++ src/modules/tas_studio/mod.rs | 54 ++- 12 files changed, 1178 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f640802..1c69437 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ color-eyre = { version = "0.6.2", default-features = false } crossbeam-channel = "0.5.8" git-version = "0.3.5" glam = "0.24.1" -hltas = { version = "0.8.0", features = ["serde1"] } +hltas = { version = "0.9.0", features = ["serde1"] } ipc-channel = "0.16.1" itertools = "0.11.0" libc = "0.2.147" @@ -51,7 +51,7 @@ features = ["libloaderapi", "psapi", "winuser", "synchapi", "handleapi", "proces [dev-dependencies] expect-test = "1.4.1" -hltas = { version = "0.8.0", features = ["serde1", "proptest1"] } +hltas = { version = "0.9.0", features = ["serde1", "proptest1"] } proptest = "1.2.0" [build-dependencies] diff --git a/bxt-ipc-types/Cargo.toml b/bxt-ipc-types/Cargo.toml index 1b2b2b1..de3b03a 100644 --- a/bxt-ipc-types/Cargo.toml +++ b/bxt-ipc-types/Cargo.toml @@ -5,5 +5,5 @@ edition = "2021" [dependencies] bxt-strafe = { version = "0.1.0", path = "../bxt-strafe" } -hltas = { version = "0.8.0", features = ["serde1"] } +hltas = { version = "0.9.0", features = ["serde1"] } serde = { version = "1.0.174", features = ["derive"] } diff --git a/bxt-strafe/Cargo.toml b/bxt-strafe/Cargo.toml index b28f537..e3284e0 100644 --- a/bxt-strafe/Cargo.toml +++ b/bxt-strafe/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" arrayvec = { version = "0.7.4", features = ["serde"] } bxt-vct = { path = "../bxt-vct" } glam = { version = "0.24.1", features = ["serde"] } -hltas = { version = "0.8.0" } +hltas = { version = "0.9.0" } serde = { version = "1.0.174", features = ["derive"] } tap = "1.0.1" @@ -15,4 +15,4 @@ tap = "1.0.1" ncollide3d = "0.33.0" proptest = "1.2.0" proptest-derive = "0.3.0" -hltas = { version = "0.8.0", features = ["proptest1"] } +hltas = { version = "0.9.0", features = ["proptest1"] } diff --git a/bxt-strafe/src/lib.rs b/bxt-strafe/src/lib.rs index 2fca200..fd0778e 100644 --- a/bxt-strafe/src/lib.rs +++ b/bxt-strafe/src/lib.rs @@ -133,6 +133,13 @@ pub struct State { // Number of frames for [`StrafeDir::LeftRight`] or [`StrafeDir::RightLeft`] which goes from // `0` to `count - 1`. pub strafe_cycle_frame_count: u32, + // Accelerated yaw speed specifics + pub max_accel_yaw_offset_value: f32, + // These values are to indicate whether we are in a "different" frame bulk. + pub prev_max_accel_yaw_offset_start: f32, + pub prev_max_accel_yaw_offset_target: f32, + pub prev_max_accel_yaw_offset_accel: f32, + pub prev_max_accel_yaw_offset_right: bool, // In case of yaw and pitch override, this might be useful. pub rendered_viewangles: Vec3, } @@ -147,6 +154,11 @@ impl State { jumped: false, move_traces: ArrayVec::new(), strafe_cycle_frame_count: 0, + max_accel_yaw_offset_value: 0., + prev_max_accel_yaw_offset_start: 0., + prev_max_accel_yaw_offset_target: 0., + prev_max_accel_yaw_offset_accel: 0., + prev_max_accel_yaw_offset_right: false, rendered_viewangles: Vec3::ZERO, }; diff --git a/bxt-strafe/src/steps.rs b/bxt-strafe/src/steps.rs index 8fbe389..021fc1b 100644 --- a/bxt-strafe/src/steps.rs +++ b/bxt-strafe/src/steps.rs @@ -350,7 +350,7 @@ impl Step for Strafe { frame_bulk.auto_actions.movement { let theta = match type_ { - StrafeType::MaxAccel => match dir { + StrafeType::MaxAccel | StrafeType::MaxAccelYawOffset { .. } => match dir { StrafeDir::Left => max_accel_theta(parameters, &state), StrafeDir::Right => -max_accel_theta(parameters, &state), StrafeDir::Yaw(yaw) => { @@ -447,6 +447,18 @@ impl Step for Strafe { (camera_yaw, entry) }; + let camera_yaw = if matches!(type_, StrafeType::MaxAccelYawOffset { .. }) { + // theta < 0. = is right + // If is right then we decreases yaw by offset. + // Therefore, positive offset in framebulk mean going more on that side. + let offset = state.max_accel_yaw_offset_value.to_radians(); + let offset = if theta < 0. { -offset } else { offset }; + + camera_yaw + angle_mod_rad(offset) + } else { + camera_yaw + }; + input.yaw = camera_yaw; input.forward = entry.forward as f32; input.side = entry.side as f32; @@ -545,6 +557,47 @@ impl Step for ResetFields { state.strafe_cycle_frame_count = 0; } + // If we have some acceleration, then this kicks in. + // It will preserve the final value across split segments. + if let Some(AutoMovement::Strafe(StrafeSettings { + type_: + StrafeType::MaxAccelYawOffset { + start, + target, + accel, + }, + dir, + })) = frame_bulk.auto_actions.movement + { + let right = matches!(dir, StrafeDir::Right); + + // Flip start and target when accel is negative. + state.max_accel_yaw_offset_value = (state.max_accel_yaw_offset_value + accel) + .max(start) + .min(target); + + // Reset value if we have different inputs. + // This means that if we split a s5x bulk, + // there won't be any side effects. + if start != state.prev_max_accel_yaw_offset_start + || target != state.prev_max_accel_yaw_offset_target + || accel != state.prev_max_accel_yaw_offset_accel + || right != state.prev_max_accel_yaw_offset_right + { + state.max_accel_yaw_offset_value = if accel.is_sign_negative() { + target + } else { + start + }; + + // Update so next time we know what to compare against. + state.prev_max_accel_yaw_offset_start = start; + state.prev_max_accel_yaw_offset_target = target; + state.prev_max_accel_yaw_offset_accel = accel; + state.prev_max_accel_yaw_offset_right = right; + }; + } + self.0 .simulate(tracer, parameters, frame_bulk, state, input) } diff --git a/src/hooks/bxt.rs b/src/hooks/bxt.rs index e09417c..82005d4 100644 --- a/src/hooks/bxt.rs +++ b/src/hooks/bxt.rs @@ -9,9 +9,9 @@ use hltas::HLTAS; use crate::modules::tas_studio; use crate::utils::{abort_on_panic, MainThreadMarker, Pointer, PointerTrait}; -pub static BXT_ON_TAS_PLAYBACK_FRAME: Pointer< +pub static BXT_ON_TAS_PLAYBACK_FRAME_V2: Pointer< *mut Option c_int>, -> = Pointer::empty(b"bxt_on_tas_playback_frame\0"); +> = Pointer::empty(b"bxt_on_tas_playback_frame_v2\0"); pub static BXT_ON_TAS_PLAYBACK_STOPPED: Pointer<*mut Option> = Pointer::empty(b"bxt_on_tas_playback_stopped\0"); pub static BXT_SIMULATION_IPC_IS_CLIENT_INITIALIZED: Pointer c_int> = @@ -31,7 +31,7 @@ pub static BXT_TAS_STUDIO_FREECAM_SET_ORIGIN: Pointer c_int { abort_on_panic(move || { let marker = MainThreadMarker::new(); diff --git a/src/modules/tas_optimizer/optimizer.rs b/src/modules/tas_optimizer/optimizer.rs index c7a505b..706601c 100644 --- a/src/modules/tas_optimizer/optimizer.rs +++ b/src/modules/tas_optimizer/optimizer.rs @@ -430,6 +430,8 @@ impl Optimizer { Line::Change(_) => (), Line::TargetYawOverride(_) => (), Line::RenderYawOverride(_) => (), + Line::PitchOverride(_) => (), + Line::RenderPitchOverride(_) => (), } } @@ -537,9 +539,12 @@ fn mutate_single_frame_bulk(change_pitch: bool, hltas: &mut HLTAS, rng: frame_bulk.auto_actions.movement.as_mut() { // Mutate strafe type. - *type_ = if let StrafeType::ConstYawspeed(yawspeed) = *type_ { + *type_ = if let StrafeType::ConstYawspeed(_) = *type_ { // Constant yawspeed will not be selected unless specified in bulk. - StrafeType::ConstYawspeed(yawspeed) + *type_ + } else if let StrafeType::MaxAccelYawOffset { .. } = *type_ { + // Max accel yaw offset will not be selected unless specified in bulk. + *type_ } else { let p = rng.gen::(); if p < 0.01 { @@ -558,6 +563,17 @@ fn mutate_single_frame_bulk(change_pitch: bool, hltas: &mut HLTAS, rng: *yawspeed = (*yawspeed + rng.gen_range(-1f32..1f32)).abs(); }; + if let StrafeType::MaxAccelYawOffset { + ref mut start, + ref mut target, + ref mut accel, + } = *type_ + { + *start += rng.gen_range(-1f32..1f32); + *target += rng.gen_range(-1f32..1f32); + *accel += rng.gen_range(-1f32..1f32); + } + // Mutate strafe direction. match dir { StrafeDir::Yaw(yaw) => { diff --git a/src/modules/tas_optimizer/simulator.rs b/src/modules/tas_optimizer/simulator.rs index d2b1d55..5665791 100644 --- a/src/modules/tas_optimizer/simulator.rs +++ b/src/modules/tas_optimizer/simulator.rs @@ -125,6 +125,8 @@ impl<'a, T: Trace> Iterator for Simulator<'a, T> { Line::Change(_) => (), Line::TargetYawOverride(_) => (), Line::RenderYawOverride(_) => (), + Line::PitchOverride(_) => (), + Line::RenderPitchOverride(_) => (), } // Advance to the next line for non-frame-bulks. @@ -141,7 +143,10 @@ mod tests { use bxt_strafe::{DummyTracer, Parameters, Player, State}; use glam::Vec3; - use hltas::types::FrameBulk; + use hltas::types::{ + ActionKeys, AutoActions, AutoMovement, FrameBulk, MovementKeys, StrafeDir, StrafeSettings, + StrafeType, + }; use super::*; @@ -283,4 +288,239 @@ mod tests { let simulator = Simulator::new(&DummyTracer, &[default_frame()], &lines); assert_eq!(simulator.count(), 2); } + + #[test] + fn simulator_accel_yawspeed_increment() { + let mut frames: Vec = vec![default_frame()]; + + let lines = [Line::FrameBulk(FrameBulk { + auto_actions: AutoActions { + movement: Some(AutoMovement::Strafe(StrafeSettings { + type_: StrafeType::MaxAccelYawOffset { + start: 0., + target: 210., + accel: 10., + }, + dir: StrafeDir::Left, + })), + leave_ground_action: None, + jump_bug: None, + duck_before_collision: None, + duck_before_ground: None, + duck_when_jump: None, + }, + movement_keys: MovementKeys::default(), + action_keys: ActionKeys::default(), + frame_time: "0.01".to_string(), + pitch: None, + frame_count: NonZeroU32::new(5).unwrap(), + console_command: None, + })]; + + let simulator = Simulator::new(&DummyTracer, &frames, &lines); + + frames.append(&mut simulator.collect::>()); + + let lines2 = [ + Line::FrameBulk(FrameBulk { + auto_actions: AutoActions { + movement: Some(AutoMovement::Strafe(StrafeSettings { + type_: StrafeType::MaxAccelYawOffset { + start: 0., + target: 210., + accel: 10., + }, + dir: StrafeDir::Left, + })), + leave_ground_action: None, + jump_bug: None, + duck_before_collision: None, + duck_before_ground: None, + duck_when_jump: None, + }, + movement_keys: MovementKeys::default(), + action_keys: ActionKeys::default(), + frame_time: "0.01".to_string(), + pitch: None, + frame_count: NonZeroU32::new(5).unwrap(), + console_command: None, + }), + Line::FrameBulk(FrameBulk { + auto_actions: AutoActions { + movement: Some(AutoMovement::Strafe(StrafeSettings { + type_: StrafeType::MaxAccelYawOffset { + start: 0., + target: 210., + accel: 10., + }, + dir: StrafeDir::Left, + })), + leave_ground_action: None, + jump_bug: None, + duck_before_collision: None, + duck_before_ground: None, + duck_when_jump: None, + }, + movement_keys: MovementKeys::default(), + action_keys: ActionKeys::default(), + frame_time: "0.01".to_string(), + pitch: None, + frame_count: NonZeroU32::new(1).unwrap(), + console_command: None, + }), + ]; + + let simulator2 = Simulator::new(&DummyTracer, &frames, &lines2); + frames.append(&mut simulator2.collect::>()); + + assert_eq!(frames.last().unwrap().state.max_accel_yaw_offset_value, 50.); + } + + #[test] + fn simulator_accel_yawspeed_reset() { + let mut frames: Vec = vec![default_frame()]; + + let mut lines = vec![Line::FrameBulk(FrameBulk { + auto_actions: AutoActions { + movement: Some(AutoMovement::Strafe(StrafeSettings { + type_: StrafeType::MaxAccelYawOffset { + start: 0., + target: 200., + accel: 10., + }, + dir: StrafeDir::Left, + })), + leave_ground_action: None, + jump_bug: None, + duck_before_collision: None, + duck_before_ground: None, + duck_when_jump: None, + }, + movement_keys: MovementKeys::default(), + action_keys: ActionKeys::default(), + frame_time: "0.01".to_string(), + pitch: None, + frame_count: NonZeroU32::new(5).unwrap(), + console_command: None, + })]; + + let simulator = Simulator::new(&DummyTracer, &frames, &lines); + frames.append(&mut simulator.collect::>()); + + // reset due to target change + lines.push(Line::FrameBulk(FrameBulk { + auto_actions: AutoActions { + movement: Some(AutoMovement::Strafe(StrafeSettings { + type_: StrafeType::MaxAccelYawOffset { + start: 0., + target: 210., + accel: 10., + }, + dir: StrafeDir::Left, + })), + leave_ground_action: None, + jump_bug: None, + duck_before_collision: None, + duck_before_ground: None, + duck_when_jump: None, + }, + movement_keys: MovementKeys::default(), + action_keys: ActionKeys::default(), + frame_time: "0.01".to_string(), + pitch: None, + frame_count: NonZeroU32::new(10).unwrap(), + console_command: None, + })); + + let simulator = Simulator::new(&DummyTracer, &frames, &lines); + frames.append(&mut simulator.collect::>()); + assert_eq!(frames.last().unwrap().state.max_accel_yaw_offset_value, 90.); + + // reset due to accel change + lines.push(Line::FrameBulk(FrameBulk { + auto_actions: AutoActions { + movement: Some(AutoMovement::Strafe(StrafeSettings { + type_: StrafeType::MaxAccelYawOffset { + start: 0., + target: 210., + accel: 9., + }, + dir: StrafeDir::Left, + })), + leave_ground_action: None, + jump_bug: None, + duck_before_collision: None, + duck_before_ground: None, + duck_when_jump: None, + }, + movement_keys: MovementKeys::default(), + action_keys: ActionKeys::default(), + frame_time: "0.01".to_string(), + pitch: None, + frame_count: NonZeroU32::new(9).unwrap(), + console_command: None, + })); + + let simulator = Simulator::new(&DummyTracer, &frames, &lines); + frames.append(&mut simulator.collect::>()); + assert_eq!(frames.last().unwrap().state.max_accel_yaw_offset_value, 72.); + + // reset due to direction change + lines.push(Line::FrameBulk(FrameBulk { + auto_actions: AutoActions { + movement: Some(AutoMovement::Strafe(StrafeSettings { + type_: StrafeType::MaxAccelYawOffset { + start: 0., + target: 210., + accel: 9., + }, + dir: StrafeDir::Right, + })), + leave_ground_action: None, + jump_bug: None, + duck_before_collision: None, + duck_before_ground: None, + duck_when_jump: None, + }, + movement_keys: MovementKeys::default(), + action_keys: ActionKeys::default(), + frame_time: "0.01".to_string(), + pitch: None, + frame_count: NonZeroU32::new(9).unwrap(), + console_command: None, + })); + + let simulator = Simulator::new(&DummyTracer, &frames, &lines); + frames.append(&mut simulator.collect::>()); + assert_eq!(frames.last().unwrap().state.max_accel_yaw_offset_value, 72.); + + // reset due to start change + lines.push(Line::FrameBulk(FrameBulk { + auto_actions: AutoActions { + movement: Some(AutoMovement::Strafe(StrafeSettings { + type_: StrafeType::MaxAccelYawOffset { + start: 10., + target: 210., + accel: 9., + }, + dir: StrafeDir::Right, + })), + leave_ground_action: None, + jump_bug: None, + duck_before_collision: None, + duck_before_ground: None, + duck_when_jump: None, + }, + movement_keys: MovementKeys::default(), + action_keys: ActionKeys::default(), + frame_time: "0.01".to_string(), + pitch: None, + frame_count: NonZeroU32::new(9).unwrap(), + console_command: None, + })); + + let simulator = Simulator::new(&DummyTracer, &frames, &lines); + frames.append(&mut simulator.collect::>()); + assert_eq!(frames.last().unwrap().state.max_accel_yaw_offset_value, 82.); + } } diff --git a/src/modules/tas_studio/editor/mod.rs b/src/modules/tas_studio/editor/mod.rs index 69983c2..1ffac2b 100644 --- a/src/modules/tas_studio/editor/mod.rs +++ b/src/modules/tas_studio/editor/mod.rs @@ -9,7 +9,7 @@ use std::time::Instant; use bxt_ipc_types::Frame; use bxt_strafe::{Hull, Trace}; use color_eyre::eyre::{self, ensure}; -use glam::{Vec2, Vec3}; +use glam::{IVec2, Vec2, Vec3}; use hltas::types::{ AutoMovement, Change, ChangeTarget, Line, StrafeDir, StrafeSettings, StrafeType, VectorialStrafingConstraints, @@ -24,11 +24,12 @@ use self::toggle_auto_action::ToggleAutoActionTarget; use self::utils::{ bulk_and_first_frame_idx, bulk_and_first_frame_idx_mut, bulk_idx_and_is_last, bulk_idx_and_repeat_at_frame, join_lines, line_first_frame_idx, line_idx_and_repeat_at_frame, - FrameBulkExt, + FrameBulkExt, MaxAccelOffsetValuesMut, }; use super::remote::{AccurateFrame, PlayRequest}; use crate::hooks::sdl::MouseState; use crate::modules::tas_optimizer::simulator::Simulator; +use crate::modules::tas_studio::editor::utils::MaxAccelOffsetValues; use crate::modules::triangle_drawing::triangle_api::{Primitive, RenderMode}; use crate::modules::triangle_drawing::TriangleApi; @@ -127,6 +128,8 @@ pub struct Editor { /// /// Adjusts the yawspeed in the same way for all adjacent frame bulks with equal yawspeed. adjacent_side_strafe_yawspeed_adjustment: Option, + /// Frame bulk max acceleration yaw offset adjustment. + max_accel_yaw_offset_adjustment: Option, // ============================================== // Camera-editor-specific state. @@ -298,6 +301,69 @@ struct AdjacentYawspeedAdjustment { bulk_count: usize, } +/// Data for handling the side strafe accelerated yawspeed adjustment. +#[derive(Debug, Clone, Copy)] +pub struct MaxAccelYawOffsetAdjustment { + /// The mouse adjustment itself. + /// + /// (start, target, accel, yaw field value) + /// + /// Yaw field value here is the first value in the yaw field. + /// + /// It would be current yaw for yaw strafe or left-right count for left-right strafing. + mouse_adjustment: MouseAdjustment, + /// Indicates which change mode is in use. + /// + /// Left click while holding down right mouse button to switch change mode. + pub mode: MaxAccelYawOffsetMode, + /// Offset the mouse delta position when mode is switched. + mouse_offset: IVec2, + /// Skip Alt cycle if the mode is not available. + cycle_again: bool, +} + +/// Modes of operation for maximum acceleration yaw offset adjustment. +/// +/// Cycling by holding right mouse button and left click. +#[derive(Debug, Clone, Copy)] +pub enum MaxAccelYawOffsetMode { + StartAndTarget = 0, + Target, + Acceleration, + Start, + /// For adjusting the yaw field value of the strafe direction. + /// + /// That could be yaw value (Sx3), left right count (Sx6/Sx7),... + StrafeDirYawField, +} + +#[derive(Debug, Clone, Copy)] +pub struct MaxAccelYawOffsetMouseAdjustment { + pub start: f32, + pub target: f32, + pub accel: f32, + pub alt: MaxAccelYawOffsetYawField, +} + +#[derive(Debug, Clone, Copy)] +pub enum MaxAccelYawOffsetYawField { + None, + Yaw(f32), + LeftRight(NonZeroU32), +} + +impl MaxAccelYawOffsetMode { + fn cycle(&self) -> Self { + match self { + MaxAccelYawOffsetMode::StartAndTarget => MaxAccelYawOffsetMode::Target, + MaxAccelYawOffsetMode::Target => MaxAccelYawOffsetMode::Acceleration, + MaxAccelYawOffsetMode::Acceleration => MaxAccelYawOffsetMode::Start, + MaxAccelYawOffsetMode::Start => MaxAccelYawOffsetMode::StrafeDirYawField, + MaxAccelYawOffsetMode::StrafeDirYawField => MaxAccelYawOffsetMode::StartAndTarget, + } + } +} + /// Data for handling the insert-camera-line adjustment. #[derive(Debug, Clone, Copy)] struct InsertCameraLineAdjustment { @@ -446,6 +512,7 @@ impl Editor { adjacent_left_right_count_adjustment: None, side_strafe_yawspeed_adjustment: None, adjacent_side_strafe_yawspeed_adjustment: None, + max_accel_yaw_offset_adjustment: None, in_camera_editor: false, auto_smoothing: false, show_player_bbox: false, @@ -532,6 +599,12 @@ impl Editor { self.undo_log.len() } + pub fn side_strafe_accelerated_yawspeed_adjustment( + &self, + ) -> Option { + self.max_accel_yaw_offset_adjustment + } + pub fn camera_view_adjustment(&self) -> Option { self.camera_view_adjustment } @@ -782,6 +855,7 @@ impl Editor { || self.adjacent_left_right_count_adjustment.is_some() || self.side_strafe_yawspeed_adjustment.is_some() || self.adjacent_side_strafe_yawspeed_adjustment.is_some() + || self.max_accel_yaw_offset_adjustment.is_some() || self.insert_camera_line_adjustment.is_some() || self.camera_view_adjustment.is_some() } @@ -795,7 +869,7 @@ impl Editor { keyboard: KeyboardState, deadline: Instant, callbacks: Callbacks, - ) -> eyre::Result<()> { + ) -> ManualOpResult<()> { let _span = info_span!("Editor::tick").entered(); // Update ongoing adjustments. @@ -807,6 +881,7 @@ impl Editor { self.tick_adjacent_left_right_count_adjustment(mouse, keyboard)?; self.tick_side_strafe_yawspeed_adjustment(mouse, keyboard)?; self.tick_adjacent_side_strafe_yawspeed_adjustment(mouse, keyboard)?; + self.tick_max_accel_yaw_offset_adjustment(mouse, keyboard, self.prev_mouse_state)?; self.tick_insert_camera_line_adjustment(mouse, keyboard)?; self.tick_camera_view_adjustment(mouse, self.prev_mouse_state, callbacks)?; @@ -994,22 +1069,99 @@ impl Editor { let a_screen = world_to_screen(pos + perp); let b_screen = world_to_screen(pos - perp); - let dir = match (a_screen, b_screen) { + let adjustment_dir = match (a_screen, b_screen) { (Some(a), Some(b)) => a - b, // Presumably, one of the points is invisible, so just fall back. _ => Vec2::X, }; - if let Some(yaw) = bulk.yaw() { - self.yaw_adjustment = Some(MouseAdjustment::new(*yaw, mouse_pos, dir)); + if let Some(MaxAccelOffsetValues { + start, + target, + accel, + .. + }) = bulk.max_accel_yaw_offset() + { + // Mode to know which to switch to. + // For left or right, it would be TargetAndEnd so we can turn better. + // For left-right and yaw, it would be Alt where we can change the + // first yaw field value. + let (mode, adjustment_dir, alt) = if let Some(AutoMovement::Strafe( + StrafeSettings { dir, .. }, + )) = bulk.auto_actions.movement + { + // Match StrafiDir so we can get adjustment mode and + // adjustment_dir. + // Then match StrafeType to get values for alt. + match dir { + StrafeDir::Left | StrafeDir::Right | StrafeDir::Best => { + let adjustment_dir = if matches!(dir, StrafeDir::Right) { + -adjustment_dir + } else { + adjustment_dir + }; + + ( + MaxAccelYawOffsetMode::StartAndTarget, + adjustment_dir, + MaxAccelYawOffsetYawField::None, + ) + } + StrafeDir::Yaw(yaw) | StrafeDir::Line { yaw } => ( + MaxAccelYawOffsetMode::StrafeDirYawField, + adjustment_dir, + MaxAccelYawOffsetYawField::Yaw(yaw), + ), + StrafeDir::LeftRight(count) | StrafeDir::RightLeft(count) => { + let adjustment_dir = + if matches!(dir, StrafeDir::RightLeft(_)) { + -adjustment_dir + } else { + adjustment_dir + }; + + ( + MaxAccelYawOffsetMode::StrafeDirYawField, + adjustment_dir, + MaxAccelYawOffsetYawField::LeftRight(count), + ) + } + _ => return Err(ManualOpError::UserError( + "Editor does not support current strafe dir for max accel yaw offset." + .to_owned(), + )), + } + } else { + unreachable!() + }; + + self.max_accel_yaw_offset_adjustment = + Some(MaxAccelYawOffsetAdjustment { + mouse_adjustment: MouseAdjustment::new( + MaxAccelYawOffsetMouseAdjustment { + start: *start, + target: *target, + accel: *accel, + alt, + }, + mouse_pos, + adjustment_dir, + ), + mode, + mouse_offset: IVec2::ZERO, + cycle_again: false, + }); + } else if let Some(yaw) = bulk.yaw() { + self.yaw_adjustment = + Some(MouseAdjustment::new(*yaw, mouse_pos, adjustment_dir)); } else if let Some(count) = bulk.left_right_count() { // Make the adjustment face the expected way. let dir = match bulk.auto_actions.movement { Some(AutoMovement::Strafe(StrafeSettings { dir: StrafeDir::RightLeft(_), .. - })) => -dir, - _ => dir, + })) => -adjustment_dir, + _ => adjustment_dir, }; self.left_right_count_adjustment = @@ -1020,8 +1172,8 @@ impl Editor { Some(AutoMovement::Strafe(StrafeSettings { dir: StrafeDir::Right, .. - })) => -dir, - _ => dir, + })) => -adjustment_dir, + _ => adjustment_dir, }; self.side_strafe_yawspeed_adjustment = @@ -1797,6 +1949,227 @@ impl Editor { Ok(()) } + fn tick_max_accel_yaw_offset_adjustment( + &mut self, + mouse: MouseState, + keyboard: KeyboardState, + prev_mouse: MouseState, + ) -> eyre::Result<()> { + let Some(adjustment) = &mut self.max_accel_yaw_offset_adjustment else { + return Ok(()); + }; + + let bulk_idx = self.selected_bulk_idx.unwrap(); + let (bulk, first_frame_idx) = + bulk_and_first_frame_idx_mut(&mut self.branches[self.branch_idx].branch.script) + .nth(bulk_idx) + .unwrap(); + + let MaxAccelOffsetValuesMut { + start, + target, + accel, + mut yaw, + mut count, + } = bulk.max_accel_yaw_offset_mut().unwrap(); + + if !mouse.buttons.is_right_down() { + if !adjustment.mouse_adjustment.changed_once { + self.max_accel_yaw_offset_adjustment = None; + return Ok(()); + } + + let op = match adjustment.mode { + MaxAccelYawOffsetMode::StartAndTarget => { + Operation::SetMaxAccelOffsetStartAndTarget { + bulk_idx, + from: ( + adjustment.mouse_adjustment.original_value.start, + adjustment.mouse_adjustment.original_value.target, + ), + to: (*start, *target), + } + } + MaxAccelYawOffsetMode::Start => Operation::SetMaxAccelOffsetStart { + bulk_idx, + from: adjustment.mouse_adjustment.original_value.start, + to: *start, + }, + MaxAccelYawOffsetMode::Target => Operation::SetMaxAccelOffsetTarget { + bulk_idx, + from: adjustment.mouse_adjustment.original_value.target, + to: *target, + }, + MaxAccelYawOffsetMode::Acceleration => Operation::SetMaxAccelOffsetAccel { + bulk_idx, + from: adjustment.mouse_adjustment.original_value.accel, + to: *accel, + }, + MaxAccelYawOffsetMode::StrafeDirYawField => { + match adjustment.mouse_adjustment.original_value.alt { + // Can't do anything. + MaxAccelYawOffsetYawField::None => { + self.max_accel_yaw_offset_adjustment = None; + return Ok(()); + } + MaxAccelYawOffsetYawField::Yaw(from) => Operation::SetYaw { + bulk_idx, + from, + to: *yaw.unwrap(), + }, + MaxAccelYawOffsetYawField::LeftRight(from) => { + Operation::SetLeftRightCount { + bulk_idx, + from: from.get(), + to: count.unwrap().get(), + } + } + } + } + }; + + self.max_accel_yaw_offset_adjustment = None; + return self.store_operation(op); + } + + let mut should_invalidate = false; + + // Mouse left click to switch mode. + // This must happen before the delta calculation. + if (mouse.buttons.is_left_down() && !prev_mouse.buttons.is_left_down()) + || adjustment.cycle_again + { + // Cycling + adjustment.mode = adjustment.mode.cycle(); + + // After switching mode, we have to reset all of the changes back to the original. + *start = adjustment.mouse_adjustment.original_value.start; + *target = adjustment.mouse_adjustment.original_value.target; + *accel = adjustment.mouse_adjustment.original_value.accel; + + if let MaxAccelYawOffsetYawField::Yaw(original) = + adjustment.mouse_adjustment.original_value.alt + { + let binding = yaw.as_deref_mut(); + *binding.unwrap() = original; + } + + if let MaxAccelYawOffsetYawField::LeftRight(original) = + adjustment.mouse_adjustment.original_value.alt + { + let binding = count.as_deref_mut(); + *binding.unwrap() = original; + } + + adjustment.mouse_adjustment.changed_once = false; + + // Change mouse position so that all of the delta for adjustment is reset. + adjustment.mouse_offset = mouse.pos + - IVec2::new( + adjustment.mouse_adjustment.pressed_at.x as i32, + adjustment.mouse_adjustment.pressed_at.y as i32, + ); + + should_invalidate = true; + adjustment.cycle_again = false; + } + + let speed = keyboard.adjustment_speed(); + let delta = adjustment + .mouse_adjustment + // Mouse offset is used here to reset "pressed_at". + .delta((mouse.pos - adjustment.mouse_offset).as_vec2()) + / 50. // Yes + * speed; + + match adjustment.mode { + MaxAccelYawOffsetMode::StartAndTarget => { + let new_start = adjustment.mouse_adjustment.original_value.start + delta; + let new_target = adjustment.mouse_adjustment.original_value.target + delta; + + if *start != new_start { + adjustment.mouse_adjustment.changed_once = true; + *start = new_start; + should_invalidate = true; + } + + if *target != new_target { + adjustment.mouse_adjustment.changed_once = true; + *target = new_target; + should_invalidate = true; + } + } + MaxAccelYawOffsetMode::Target => { + let new_target = adjustment.mouse_adjustment.original_value.target + delta; + + if *target != new_target { + adjustment.mouse_adjustment.changed_once = true; + *target = new_target; + should_invalidate = true; + } + } + MaxAccelYawOffsetMode::Acceleration => { + // Accel is very delicate. Need to tone it down. + let delta = delta / 20.; + let new_accel = adjustment.mouse_adjustment.original_value.accel + delta; + + if *accel != new_accel { + adjustment.mouse_adjustment.changed_once = true; + *accel = new_accel; + should_invalidate = true; + } + } + MaxAccelYawOffsetMode::Start => { + let new_start = adjustment.mouse_adjustment.original_value.start + delta; + + if *start != new_start { + adjustment.mouse_adjustment.changed_once = true; + *start = new_start; + should_invalidate = true; + } + } + MaxAccelYawOffsetMode::StrafeDirYawField => { + // Undo delta decrease so we can adjust yaw and left right count. + // * 0.1 to match the left-right and yaw adjustment. + let delta = delta * 50. * 0.1; + + match adjustment.mouse_adjustment.original_value.alt { + MaxAccelYawOffsetYawField::None => { + adjustment.cycle_again = true; + } + MaxAccelYawOffsetYawField::Yaw(from) => { + let new_yaw = from + delta; + + // We make sure the original_value.alt is correct. + if *yaw.as_deref().unwrap() != new_yaw { + adjustment.mouse_adjustment.changed_once = true; + *yaw.unwrap() = new_yaw; + should_invalidate = true; + } + } + MaxAccelYawOffsetYawField::LeftRight(from) => { + let new_count = from + .get() + .saturating_add_signed((delta).round() as i32) + .max(1); + + if count.as_ref().unwrap().get() != new_count { + adjustment.mouse_adjustment.changed_once = true; + *count.unwrap() = NonZeroU32::new(new_count).unwrap(); + should_invalidate = true; + } + } + }; + } + } + + if should_invalidate { + self.invalidate(first_frame_idx); + } + + Ok(()) + } + fn tick_insert_camera_line_adjustment( &mut self, mouse: MouseState, @@ -2251,6 +2624,63 @@ impl Editor { } } + if let Some(adjustment) = self.max_accel_yaw_offset_adjustment.take() { + let original_value = adjustment.mouse_adjustment.original_value; + + let bulk_idx = self.selected_bulk_idx.unwrap(); + let (bulk, first_frame_idx) = + bulk_and_first_frame_idx_mut(&mut self.branch_mut().branch.script) + .nth(bulk_idx) + .unwrap(); + + let MaxAccelOffsetValuesMut { + start, + target, + accel, + yaw, + count, + } = bulk.max_accel_yaw_offset_mut().unwrap(); + let mut should_invalidate = false; + + if (*start, *target, *accel) + != ( + original_value.start, + original_value.target, + original_value.accel, + ) + { + *start = original_value.start; + *target = original_value.target; + *accel = original_value.accel; + + should_invalidate = true; + } + + if let MaxAccelYawOffsetYawField::Yaw(original) = + adjustment.mouse_adjustment.original_value.alt + { + if *yaw.as_deref().unwrap() != original { + *yaw.unwrap() = original; + } + + should_invalidate = true; + } + + if let MaxAccelYawOffsetYawField::LeftRight(original) = + adjustment.mouse_adjustment.original_value.alt + { + if *count.as_deref().unwrap() != original { + *count.unwrap() = original; + } + + should_invalidate = true; + } + + if should_invalidate { + self.invalidate(first_frame_idx); + } + } + if let Some(InsertCameraLineAdjustment { camera_line_idx, did_split, diff --git a/src/modules/tas_studio/editor/operation.rs b/src/modules/tas_studio/editor/operation.rs index 7cc27db..cb5f279 100644 --- a/src/modules/tas_studio/editor/operation.rs +++ b/src/modules/tas_studio/editor/operation.rs @@ -8,10 +8,12 @@ use serde::{Deserialize, Serialize}; use super::utils::{line_first_frame_idx, line_first_frame_idx_and_frame_count}; use crate::modules::tas_studio::editor::utils::{ bulk_and_first_frame_idx_mut, line_idx_and_repeat_at_frame, FrameBulkExt, + MaxAccelOffsetValuesMut, }; // This enum is stored in a SQLite DB as bincode bytes. All changes MUST BE BACKWARDS COMPATIBLE to // be able to load old projects. +// Make sure that newer operations are added at the end of the enum. /// A basic operation on a HLTAS. /// /// All operations can be applied and undone. They therefore store enough information to be able to @@ -102,6 +104,26 @@ pub enum Operation { from: Option, to: Option, }, + SetMaxAccelOffsetStart { + bulk_idx: usize, + from: f32, + to: f32, + }, + SetMaxAccelOffsetTarget { + bulk_idx: usize, + from: f32, + to: f32, + }, + SetMaxAccelOffsetAccel { + bulk_idx: usize, + from: f32, + to: f32, + }, + SetMaxAccelOffsetStartAndTarget { + bulk_idx: usize, + from: (f32, f32), + to: (f32, f32), + }, } #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] @@ -371,6 +393,74 @@ impl Operation { *yawspeed = to; return Some(first_frame_idx); } + Operation::SetMaxAccelOffsetStart { bulk_idx, from, to } => { + let (bulk, first_frame_idx) = bulk_and_first_frame_idx_mut(hltas) + .nth(bulk_idx) + .expect("invalid bulk index"); + + let start = bulk + .max_accel_yaw_offset_mut() + .expect("frame bulk should have starting yaw offset") + .start; + assert_eq!(from, *start, "wrong current starting yaw offset"); + + if from != to { + *start = to; + return Some(first_frame_idx); + } + } + Operation::SetMaxAccelOffsetTarget { bulk_idx, from, to } => { + let (bulk, first_frame_idx) = bulk_and_first_frame_idx_mut(hltas) + .nth(bulk_idx) + .expect("invalid bulk index"); + + let target = bulk + .max_accel_yaw_offset_mut() + .expect("frame bulk should have target yaw offset") + .target; + assert_eq!(from, *target, "wrong current target yaw offset"); + + if from != to { + *target = to; + return Some(first_frame_idx); + } + } + Operation::SetMaxAccelOffsetAccel { bulk_idx, from, to } => { + let (bulk, first_frame_idx) = bulk_and_first_frame_idx_mut(hltas) + .nth(bulk_idx) + .expect("invalid bulk index"); + + let accel = bulk + .max_accel_yaw_offset_mut() + .expect("frame bulk should have yaw acceleration") + .accel; + assert_eq!(from, *accel, "wrong current yaw acceleration"); + + if from != to { + *accel = to; + return Some(first_frame_idx); + } + } + Operation::SetMaxAccelOffsetStartAndTarget { bulk_idx, from, to } => { + let (bulk, first_frame_idx) = bulk_and_first_frame_idx_mut(hltas) + .nth(bulk_idx) + .expect("invalid bulk index"); + + let MaxAccelOffsetValuesMut { start, target, .. } = bulk + .max_accel_yaw_offset_mut() + .expect("frame bulk should have starting and target yaw offset"); + + assert_eq!( + from, + (*start, *target), + "wrong current starting and target yaw offset" + ); + + if from != to { + (*start, *target) = to; + return Some(first_frame_idx); + } + } Operation::SetFrameTime { bulk_idx, ref from, @@ -659,6 +749,74 @@ impl Operation { *yawspeed = from; return Some(first_frame_idx); } + Operation::SetMaxAccelOffsetStart { bulk_idx, from, to } => { + let (bulk, first_frame_idx) = bulk_and_first_frame_idx_mut(hltas) + .nth(bulk_idx) + .expect("invalid bulk index"); + + let start = bulk + .max_accel_yaw_offset_mut() + .expect("frame bulk should have starting yaw offset") + .start; + assert_eq!(to, *start, "wrong current starting yaw offset"); + + if from != to { + *start = from; + return Some(first_frame_idx); + } + } + Operation::SetMaxAccelOffsetTarget { bulk_idx, from, to } => { + let (bulk, first_frame_idx) = bulk_and_first_frame_idx_mut(hltas) + .nth(bulk_idx) + .expect("invalid bulk index"); + + let target = bulk + .max_accel_yaw_offset_mut() + .expect("frame bulk should have target yaw offset") + .target; + assert_eq!(to, *target, "wrong current target yaw offset"); + + if from != to { + *target = from; + return Some(first_frame_idx); + } + } + Operation::SetMaxAccelOffsetAccel { bulk_idx, from, to } => { + let (bulk, first_frame_idx) = bulk_and_first_frame_idx_mut(hltas) + .nth(bulk_idx) + .expect("invalid bulk index"); + + let accel = bulk + .max_accel_yaw_offset_mut() + .expect("frame bulk should have yaw acceleration") + .accel; + assert_eq!(to, *accel, "wrong current yaw acceleration"); + + if from != to { + *accel = from; + return Some(first_frame_idx); + } + } + Operation::SetMaxAccelOffsetStartAndTarget { bulk_idx, from, to } => { + let (bulk, first_frame_idx) = bulk_and_first_frame_idx_mut(hltas) + .nth(bulk_idx) + .expect("invalid bulk index"); + + let MaxAccelOffsetValuesMut { start, target, .. } = bulk + .max_accel_yaw_offset_mut() + .expect("frame bulk should have starting and target yaw offset"); + + assert_eq!( + to, + (*start, *target), + "wrong current starting and target yaw offset" + ); + + if from != to { + (*start, *target) = from; + return Some(first_frame_idx); + } + } Operation::SetFrameTime { bulk_idx, ref from, @@ -1219,4 +1377,99 @@ s41-------|------|------|0.004|70|-|10 ----------|------|------|0.004|70|-|6", ); } + + #[test] + fn op_set_accelerated_yawspeed_start() { + check_op( + "\ +----------|------|------|0.004|10|-|6 +s50-------|------|------|0.004|- 0 0 0|-|10 +s51-------|------|------|0.004|- 0 70 10|-|10", + Operation::SetMaxAccelOffsetStart { + bulk_idx: 1, + from: 0., + to: 69., + }, + "\ +----------|------|------|0.004|10|-|6 +s50-------|------|------|0.004|- 69 0 0|-|10 +s51-------|------|------|0.004|- 0 70 10|-|10", + ); + } + + #[test] + fn op_set_accelerated_yawspeed_start_and_target() { + check_op( + "\ +----------|------|------|0.004|10|-|6 +s50-------|------|------|0.004|- 0 0 0|-|10 +s51-------|------|------|0.004|- 0 70 10|-|10", + Operation::SetMaxAccelOffsetStartAndTarget { + bulk_idx: 1, + from: (0., 0.), + to: (69., -89.34), + }, + "\ +----------|------|------|0.004|10|-|6 +s50-------|------|------|0.004|- 69 -89.34 0|-|10 +s51-------|------|------|0.004|- 0 70 10|-|10", + ); + } + + #[test] + fn op_set_accelerated_yawspeed_yaw() { + check_op( + "\ +----------|------|------|0.004|10|-|6 +s53-------|------|------|0.004|0 0 0 0|-|10 +s51-------|------|------|0.004|- 0 70 10|-|10", + Operation::SetYaw { + bulk_idx: 1, + from: 0., + to: 169.3, + }, + "\ +----------|------|------|0.004|10|-|6 +s53-------|------|------|0.004|169.3 0 0 0|-|10 +s51-------|------|------|0.004|- 0 70 10|-|10", + ); + } + + #[test] + fn op_set_accelerated_yawspeed_count() { + check_op( + "\ +----------|------|------|0.004|10|-|6 +s57-------|------|------|0.004|13 0 0 0|-|10 +s51-------|------|------|0.004|- 0 70 10|-|10", + Operation::SetLeftRightCount { + bulk_idx: 1, + from: 13, + to: 36, + }, + "\ +----------|------|------|0.004|10|-|6 +s57-------|------|------|0.004|36 0 0 0|-|10 +s51-------|------|------|0.004|- 0 70 10|-|10", + ); + } + + #[test] + fn op_set_accelerated_yawspeed_acceleration() { + check_op( + "\ +----------|------|------|0.004|10|-|6 +s53-------|------|------|0.004|13 0 0 0|-|10 +s51-------|------|------|0.004|- 0 70 10|-|10", + Operation::SetMaxAccelOffsetAccel { + bulk_idx: 1, + from: 0., + to: -14.3, + }, + "\ +----------|------|------|0.004|10|-|6 +s53-------|------|------|0.004|13 0 0 -14.3|-|10 +s51-------|------|------|0.004|- 0 70 10|-|10", + ); + } } diff --git a/src/modules/tas_studio/editor/utils.rs b/src/modules/tas_studio/editor/utils.rs index bb61a76..46a37be 100644 --- a/src/modules/tas_studio/editor/utils.rs +++ b/src/modules/tas_studio/editor/utils.rs @@ -24,6 +24,32 @@ pub trait FrameBulkExt { /// Returns a mutable reference to the yawspeed stored in the framebulk, if any. fn yawspeed_mut(&mut self) -> Option<&mut f32>; + + /// Return a reference to the starting yaw offset, target yaw offset, and acceleration stored in + /// the framebulk, if any. + fn max_accel_yaw_offset(&self) -> Option; + + // Return a mutable reference to the starting yaw offset, target yaw offset, acceleration, + // and original yaw field value stored in the framebulk, if any. + fn max_accel_yaw_offset_mut(&mut self) -> Option; +} + +pub struct MaxAccelOffsetValues<'a> { + pub start: &'a f32, + pub target: &'a f32, + pub accel: &'a f32, + #[allow(dead_code)] + pub yaw: Option<&'a f32>, + #[allow(dead_code)] + pub count: Option<&'a NonZeroU32>, +} + +pub struct MaxAccelOffsetValuesMut<'a> { + pub start: &'a mut f32, + pub target: &'a mut f32, + pub accel: &'a mut f32, + pub yaw: Option<&'a mut f32>, + pub count: Option<&'a mut NonZeroU32>, } impl FrameBulkExt for FrameBulk { @@ -88,6 +114,68 @@ impl FrameBulkExt for FrameBulk { _ => None, } } + + fn max_accel_yaw_offset(&self) -> Option { + match &self.auto_actions.movement { + Some(AutoMovement::Strafe(StrafeSettings { + type_: + StrafeType::MaxAccelYawOffset { + start, + target, + accel, + }, + dir, + })) => { + let (yaw, count) = match dir { + StrafeDir::Yaw(yaw) | StrafeDir::Line { yaw } => (Some(yaw), None), + StrafeDir::LeftRight(count) | StrafeDir::RightLeft(count) => { + (None, Some(count)) + } + _ => (None, None), + }; + + Some(MaxAccelOffsetValues { + start, + target, + accel, + yaw, + count, + }) + } + _ => None, + } + } + + fn max_accel_yaw_offset_mut(&mut self) -> Option { + match &mut self.auto_actions.movement { + Some(AutoMovement::Strafe(StrafeSettings { + type_: + StrafeType::MaxAccelYawOffset { + start, + target, + accel, + }, + dir, + })) => { + let (yaw, count) = match dir { + StrafeDir::Yaw(yaw) | StrafeDir::Line { yaw } => (Some(yaw), None), + StrafeDir::LeftRight(count) | StrafeDir::RightLeft(count) => { + (None, Some(count)) + } + _ => (None, None), + }; + + Some(MaxAccelOffsetValuesMut { + start, + target, + accel, + yaw, + count, + }) + } + _ => None, + } + } } /// Returns, for every simulated frame, the index of the frame bulk that was used for simulating diff --git a/src/modules/tas_studio/mod.rs b/src/modules/tas_studio/mod.rs index 45732e0..73fc6f6 100644 --- a/src/modules/tas_studio/mod.rs +++ b/src/modules/tas_studio/mod.rs @@ -38,7 +38,7 @@ use crate::handler; use crate::hooks::bxt::{OnTasPlaybackFrameData, BXT_IS_TAS_EDITOR_ACTIVE}; use crate::hooks::engine::con_print; use crate::hooks::{bxt, client, engine, sdl}; -use crate::modules::tas_studio::editor::CameraViewAdjustmentMode; +use crate::modules::tas_studio::editor::{CameraViewAdjustmentMode, MaxAccelYawOffsetMode}; use crate::utils::*; pub struct TasStudio; @@ -119,7 +119,7 @@ impl Module for TasStudio { && sdl::SDL_GetMouseState.is_set(marker) && bxt::BXT_TAS_LOAD_SCRIPT_FROM_STRING.is_set(marker) && bxt::BXT_IS_TAS_EDITOR_ACTIVE.is_set(marker) - && bxt::BXT_ON_TAS_PLAYBACK_FRAME.is_set(marker) + && bxt::BXT_ON_TAS_PLAYBACK_FRAME_V2.is_set(marker) && bxt::BXT_ON_TAS_PLAYBACK_STOPPED.is_set(marker) && bxt::BXT_TAS_NEW.is_set(marker) && bxt::BXT_TAS_NOREFRESH_UNTIL_LAST_FRAMES.is_set(marker) @@ -1137,6 +1137,8 @@ Values that you can toggle: - s07: right-left strafing - s40: constant turn rate to the left - s41: constant turn rate to the right +- s50: accelerated turn to the left +- s51: accelerated turn to the right - lgagst: makes autojump and ducktap trigger at optimal speed - autojump - ducktap @@ -1214,6 +1216,22 @@ fn toggle(marker: MainThreadMarker, what: String) { dir: StrafeDir::Right, type_: StrafeType::ConstYawspeed(210.), }, + "s50" => ToggleAutoActionTarget::Strafe { + dir: StrafeDir::Left, + type_: StrafeType::MaxAccelYawOffset { + start: 0., + target: 0., + accel: 0., + }, + }, + "s51" => ToggleAutoActionTarget::Strafe { + dir: StrafeDir::Right, + type_: StrafeType::MaxAccelYawOffset { + start: 0., + target: 0., + accel: 0., + }, + }, "lgagst" => ToggleAutoActionTarget::LeaveGroundAtOptimalSpeed, "autojump" => ToggleAutoActionTarget::AutoJump, "ducktap" => ToggleAutoActionTarget::DuckTap, @@ -1769,6 +1787,12 @@ pub unsafe fn on_tas_playback_frame( // TODO: prev_frame_input, which is not set here, is important. let mut strafe_state = bxt_strafe::State::new(&tracer, params, player); strafe_state.strafe_cycle_frame_count = data.strafe_cycle_frame_count; + strafe_state.max_accel_yaw_offset_value = data.max_accel_yaw_offset.value; + strafe_state.prev_max_accel_yaw_offset_start = data.max_accel_yaw_offset.start; + strafe_state.prev_max_accel_yaw_offset_target = data.max_accel_yaw_offset.target; + strafe_state.prev_max_accel_yaw_offset_accel = data.max_accel_yaw_offset.accel; + // LEFT = 0, RIGHT = 1. Very nice. + strafe_state.prev_max_accel_yaw_offset_right = data.max_accel_yaw_offset.dir == 1; // Get view angles for this frame. unsafe { @@ -2062,6 +2086,14 @@ fn add_frame_bulk_hud_lines(text: &mut Vec, bulk: &FrameBulk) { StrafeType::ConstYawspeed(yawspeed) => { write!(text, "turn rate: {yawspeed:.0}").unwrap(); } + StrafeType::MaxAccelYawOffset { + start, + target, + accel, + } => { + // The values are so tiny that only this would make it sensible. + write!(text, "{start:.4} {target:.4} {accel:.4}").unwrap(); + } } text.extend(b")\0"); } @@ -2226,6 +2258,24 @@ pub fn draw_hud(marker: MainThreadMarker, draw: &hud::Draw) { ml.line(line); } + if let Some(side_strafe_accelerated_yawspeed_adjustment) = + editor.side_strafe_accelerated_yawspeed_adjustment() + { + draw.string( + IVec2::new( + info.iWidth / 2 - info.iCharHeight * 3, + info.iHeight / 2 + info.iCharHeight * 2, + ), + match side_strafe_accelerated_yawspeed_adjustment.mode { + MaxAccelYawOffsetMode::StartAndTarget => b"Start and Target\0", + MaxAccelYawOffsetMode::Target => b"Target\0", + MaxAccelYawOffsetMode::Acceleration => b"Acceleration\0", + MaxAccelYawOffsetMode::Start => b"Start\0", + MaxAccelYawOffsetMode::StrafeDirYawField => b"Strafe Dir Yaw Field\0", + }, + ); + } + if let Some(camera_view_adjustment) = editor.camera_view_adjustment() { draw.string( IVec2::new(