Skip to main content

chunkedge_server/
abilities.rs

1use bevy_app::prelude::*;
2use bevy_ecs::prelude::*;
3pub use chunkedge_protocol::packets::play::player_abilities_s2c::PlayerAbilitiesFlags;
4use chunkedge_protocol::packets::play::{PlayerAbilitiesC2s, PlayerAbilitiesS2c};
5use chunkedge_protocol::{GameMode, WritePacket};
6use derive_more::{Deref, DerefMut};
7
8use crate::client::{Client, UpdateClientsSet, update_game_mode};
9use crate::event_loop::{EventLoopPreUpdate, PacketMessage};
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 [`PlayerAbilitiesC2s::StartFlying`]
37#[derive(Message)]
38pub struct PlayerStartFlyingMessage {
39    pub client: Entity,
40}
41
42/// Send if the client sends [`PlayerAbilitiesC2s::StopFlying`]
43#[derive(Message)]
44pub struct PlayerStopFlyingMessage {
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 [`PlayerAbilitiesC2s`] packets =>
60///    Update [`PlayerAbilitiesFlags`] according to the packet
61pub struct AbilitiesPlugin;
62
63impl Plugin for AbilitiesPlugin {
64    fn build(&self, app: &mut App) {
65        app.add_message::<PlayerStartFlyingMessage>()
66            .add_message::<PlayerStopFlyingMessage>()
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_message_writer: MessageWriter<PlayerStartFlyingMessage>,
108    mut player_stop_flying_message_writer: MessageWriter<PlayerStopFlyingMessage>,
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_message_writer
125                    .write(PlayerStartFlyingMessage { client: entity });
126            }
127            GameMode::Survival => {
128                flags.set_invulnerable(false);
129                flags.set_allow_flying(false);
130                flags.set_instant_break(false);
131                flags.set_flying(false);
132                player_stop_flying_message_writer.write(PlayerStopFlyingMessage { client: entity });
133            }
134            GameMode::Adventure => {
135                flags.set_invulnerable(false);
136                flags.set_allow_flying(false);
137                flags.set_instant_break(false);
138                flags.set_flying(false);
139                player_stop_flying_message_writer.write(PlayerStopFlyingMessage { client: entity });
140            }
141        }
142    }
143}
144
145/// /!\ This system does not trigger change detection on
146/// [`PlayerAbilitiesFlags`]
147fn update_server_player_abilities(
148    mut packet_messages: MessageReader<PacketMessage>,
149    mut player_start_flying_message_writer: MessageWriter<PlayerStartFlyingMessage>,
150    mut player_stop_flying_message_writer: MessageWriter<PlayerStopFlyingMessage>,
151    mut client_query: Query<&mut PlayerAbilitiesFlags>,
152) {
153    for packets in packet_messages.read() {
154        if let Some(pkt) = packets.decode::<PlayerAbilitiesC2s>()
155            && let Ok(mut mut_flags) = client_query.get_mut(packets.client)
156        {
157            let flags = mut_flags.bypass_change_detection();
158            match pkt {
159                PlayerAbilitiesC2s::StartFlying => {
160                    flags.set_flying(true);
161                    player_start_flying_message_writer.write(PlayerStartFlyingMessage {
162                        client: packets.client,
163                    });
164                }
165                PlayerAbilitiesC2s::StopFlying => {
166                    flags.set_flying(false);
167                    player_stop_flying_message_writer.write(PlayerStopFlyingMessage {
168                        client: packets.client,
169                    });
170                }
171            }
172        }
173    }
174}