Skip to main content

chunkedge_scoreboard/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod components;
4use std::collections::BTreeSet;
5
6use bevy_app::prelude::*;
7use bevy_ecs::prelude::*;
8use chunkedge_server::client::{Client, OldVisibleEntityLayers, VisibleEntityLayers};
9use chunkedge_server::entity::EntityLayerId;
10use chunkedge_server::layer::UpdateLayersPreClientSet;
11use chunkedge_server::protocol::packets::play::set_display_objective_s2c::ScoreboardPosition;
12use chunkedge_server::protocol::packets::play::set_objective_s2c::{
13    ObjectiveMode, ObjectiveRenderType,
14};
15use chunkedge_server::protocol::packets::play::{
16    ResetScoreS2c, SetDisplayObjectiveS2c, SetObjectiveS2c, SetScoreS2c,
17};
18use chunkedge_server::protocol::{IntoTextComponent, VarInt, WritePacket};
19use chunkedge_server::{Despawned, EntityLayer};
20pub use components::*;
21use tracing::{debug, warn};
22
23/// Provides all necessary systems to manage scoreboards.
24pub struct ScoreboardPlugin;
25
26impl Plugin for ScoreboardPlugin {
27    fn build(&self, app: &mut App) {
28        app.configure_sets(PostUpdate, ScoreboardSet.before(UpdateLayersPreClientSet));
29
30        app.add_systems(
31            PostUpdate,
32            (
33                create_or_update_objectives,
34                display_objectives.after(create_or_update_objectives),
35            )
36                .in_set(ScoreboardSet),
37        )
38        .add_systems(
39            PostUpdate,
40            remove_despawned_objectives.in_set(ScoreboardSet),
41        )
42        .add_systems(PostUpdate, handle_new_clients.in_set(ScoreboardSet))
43        .add_systems(
44            PostUpdate,
45            update_scores
46                .after(create_or_update_objectives)
47                .after(handle_new_clients)
48                .in_set(ScoreboardSet),
49        );
50    }
51}
52
53#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
54pub struct ScoreboardSet;
55
56fn create_or_update_objectives(
57    objectives: Query<
58        (
59            Ref<Objective>,
60            &ObjectiveDisplay,
61            &ObjectiveRenderType,
62            &ObjectiveNumberFormat,
63            &EntityLayerId,
64        ),
65        Or<(Changed<ObjectiveDisplay>, Changed<ObjectiveRenderType>)>,
66    >,
67    mut layers: Query<&mut EntityLayer>,
68) {
69    for (objective, display, render_type, number_format, entity_layer) in objectives.iter() {
70        if objective.name().is_empty() {
71            warn!("Objective name is empty");
72        }
73        let mode = if objective.is_added() {
74            ObjectiveMode::Create {
75                objective_display_name: (&display.0).into_cow_text_component(),
76                render_type: *render_type,
77                number_format: number_format.0.clone(),
78            }
79        } else {
80            ObjectiveMode::Update {
81                objective_display_name: (&display.0).into_cow_text_component(),
82                render_type: *render_type,
83                number_format: number_format.0.clone(),
84            }
85        };
86
87        let Ok(mut layer) = layers.get_mut(entity_layer.0) else {
88            warn!(
89                "No layer found for entity layer ID {:?}, can't update scoreboard objective",
90                entity_layer
91            );
92            continue;
93        };
94
95        layer.write_packet(&SetObjectiveS2c {
96            objective_name: &objective.0,
97            mode,
98        });
99    }
100}
101
102/// Must occur after `create_or_update_objectives`.
103fn display_objectives(
104    objectives: Query<
105        (&Objective, Ref<ScoreboardPosition>, &EntityLayerId),
106        Changed<ScoreboardPosition>,
107    >,
108    mut layers: Query<&mut EntityLayer>,
109) {
110    for (objective, position, entity_layer) in objectives.iter() {
111        let packet = SetDisplayObjectiveS2c {
112            score_name: &objective.0,
113            position: *position,
114        };
115
116        let Ok(mut layer) = layers.get_mut(entity_layer.0) else {
117            warn!(
118                "No layer found for entity layer ID {:?}, can't update scoreboard display",
119                entity_layer
120            );
121            continue;
122        };
123
124        layer.write_packet(&packet);
125    }
126}
127
128fn remove_despawned_objectives(
129    mut commands: Commands,
130    objectives: Query<(Entity, &Objective, &EntityLayerId), With<Despawned>>,
131    mut layers: Query<&mut EntityLayer>,
132) {
133    for (entity, objective, entity_layer) in objectives.iter() {
134        commands.entity(entity).despawn();
135        let Ok(mut layer) = layers.get_mut(entity_layer.0) else {
136            warn!(
137                "No layer found for entity layer ID {:?}, can't remove scoreboard objective",
138                entity_layer
139            );
140            continue;
141        };
142
143        layer.write_packet(&SetObjectiveS2c {
144            objective_name: &objective.0,
145            mode: ObjectiveMode::Remove,
146        });
147    }
148}
149
150fn handle_new_clients(
151    mut clients: Query<
152        (&mut Client, &VisibleEntityLayers, &OldVisibleEntityLayers),
153        Or<(Added<Client>, Changed<VisibleEntityLayers>)>,
154    >,
155    objectives: Query<
156        (
157            &Objective,
158            &ObjectiveDisplay,
159            &ObjectiveRenderType,
160            &ObjectiveNumberFormat,
161            &ScoreboardPosition,
162            &ObjectiveScores,
163            &EntityLayerId,
164        ),
165        Without<Despawned>,
166    >,
167) {
168    // Remove objectives from the old visible layers that are not in the new visible
169    // layers.
170    for (mut client, visible_layers, old_visible_layers) in &mut clients {
171        let removed_layers: BTreeSet<_> = old_visible_layers
172            .get()
173            .difference(&visible_layers.0)
174            .collect();
175
176        for (objective, _, _, _, _, _, layer) in objectives.iter() {
177            if !removed_layers.contains(&layer.0) {
178                continue;
179            }
180            client.write_packet(&SetObjectiveS2c {
181                objective_name: &objective.0,
182                mode: ObjectiveMode::Remove,
183            });
184        }
185    }
186
187    // Add objectives from the new visible layers that are not in the old visible
188    // layers, or send all objectives if the client is new.
189    for (mut client, visible_layers, old_visible_layers) in &mut clients {
190        // not sure how to avoid the clone here
191        let added_layers = if client.is_added() {
192            debug!("client is new, sending all objectives");
193            visible_layers.0.clone()
194        } else {
195            visible_layers
196                .0
197                .difference(old_visible_layers.get())
198                .copied()
199                .collect::<BTreeSet<_>>()
200        };
201
202        for (objective, display, render_type, number_format, position, scores, layer) in
203            objectives.iter()
204        {
205            if !added_layers.contains(&layer.0) {
206                continue;
207            }
208
209            client.write_packet(&SetObjectiveS2c {
210                objective_name: &objective.0,
211                mode: ObjectiveMode::Create {
212                    objective_display_name: (&display.0).into_cow_text_component(),
213                    render_type: *render_type,
214                    number_format: number_format.0.clone(),
215                },
216            });
217            client.write_packet(&SetDisplayObjectiveS2c {
218                score_name: &objective.0,
219                position: *position,
220            });
221
222            for (key, score) in &scores.0 {
223                let packet = SetScoreS2c {
224                    entity_name: key,
225                    objective_name: &objective.0,
226                    value: VarInt(*score),
227                    display_name: None,
228                    number_format: None,
229                };
230
231                client.write_packet(&packet);
232            }
233        }
234    }
235}
236
237fn update_scores(
238    mut objectives: Query<
239        (
240            &Objective,
241            &ObjectiveScores,
242            &mut OldObjectiveScores,
243            &EntityLayerId,
244        ),
245        (Changed<ObjectiveScores>, Without<Despawned>),
246    >,
247    mut layers: Query<&mut EntityLayer>,
248) {
249    for (objective, scores, mut old_scores, entity_layer) in &mut objectives {
250        let Ok(mut layer) = layers.get_mut(entity_layer.0) else {
251            warn!(
252                "No layer found for entity layer ID {:?}, can't update scores",
253                entity_layer
254            );
255            continue;
256        };
257
258        for changed_key in old_scores.diff(scores) {
259            match scores.0.get(changed_key) {
260                Some(score) => {
261                    let packet = SetScoreS2c {
262                        entity_name: changed_key,
263                        objective_name: &objective.0,
264                        value: VarInt(*score),
265                        display_name: None,
266                        number_format: None,
267                    };
268
269                    layer.write_packet(&packet);
270                }
271                None => {
272                    let packet = ResetScoreS2c {
273                        entity_name: changed_key,
274                        objective_name: Some(&objective.0),
275                    };
276
277                    layer.write_packet(&packet);
278                }
279            };
280        }
281
282        old_scores.0.clone_from(&scores.0);
283    }
284}