valence_server/
abilities.rs

1use bevy_app::prelude::*;
2use bevy_ecs::prelude::*;
3use derive_more::{Deref, DerefMut};
4pub use valence_protocol::packets::play::player_abilities_s2c::PlayerAbilitiesFlags;
5use valence_protocol::packets::play::{PlayerAbilitiesS2c, UpdatePlayerAbilitiesC2s};
6use valence_protocol::{GameMode, WritePacket};
7
8use crate::client::{update_game_mode, Client, UpdateClientsSet};
9use crate::event_loop::{EventLoopPreUpdate, PacketEvent};
10
11/// [`Component`] that stores the player's flying speed ability.
12///
13/// [`Default`] value: `0.05`.
14#[derive(Component, Deref, DerefMut)]
15pub struct FlyingSpeed(pub f32);
16
17impl Default for FlyingSpeed {
18    fn default() -> Self {
19        Self(0.05)
20    }
21}
22
23/// [`Component`] that stores the player's field of view modifier ability.
24/// The lower the value, the higher the field of view.
25///
26/// [`Default`] value: `0.1`.
27#[derive(Component, Deref, DerefMut)]
28pub struct FovModifier(pub f32);
29
30impl Default for FovModifier {
31    fn default() -> Self {
32        Self(0.1)
33    }
34}
35
36/// Send if the client sends [`UpdatePlayerAbilitiesC2s::StartFlying`]
37#[derive(Event)]
38pub struct PlayerStartFlyingEvent {
39    pub client: Entity,
40}
41
42/// Send if the client sends [`UpdatePlayerAbilitiesC2s::StopFlying`]
43#[derive(Event)]
44pub struct PlayerStopFlyingEvent {
45    pub client: Entity,
46}
47
48/// Order of execution:
49/// 1. `update_game_mode`: Watch [`GameMode`] changes => Send
50///    `GameStateChangeS2c` to update the client's gamemode
51///
52/// 2. `update_client_player_abilities`: Watch [`PlayerAbilitiesFlags`],
53///    [`FlyingSpeed`] and [`FovModifier`] changes => Send
54///    [`PlayerAbilitiesS2c`] to update the client's abilities
55///
56/// 3. `update_player_abilities`: Watch [`GameMode`] changes => Update
57///    [`PlayerAbilitiesFlags`] according to the [`GameMode`]
58///
59/// 4. `update_server_player_abilities`: Watch [`UpdatePlayerAbilitiesC2s`]
60///    packets => Update [`PlayerAbilitiesFlags`] according to the packet
61pub struct AbilitiesPlugin;
62
63impl Plugin for AbilitiesPlugin {
64    fn build(&self, app: &mut App) {
65        app.add_event::<PlayerStartFlyingEvent>()
66            .add_event::<PlayerStopFlyingEvent>()
67            .add_systems(
68                PostUpdate,
69                (
70                    update_client_player_abilities,
71                    update_player_abilities.before(update_client_player_abilities),
72                )
73                    .in_set(UpdateClientsSet)
74                    .after(update_game_mode),
75            )
76            .add_systems(EventLoopPreUpdate, update_server_player_abilities);
77    }
78}
79
80fn update_client_player_abilities(
81    mut clients_query: Query<
82        (
83            &mut Client,
84            &PlayerAbilitiesFlags,
85            &FlyingSpeed,
86            &FovModifier,
87        ),
88        Or<(
89            Changed<PlayerAbilitiesFlags>,
90            Changed<FlyingSpeed>,
91            Changed<FovModifier>,
92        )>,
93    >,
94) {
95    for (mut client, flags, flying_speed, fov_modifier) in &mut clients_query {
96        client.write_packet(&PlayerAbilitiesS2c {
97            flags: *flags,
98            flying_speed: flying_speed.0,
99            fov_modifier: fov_modifier.0,
100        })
101    }
102}
103
104/// /!\ This system does not trigger change detection on
105/// [`PlayerAbilitiesFlags`]
106fn update_player_abilities(
107    mut player_start_flying_event_writer: EventWriter<PlayerStartFlyingEvent>,
108    mut player_stop_flying_event_writer: EventWriter<PlayerStopFlyingEvent>,
109    mut client_query: Query<(Entity, &mut PlayerAbilitiesFlags, &GameMode), Changed<GameMode>>,
110) {
111    for (entity, mut mut_flags, gamemode) in &mut client_query {
112        let flags = mut_flags.bypass_change_detection();
113        match gamemode {
114            GameMode::Creative => {
115                flags.set_invulnerable(true);
116                flags.set_allow_flying(true);
117                flags.set_instant_break(true);
118            }
119            GameMode::Spectator => {
120                flags.set_invulnerable(true);
121                flags.set_allow_flying(true);
122                flags.set_instant_break(false);
123                flags.set_flying(true);
124                player_start_flying_event_writer.send(PlayerStartFlyingEvent { client: entity });
125            }
126            GameMode::Survival => {
127                flags.set_invulnerable(false);
128                flags.set_allow_flying(false);
129                flags.set_instant_break(false);
130                flags.set_flying(false);
131                player_stop_flying_event_writer.send(PlayerStopFlyingEvent { client: entity });
132            }
133            GameMode::Adventure => {
134                flags.set_invulnerable(false);
135                flags.set_allow_flying(false);
136                flags.set_instant_break(false);
137                flags.set_flying(false);
138                player_stop_flying_event_writer.send(PlayerStopFlyingEvent { client: entity });
139            }
140        }
141    }
142}
143
144/// /!\ This system does not trigger change detection on
145/// [`PlayerAbilitiesFlags`]
146fn update_server_player_abilities(
147    mut packet_events: EventReader<PacketEvent>,
148    mut player_start_flying_event_writer: EventWriter<PlayerStartFlyingEvent>,
149    mut player_stop_flying_event_writer: EventWriter<PlayerStopFlyingEvent>,
150    mut client_query: Query<&mut PlayerAbilitiesFlags>,
151) {
152    for packets in packet_events.read() {
153        if let Some(pkt) = packets.decode::<UpdatePlayerAbilitiesC2s>() {
154            if let Ok(mut mut_flags) = client_query.get_mut(packets.client) {
155                let flags = mut_flags.bypass_change_detection();
156                match pkt {
157                    UpdatePlayerAbilitiesC2s::StartFlying => {
158                        flags.set_flying(true);
159                        player_start_flying_event_writer.send(PlayerStartFlyingEvent {
160                            client: packets.client,
161                        });
162                    }
163                    UpdatePlayerAbilitiesC2s::StopFlying => {
164                        flags.set_flying(false);
165                        player_stop_flying_event_writer.send(PlayerStopFlyingEvent {
166                            client: packets.client,
167                        });
168                    }
169                }
170            }
171        }
172    }
173}