azalea_protocol/packets/game/
c_update_advancements.rs

1use std::collections::HashMap;
2use std::io::Cursor;
3
4use azalea_buf::AzBuf;
5use azalea_chat::FormattedText;
6use azalea_core::resource_location::ResourceLocation;
7use azalea_inventory::ItemStack;
8use azalea_protocol_macros::ClientboundGamePacket;
9
10#[derive(Clone, Debug, AzBuf, ClientboundGamePacket)]
11pub struct ClientboundUpdateAdvancements {
12    pub reset: bool,
13    pub added: Vec<AdvancementHolder>,
14    pub removed: Vec<ResourceLocation>,
15    pub progress: HashMap<ResourceLocation, AdvancementProgress>,
16    pub show_advancements: bool,
17}
18
19#[derive(Clone, Debug, AzBuf)]
20pub struct Advancement {
21    pub parent_id: Option<ResourceLocation>,
22    pub display: Option<DisplayInfo>,
23    pub requirements: Vec<Vec<String>>,
24    pub sends_telemetry_event: bool,
25}
26
27#[derive(Clone, Debug)]
28pub struct DisplayInfo {
29    pub title: FormattedText,
30    pub description: FormattedText,
31    pub icon: ItemStack,
32    pub frame: FrameType,
33    pub show_toast: bool,
34    pub hidden: bool,
35    pub background: Option<ResourceLocation>,
36    pub x: f32,
37    pub y: f32,
38}
39
40impl azalea_buf::AzaleaWrite for DisplayInfo {
41    fn azalea_write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
42        self.title.azalea_write(buf)?;
43        self.description.azalea_write(buf)?;
44        self.icon.azalea_write(buf)?;
45        self.frame.azalea_write(buf)?;
46
47        let mut data: u32 = 0;
48        if self.background.is_some() {
49            data |= 0b001;
50        }
51        if self.show_toast {
52            data |= 0b010;
53        }
54        if self.hidden {
55            data |= 0b100;
56        }
57        data.azalea_write(buf)?;
58
59        if let Some(background) = &self.background {
60            background.azalea_write(buf)?;
61        }
62        self.x.azalea_write(buf)?;
63        self.y.azalea_write(buf)?;
64        Ok(())
65    }
66}
67impl azalea_buf::AzaleaRead for DisplayInfo {
68    fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> {
69        let title = azalea_buf::AzaleaRead::azalea_read(buf)?;
70        let description = azalea_buf::AzaleaRead::azalea_read(buf)?;
71        let icon = azalea_buf::AzaleaRead::azalea_read(buf)?;
72        let frame = azalea_buf::AzaleaRead::azalea_read(buf)?;
73
74        let data = u32::azalea_read(buf)?;
75        let has_background = (data & 0b1) != 0;
76        let show_toast = (data & 0b10) != 0;
77        let hidden = (data & 0b100) != 0;
78
79        let background = if has_background {
80            Some(ResourceLocation::azalea_read(buf)?)
81        } else {
82            None
83        };
84        let x = azalea_buf::AzaleaRead::azalea_read(buf)?;
85        let y = azalea_buf::AzaleaRead::azalea_read(buf)?;
86        Ok(DisplayInfo {
87            title,
88            description,
89            icon,
90            frame,
91            show_toast,
92            hidden,
93            background,
94            x,
95            y,
96        })
97    }
98}
99
100#[derive(Clone, Debug, Copy, AzBuf)]
101pub enum FrameType {
102    Task = 0,
103    Challenge = 1,
104    Goal = 2,
105}
106
107pub type AdvancementProgress = HashMap<String, CriterionProgress>;
108
109#[derive(Clone, Debug, AzBuf)]
110pub struct CriterionProgress {
111    pub date: Option<u64>,
112}
113
114#[derive(Clone, Debug, AzBuf)]
115pub struct AdvancementHolder {
116    pub id: ResourceLocation,
117    pub value: Advancement,
118}
119
120#[cfg(test)]
121mod tests {
122    use azalea_buf::{AzaleaRead, AzaleaWrite};
123
124    use super::*;
125
126    #[test]
127    fn test() {
128        let packet = ClientboundUpdateAdvancements {
129            reset: true,
130            added: [AdvancementHolder {
131                id: ResourceLocation::new("minecraft:test"),
132                value: Advancement {
133                    parent_id: None,
134                    display: Some(DisplayInfo {
135                        title: FormattedText::from("title".to_string()),
136                        description: FormattedText::from("description".to_string()),
137                        icon: ItemStack::Empty,
138                        frame: FrameType::Task,
139                        show_toast: true,
140                        hidden: false,
141                        background: None,
142                        x: 0.0,
143                        y: 0.0,
144                    }),
145                    requirements: Vec::new(),
146                    sends_telemetry_event: false,
147                },
148            }]
149            .into_iter()
150            .collect(),
151            removed: vec![ResourceLocation::new("minecraft:test2")],
152            progress: [(
153                ResourceLocation::new("minecraft:test3"),
154                [(
155                    "minecraft:test4".to_string(),
156                    CriterionProgress {
157                        date: Some(123456789),
158                    },
159                )]
160                .into_iter()
161                .collect(),
162            )]
163            .into_iter()
164            .collect(),
165            show_advancements: false,
166        };
167
168        let mut data = Vec::new();
169        packet.azalea_write(&mut data).unwrap();
170        let mut buf: Cursor<&[u8]> = Cursor::new(&data);
171
172        let read_packet = ClientboundUpdateAdvancements::azalea_read(&mut buf).unwrap();
173        assert_eq!(packet.reset, read_packet.reset);
174        assert_eq!(packet.removed, read_packet.removed);
175
176        let advancement = packet
177            .added
178            .into_iter()
179            .find_map(|a| {
180                if a.id == ResourceLocation::new("minecraft:test") {
181                    Some(a.value)
182                } else {
183                    None
184                }
185            })
186            .unwrap()
187            .clone();
188        let read_advancement = read_packet
189            .added
190            .into_iter()
191            .find_map(|a| {
192                if a.id == ResourceLocation::new("minecraft:test") {
193                    Some(a.value)
194                } else {
195                    None
196                }
197            })
198            .unwrap()
199            .clone();
200        assert_eq!(advancement.parent_id, read_advancement.parent_id);
201
202        let display = advancement.display.unwrap();
203        let read_display = read_advancement.display.unwrap();
204        assert_eq!(display.title, read_display.title);
205        assert_eq!(display.description, read_display.description);
206    }
207}