1#![doc = include_str!("../README.md")]
2
3pub mod event;
4
5use std::borrow::Cow;
6use std::io::Write;
7use std::time::{SystemTime, UNIX_EPOCH};
8
9use bevy_app::prelude::*;
10use bevy_ecs::prelude::*;
11use bevy_ecs::system::SystemParam;
12pub use bevy_hierarchy;
13use bevy_hierarchy::{Children, HierarchyPlugin, Parent};
14use derive_more::{Deref, DerefMut};
15use event::{handle_advancement_tab_change, AdvancementTabChangeEvent};
16use rustc_hash::FxHashMap;
17use valence_server::client::{Client, FlushPacketsSet, SpawnClientsSet};
18use valence_server::protocol::packets::play::{
19 advancement_update_s2c as packet, SelectAdvancementTabS2c,
20};
21use valence_server::protocol::{
22 anyhow, packet_id, Encode, Packet, PacketSide, PacketState, RawBytes, VarInt, WritePacket,
23};
24use valence_server::{Ident, ItemStack, Text};
25
26pub struct AdvancementPlugin;
27
28#[derive(SystemSet, Clone, Copy, Eq, PartialEq, Hash, Debug)]
29pub struct WriteAdvancementPacketToClientsSet;
30
31#[derive(SystemSet, Clone, Copy, Eq, PartialEq, Hash, Debug)]
32pub struct WriteAdvancementToCacheSet;
33
34impl Plugin for AdvancementPlugin {
35 fn build(&self, app: &mut bevy_app::App) {
36 app.add_plugins(HierarchyPlugin)
37 .configure_sets(
38 PostUpdate,
39 (
40 WriteAdvancementPacketToClientsSet.before(FlushPacketsSet),
41 WriteAdvancementToCacheSet.before(WriteAdvancementPacketToClientsSet),
42 ),
43 )
44 .add_event::<AdvancementTabChangeEvent>()
45 .add_systems(
46 PreUpdate,
47 (
48 add_advancement_update_component_to_new_clients.after(SpawnClientsSet),
49 handle_advancement_tab_change,
50 ),
51 )
52 .add_systems(
53 PostUpdate,
54 (
55 update_advancement_cached_bytes.in_set(WriteAdvancementToCacheSet),
56 send_advancement_update_packet.in_set(WriteAdvancementPacketToClientsSet),
57 ),
58 );
59 }
60}
61
62#[derive(Bundle)]
67pub struct AdvancementBundle {
68 pub advancement: Advancement,
69 pub requirements: AdvancementRequirements,
70 pub cached_bytes: AdvancementCachedBytes,
71}
72
73fn add_advancement_update_component_to_new_clients(
74 mut commands: Commands,
75 query: Query<Entity, Added<Client>>,
76) {
77 for client in query.iter() {
78 commands
79 .entity(client)
80 .insert(AdvancementClientUpdate::default());
81 }
82}
83
84#[derive(SystemParam, Debug)]
85struct UpdateAdvancementCachedBytesQuery<'w, 's> {
86 advancement_id_query: Query<'w, 's, &'static Advancement>,
87 criteria_query: Query<'w, 's, &'static AdvancementCriteria>,
88}
89
90impl UpdateAdvancementCachedBytesQuery<'_, '_> {
91 fn write(
92 &self,
93 a_identifier: &Advancement,
94 a_requirements: &AdvancementRequirements,
95 a_display: Option<&AdvancementDisplay>,
96 a_children: Option<&Children>,
97 a_parent: Option<&Parent>,
98 w: impl Write,
99 ) -> anyhow::Result<()> {
100 let Self {
101 advancement_id_query,
102 criteria_query,
103 } = self;
104
105 let mut pkt = packet::Advancement {
106 parent_id: None,
107 display_data: None,
108 criteria: vec![],
109 requirements: vec![],
110 sends_telemetry_data: false,
111 };
112
113 if let Some(a_parent) = a_parent {
114 let a_identifier = advancement_id_query.get(a_parent.get())?;
115 pkt.parent_id = Some(a_identifier.0.borrowed());
116 }
117
118 if let Some(a_display) = a_display {
119 pkt.display_data = Some(packet::AdvancementDisplay {
120 title: Cow::Borrowed(&a_display.title),
121 description: Cow::Borrowed(&a_display.description),
122 icon: &a_display.icon,
123 frame_type: VarInt(a_display.frame_type as i32),
124 flags: a_display.flags(),
125 background_texture: a_display.background_texture.as_ref().map(|v| v.borrowed()),
126 x_coord: a_display.x_coord,
127 y_coord: a_display.y_coord,
128 });
129 }
130
131 if let Some(a_children) = a_children {
132 for a_child in a_children {
133 let Ok(c_identifier) = criteria_query.get(*a_child) else {
134 continue;
135 };
136 pkt.criteria.push((c_identifier.0.borrowed(), ()));
137 }
138 }
139
140 for requirements in &a_requirements.0 {
141 let mut requirements_p = vec![];
142 for requirement in requirements {
143 let c_identifier = criteria_query.get(*requirement)?;
144 requirements_p.push(c_identifier.0.as_str());
145 }
146 pkt.requirements.push(packet::AdvancementRequirements {
147 requirement: requirements_p,
148 });
149 }
150
151 (&a_identifier.0, pkt).encode(w)
152 }
153}
154
155fn update_advancement_cached_bytes(
156 mut query: Query<
157 (
158 &Advancement,
159 &AdvancementRequirements,
160 &mut AdvancementCachedBytes,
161 Option<&AdvancementDisplay>,
162 Option<&Children>,
163 Option<&Parent>,
164 ),
165 Or<(
166 Changed<AdvancementDisplay>,
167 Changed<Children>,
168 Changed<Parent>,
169 Changed<AdvancementRequirements>,
170 )>,
171 >,
172 update_advancement_cached_bytes_query: UpdateAdvancementCachedBytesQuery,
173) {
174 for (a_identifier, a_requirements, mut a_bytes, a_display, a_children, a_parent) in &mut query {
175 a_bytes.0.clear();
176 update_advancement_cached_bytes_query
177 .write(
178 a_identifier,
179 a_requirements,
180 a_display,
181 a_children,
182 a_parent,
183 &mut a_bytes.0,
184 )
185 .expect("Failed to write an advancement");
186 }
187}
188
189#[derive(SystemParam, Debug)]
190#[allow(clippy::type_complexity)]
191pub(crate) struct SingleAdvancementUpdateQuery<'w, 's> {
192 advancement_bytes: Query<'w, 's, &'static AdvancementCachedBytes>,
193 advancement_id: Query<'w, 's, &'static Advancement>,
194 criteria: Query<'w, 's, &'static AdvancementCriteria>,
195 parent: Query<'w, 's, &'static Parent>,
196}
197
198#[derive(Debug)]
199pub(crate) struct AdvancementUpdateEncodeS2c<'w, 's, 'a> {
200 client_update: AdvancementClientUpdate,
201 queries: &'a SingleAdvancementUpdateQuery<'w, 's>,
202}
203
204impl Encode for AdvancementUpdateEncodeS2c<'_, '_, '_> {
205 fn encode(&self, w: impl Write) -> anyhow::Result<()> {
206 let SingleAdvancementUpdateQuery {
207 advancement_bytes: advancement_bytes_query,
208 advancement_id: advancement_id_query,
209 criteria: criteria_query,
210 parent: parent_query,
211 } = self.queries;
212
213 let AdvancementClientUpdate {
214 new_advancements,
215 remove_advancements,
216 progress,
217 reset,
218 ..
219 } = &self.client_update;
220
221 let mut pkt = packet::GenericAdvancementUpdateS2c {
222 reset: *reset,
223 advancement_mapping: vec![],
224 identifiers: vec![],
225 progress_mapping: vec![],
226 };
227
228 for new_advancement in new_advancements {
229 let a_cached_bytes = advancement_bytes_query.get(*new_advancement)?;
230 pkt.advancement_mapping
231 .push(RawBytes(a_cached_bytes.0.as_slice()));
232 }
233
234 for remove_advancement in remove_advancements {
235 let a_identifier = advancement_id_query.get(*remove_advancement)?;
236 pkt.identifiers.push(a_identifier.0.borrowed());
237 }
238
239 let mut progress_mapping: FxHashMap<Entity, Vec<(Entity, Option<i64>)>> =
240 FxHashMap::default();
241 for progress in progress {
242 let a = parent_query.get(progress.0)?;
243 progress_mapping
244 .entry(a.get())
245 .and_modify(|v| v.push(*progress))
246 .or_insert(vec![*progress]);
247 }
248
249 for (a, c_progresses) in progress_mapping {
250 let a_identifier = advancement_id_query.get(a)?;
251 let mut c_progresses_p = vec![];
252 for (c, c_progress) in c_progresses {
253 let c_identifier = criteria_query.get(c)?;
254 c_progresses_p.push(packet::AdvancementCriteria {
255 criterion_identifier: c_identifier.0.borrowed(),
256 criterion_progress: c_progress,
257 });
258 }
259 pkt.progress_mapping
260 .push((a_identifier.0.borrowed(), c_progresses_p));
261 }
262
263 pkt.encode(w)
264 }
265}
266
267impl Packet for AdvancementUpdateEncodeS2c<'_, '_, '_> {
268 const ID: i32 = packet_id::ADVANCEMENT_UPDATE_S2C;
269 const NAME: &'static str = "AdvancementUpdateEncodeS2c";
270 const SIDE: PacketSide = PacketSide::Clientbound;
271 const STATE: PacketState = PacketState::Play;
272}
273
274#[allow(clippy::type_complexity)]
275fn send_advancement_update_packet(
276 mut client: Query<(&mut AdvancementClientUpdate, &mut Client)>,
277 update_single_query: SingleAdvancementUpdateQuery,
278) {
279 for (mut advancement_client_update, mut client) in &mut client {
280 match advancement_client_update.force_tab_update {
281 ForceTabUpdate::None => {}
282 ForceTabUpdate::First => {
283 client.write_packet(&SelectAdvancementTabS2c { identifier: None })
284 }
285 ForceTabUpdate::Spec(spec) => {
286 if let Ok(a_identifier) = update_single_query.advancement_id.get(spec) {
287 client.write_packet(&SelectAdvancementTabS2c {
288 identifier: Some(a_identifier.0.borrowed()),
289 });
290 }
291 }
292 }
293
294 if ForceTabUpdate::None != advancement_client_update.force_tab_update {
295 advancement_client_update.force_tab_update = ForceTabUpdate::None;
296 }
297
298 if advancement_client_update.new_advancements.is_empty()
299 && advancement_client_update.progress.is_empty()
300 && advancement_client_update.remove_advancements.is_empty()
301 && !advancement_client_update.reset
302 {
303 continue;
304 }
305
306 let advancement_client_update = std::mem::replace(
307 advancement_client_update.as_mut(),
308 AdvancementClientUpdate {
309 reset: false,
310 ..Default::default()
311 },
312 );
313
314 client.write_packet(&AdvancementUpdateEncodeS2c {
315 queries: &update_single_query,
316 client_update: advancement_client_update,
317 });
318 }
319}
320
321#[derive(Component, Deref)]
323pub struct Advancement(Ident<Cow<'static, str>>);
324
325impl Advancement {
326 pub fn new(ident: Ident<Cow<'static, str>>) -> Advancement {
327 Self(ident)
328 }
329
330 pub fn get(&self) -> &Ident<Cow<'static, str>> {
331 &self.0
332 }
333}
334
335#[derive(Clone, Copy)]
336pub enum AdvancementFrameType {
337 Task,
338 Challenge,
339 Goal,
340}
341
342#[derive(Component)]
344pub struct AdvancementDisplay {
345 pub title: Text,
346 pub description: Text,
347 pub icon: ItemStack,
348 pub frame_type: AdvancementFrameType,
349 pub show_toast: bool,
350 pub hidden: bool,
351 pub background_texture: Option<Ident<Cow<'static, str>>>,
352 pub x_coord: f32,
353 pub y_coord: f32,
354}
355
356impl AdvancementDisplay {
357 pub(crate) fn flags(&self) -> i32 {
358 let mut flags = 0;
359 flags |= i32::from(self.background_texture.is_some());
360 flags |= i32::from(self.show_toast) << 1;
361 flags |= i32::from(self.hidden) << 2;
362 flags
363 }
364}
365
366#[derive(Component, Deref)]
368pub struct AdvancementCriteria(Ident<Cow<'static, str>>);
369
370impl AdvancementCriteria {
371 pub fn new(ident: Ident<Cow<'static, str>>) -> Self {
372 Self(ident)
373 }
374
375 pub fn get(&self) -> &Ident<Cow<'static, str>> {
376 &self.0
377 }
378}
379
380#[derive(Component, Default, Deref, DerefMut)]
384pub struct AdvancementRequirements(pub Vec<Vec<Entity>>);
385
386#[derive(Component, Default)]
387pub struct AdvancementCachedBytes(pub(crate) Vec<u8>);
388
389#[derive(Default, Debug, PartialEq)]
390pub enum ForceTabUpdate {
391 #[default]
392 None,
393 First,
394 Spec(Entity),
396}
397
398#[derive(Component, Debug)]
399pub struct AdvancementClientUpdate {
400 pub new_advancements: Vec<Entity>,
402 pub remove_advancements: Vec<Entity>,
404 pub progress: Vec<(Entity, Option<i64>)>,
407 pub force_tab_update: ForceTabUpdate,
409 pub reset: bool,
413}
414
415impl Default for AdvancementClientUpdate {
416 fn default() -> Self {
417 Self {
418 new_advancements: vec![],
419 remove_advancements: vec![],
420 progress: vec![],
421 force_tab_update: ForceTabUpdate::default(),
422 reset: true,
423 }
424 }
425}
426
427impl AdvancementClientUpdate {
428 pub(crate) fn walk_advancements(
429 root: Entity,
430 children_query: &Query<&Children>,
431 advancement_check_query: &Query<(), With<Advancement>>,
432 func: &mut impl FnMut(Entity),
433 ) {
434 func(root);
435 if let Ok(children) = children_query.get(root) {
436 for child in children {
437 let child = *child;
438 if advancement_check_query.get(child).is_ok() {
439 Self::walk_advancements(child, children_query, advancement_check_query, func);
440 }
441 }
442 }
443 }
444
445 pub fn send_advancements(
447 &mut self,
448 root: Entity,
449 children_query: &Query<&Children>,
450 advancement_check_query: &Query<(), With<Advancement>>,
451 ) {
452 Self::walk_advancements(root, children_query, advancement_check_query, &mut |e| {
453 self.new_advancements.push(e)
454 });
455 }
456
457 pub fn remove_advancements(
459 &mut self,
460 root: Entity,
461 children_query: &Query<&Children>,
462 advancement_check_query: &Query<(), With<Advancement>>,
463 ) {
464 Self::walk_advancements(root, children_query, advancement_check_query, &mut |e| {
465 self.remove_advancements.push(e)
466 });
467 }
468
469 pub fn criteria_done(&mut self, criteria: Entity) {
471 self.progress.push((
472 criteria,
473 Some(
474 SystemTime::now()
475 .duration_since(UNIX_EPOCH)
476 .unwrap()
477 .as_millis() as i64,
478 ),
479 ))
480 }
481
482 pub fn criteria_undone(&mut self, criteria: Entity) {
484 self.progress.push((criteria, None))
485 }
486}