srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/steam/src/race/signal.rs
blob: 1046fc8dba3e30bbdc8bb51cebc4b0a11001dbf7 (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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
use super::RaceVisual;
use crate::racer::{DemoRace, Racer};
use bevy::prelude::*;

/// "Signal Run" - runners are pulses of light traveling along a circuit
/// trace toward a destination node.
pub struct SignalRunPlugin;

const TRACK_START_X: f32 = -500.0;
const TRACK_END_X: f32 = 500.0;
const TRACK_Y: f32 = 0.0;
const LANE_SPACING: f32 = 60.0;

impl Plugin for SignalRunPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(OnEnter(RaceVisual::SignalRun), spawn_pulses)
            .add_systems(OnExit(RaceVisual::SignalRun), despawn_pulses)
            .add_systems(
                Update,
                (draw_track, move_pulses).run_if(in_state(RaceVisual::SignalRun)),
            );
    }
}

#[derive(Component)]
struct SignalEntity;

#[derive(Component)]
struct Pulse {
    racer: Entity,
}

fn lane_y(lane: usize) -> f32 {
    TRACK_Y - lane as f32 * LANE_SPACING
}

fn spawn_pulses(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    racers: Query<(Entity, &Racer), With<DemoRace>>,
) {
    let pulse_texture: Handle<Image> = asset_server.load("sprites/pulse.png");
    let marker_texture: Handle<Image> = asset_server.load("sprites/racer_marker.png");

    for (lane, (entity, racer)) in racers.iter().enumerate() {
        let color = if racer.is_local {
            Color::srgb(0.0, 1.0, 0.62)
        } else {
            Color::srgb(1.0, 0.3, 0.5)
        };
        commands.spawn((
            SignalEntity,
            Pulse { racer: entity },
            Sprite {
                image: pulse_texture.clone(),
                color,
                custom_size: Some(Vec2::splat(22.0)),
                ..default()
            },
            Transform::from_xyz(TRACK_START_X, lane_y(lane), 1.0),
        ));

        // Shared racer marker (see racer.rs's is_local) sits above the
        // local player's lane, on-brand with the same asset the Breach
        // Protocol visual uses for the same purpose.
        if racer.is_local {
            commands.spawn((
                SignalEntity,
                // Tagged with Pulse too, purely so move_pulses (below) keeps
                // its x position tracking the same racer's progress - its
                // spawned y offset above the lane is left untouched since
                // that system only ever writes .x.
                Pulse { racer: entity },
                Sprite {
                    image: marker_texture.clone(),
                    color: Color::srgb(0.0, 1.0, 0.62),
                    custom_size: Some(Vec2::splat(20.0)),
                    ..default()
                },
                Transform::from_xyz(TRACK_START_X, lane_y(lane) + 26.0, 1.0),
            ));
        }
    }
}

fn move_pulses(racers: Query<&Racer>, mut pulses: Query<(&Pulse, &mut Transform)>) {
    for (pulse, mut transform) in &mut pulses {
        if let Ok(racer) = racers.get(pulse.racer) {
            transform.translation.x =
                TRACK_START_X + (TRACK_END_X - TRACK_START_X) * racer.progress;
        }
    }
}

fn draw_track(mut gizmos: Gizmos, racers: Query<&Racer, With<DemoRace>>) {
    for lane in 0..racers.iter().len() {
        let y = lane_y(lane);
        gizmos.line_2d(
            Vec2::new(TRACK_START_X, y),
            Vec2::new(TRACK_END_X, y),
            Color::srgba(0.0, 1.0, 0.62, 0.25),
        );
    }
}

fn despawn_pulses(mut commands: Commands, pulses: Query<Entity, With<SignalEntity>>) {
    for entity in &pulses {
        commands.entity(entity).despawn();
    }
}