gamebridge: Twitch Control #2
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Instant>,
|
||||
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<RwLock<State>>;
|
||||
|
@ -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 [
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue