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