use rusting_engine::prelude::*;
const BEAT_MS: i32 = 500;
const CHART: [(i32, i32); 16] = [
(4, 1),
(5, 2),
(6, 3),
(7, 2),
(8, 1),
(9, 1),
(10, 3),
(11, 3),
(12, 2),
(13, 1),
(14, 2),
(15, 3),
(16, 1),
(17, 3),
(18, 2),
(19, 2),
];
const PERFECT_MS: i32 = 60;
const GOOD_MS: i32 = 140;
const TRAVEL_MS: i32 = 1500;
const LANE_X: [f32; 3] = [-1.5, 0.0, 1.5];
const HIT_Z: f32 = 2.0;
const NOTE_SPEED: f32 = 8.0;
fn judge(scene: &mut GameScene<'_>, text: &str, color: [f32; 4]) {
scene.set_hud("Judgement", |hud| {
hud.text = text.into();
hud.color = color;
});
}
pub fn update(scene: &mut GameScene<'_>, time: &FrameTime) {
if scene.pressed("restart") {
scene.restart();
return;
}
if scene.counter_complete("played") || scene.counter_complete("misses") {
return;
}
let ms = (time.delta.as_secs_f32() * 1000.0).round() as i32;
let clock = scene.add_to_counter("clock_ms", ms);
if clock / BEAT_MS != (clock - ms) / BEAT_MS {
scene.flash("Hit Line");
}
let spawned = scene.counter_or("spawned", 0);
let next = CHART.get(spawned as usize).copied();
if let Some((_, lane)) =
next.filter(|(beat, _)| clock >= beat * BEAT_MS - TRAVEL_MS)
{
let name = format!("Note {}", spawned + 1);
let x = LANE_X[lane as usize - 1];
scene.spawn_copy("Note Template", &name, [x, 0.2, -20.0]);
scene.set_visible(&name, true);
scene.add_class(&name, "note");
scene.set_counter("spawned", spawned + 1);
}
let notes: Vec<(String, usize, i32)> = scene
.in_class("note")
.into_iter()
.map(|name| {
let n: usize = name["Note ".len()..].parse().unwrap_or(1);
let early = CHART[n - 1].0 * BEAT_MS - clock;
(name, n - 1, early)
})
.collect();
let mut hit = Vec::new();
for lane in 1..=3 {
if !scene.pressed(&format!("lane_{lane}")) {
continue;
}
scene.flash(&format!("Pad {lane}"));
let Some((name, _, early)) = notes
.iter()
.filter(|(_, n, early)| {
CHART[*n].1 == lane && early.abs() <= GOOD_MS
})
.min_by_key(|(_, n, _)| *n)
else {
continue;
};
let (points, text, color) = if early.abs() <= PERFECT_MS {
(100, "PERFECT", [1.0, 0.85, 0.2, 1.0])
} else {
(50, "GOOD", [0.4, 0.9, 1.0, 1.0])
};
scene.despawn(name);
scene.add_to_counter("score", points);
scene.add_to_counter("played", 1);
judge(scene, text, color);
hit.push(name.clone());
}
for (name, _, early) in notes {
if hit.contains(&name) {
continue;
}
if early < -GOOD_MS {
scene.despawn(&name);
scene.add_to_counter("misses", 1);
scene.add_to_counter("played", 1);
scene.add_trauma("Game Camera", 0.3);
judge(scene, "MISS", [1.0, 0.3, 0.3, 1.0]);
continue;
}
let mut at = scene.object(&name).position();
at[2] = HIT_Z - early as f32 / 1000.0 * NOTE_SPEED;
scene.object(&name).set_position(at);
}
}
rusting_game!(update);