use serde::{Deserialize, Serialize};
use crate::spooler::AgentInputFrame;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SkillMacro {
pub name: String,
pub steps: Vec<MacroStep>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MacroStep {
pub ticks: u32,
pub buttons: u16,
pub left_stick_x: f32,
pub left_stick_y: f32,
pub right_stick_x: f32,
pub right_stick_y: f32,
pub left_trigger: f32,
pub right_trigger: f32,
}
pub fn compile_macro(skill: &SkillMacro) -> Vec<AgentInputFrame> {
skill
.steps
.iter()
.flat_map(|step| {
let frame = AgentInputFrame {
buttons: step.buttons,
_pad: 0,
left_stick_x: step.left_stick_x.clamp(-1.0, 1.0),
left_stick_y: step.left_stick_y.clamp(-1.0, 1.0),
right_stick_x: step.right_stick_x.clamp(-1.0, 1.0),
right_stick_y: step.right_stick_y.clamp(-1.0, 1.0),
left_trigger: step.left_trigger.clamp(0.0, 1.0),
right_trigger: step.right_trigger.clamp(0.0, 1.0),
_pad_end: 0,
};
std::iter::repeat(frame).take(step.ticks as usize)
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
fn macro_step_strategy() -> impl Strategy<Value = MacroStep> {
(
0u32..=50, any::<u16>(), any::<f32>(), any::<f32>(), any::<f32>(), any::<f32>(), any::<f32>(), any::<f32>(), )
.prop_map(
|(ticks, buttons, lsx, lsy, rsx, rsy, lt, rt)| MacroStep {
ticks,
buttons,
left_stick_x: lsx,
left_stick_y: lsy,
right_stick_x: rsx,
right_stick_y: rsy,
left_trigger: lt,
right_trigger: rt,
},
)
}
fn finite_macro_step_strategy() -> impl Strategy<Value = MacroStep> {
(
0u32..=50,
any::<u16>(),
-100.0f32..100.0f32,
-100.0f32..100.0f32,
-100.0f32..100.0f32,
-100.0f32..100.0f32,
-100.0f32..100.0f32,
-100.0f32..100.0f32,
)
.prop_map(
|(ticks, buttons, lsx, lsy, rsx, rsy, lt, rt)| MacroStep {
ticks,
buttons,
left_stick_x: lsx,
left_stick_y: lsy,
right_stick_x: rsx,
right_stick_y: rsy,
left_trigger: lt,
right_trigger: rt,
},
)
}
fn skill_macro_strategy() -> impl Strategy<Value = SkillMacro> {
(
"[a-zA-Z0-9_]{1,20}",
proptest::collection::vec(macro_step_strategy(), 0..10),
)
.prop_map(|(name, steps)| SkillMacro { name, steps })
}
fn finite_skill_macro_strategy() -> impl Strategy<Value = SkillMacro> {
(
"[a-zA-Z0-9_]{1,20}",
proptest::collection::vec(finite_macro_step_strategy(), 0..10),
)
.prop_map(|(name, steps)| SkillMacro { name, steps })
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(200))]
#[test]
fn prop_macro_expansion_length(skill in skill_macro_strategy()) {
let frames = compile_macro(&skill);
let expected: u32 = skill.steps.iter().map(|s| s.ticks).sum();
prop_assert_eq!(frames.len(), expected as usize);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(200))]
#[test]
fn prop_macro_value_clamping(step in macro_step_strategy()) {
prop_assume!(!step.left_stick_x.is_nan());
prop_assume!(!step.left_stick_y.is_nan());
prop_assume!(!step.right_stick_x.is_nan());
prop_assume!(!step.right_stick_y.is_nan());
prop_assume!(!step.left_trigger.is_nan());
prop_assume!(!step.right_trigger.is_nan());
let skill = SkillMacro {
name: "test".to_string(),
steps: vec![MacroStep { ticks: 1, ..step }],
};
let frames = compile_macro(&skill);
for frame in &frames {
prop_assert!(frame.left_stick_x >= -1.0 && frame.left_stick_x <= 1.0,
"left_stick_x out of range: {}", frame.left_stick_x);
prop_assert!(frame.left_stick_y >= -1.0 && frame.left_stick_y <= 1.0,
"left_stick_y out of range: {}", frame.left_stick_y);
prop_assert!(frame.right_stick_x >= -1.0 && frame.right_stick_x <= 1.0,
"right_stick_x out of range: {}", frame.right_stick_x);
prop_assert!(frame.right_stick_y >= -1.0 && frame.right_stick_y <= 1.0,
"right_stick_y out of range: {}", frame.right_stick_y);
prop_assert!(frame.left_trigger >= 0.0 && frame.left_trigger <= 1.0,
"left_trigger out of range: {}", frame.left_trigger);
prop_assert!(frame.right_trigger >= 0.0 && frame.right_trigger <= 1.0,
"right_trigger out of range: {}", frame.right_trigger);
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(200))]
#[test]
fn prop_skill_macro_json_roundtrip(skill in finite_skill_macro_strategy()) {
let json = serde_json::to_string(&skill).expect("serialize");
let back: SkillMacro = serde_json::from_str(&json).expect("deserialize");
prop_assert_eq!(&skill, &back);
}
}
}