From bf3042cbf05c6648b71e925b1afa45728c084f2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wi=C5=9Bnia?= Date: Sun, 23 Feb 2025 18:10:36 +0100 Subject: [PATCH] Initial commit --- Cargo.toml | 15 ++++++ build.rs | 3 ++ src/main.rs | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++ ui/main.slint | 99 +++++++++++++++++++++++++++++++++++++ 4 files changed, 249 insertions(+) create mode 100644 Cargo.toml create mode 100644 build.rs create mode 100644 src/main.rs create mode 100644 ui/main.slint diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c6c0258 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "shockosc-rs" +version = "0.1.0" +edition = "2021" + +[dependencies] +dotenv = "0.15.0" +rand = "0.9.0" +rosc = "0.10.1" +rzap = "0.2.1" +slint = "1.9.2" +tokio = {version = "1.43.0", features = ["macros", "rt-multi-thread"] } + +[build-dependencies] +slint-build = "1.8.0" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..0ae96db --- /dev/null +++ b/build.rs @@ -0,0 +1,3 @@ +fn main() { + slint_build::compile("ui/main.slint").expect("Slint build failed"); +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..dab3fb6 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,132 @@ +use std::{net::SocketAddrV4, str::FromStr, sync::Arc, time::Duration}; + +use rand::{rng, Rng}; +use rosc::{decoder, encoder, OscMessage, OscPacket, OscType}; +use rzap::api_builder::OpenShockAPIBuilder; +use tokio::{net::UdpSocket, sync::Mutex}; + +slint::include_modules!(); + +struct Values { + pub min_shocking: i32, + pub max_shocking: i32, + pub min_time: i32, + pub max_time: i32, + pub cooldown: i32, +} +impl Values { + pub fn new() -> Self { + Values{ min_shocking: 1, max_shocking: 20, min_time: 300, max_time: 3000, cooldown: 5000 } + } +} + +#[tokio::main] +async fn main() { + let openshock_token = dotenv::var("OPENSHOCK_TOKEN").expect("missing OPENSHOCK_TOKEN"); + let app_name = env!("CARGO_PKG_NAME"); + let app_version = env!("CARGO_PKG_VERSION"); + + assert_ne!(openshock_token, ""); + + let openshock_api = OpenShockAPIBuilder::new() + .with_app(app_name.to_string(), Some(app_version.to_string())) + .with_default_api_token(openshock_token) + .build() + .unwrap(); + + let state = Arc::new(Mutex::new(false)); + let state_cloned = state.clone(); + let sock = UdpSocket::bind(SocketAddrV4::from_str("127.0.0.1:9001").unwrap()).await.unwrap(); + tokio::task::spawn(async move { + println!("starting lisening"); + loop { + let mut buf = vec![0; 1024]; + let msg = sock.recv_from(&mut buf).await; + if let Err(_) = msg { + println!("Couldnt recv packet"); + + continue; + } + let osc_msg = decoder::decode_udp(&buf); + if let Err(_) = osc_msg { + println!("Couldnt decode packet"); + continue; + } + match osc_msg.unwrap().1 { + OscPacket::Message(msg) => { + if !msg.addr.eq("/avatar/parameters/ShockOsc/leftleg") { + continue; + } + let binding = msg.args[0].clone().bool(); + if let Some(state) = binding { + *state_cloned.lock().await = state; + } + }, + OscPacket::Bundle(_) => {} + } + } + }); + let send_addr: SocketAddrV4 = SocketAddrV4::from_str("127.0.0.1:9000").unwrap(); + let sock = UdpSocket::bind(SocketAddrV4::from_str("0.0.0.0:64000").unwrap()).await.unwrap(); + let values = Arc::new(Mutex::new(Values::new())); + let vals = values.clone(); + tokio::task::spawn(async move { + let mut enter: i32 = 0; + let mut cooldown: i32 = 0; + let mut task_interval = tokio::time::interval(Duration::from_millis(10)); + loop { + let state = *state.lock().await; + if state { + if enter > 200 { + enter = 0; + // Shock + let vals = vals.lock().await; + cooldown = vals.cooldown; + let intensity = rng().random_range(vals.min_shocking..vals.max_shocking); + let dura = (rng().random_range(vals.min_time..vals.max_time) as f32 / 100.0).round() as i32 *100; + + println!("Shocking {intensity}% for {dura}ms"); + let msg; + match openshock_api.post_control("ef258419-fb00-422e-9609-70cc6dfd64b7".to_string(), rzap::data_type::ControlType::Shock, intensity.try_into().unwrap(), dura.try_into().unwrap(), None).await { + Ok(_) => msg = format!("[ShockOSC-rs]\nIntensity: {intensity}\nDuration: {}s", (dura as f32)/1000.0), + Err(_) => msg = "[ShockOSC-rs]\nShocker is paused".to_string(), + } + let msg_buf = encoder::encode(&OscPacket::Message(OscMessage { + addr: "/chatbox/input".to_string(), + args: vec![OscType::String(msg), rosc::OscType::Bool(true),rosc::OscType::Bool(false)], + })) + .unwrap(); + sock.send_to(&msg_buf, send_addr).await.unwrap(); + }else if cooldown <= 0 { + enter += 10; + } + }else { + enter = 0; + } + if cooldown > 0 { + cooldown -= 10; + } + task_interval.tick().await; + } + }); + + let ui = ShockerConfig::new().unwrap(); + let vals = values.clone(); + let ui_handle = ui.as_weak(); + ui.on_update(move || { + let ui = ui_handle.unwrap(); + let min_shocking = ui.get_min_shock(); + let max_shocking = ui.get_max_shock(); + let min_time = ui.get_min_time(); + let max_time = ui.get_max_time(); + let cooldown = ui.get_cooldown(); + let new_vals = Values { min_shocking, max_shocking, min_time, max_time, cooldown }; + let vals = vals.clone(); + tokio::task::spawn(async move { + *vals.lock().await = new_vals; + }); + }); + + ui.run().unwrap(); +} + diff --git a/ui/main.slint b/ui/main.slint new file mode 100644 index 0000000..01b7a2e --- /dev/null +++ b/ui/main.slint @@ -0,0 +1,99 @@ +import { Button, VerticalBox, Slider, HorizontalBox, GroupBox } from "std-widgets.slint"; + +export component ShockerConfig inherits Window { + in-out property min_shock: 10; + in-out property max_shock: 50; + in-out property min_time: 300; + in-out property max_time: 3000; + in-out property cooldown: 5000; + width: 400px; + height: 400px; + title: "ShockOSC-rs"; + callback update(); + VerticalBox { + GroupBox { + title: "Shock Time"; + VerticalBox { + Text { + text: min_time + " ms"; + } + Slider { + minimum: 300; + value: min_time; + maximum: max_time; + step: 100; + changed => { + min_time = round(self.value / 100) * 100; + root.update(); + } + } + } + VerticalBox { + Text { + text: max_time + " ms"; + } + Slider { + minimum: min_time; + value: max_time; + maximum: 30000; + step: 100; + changed => { + max_time = round(self.value / 100) * 100; + root.update(); + } + } + } + } + GroupBox { + title: "Shock Intensity"; + VerticalBox { + Text { + text: min_shock + " %"; + } + Slider { + minimum: 1; + value: min_shock; + maximum: max_shock; + step: 1; + changed => { + min_shock = round(self.value); + root.update(); + } + } + } + VerticalBox { + Text { + text: max_shock + " %"; + } + Slider { + minimum: min_shock; + value: max_shock; + maximum: 100; + step: 1; + changed => { + max_shock = round(self.value); + root.update(); + } + } + } + } + GroupBox { + title: "Cooldown"; + VerticalBox { + Text { + text: cooldown + " ms"; + } + Slider { + minimum: 1; + value: cooldown; + maximum: 30000; + step: 1; + changed => { + cooldown = round(self.value / 100)*100; + root.update(); + } + } + } + } + } +} \ No newline at end of file