diff --git a/src/hooks/bxt.rs b/src/hooks/bxt.rs index 9d62fe4..13246d9 100644 --- a/src/hooks/bxt.rs +++ b/src/hooks/bxt.rs @@ -1,7 +1,7 @@ //! Bunnymod XT. use std::ffi::CString; -use std::os::raw::{c_char, c_int}; +use std::os::raw::{c_char, c_float, c_int}; use std::ptr::NonNull; use hltas::HLTAS; @@ -27,6 +27,8 @@ pub static BXT_TAS_NOREFRESH_UNTIL_LAST_FRAMES: Pointer = Pointer::empty(b"bxt_tas_studio_norefresh_override\0"); +pub static BXT_TAS_STUDIO_FREECAM_SET_ORIGIN: Pointer = + Pointer::empty(b"bxt_tas_studio_freecam_set_origin\0"); static POINTERS: &[&dyn PointerTrait] = &[ &BXT_ON_TAS_PLAYBACK_FRAME, @@ -37,6 +39,7 @@ static POINTERS: &[&dyn PointerTrait] = &[ &BXT_TAS_NEW, &BXT_TAS_NOREFRESH_UNTIL_LAST_FRAMES, &BXT_TAS_STUDIO_NOREFRESH_OVERRIDE, + &BXT_TAS_STUDIO_FREECAM_SET_ORIGIN, ]; #[cfg(unix)] diff --git a/src/modules/tas_studio/editor/mod.rs b/src/modules/tas_studio/editor/mod.rs index 411de74..3638047 100644 --- a/src/modules/tas_studio/editor/mod.rs +++ b/src/modules/tas_studio/editor/mod.rs @@ -139,6 +139,13 @@ pub struct Editor { /// Adjustment to insert a camera line. insert_camera_line_adjustment: Option, + /// Adjustment to a camera line depending on the context. + /// + /// Adjusts the yaw of `target_yaw x` line. + /// + /// Adjusts the change mode and final value of `change x to x over x` line. + camera_view_adjustment: Option, + /// Smoothing window size in seconds. smooth_window_s: f32, /// Smoothing small window size in seconds. @@ -193,14 +200,20 @@ struct ExtraCameraEditorFrameData { smoothing_input_region: Option<(usize, usize)>, /// Index into `script.lines` of a change frame that ends on this frame. change_line_that_ends_here: Vec, - /// Index of a frame where the change line which starts on this frame ends. - change_ends_at: Vec, + /// Index of a change line and a frame where the frame is end frame of change line. + change_ends_at: Vec, /// Index into `script.lines` of a camera frame that starts or ends on this frame. camera_line_that_starts_or_ends_here: Vec, /// Index into `script.lines` of a camera frame that starts on this frame. camera_line_that_starts_here: Vec, } +#[derive(Debug, Default, Clone)] +struct LineChangeEndsAt { + line_idx: usize, + end_frame_idx: usize, +} + /// Data for handling adjustment done by pressing and dragging the mouse. #[derive(Debug, Clone, Copy)] struct MouseAdjustment { @@ -296,6 +309,22 @@ struct InsertCameraLineAdjustment { did_split: bool, } +/// `CameraViewAdjustmentMode::Alt` adjusts different things depending on the line type. For +/// target_yaw lines, it adjusts the velocity_lock angle. +#[derive(Debug, Clone, Copy)] +pub enum CameraViewAdjustmentMode { + Yaw, + Pitch, + Alt, +} + +/// Data for handling camera view adjustment. +#[derive(Debug, Clone, Copy)] +pub struct CameraViewAdjustment { + pub mode: CameraViewAdjustmentMode, + camera_line_idx: usize, +} + #[derive(Debug, Default, Clone, Copy)] pub struct KeyboardState { /// Whether the "faster" key is pressed. @@ -374,6 +403,14 @@ impl ManualOpError { } } +#[derive(Clone, Copy)] +pub struct Callbacks<'a> { + pub enable_mouse_look: &'a dyn Fn(), + pub disable_mouse_look: &'a dyn Fn(), + pub get_viewangles: &'a dyn Fn() -> [f32; 3], + pub change_view_origin: &'a dyn Fn(Vec3), +} + impl Editor { pub fn open_db(mut db: Db) -> eyre::Result { let branches = db.branches()?; @@ -415,6 +452,7 @@ impl Editor { first_shown_frame_idx: 0, hovered_line_idx: None, insert_camera_line_adjustment: None, + camera_view_adjustment: None, smooth_window_s: 0.15, smooth_small_window_s: 0.03, smooth_small_window_multiplier: 3., @@ -482,6 +520,10 @@ impl Editor { self.undo_log.len() } + pub fn camera_view_adjustment(&self) -> Option { + self.camera_view_adjustment + } + pub fn set_in_camera_editor(&mut self, value: bool) { if self.in_camera_editor == value { return; @@ -687,7 +729,10 @@ impl Editor { .push(line_idx); branch.extra_cam[frame_idx] .change_ends_at - .push(end_frame_idx); + .push(LineChangeEndsAt { + line_idx, + end_frame_idx, + }); branch.extra_cam[end_frame_idx] .camera_line_that_starts_or_ends_here .push(line_idx); @@ -726,6 +771,7 @@ impl Editor { || self.side_strafe_yawspeed_adjustment.is_some() || self.adjacent_side_strafe_yawspeed_adjustment.is_some() || self.insert_camera_line_adjustment.is_some() + || self.camera_view_adjustment.is_some() } /// Updates the editor state. @@ -736,6 +782,7 @@ impl Editor { mouse: MouseState, keyboard: KeyboardState, deadline: Instant, + callbacks: Callbacks, ) -> eyre::Result<()> { let _span = info_span!("Editor::tick").entered(); @@ -750,6 +797,7 @@ impl Editor { self.tick_adjacent_side_strafe_yawspeed_adjustment(mouse, keyboard)?; self.tick_insert_camera_line_adjustment(mouse, keyboard)?; + self.tick_camera_view_adjustment(mouse, self.prev_mouse_state, callbacks)?; // Predict any frames that need prediction. // @@ -1229,6 +1277,71 @@ impl Editor { }); } } + + if let Some(hovered_line_idx) = self.hovered_line_idx { + // For camera editor camera line editing. + if mouse.buttons.is_right_down() && !self.prev_mouse_state.buttons.is_right_down() { + assert!(self.camera_view_adjustment.is_none()); + assert!(!self.is_any_adjustment_active()); + + let line = &self.branch().branch.script.lines[hovered_line_idx]; + + // Find the end frame of change line or camera line so we can change origin. + // In case of change line we need to know the end frame. + // Camera line we can just use the `line_first_frame_idx`. + let line_first_frame_idx = line_first_frame_idx(self.script()) + .nth(hovered_line_idx) + .expect("invalid line index"); + + let change_line_end_frame_index = &self.branch().extra_cam + [line_first_frame_idx] + .change_ends_at + .iter() + .find(|LineChangeEndsAt { line_idx, .. }| *line_idx == hovered_line_idx) + .map(|LineChangeEndsAt { end_frame_idx, .. }| *end_frame_idx); + + let end_frame_index = + if let Some(change_line_end_frame_index) = change_line_end_frame_index { + *change_line_end_frame_index + } else { + // So, for camera line, it is 1 off but not for the change line. + // If anything crashes, it is this one. + line_first_frame_idx - 1 + }; + + // Enable mouse look so we can look around to have a good reference. + (callbacks.enable_mouse_look)(); + + // Change origin to the position of the end of that line. + let mut vieworg = self.branch().frames[end_frame_index].state.player.pos; + + vieworg[2] += 28.; + if self.branch().frames[end_frame_index].state.player.ducking { + vieworg[2] -= 16.; + } + + (callbacks.change_view_origin)(vieworg); + + // Figuring whether the line is generally changing pitch or yaw. + let mode = match line { + Line::VectorialStrafingConstraints(_) => CameraViewAdjustmentMode::Yaw, + Line::Change(Change { target, .. }) => match target { + ChangeTarget::Yaw + | ChangeTarget::VectorialStrafingYaw + | ChangeTarget::VectorialStrafingYawOffset => { + CameraViewAdjustmentMode::Yaw + } + ChangeTarget::Pitch => CameraViewAdjustmentMode::Pitch, + }, + _ => CameraViewAdjustmentMode::Alt, + }; + + self.camera_view_adjustment = Some(CameraViewAdjustment { + mode, + camera_line_idx: hovered_line_idx, + }); + } + } } // Finally, update the previous mouse and keyboard state. @@ -1832,6 +1945,130 @@ impl Editor { Ok(()) } + pub fn tick_camera_view_adjustment( + &mut self, + mouse: MouseState, + prev_mouse: MouseState, + callbacks: Callbacks, + ) -> eyre::Result<()> { + let Some(CameraViewAdjustment { + ref mut mode, + camera_line_idx, + }) = &mut self.camera_view_adjustment + else { + return Ok(()); + }; + + // Switching line mode. + // Should be a click. + if mouse.buttons.is_left_down() && !prev_mouse.buttons.is_left_down() { + // Cycling + // Currently there is no `pitch` line... + *mode = match *mode { + CameraViewAdjustmentMode::Yaw => CameraViewAdjustmentMode::Pitch, + CameraViewAdjustmentMode::Pitch => CameraViewAdjustmentMode::Alt, + CameraViewAdjustmentMode::Alt => CameraViewAdjustmentMode::Yaw, + } + } + + // Right mouse button hold means adjusting. + if mouse.buttons.is_right_down() { + // Keep looking around + return Ok(()); + } + + let branch = &mut self.branches[self.branch_idx]; + + let viewangles = (callbacks.get_viewangles)(); + let line = &mut branch.branch.script.lines[*camera_line_idx]; + + // Disable as soon as possible in case of failure or lock. + (callbacks.disable_mouse_look)(); + + let mut buffer = Vec::new(); + hltas::write::gen_line(&mut buffer, line)?; + let from = String::from_utf8(buffer) + .expect("Line serialization should never produce invalid UTF-8"); + + match line { + Line::VectorialStrafingConstraints(constraints) => match constraints { + // Since there is no `pitch` line, + // there won't be any attempts to change `target_yaw` into `pitch`. + // `target_yaw velocity_lock` will be changed to `target_yaw x` + VectorialStrafingConstraints::VelocityYawLocking { .. } => match mode { + CameraViewAdjustmentMode::Yaw => { + *constraints = VectorialStrafingConstraints::Yaw { + yaw: viewangles[1], + tolerance: 0., + }; + } + CameraViewAdjustmentMode::Pitch => (), + CameraViewAdjustmentMode::Alt => (), + }, + VectorialStrafingConstraints::Yaw { yaw, .. } => { + match mode { + CameraViewAdjustmentMode::Yaw => *yaw = viewangles[1], + CameraViewAdjustmentMode::Pitch => (), + // Alt mode will change this to `target_yaw velocity_lock`. + CameraViewAdjustmentMode::Alt => { + *constraints = + VectorialStrafingConstraints::VelocityYawLocking { tolerance: 0. }; + } + } + } + _ => (), + }, + Line::Change(Change { + target, + final_value, + .. + }) => { + match target { + ChangeTarget::Yaw | ChangeTarget::VectorialStrafingYaw => match mode { + CameraViewAdjustmentMode::Yaw => *final_value = viewangles[1], + CameraViewAdjustmentMode::Pitch => { + *target = ChangeTarget::Pitch; + *final_value = viewangles[0]; + } + CameraViewAdjustmentMode::Alt => (), + }, + ChangeTarget::Pitch => match mode { + CameraViewAdjustmentMode::Yaw => { + // Pitch will always do `target_yaw` change. + *target = ChangeTarget::VectorialStrafingYaw; + *final_value = viewangles[1]; + } + CameraViewAdjustmentMode::Pitch => *final_value = viewangles[0], + CameraViewAdjustmentMode::Alt => (), + }, + ChangeTarget::VectorialStrafingYawOffset => { + if matches!(mode, CameraViewAdjustmentMode::Yaw) { + *final_value = viewangles[1]; + } + } + } + } + _ => (), + }; + + let mut buffer = Vec::new(); + hltas::write::gen_line(&mut buffer, line)?; + let to = String::from_utf8(buffer) + .expect("Line serialization should never produce invalid UTF-8"); + + let op = Operation::Replace { + line_idx: *camera_line_idx, + from, + to, + }; + self.apply_operation(op)?; + + // Setting back to `None` to indicate finished operation. + self.camera_view_adjustment = None; + + Ok(()) + } + pub fn cancel_ongoing_adjustments(&mut self) { if let Some(adjustment) = self.frame_count_adjustment.take() { let original_value = adjustment.original_value; @@ -2027,6 +2264,11 @@ impl Editor { self.invalidate(curr_frame_idx); } + + // Camera adjustment only takes effects after the key is released. + // So there is no need to add this. + // if let Some(CameraViewAdjustment { mode, starting_frame_idx, camera_line_idx }) = + // self.camera_view_adjustment.take() {} } /// Stores already-applied operation. diff --git a/src/modules/tas_studio/mod.rs b/src/modules/tas_studio/mod.rs index cf2c498..1a762db 100644 --- a/src/modules/tas_studio/mod.rs +++ b/src/modules/tas_studio/mod.rs @@ -22,7 +22,7 @@ use hltas::HLTAS; use self::editor::operation::Key; use self::editor::toggle_auto_action::ToggleAutoActionTarget; use self::editor::utils::{bulk_and_first_frame_idx, FrameBulkExt}; -use self::editor::KeyboardState; +use self::editor::{Callbacks, KeyboardState}; use self::remote::{AccurateFrame, PlayRequest}; use super::commands::{Command, Commands}; use super::cvars::CVar; @@ -38,6 +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::utils::*; pub struct TasStudio; @@ -122,6 +123,7 @@ impl Module for TasStudio { && bxt::BXT_TAS_NEW.is_set(marker) && bxt::BXT_TAS_NOREFRESH_UNTIL_LAST_FRAMES.is_set(marker) && bxt::BXT_TAS_STUDIO_NOREFRESH_OVERRIDE.is_set(marker) + && bxt::BXT_TAS_STUDIO_FREECAM_SET_ORIGIN.is_set(marker) && TriangleDrawing.is_enabled(marker) && Commands.is_enabled(marker) && PlayerMovementTracing.is_enabled(marker) @@ -1811,7 +1813,20 @@ pub fn draw(marker: MainThreadMarker, tri: &TriangleApi) { }; let deadline = Instant::now() + Duration::from_millis(20); - if let Err(err) = editor.tick(&tracer, world_to_screen, mouse, keyboard, deadline) { + let callbacks = Callbacks { + enable_mouse_look: &|| enable_mouse_look(marker), + disable_mouse_look: &|| disable_mouse_look(marker), + get_viewangles: &|| unsafe { get_viewangles(marker) }, + change_view_origin: &|origin| change_view_origin(marker, origin), + }; + if let Err(err) = editor.tick( + &tracer, + world_to_screen, + mouse, + keyboard, + deadline, + callbacks, + ) { con_print(marker, &format!("Error ticking the TAS editor: {err}\n")); *state = State::Idle; return; @@ -2033,6 +2048,20 @@ pub fn draw_hud(marker: MainThreadMarker, draw: &hud::Draw) { for line in text.split_inclusive(|c| *c == b'\0') { ml.line(line); } + + if let Some(camera_view_adjustment) = editor.camera_view_adjustment() { + draw.string( + IVec2::new( + info.iWidth / 2 - info.iCharHeight * 3, + info.iHeight / 2 + info.iCharHeight * 2, + ), + match camera_view_adjustment.mode { + CameraViewAdjustmentMode::Yaw => b"Camera view change: Yaw\0", + CameraViewAdjustmentMode::Pitch => b"Camera view change: Pitch\0", + CameraViewAdjustmentMode::Alt => b"Camera view change: Alt\0", + }, + ); + } } fn add_hovered_frame_hud_lines(text: &mut Vec, frame_idx: usize, frame: &Frame) { @@ -2158,3 +2187,25 @@ pub unsafe fn maybe_enable_freecam(marker: MainThreadMarker) { engine::prepend_command(marker, "bxt_freecam 1\n"); } } + +fn enable_mouse_look(marker: MainThreadMarker) { + sdl::set_relative_mouse_mode(marker, true); + client::activate_mouse(marker, true); +} + +fn disable_mouse_look(marker: MainThreadMarker) { + sdl::set_relative_mouse_mode(marker, false); + client::activate_mouse(marker, false); +} + +unsafe fn get_viewangles(marker: MainThreadMarker) -> [f32; 3] { + let mut view_angles = [0.; 3]; + + engine::hudGetViewAngles.get(marker)(&mut view_angles); + + view_angles +} + +fn change_view_origin(marker: MainThreadMarker, origin: Vec3) { + unsafe { bxt::BXT_TAS_STUDIO_FREECAM_SET_ORIGIN.get(marker)(origin.into()) }; +}