valence_advancement/
lib.rs

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/// Components for advancement that are required
63/// Optional components:
64/// [`AdvancementDisplay`]
65/// [`Parent`] - parent advancement
66#[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/// Advancement's id. May not be updated.
322#[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/// Advancement display. Optional component
343#[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/// Criteria's identifier. May not be updated
367#[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/// Requirements for advancement to be completed.
381/// All columns should be completed, column is completed when any of criteria in
382/// this column is completed.
383#[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    /// Should contain only root advancement otherwise the first will be chosen
395    Spec(Entity),
396}
397
398#[derive(Component, Debug)]
399pub struct AdvancementClientUpdate {
400    /// Which advancement's descriptions send to client
401    pub new_advancements: Vec<Entity>,
402    /// Which advancements remove from client
403    pub remove_advancements: Vec<Entity>,
404    /// Criteria progress update.
405    /// If None then criteria is not done otherwise it is done
406    pub progress: Vec<(Entity, Option<i64>)>,
407    /// Forces client to open a tab
408    pub force_tab_update: ForceTabUpdate,
409    /// Defines if other advancements should be removed.
410    /// Also with this flag, client will not show a toast for advancements,
411    /// which are completed. When the packet is sent, turns to false
412    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    /// Sends all advancements from the root
446    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    /// Removes all advancements from the root
458    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    /// Marks criteria as done
470    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    /// Marks criteria as undone
483    pub fn criteria_undone(&mut self, criteria: Entity) {
484        self.progress.push((criteria, None))
485    }
486}