Initial commit

This commit is contained in:
Jakub Wiśnia 2025-02-23 18:10:36 +01:00
parent 623f78355d
commit bf3042cbf0
4 changed files with 249 additions and 0 deletions

15
Cargo.toml Normal file
View File

@ -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"

3
build.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
slint_build::compile("ui/main.slint").expect("Slint build failed");
}

132
src/main.rs Normal file
View File

@ -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();
}

99
ui/main.slint Normal file
View File

@ -0,0 +1,99 @@
import { Button, VerticalBox, Slider, HorizontalBox, GroupBox } from "std-widgets.slint";
export component ShockerConfig inherits Window {
in-out property <int> min_shock: 10;
in-out property <int> max_shock: 50;
in-out property <int> min_time: 300;
in-out property <int> max_time: 3000;
in-out property <int> 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();
}
}
}
}
}
}