Skip to main content

cuqueclicker_lib/ui/
effects.rs

1use ratatui::prelude::*;
2
3use crate::game::state::{MisclickParticle, Particle, ParticleKind, biscuit_frac_to_screen};
4
5const PARTICLE_LIFE_F: f32 = 20.0;
6const MISCLICK_LIFE_F: f32 = 8.0;
7
8/// Render auto/click/golden/confetti particles. Positions are resolved
9/// against the CURRENT `biscuit` rect from each particle's fractional
10/// anchor, so they travel with the biscuit on zoom/resize. `area` is the
11/// visual clip (same as the resolution target).
12pub fn draw_particles(frame: &mut Frame, biscuit: Rect, particles: &[Particle]) {
13    if biscuit.width == 0 || biscuit.height == 0 {
14        return;
15    }
16    let buf = frame.buffer_mut();
17    for p in particles {
18        let (col, row) = biscuit_frac_to_screen(p.frac_x, p.frac_y, biscuit);
19        if row < biscuit.y || row >= biscuit.y + biscuit.height {
20            continue;
21        }
22        if col < biscuit.x || col >= biscuit.x + biscuit.width {
23            continue;
24        }
25        let t = (p.life as f32 / PARTICLE_LIFE_F).clamp(0.0, 1.0);
26        let style = particle_style(p.kind, t);
27        buf.set_string(col, row, &p.text, style);
28    }
29}
30
31/// Style picker per kind. `t` ∈ `[0,1]` is "remaining life fraction" so
32/// 1.0 = freshly spawned, 0.0 = about to despawn. All kinds fade out as t
33/// drops; the difference is base color and BOLD weight.
34fn particle_style(kind: ParticleKind, t: f32) -> Style {
35    let dim = (t * 255.0) as u8;
36    match kind {
37        // Default click / auto: white that fades to red — same as before.
38        ParticleKind::Click | ParticleKind::Auto => Style::default()
39            .fg(Color::Rgb(255, dim, dim))
40            .add_modifier(Modifier::BOLD),
41        // High-power click (Frenzy, big mults): warm yellow → red, BOLD,
42        // so a +777 reads distinctly from a stream of +1s.
43        ParticleKind::ClickBig => Style::default()
44            .fg(Color::Rgb(255, 255 - (255 - dim) / 2, dim))
45            .add_modifier(Modifier::BOLD),
46        // Golden catch: gold for Lucky-style labels, fades to white.
47        ParticleKind::Golden => Style::default()
48            .fg(Color::Rgb(255, 230, (dim / 2).max(80)))
49            .add_modifier(Modifier::BOLD),
50        // Confetti glyphs: cyan/magenta-ish so they read as celebratory
51        // detail rather than another `+N` floating up.
52        ParticleKind::Confetti => Style::default()
53            .fg(Color::Rgb(((1.0 - t) * 200.0) as u8 + 55, dim, 255))
54            .add_modifier(Modifier::BOLD),
55    }
56}
57
58/// Render screen-anchored "misclick" tap particles. These ignore the biscuit
59/// rect — they live at the exact column/row the click missed at, so dead-zone
60/// clicks visibly *register* even if no game state changed.
61pub fn draw_misclicks(frame: &mut Frame, particles: &[MisclickParticle]) {
62    let buf = frame.buffer_mut();
63    let area = buf.area;
64    for m in particles {
65        if m.col < area.x
66            || m.col >= area.x + area.width
67            || m.row < area.y
68            || m.row >= area.y + area.height
69        {
70            continue;
71        }
72        let t = (m.life as f32 / MISCLICK_LIFE_F).clamp(0.0, 1.0);
73        let v = (t * 180.0) as u8 + 60;
74        let style = Style::default().fg(Color::Rgb(v, v, v));
75        buf.set_string(m.col, m.row, "·", style);
76    }
77}