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