diff --git a/gamebridge/src/au.rs b/gamebridge/src/au.rs new file mode 100644 index 0000000..4a71221 --- /dev/null +++ b/gamebridge/src/au.rs @@ -0,0 +1,99 @@ +#[derive(Copy, Clone)] +pub(crate) struct Lerper { + extended_tick: u64, + lerp_time: f64, + goal: i64, + pub(crate) scalar: i64, + max: i64, + min: i64, +} + +impl Lerper { + pub(crate) fn init(lerp_time: f64, max: i64, min: i64, goal: i64) -> Lerper { + Lerper { + extended_tick: 0, + lerp_time: lerp_time, + goal: goal, + scalar: 0, // I hope to GOD that 0 is the resting point + max: max, + min: min, + } + } + + pub(crate) fn add(&mut self, new_scalar: i64) { + self.scalar += new_scalar; + } + + pub(crate) fn update(&mut self, new_scalar: i64) { + self.scalar = new_scalar; + } + + pub(crate) fn apply(&mut self, now: u64) -> i64 { + let scalar = self.scalar; + self.scalar = match scalar { + _ if scalar == self.goal => self.goal, + _ if scalar == self.max => { + self.extended_tick = now; + scalar -1 + }, + _ if scalar == self.min => { + self.extended_tick = now; + scalar - 1 + }, + _ => { + let t = (now - self.extended_tick) as f64 / self.lerp_time; + lerp(self.scalar, 0, t) + }, + }; + + if self.scalar >= self.max { + return self.max; + } + + if self.scalar <= self.min { + return self.min; + } + + log::info!("before: {}, after: {}", scalar, self.scalar); + self.scalar + } + + pub(crate) fn pressed(&mut self, threshold: i64) -> bool { + if self.scalar <= threshold { + self.scalar = 0; + } + + self.scalar >= threshold + } +} + +fn lerp(start: i64, end: i64, t: f64) -> i64 { + (start as f64 * (1.0 - t) + (end as f64) * t) as i64 +} + +#[cfg(test)] +mod test { + #[test] + fn lerp_scale() { + for case in [(0.1, 10), (0.5, 31)].iter() { + let t = case.0; + let start = 127.0 * t; + assert_eq!(super::lerp(start as i64, 0, t), case.1); + } + } + + #[test] + fn lerper() { + use super::Lerper; + let mut lerper = Lerper::init(15.0, 127, -128, 0); + + for case in [(127, 3, 126), (100, 8, 66)].iter() { + let scalar = case.0; + let now = case.1; + let want = case.2; + + lerper.update(scalar); + assert_eq!(lerper.apply(now), want); + } + } +} diff --git a/gamebridge/src/controller.rs b/gamebridge/src/controller.rs index 3daa1d4..5458d39 100644 --- a/gamebridge/src/controller.rs +++ b/gamebridge/src/controller.rs @@ -20,12 +20,6 @@ bitflags! { } } -impl HiButtons { - pub(crate) fn clear(&mut self) { - self.bits = 0; - } -} - bitflags! { // 0x0001 C-Right // 0x0002 C-Left @@ -45,42 +39,3 @@ bitflags! { const L_BUTTON = 0x20; } } - -impl LoButtons { - pub(crate) fn clear(&mut self) { - self.bits = 0; - } -} - -pub(crate) fn test(st: crate::MTState) { - let mut lo: LoButtons = LoButtons::NONE; - let mut hi: HiButtons = HiButtons::NONE; - - loop { - use std::{thread::sleep, time::Duration}; - let one_second = Duration::new(1, 0); - - hi = HiButtons::A_BUTTON | HiButtons::START; - - { - println!("pressing a + start"); - let mut data = st.write().unwrap(); - data.controller[0] = hi.bits as u8; - data.controller[1] = lo.bits as u8; - } - - sleep(one_second); - - hi.clear(); - lo.clear(); - - { - println!("releasing a + start"); - let mut data = st.write().unwrap(); - data.controller[0] = hi.bits as u8; - data.controller[1] = lo.bits as u8; - } - - sleep(one_second); - } -} diff --git a/gamebridge/src/main.rs b/gamebridge/src/main.rs index 48a5543..38c1e25 100644 --- a/gamebridge/src/main.rs +++ b/gamebridge/src/main.rs @@ -1,9 +1,12 @@ #[macro_use] extern crate bitflags; +pub(crate) mod au; pub(crate) mod controller; pub(crate) mod twitch; +use crate::au::Lerper; + use anyhow::{anyhow, Result}; use log::{debug, error, info, warn}; use std::{ @@ -12,15 +15,22 @@ use std::{ str::from_utf8, sync::{Arc, RwLock}, thread::spawn, - time::Instant, }; -#[derive(Debug)] pub(crate) struct State { - controller: [u8; 4], - last_got: Box, - ok: bool, frame: u64, + + stickx: Lerper, + sticky: Lerper, + a_button: Lerper, + b_button: Lerper, + z_button: Lerper, + r_button: Lerper, + start: Lerper, + c_left: Lerper, + c_right: Lerper, + c_up: Lerper, + c_down: Lerper, } pub(crate) type MTState = Arc>; @@ -32,12 +42,24 @@ fn main() -> Result<()> { let mut vblank = File::open("vblank")?; let mut input = OpenOptions::new().write(true).open("input")?; + const STICK_LERP_TIME: f64 = 330.0; // 330 frames to lerp stick positions down to 0 + const BUTTON_LERP_TIME: f64 = 20.0; // 20 frames to lerp button inputs down to 0 + let st = { let st = State { - controller: [0; 4], - last_got: Box::new(Instant::now()), - ok: true, frame: 0, + + stickx: Lerper::init(STICK_LERP_TIME, 127, -128, 0), + sticky: Lerper::init(STICK_LERP_TIME, 127, -128, 0), + a_button: Lerper::init(BUTTON_LERP_TIME, 64, -1, 0), + b_button: Lerper::init(BUTTON_LERP_TIME, 64, -1, 0), + z_button: Lerper::init(BUTTON_LERP_TIME / 4.0, 64, -1, 0), // z button is special + r_button: Lerper::init(BUTTON_LERP_TIME, 64, -1, 0), + start: Lerper::init(BUTTON_LERP_TIME / 4.0, 64, -1, 0), // z button is special + c_left: Lerper::init(BUTTON_LERP_TIME, 64, -1, 0), + c_right: Lerper::init(BUTTON_LERP_TIME, 64, -1, 0), + c_up: Lerper::init(BUTTON_LERP_TIME, 64, -1, 0), + c_down: Lerper::init(BUTTON_LERP_TIME, 64, -1, 0), }; Arc::new(RwLock::new(st)) @@ -50,10 +72,6 @@ fn main() -> Result<()> { spawn(move || twitch::run(st)); } - const LERP_TIME: f64 = 330.0; // 330 frames to lerp stick positions down to 0 - let mut xmax_frame: u64 = 0; - let mut ymax_frame: u64 = 0; - loop { let mut data = [0; 3]; debug!("waiting for vblank"); @@ -61,59 +79,63 @@ fn main() -> Result<()> { let str = from_utf8(&data)?; debug!("got data: {}", str); + let mut controller = [0; 4]; + match str { "OK\n" => { - let mut data = st.write().unwrap(); - data.frame += 1; - - let mut stickx = data.controller[2] as i8; - let mut sticky = data.controller[3] as i8; - - let dist = stick_distance(stickx, sticky); - if dist <= 10 { - stickx = 0; - sticky = 0; - xmax_frame = 0; - ymax_frame = 0; + { + let mut data = st.write().unwrap(); + data.frame += 1; } - stickx = match stickx { - 0 => stickx, - 127 => { - xmax_frame = data.frame; - stickx - 10 - }, - -128 => { - xmax_frame = data.frame; - stickx + 10 - }, - _ => { - let t = (data.frame - xmax_frame) as f64 / (LERP_TIME as f64); - lerp(stickx, 0, t) - }, - }; + let mut data = st.write().unwrap(); + let frame = data.frame + 1; - sticky = match sticky { - 0 => sticky, - 127 => { - ymax_frame = data.frame; - sticky - 10 - }, - -128 => { - ymax_frame = data.frame; - sticky + 10 - }, - _ => { - let t = (data.frame - ymax_frame) as f64 / (LERP_TIME as f64); - lerp(sticky, 0, t) - }, - }; + //data.stickx.update(data.controller[2] as i64); + //data.sticky.update(data.controller[3] as i64); + let mut stickx_scalar = data.stickx.apply(frame) as i8; + let mut sticky_scalar = data.sticky.apply(frame) as i8; - input.write(&data.controller)?; - data.controller[0] = 0; - data.controller[1] = 0; - data.controller[2] = stickx as u8; - data.controller[3] = sticky as u8; + let dist = stick_distance(stickx_scalar, sticky_scalar); + if dist <= 10 { + stickx_scalar = 0; + sticky_scalar = 0; + } + + use controller::{HiButtons, LoButtons}; + + let mut hi = HiButtons::NONE; + let mut lo = LoButtons::NONE; + const BUTTON_PUSH_THRESHOLD: i64 = 16; + + // high buttons + data.a_button.apply(frame); + if data.a_button.pressed(BUTTON_PUSH_THRESHOLD) { hi = hi | HiButtons::A_BUTTON; } + data.b_button.apply(frame); + if data.b_button.pressed(BUTTON_PUSH_THRESHOLD) { hi = hi | HiButtons::B_BUTTON; } + data.z_button.apply(frame); + if data.z_button.pressed(BUTTON_PUSH_THRESHOLD) { hi = hi | HiButtons::Z_BUTTON; } + data.start.apply(frame); + if data.start.pressed(BUTTON_PUSH_THRESHOLD) { hi = hi | HiButtons::START; } + info!("start: {}", data.start.scalar); + + data.r_button.apply(frame); + if data.r_button.pressed(BUTTON_PUSH_THRESHOLD) { lo = lo | LoButtons::R_BUTTON; } + data.c_up.apply(frame); + if data.c_up.pressed(BUTTON_PUSH_THRESHOLD) { lo = lo | LoButtons::C_UP; } + data.c_down.apply(frame); + if data.c_down.pressed(BUTTON_PUSH_THRESHOLD) { lo = lo | LoButtons::C_DOWN; } + data.c_left.apply(frame); + if data.c_left.pressed(BUTTON_PUSH_THRESHOLD) { lo = lo | LoButtons::C_LEFT; } + data.c_right.apply(frame); + if data.c_right.pressed(BUTTON_PUSH_THRESHOLD) { lo = lo | LoButtons::C_RIGHT; } + + controller[0] = hi.bits() as u8; + controller[1] = lo.bits() as u8; + controller[2] = stickx_scalar as u8; + controller[3] = sticky_scalar as u8; + + input.write(&controller)?; } "BYE" => { warn!("asked to exit by the game"); @@ -127,10 +149,6 @@ fn main() -> Result<()> { } } -fn lerp(start: i8, end: i8, t: f64) -> i8 { - (start as f64 * (1.0 - t) + (end as f64) * t) as i8 -} - fn stick_distance(x: i8, y: i8) -> i8 { let x = (x as f64).powi(2); let y = (y as f64).powi(2); @@ -139,15 +157,6 @@ fn stick_distance(x: i8, y: i8) -> i8 { #[cfg(test)] mod test { - #[test] - fn lerp_scale() { - for case in [(0.1, 10), (0.5, 31)].iter() { - let t = case.0; - let start = 127.0 * t; - assert_eq!(crate::lerp(start as i8, 0, t), case.1); - } - } - #[test] fn stick_distance() { for case in [ diff --git a/gamebridge/src/twitch.rs b/gamebridge/src/twitch.rs index 795e1bc..068173b 100644 --- a/gamebridge/src/twitch.rs +++ b/gamebridge/src/twitch.rs @@ -1,7 +1,4 @@ -use crate::{ - controller::{HiButtons, LoButtons}, - MTState, -}; +use crate::MTState; use tokio::stream::StreamExt as _; use twitchchat::{events, Control, Dispatcher, Runner, Status}; @@ -80,46 +77,28 @@ async fn run_loop( eprintln!("{} left {}", msg.name, msg.channel); } Some(msg) = pmsg.next() => { - let mut hi = HiButtons::NONE; - let mut lo = LoButtons::NONE; - let mut stickx: i8 = 0; - let mut sticky: i8 = 0; + let chatline = msg.data.to_string(); + let chatline = chatline.to_ascii_lowercase(); + let mut data = st.write().unwrap(); - { - let data = st.read().unwrap(); - stickx = data.controller[2] as i8; - sticky = data.controller[3] as i8; - } - - let mut data = msg.data.to_string(); - let data = data.to_ascii_lowercase(); - - match data.as_str() { - "a" => hi = hi | HiButtons::A_BUTTON, - "b" => hi = hi | HiButtons::B_BUTTON, - "z" => hi = hi | HiButtons::Z_BUTTON, - "r" => lo = lo | LoButtons::R_BUTTON, - "cup" => lo = lo | LoButtons::C_UP, - "cdown" => lo = lo | LoButtons::C_DOWN, - "cleft" => lo = lo | LoButtons::C_LEFT, - "cright" => lo = lo | LoButtons::C_RIGHT, - "start" => hi = hi | HiButtons::START, - "up" => sticky = 127, - "down" => sticky = -128, - "left" => stickx = -128, - "right" => stickx = 127, - "stop" => {stickx = 0; sticky = 0;}, + match chatline.as_str() { + "a" => data.a_button.add(1024), + "b" => data.b_button.add(1024), + "z" => data.z_button.add(1024), + "r" => data.r_button.add(1024), + "cup" => data.c_up.add(1024), + "cdown" => data.c_down.add(1024), + "cleft" => data.c_left.add(1024), + "cright" => data.c_right.add(1024), + "start" => data.start.add(1024), + "up" => data.stickx.add(127), + "down" => data.sticky.add(-128), + "left" => data.stickx.add(-128), + "right" => data.stickx.add(127), + "stop" => {data.stickx.update(0); data.sticky.update(0);}, _ => {}, } - { - let mut data = st.write().unwrap(); - data.controller[0] = hi.bits() as u8; - data.controller[1] = lo.bits() as u8; - data.controller[2] = stickx as u8; - data.controller[3] = sticky as u8; - } - eprintln!("[{}] {}: {}", msg.channel, msg.name, msg.data); }