1mod nbt;
3
4pub use nbt::command_nbt_component;
5
6use crate::{
7 hash::{ComponentHasher, HashComponent, HashEntry, sort_map_entries},
8 serial::ReadFrom,
9 translations_registry::TRANSLATIONS,
10};
11use simdnbt::owned::read_tag;
12use std::io::{self, Cursor};
13use text_components::{
14 TextComponent,
15 content::{Content, NbtSource, Object, PlayerModel, Resolvable},
16 custom::{CustomData, Payload},
17 format::Format,
18 interactivity::{ClickEvent, Dialog, HoverEvent},
19 resolving::TextResolutor,
20};
21
22pub struct DisplayResolutor;
24impl TextResolutor for DisplayResolutor {
25 fn resolve_content(&self, resolvable: &Resolvable) -> TextComponent {
26 TextComponent {
27 content: Content::Resolvable(resolvable.clone()),
28 ..Default::default()
29 }
30 }
31
32 fn resolve_custom(&self, _data: &CustomData) -> Option<TextComponent> {
33 None
34 }
35
36 fn translate(&self, key: &str) -> Option<String> {
37 TRANSLATIONS.get(key).map(ToString::to_string)
38 }
39}
40
41impl ReadFrom for TextComponent {
42 fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
43 let nbt_tag =
45 read_tag(data).map_err(|e| io::Error::other(format!("Failed to read NBT: {e:?}")))?;
46
47 Self::from_nbt(&nbt_tag)
48 .ok_or_else(|| io::Error::other("Failed to parse TextComponent from NBT"))
49 }
50}
51
52impl HashComponent for TextComponent {
53 fn hash_component(&self, hasher: &mut ComponentHasher) {
54 if let Content::Text { text } = &self.content
60 && self.format.is_none()
61 && self.interactions.is_none()
62 && self.children.is_empty()
63 {
64 hasher.put_string(text);
65 return;
66 }
67 hash_component_as_map(self, hasher);
69 }
70}
71
72fn hash_component_as_map(component: &TextComponent, hasher: &mut ComponentHasher) {
74 let mut entries: Vec<HashEntry> = Vec::new();
76
77 hash_content_fields(&component.content, &mut entries);
79
80 hash_format_fields(&component.format, &mut entries);
82
83 if let Some(insertion) = &component.interactions.insertion {
84 let mut key_hasher = ComponentHasher::new();
85 key_hasher.put_string("insertion");
86 let mut value_hasher = ComponentHasher::new();
87 value_hasher.put_string(insertion);
88 entries.push(HashEntry::new(key_hasher, value_hasher));
89 }
90
91 if let Some(hover_event) = &component.interactions.hover {
92 let mut key_hasher = ComponentHasher::new();
93 key_hasher.put_string("hover_event");
94 let mut value_hasher = ComponentHasher::new();
95 hash_hover_fields(hover_event, &mut value_hasher);
96 entries.push(HashEntry::new(key_hasher, value_hasher));
97 }
98
99 if let Some(click_event) = &component.interactions.click {
100 let mut key_hasher = ComponentHasher::new();
101 key_hasher.put_string("click_event");
102 let mut value_hasher = ComponentHasher::new();
103 hash_click_fields(click_event, &mut value_hasher);
104 entries.push(HashEntry::new(key_hasher, value_hasher));
105 }
106
107 if !component.children.is_empty() {
109 let mut key_hasher = ComponentHasher::new();
110 key_hasher.put_string("extra");
111 let mut value_hasher = ComponentHasher::new();
112 value_hasher.start_list();
113 for child in &component.children {
114 value_hasher.put_component_hash(child);
115 }
116 value_hasher.end_list();
117 entries.push(HashEntry::new(key_hasher, value_hasher));
118 }
119
120 sort_map_entries(&mut entries);
122
123 hasher.start_map();
126 for entry in entries {
127 hasher.put_raw_bytes(&entry.key_bytes);
128 hasher.put_raw_bytes(&entry.value_bytes);
129 }
130 hasher.end_map();
131}
132
133#[expect(
134 clippy::too_many_lines,
135 reason = "each Content variant requires distinct hashing logic; splitting would hurt readability"
136)]
137fn hash_content_fields(content: &Content, entries: &mut Vec<HashEntry>) {
138 match content {
139 Content::Text { text } => {
140 let mut key_hasher = ComponentHasher::new();
141 key_hasher.put_string("text");
142 let mut value_hasher = ComponentHasher::new();
143 value_hasher.put_string(text);
144 entries.push(HashEntry::new(key_hasher, value_hasher));
145 }
146 Content::Translate(message) => {
147 {
149 let mut key_hasher = ComponentHasher::new();
150 key_hasher.put_string("translate");
151 let mut value_hasher = ComponentHasher::new();
152 value_hasher.put_string(&message.key);
153 entries.push(HashEntry::new(key_hasher, value_hasher));
154 }
155 if let Some(fallback) = &message.fallback {
157 let mut key_hasher = ComponentHasher::new();
158 key_hasher.put_string("fallback");
159 let mut value_hasher = ComponentHasher::new();
160 value_hasher.put_string(fallback);
161 entries.push(HashEntry::new(key_hasher, value_hasher));
162 }
163 if let Some(args) = &message.args
165 && !args.is_empty()
166 {
167 let mut key_hasher = ComponentHasher::new();
168 key_hasher.put_string("with");
169 let mut value_hasher = ComponentHasher::new();
170 value_hasher.start_list();
171 for arg in args {
172 value_hasher.put_component_hash(arg);
173 }
174 value_hasher.end_list();
175 entries.push(HashEntry::new(key_hasher, value_hasher));
176 }
177 }
178 Content::Keybind { keybind } => {
179 let mut key_hasher = ComponentHasher::new();
180 key_hasher.put_string("keybind");
181 let mut value_hasher = ComponentHasher::new();
182 value_hasher.put_string(keybind);
183 entries.push(HashEntry::new(key_hasher, value_hasher));
184 }
185 Content::Object(Object::Atlas {
186 atlas,
187 sprite,
188 fallback,
189 }) => {
190 {
191 let mut key_hasher = ComponentHasher::new();
192 key_hasher.put_string("sprite");
193 let mut value_hasher = ComponentHasher::new();
194 value_hasher.put_string(sprite);
195 entries.push(HashEntry::new(key_hasher, value_hasher));
196 }
197 if atlas != "minecraft:blocks" {
198 let mut key_hasher = ComponentHasher::new();
199 key_hasher.put_string("atlas");
200 let mut value_hasher = ComponentHasher::new();
201 value_hasher.put_string(atlas);
202 entries.push(HashEntry::new(key_hasher, value_hasher));
203 }
204 if let Some(fallback) = fallback {
205 let mut key_hasher = ComponentHasher::new();
206 key_hasher.put_string("fallback");
207 let mut value_hasher = ComponentHasher::new();
208 fallback.hash_component(&mut value_hasher);
209 entries.push(HashEntry::new(key_hasher, value_hasher));
210 }
211 }
212 Content::Object(Object::Player {
213 player,
214 hat,
215 fallback,
216 }) => {
217 {
218 let mut inner_entries: Vec<HashEntry> = Vec::new();
219 if let Some(id) = &player.id {
220 let mut key_hasher = ComponentHasher::new();
221 key_hasher.put_string("id");
222 let mut value_hasher = ComponentHasher::new();
223 value_hasher.put_int_array(id);
224 inner_entries.push(HashEntry::new(key_hasher, value_hasher));
225 }
226 if let Some(name) = &player.name {
227 let mut key_hasher = ComponentHasher::new();
228 key_hasher.put_string("name");
229 let mut value_hasher = ComponentHasher::new();
230 value_hasher.put_string(name);
231 inner_entries.push(HashEntry::new(key_hasher, value_hasher));
232 }
233 if let Some(texture) = &player.texture {
234 let mut key_hasher = ComponentHasher::new();
235 key_hasher.put_string("texture");
236 let mut value_hasher = ComponentHasher::new();
237 value_hasher.put_string(texture);
238 inner_entries.push(HashEntry::new(key_hasher, value_hasher));
239 }
240 if let Some(cape) = &player.cape {
241 let mut key_hasher = ComponentHasher::new();
242 key_hasher.put_string("cape");
243 let mut value_hasher = ComponentHasher::new();
244 value_hasher.put_string(cape);
245 inner_entries.push(HashEntry::new(key_hasher, value_hasher));
246 }
247 if let Some(elytra) = &player.elytra {
248 let mut key_hasher = ComponentHasher::new();
249 key_hasher.put_string("elytra");
250 let mut value_hasher = ComponentHasher::new();
251 value_hasher.put_string(elytra);
252 inner_entries.push(HashEntry::new(key_hasher, value_hasher));
253 }
254 if let Some(model) = player.model {
255 let mut key_hasher = ComponentHasher::new();
256 key_hasher.put_string("model");
257 let mut value_hasher = ComponentHasher::new();
258 value_hasher.put_string(match model {
259 PlayerModel::Slim => "slim",
260 PlayerModel::Wide => "wide",
261 });
262 inner_entries.push(HashEntry::new(key_hasher, value_hasher));
263 }
264 if !player.properties.is_empty() {
265 let mut key_hasher = ComponentHasher::new();
266 key_hasher.put_string("properties");
267 let mut value_hasher = ComponentHasher::new();
268 value_hasher.start_list();
269 for property in &player.properties {
270 let mut entries: Vec<HashEntry> = Vec::new();
271 {
272 let mut key_hasher = ComponentHasher::new();
273 key_hasher.put_string("name");
274 let mut value_hasher = ComponentHasher::new();
275 value_hasher.put_string(&property.name);
276 entries.push(HashEntry::new(key_hasher, value_hasher));
277 }
278 {
279 let mut key_hasher = ComponentHasher::new();
280 key_hasher.put_string("value");
281 let mut value_hasher = ComponentHasher::new();
282 value_hasher.put_string(&property.value);
283 entries.push(HashEntry::new(key_hasher, value_hasher));
284 }
285 if let Some(signature) = &property.signature {
286 let mut key_hasher = ComponentHasher::new();
287 key_hasher.put_string("signature");
288 let mut value_hasher = ComponentHasher::new();
289 value_hasher.put_string(signature);
290 entries.push(HashEntry::new(key_hasher, value_hasher));
291 }
292
293 sort_map_entries(&mut entries);
295 let mut hasher = ComponentHasher::new();
296 hasher.start_map();
297 for entry in entries {
298 hasher.put_raw_bytes(&entry.key_bytes);
299 hasher.put_raw_bytes(&entry.value_bytes);
300 }
301 hasher.end_map();
302 let property_hash = hasher.finish();
304 value_hasher.put_raw_bytes(&property_hash.to_le_bytes());
305 }
306 value_hasher.end_list();
307 inner_entries.push(HashEntry::new(key_hasher, value_hasher));
308 }
309 sort_map_entries(&mut inner_entries);
311
312 let mut key_hasher = ComponentHasher::new();
314 key_hasher.put_string("player");
315 let mut value_hasher = ComponentHasher::new();
316 value_hasher.start_map();
317 for entry in inner_entries {
318 value_hasher.put_raw_bytes(&entry.key_bytes);
319 value_hasher.put_raw_bytes(&entry.value_bytes);
320 }
321 value_hasher.end_map();
322 entries.push(HashEntry::new(key_hasher, value_hasher));
323 }
324 if !hat {
325 let mut key_hasher = ComponentHasher::new();
326 key_hasher.put_string("hat");
327 let mut value_hasher = ComponentHasher::new();
328 value_hasher.put_bool(*hat);
329 entries.push(HashEntry::new(key_hasher, value_hasher));
330 }
331 if let Some(fallback) = fallback {
332 let mut key_hasher = ComponentHasher::new();
333 key_hasher.put_string("fallback");
334 let mut value_hasher = ComponentHasher::new();
335 fallback.hash_component(&mut value_hasher);
336 entries.push(HashEntry::new(key_hasher, value_hasher));
337 }
338 }
339 Content::Resolvable(Resolvable::Entity {
340 selector,
341 separator,
342 }) => {
343 {
345 let mut key_hasher = ComponentHasher::new();
346 key_hasher.put_string("selector");
347 let mut value_hasher = ComponentHasher::new();
348 value_hasher.put_string(selector);
349 entries.push(HashEntry::new(key_hasher, value_hasher));
350 }
351 if let Some(separator) = separator {
353 let mut key_hasher = ComponentHasher::new();
354 key_hasher.put_string("separator");
355 let mut value_hasher = ComponentHasher::new();
356 separator.hash_component(&mut value_hasher);
357 entries.push(HashEntry::new(key_hasher, value_hasher));
358 }
359 }
360 Content::Resolvable(Resolvable::Scoreboard {
361 selector,
362 objective,
363 }) => {
364 let mut inner_entries: Vec<HashEntry> = Vec::new();
366 {
367 let mut key_hasher = ComponentHasher::new();
368 key_hasher.put_string("name");
369 let mut value_hasher = ComponentHasher::new();
370 value_hasher.put_string(selector);
371 inner_entries.push(HashEntry::new(key_hasher, value_hasher));
372 }
373 {
374 let mut key_hasher = ComponentHasher::new();
375 key_hasher.put_string("objective");
376 let mut value_hasher = ComponentHasher::new();
377 value_hasher.put_string(objective);
378 inner_entries.push(HashEntry::new(key_hasher, value_hasher));
379 }
380 sort_map_entries(&mut inner_entries);
382
383 let mut key_hasher = ComponentHasher::new();
385 key_hasher.put_string("score");
386 let mut value_hasher = ComponentHasher::new();
387 value_hasher.start_map();
388 for entry in inner_entries {
389 value_hasher.put_raw_bytes(&entry.key_bytes);
390 value_hasher.put_raw_bytes(&entry.value_bytes);
391 }
392 value_hasher.end_map();
393 entries.push(HashEntry::new(key_hasher, value_hasher));
394 }
395 Content::Resolvable(Resolvable::NBT {
396 path,
397 interpret,
398 plain,
399 separator,
400 source,
401 }) => {
402 {
404 let mut key_hasher = ComponentHasher::new();
405 key_hasher.put_string("nbt");
406 let mut value_hasher = ComponentHasher::new();
407 value_hasher.put_string(path);
408 entries.push(HashEntry::new(key_hasher, value_hasher));
409 }
410 if *interpret {
412 let mut key_hasher = ComponentHasher::new();
413 key_hasher.put_string("interpret");
414 let mut value_hasher = ComponentHasher::new();
415 value_hasher.put_bool(true);
416 entries.push(HashEntry::new(key_hasher, value_hasher));
417 }
418 if *plain {
420 let mut key_hasher = ComponentHasher::new();
421 key_hasher.put_string("plain");
422 let mut value_hasher = ComponentHasher::new();
423 value_hasher.put_bool(true);
424 entries.push(HashEntry::new(key_hasher, value_hasher));
425 }
426 if let Some(separator) = separator {
428 let mut key_hasher = ComponentHasher::new();
429 key_hasher.put_string("separator");
430 let mut value_hasher = ComponentHasher::new();
431 separator.hash_component(&mut value_hasher);
432 entries.push(HashEntry::new(key_hasher, value_hasher));
433 }
434 match source {
436 NbtSource::Entity(selector) => {
437 let mut key_hasher = ComponentHasher::new();
438 key_hasher.put_string("entity");
439 let mut value_hasher = ComponentHasher::new();
440 value_hasher.put_string(selector);
441 entries.push(HashEntry::new(key_hasher, value_hasher));
442 }
443 NbtSource::Block(pos) => {
444 let mut key_hasher = ComponentHasher::new();
445 key_hasher.put_string("block");
446 let mut value_hasher = ComponentHasher::new();
447 value_hasher.put_string(pos);
448 entries.push(HashEntry::new(key_hasher, value_hasher));
449 }
450 NbtSource::Storage(id) => {
451 let mut key_hasher = ComponentHasher::new();
452 key_hasher.put_string("storage");
453 let mut value_hasher = ComponentHasher::new();
454 value_hasher.put_string(id);
455 entries.push(HashEntry::new(key_hasher, value_hasher));
456 }
457 }
458 }
459 Content::Custom(_custom_data) => {
460 let mut key_hasher = ComponentHasher::new();
464 key_hasher.put_string("text");
465 let mut value_hasher = ComponentHasher::new();
466 value_hasher.put_string("");
467 entries.push(HashEntry::new(key_hasher, value_hasher));
468 }
469 }
470}
471
472fn hash_format_fields(format: &Format, entries: &mut Vec<HashEntry>) {
475 if let Some(color) = &format.color {
477 let mut key_hasher = ComponentHasher::new();
478 key_hasher.put_string("color");
479 let mut value_hasher = ComponentHasher::new();
480 value_hasher.put_string(&color.to_string());
481 entries.push(HashEntry::new(key_hasher, value_hasher));
482 }
483
484 if let Some(shadow_color) = &format.shadow_color {
486 let mut key_hasher = ComponentHasher::new();
487 key_hasher.put_string("shadow_color");
488 let mut value_hasher = ComponentHasher::new();
489 value_hasher.put_int(*shadow_color);
490 entries.push(HashEntry::new(key_hasher, value_hasher));
491 }
492
493 if let Some(bold) = format.bold {
495 let mut key_hasher = ComponentHasher::new();
496 key_hasher.put_string("bold");
497 let mut value_hasher = ComponentHasher::new();
498 value_hasher.put_bool(bold);
499 entries.push(HashEntry::new(key_hasher, value_hasher));
500 }
501
502 if let Some(italic) = format.italic {
504 let mut key_hasher = ComponentHasher::new();
505 key_hasher.put_string("italic");
506 let mut value_hasher = ComponentHasher::new();
507 value_hasher.put_bool(italic);
508 entries.push(HashEntry::new(key_hasher, value_hasher));
509 }
510
511 if let Some(underlined) = format.underlined {
513 let mut key_hasher = ComponentHasher::new();
514 key_hasher.put_string("underlined");
515 let mut value_hasher = ComponentHasher::new();
516 value_hasher.put_bool(underlined);
517 entries.push(HashEntry::new(key_hasher, value_hasher));
518 }
519
520 if let Some(strikethrough) = format.strikethrough {
522 let mut key_hasher = ComponentHasher::new();
523 key_hasher.put_string("strikethrough");
524 let mut value_hasher = ComponentHasher::new();
525 value_hasher.put_bool(strikethrough);
526 entries.push(HashEntry::new(key_hasher, value_hasher));
527 }
528
529 if let Some(obfuscated) = format.obfuscated {
531 let mut key_hasher = ComponentHasher::new();
532 key_hasher.put_string("obfuscated");
533 let mut value_hasher = ComponentHasher::new();
534 value_hasher.put_bool(obfuscated);
535 entries.push(HashEntry::new(key_hasher, value_hasher));
536 }
537
538 if let Some(font) = &format.font {
540 let mut key_hasher = ComponentHasher::new();
541 key_hasher.put_string("font");
542 let mut value_hasher = ComponentHasher::new();
543 value_hasher.put_string(font);
544 entries.push(HashEntry::new(key_hasher, value_hasher));
545 }
546}
547
548fn hash_hover_fields(event: &HoverEvent, hasher: &mut ComponentHasher) {
549 let mut entries: Vec<HashEntry> = Vec::new();
550
551 match event {
552 HoverEvent::ShowText { value } => {
553 {
554 let mut key_hasher = ComponentHasher::new();
555 key_hasher.put_string("action");
556 let mut value_hasher = ComponentHasher::new();
557 value_hasher.put_string("show_text");
558 entries.push(HashEntry::new(key_hasher, value_hasher));
559 }
560 {
561 let mut key_hasher = ComponentHasher::new();
562 key_hasher.put_string("value");
563 let mut value_hasher = ComponentHasher::new();
564 value.hash_component(&mut value_hasher);
565 entries.push(HashEntry::new(key_hasher, value_hasher));
566 }
567 }
568 HoverEvent::ShowItem {
569 id,
570 count,
571 components,
572 } => {
573 {
574 let mut key_hasher = ComponentHasher::new();
575 key_hasher.put_string("action");
576 let mut value_hasher = ComponentHasher::new();
577 value_hasher.put_string("show_item");
578 entries.push(HashEntry::new(key_hasher, value_hasher));
579 }
580 {
581 let mut key_hasher = ComponentHasher::new();
582 key_hasher.put_string("id");
583 let mut value_hasher = ComponentHasher::new();
584 value_hasher.put_string(id);
585 entries.push(HashEntry::new(key_hasher, value_hasher));
586 }
587 if *count != 1 {
588 let mut key_hasher = ComponentHasher::new();
589 key_hasher.put_string("count");
590 let mut value_hasher = ComponentHasher::new();
591 value_hasher.put_int(*count);
592 entries.push(HashEntry::new(key_hasher, value_hasher));
593 }
594 if let Some(components) = components
595 && !components.is_empty_compound()
596 {
597 let mut key_hasher = ComponentHasher::new();
598 key_hasher.put_string("components");
599 let mut value_hasher = ComponentHasher::new();
600 components.as_nbt().hash_component(&mut value_hasher);
602 entries.push(HashEntry::new(key_hasher, value_hasher));
603 }
604 }
605 HoverEvent::ShowEntity { name, id, uuid } => {
606 {
607 let mut key_hasher = ComponentHasher::new();
608 key_hasher.put_string("action");
609 let mut value_hasher = ComponentHasher::new();
610 value_hasher.put_string("show_entity");
611 entries.push(HashEntry::new(key_hasher, value_hasher));
612 }
613 {
614 let mut key_hasher = ComponentHasher::new();
615 key_hasher.put_string("id");
616 let mut value_hasher = ComponentHasher::new();
617 value_hasher.put_string(id);
618 entries.push(HashEntry::new(key_hasher, value_hasher));
619 }
620 {
621 let mut key_hasher = ComponentHasher::new();
622 key_hasher.put_string("uuid");
623 let mut value_hasher = ComponentHasher::new();
624 let (most, least) = uuid.as_u64_pair();
625 value_hasher.put_int_array(&[
626 (most >> 32) as i32,
627 most as i32,
628 (least >> 32) as i32,
629 least as i32,
630 ]);
631 entries.push(HashEntry::new(key_hasher, value_hasher));
632 }
633 if let Some(name) = name {
634 let mut key_hasher = ComponentHasher::new();
635 key_hasher.put_string("name");
636 let mut value_hasher = ComponentHasher::new();
637 name.hash_component(&mut value_hasher);
638 entries.push(HashEntry::new(key_hasher, value_hasher));
639 }
640 }
641 }
642
643 sort_map_entries(&mut entries);
645
646 hasher.start_map();
648 for entry in entries {
649 hasher.put_raw_bytes(&entry.key_bytes);
650 hasher.put_raw_bytes(&entry.value_bytes);
651 }
652 hasher.end_map();
653}
654
655#[expect(
656 clippy::too_many_lines,
657 reason = "each ClickEvent variant requires distinct hashing logic; splitting would hurt readability"
658)]
659fn hash_click_fields(event: &ClickEvent, hasher: &mut ComponentHasher) {
660 let mut entries: Vec<HashEntry> = Vec::new();
661
662 match event {
663 ClickEvent::OpenUrl { url } => {
664 {
665 let mut key_hasher = ComponentHasher::new();
666 key_hasher.put_string("action");
667 let mut value_hasher = ComponentHasher::new();
668 value_hasher.put_string("open_url");
669 entries.push(HashEntry::new(key_hasher, value_hasher));
670 }
671 {
672 let mut key_hasher = ComponentHasher::new();
673 key_hasher.put_string("url");
674 let mut value_hasher = ComponentHasher::new();
675 value_hasher.put_string(url);
676 entries.push(HashEntry::new(key_hasher, value_hasher));
677 }
678 }
679 ClickEvent::RunCommand { command } => {
680 {
681 let mut key_hasher = ComponentHasher::new();
682 key_hasher.put_string("action");
683 let mut value_hasher = ComponentHasher::new();
684 value_hasher.put_string("run_command");
685 entries.push(HashEntry::new(key_hasher, value_hasher));
686 }
687 {
688 let mut key_hasher = ComponentHasher::new();
689 key_hasher.put_string("command");
690 let mut value_hasher = ComponentHasher::new();
691 value_hasher.put_string(command);
692 entries.push(HashEntry::new(key_hasher, value_hasher));
693 }
694 }
695 ClickEvent::SuggestCommand { command } => {
696 {
697 let mut key_hasher = ComponentHasher::new();
698 key_hasher.put_string("action");
699 let mut value_hasher = ComponentHasher::new();
700 value_hasher.put_string("suggest_command");
701 entries.push(HashEntry::new(key_hasher, value_hasher));
702 }
703 {
704 let mut key_hasher = ComponentHasher::new();
705 key_hasher.put_string("command");
706 let mut value_hasher = ComponentHasher::new();
707 value_hasher.put_string(command);
708 entries.push(HashEntry::new(key_hasher, value_hasher));
709 }
710 }
711 ClickEvent::ChangePage { page } => {
712 {
713 let mut key_hasher = ComponentHasher::new();
714 key_hasher.put_string("action");
715 let mut value_hasher = ComponentHasher::new();
716 value_hasher.put_string("change_page");
717 entries.push(HashEntry::new(key_hasher, value_hasher));
718 }
719 {
720 let mut key_hasher = ComponentHasher::new();
721 key_hasher.put_string("page");
722 let mut value_hasher = ComponentHasher::new();
723 value_hasher.put_int(*page);
724 entries.push(HashEntry::new(key_hasher, value_hasher));
725 }
726 }
727 ClickEvent::CopyToClipboard { value } => {
728 {
729 let mut key_hasher = ComponentHasher::new();
730 key_hasher.put_string("action");
731 let mut value_hasher = ComponentHasher::new();
732 value_hasher.put_string("copy_to_clipboard");
733 entries.push(HashEntry::new(key_hasher, value_hasher));
734 }
735 {
736 let mut key_hasher = ComponentHasher::new();
737 key_hasher.put_string("value");
738 let mut value_hasher = ComponentHasher::new();
739 value_hasher.put_string(value);
740 entries.push(HashEntry::new(key_hasher, value_hasher));
741 }
742 }
743 ClickEvent::ShowDialog { dialog } => {
744 {
745 let mut key_hasher = ComponentHasher::new();
746 key_hasher.put_string("action");
747 let mut value_hasher = ComponentHasher::new();
748 value_hasher.put_string("show_dialog");
749 entries.push(HashEntry::new(key_hasher, value_hasher));
750 }
751 {
752 let mut key_hasher = ComponentHasher::new();
753 key_hasher.put_string("dialog");
754 let mut value_hasher = ComponentHasher::new();
755 match dialog {
756 Dialog::Reference(reference) => value_hasher.put_string(reference),
757 Dialog::Inline(value) => {
758 value.as_nbt().hash_component(&mut value_hasher);
760 }
761 }
762 entries.push(HashEntry::new(key_hasher, value_hasher));
763 }
764 }
765 ClickEvent::Custom(custom_data) => {
766 {
767 let mut key_hasher = ComponentHasher::new();
768 key_hasher.put_string("action");
769 let mut value_hasher = ComponentHasher::new();
770 value_hasher.put_string("custom");
771 entries.push(HashEntry::new(key_hasher, value_hasher));
772 }
773 if let Payload::Nbt(payload) = &custom_data.payload {
774 let mut key_hasher = ComponentHasher::new();
775 key_hasher.put_string("payload");
776 let mut value_hasher = ComponentHasher::new();
777 payload.as_nbt().hash_component(&mut value_hasher);
778 entries.push(HashEntry::new(key_hasher, value_hasher));
779 }
780 {
781 let mut key_hasher = ComponentHasher::new();
782 key_hasher.put_string("id");
783 let mut value_hasher = ComponentHasher::new();
784 value_hasher.put_string(&custom_data.id);
785 entries.push(HashEntry::new(key_hasher, value_hasher));
786 }
787 }
788 }
789
790 sort_map_entries(&mut entries);
792
793 hasher.start_map();
795 for entry in entries {
796 hasher.put_raw_bytes(&entry.key_bytes);
797 hasher.put_raw_bytes(&entry.value_bytes);
798 }
799 hasher.end_map();
800}
801
802#[cfg(test)]
803mod tests {
804 use std::{borrow::Cow, io::Cursor};
805
806 use simdnbt::owned::{NbtList, NbtTag, read_tag};
807 use text_components::{
808 Modifier as _, TextComponent, interactivity::HoverEvent, translation::TranslatedMessage,
809 };
810
811 use crate::serial::{ReadFrom as _, WriteTo as _};
812
813 #[test]
814 fn component_stream_codec_round_trips_an_unnamed_nbt_tag() {
815 let component = TextComponent::plain("hello");
816 let mut encoded = Vec::new();
817 component
818 .write(&mut encoded)
819 .expect("text component should encode");
820
821 assert_eq!(
822 TextComponent::read(&mut Cursor::new(encoded.as_slice()))
823 .expect("text component should decode"),
824 component
825 );
826 }
827
828 #[test]
829 fn component_codec_recursively_collapses_plain_components() {
830 let component = TextComponent::translated(TranslatedMessage {
831 key: Cow::Borrowed("test.message"),
832 fallback: None,
833 args: Some(Box::new([TextComponent::plain("argument")])),
834 })
835 .add_child(TextComponent::plain("child"))
836 .hover_event(HoverEvent::show_text("hover"));
837
838 let NbtTag::Compound(encoded) = component.to_codec_nbt() else {
839 panic!("styled component should encode as a compound");
840 };
841 assert_eq!(
842 encoded.get("with"),
843 Some(&NbtTag::List(NbtList::String(vec!["argument".into()])))
844 );
845 assert_eq!(
846 encoded.get("extra"),
847 Some(&NbtTag::List(NbtList::String(vec!["child".into()])))
848 );
849 let hover = encoded
850 .get("hover_event")
851 .and_then(NbtTag::compound)
852 .expect("hover event should encode as a compound");
853 assert_eq!(hover.get("value"), Some(&NbtTag::String("hover".into())));
854 }
855
856 #[test]
857 fn component_stream_codec_writes_collapsed_string_tag() {
858 let mut encoded = Vec::new();
859 TextComponent::plain("hello")
860 .write(&mut encoded)
861 .expect("text component should encode");
862 let tag = read_tag(&mut Cursor::new(encoded.as_slice()))
863 .expect("encoded component should be NBT");
864
865 assert_eq!(tag, NbtTag::String("hello".into()));
866 }
867}