srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/steam/src/race/breach.rs
blob: 9151774d798d72d8f0708aba20310e76e31a6497 (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
111
112
113
114
use super::RaceVisual;
use crate::lore;
use crate::racer::{DemoRace, Racer};
use bevy::prelude::*;

/// "Breach Protocol" - each runner chips through a vertical stack of
/// defensive layers ("Wardens") as they type correctly; first to clear
/// their whole stack wins. Doubles as the visual for a hacking-themed mode.
pub struct BreachProtocolPlugin;

const LAYER_COUNT: usize = 8;
const LAYER_SIZE: Vec2 = Vec2::new(140.0, 28.0);
const LAYER_GAP: f32 = 6.0;
const LANE_GAP: f32 = 240.0;

impl Plugin for BreachProtocolPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(OnEnter(RaceVisual::BreachProtocol), spawn_lanes)
            .add_systems(OnExit(RaceVisual::BreachProtocol), despawn_lanes)
            .add_systems(
                Update,
                update_layers.run_if(in_state(RaceVisual::BreachProtocol)),
            );
    }
}

#[derive(Component)]
struct BreachRoot;

#[derive(Component)]
struct WardenLayer {
    racer: Entity,
    index: usize,
}

fn spawn_lanes(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    racers: Query<(Entity, &Racer), With<DemoRace>>,
) {
    let warden_texture: Handle<Image> = asset_server.load("sprites/warden_layer.png");
    let marker_texture: Handle<Image> = asset_server.load("sprites/racer_marker.png");
    let lanes: Vec<_> = racers.iter().collect();
    let start_x = -(LANE_GAP * (lanes.len().max(1) as f32 - 1.0)) / 2.0;

    for (lane_index, (entity, racer)) in lanes.iter().enumerate() {
        let x = start_x + lane_index as f32 * LANE_GAP;
        let stack_top = -200.0 + LAYER_COUNT as f32 * (LAYER_SIZE.y + LAYER_GAP);

        commands.spawn((
            BreachRoot,
            Text2d::new(format!("{} - {}", racer.name, lore::WARDEN_LABEL)),
            TextFont {
                font_size: bevy::text::FontSize::Px(18.0),
                ..default()
            },
            TextColor(Color::srgb(0.6, 0.6, 0.65)),
            Transform::from_xyz(x, stack_top + 20.0, 0.0),
        ));

        // Shared racer marker (see racer.rs's is_local) sits above the
        // local player's own stack, same asset Signal Run uses for the
        // same purpose.
        if racer.is_local {
            commands.spawn((
                BreachRoot,
                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(x, stack_top + 44.0, 0.0),
            ));
        }

        for i in 0..LAYER_COUNT {
            let y = -200.0 + i as f32 * (LAYER_SIZE.y + LAYER_GAP);
            commands.spawn((
                BreachRoot,
                WardenLayer {
                    racer: *entity,
                    index: i,
                },
                Sprite {
                    image: warden_texture.clone(),
                    color: Color::srgb(0.15, 0.15, 0.18),
                    custom_size: Some(LAYER_SIZE),
                    ..default()
                },
                Transform::from_xyz(x, y, 0.0),
            ));
        }
    }
}

fn update_layers(racers: Query<&Racer>, mut layers: Query<(&WardenLayer, &mut Sprite)>) {
    for (layer, mut sprite) in &mut layers {
        if let Ok(racer) = racers.get(layer.racer) {
            let cleared = (racer.progress * LAYER_COUNT as f32).floor() as usize;
            sprite.color = if layer.index < cleared {
                Color::srgb(0.0, 1.0, 0.62)
            } else {
                Color::srgb(0.15, 0.15, 0.18)
            };
        }
    }
}

fn despawn_lanes(mut commands: Commands, root: Query<Entity, With<BreachRoot>>) {
    for entity in &root {
        commands.entity(entity).despawn();
    }
}