srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/steam/src/racer.rs
blob: 790833e9649c6dce8ec6a492019ecd0432c68b8c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
use crate::typing::TypingSession;
use bevy::prelude::*;

/// A participant in a race visual. Decoupled from networking on purpose --
/// the three visualizations below only ever read this component, so they
/// work identically whether progress comes from a local typing session, a
/// bot, or the real multiplayer connection.
#[derive(Component, Debug, Clone)]
pub struct Racer {
    pub name: String,
    pub is_local: bool,
    /// 0.0 (start) to 1.0 (finished).
    pub progress: f32,
    pub wpm: f32,
    pub accuracy: f32,
    pub mistakes: u32,
}

/// Marks the set of racer entities that belong to the current demo race, so
/// visualization plugins can query just their own participants.
#[derive(Component)]
pub struct DemoRace;

pub struct DemoProgressPlugin;

impl Plugin for DemoProgressPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(Startup, spawn_demo_racers)
            .add_systems(Update, drive_demo_progress);
    }
}

fn spawn_demo_racers(mut commands: Commands) {
    // Every other racer on screen comes from multiplayer_plugin.rs, spawned
    // and updated from the actual server. This one is "You" - driven for
    // real by typing.rs once a race is underway (see update_local_racer),
    // and by drive_demo_progress's idle animation before that.
    commands.spawn((
        Racer {
            name: "You".into(),
            is_local: true,
            progress: 0.0,
            wpm: 0.0,
            accuracy: 100.0,
            mistakes: 0,
        },
        DemoRace,
    ));
}

// Idle animation only, for the brief window before a real race has started
// (typing.rs's TypingSession isn't active yet) - once real keystrokes are
// driving the local racer, this backs off so it doesn't fight that.
fn drive_demo_progress(time: Res<Time>, session: Res<TypingSession>, mut racers: Query<&mut Racer, With<DemoRace>>) {
    if session.is_active() {
        return;
    }
    for mut racer in &mut racers {
        if !racer.is_local || racer.progress >= 1.0 {
            continue;
        }
        let base_speed = 0.12;
        racer.progress = (racer.progress + base_speed * time.delta_secs()).min(1.0);
        racer.wpm = 60.0 + (racer.progress * 40.0);
    }
}