1use super::{
2 SpriteKind,
3 sprite::{self, RelativeNeighborPosition},
4};
5use crate::{
6 comp::{fluid_dynamics::LiquidKind, tool::ToolKind},
7 consts::FRIC_GROUND,
8 effect::BuffEffect,
9 make_case_elim, rtsim,
10 vol::FilledVox,
11};
12use num_derive::FromPrimitive;
13use num_traits::FromPrimitive;
14use serde::{Deserialize, Serialize};
15use std::ops::Deref;
16use strum::{Display, EnumIter, EnumString};
17use vek::*;
18
19make_case_elim!(
20 block_kind,
21 #[derive(
22 Copy,
23 Clone,
24 Debug,
25 Hash,
26 Eq,
27 PartialEq,
28 Serialize,
29 Deserialize,
30 FromPrimitive,
31 EnumString,
32 EnumIter,
33 Display,
34 )]
35 #[repr(u8)]
36 pub enum BlockKind {
37 Air = 0x00, Water = 0x01,
39 Rock = 0x10,
44 WeakRock = 0x11, Lava = 0x12, GlowingRock = 0x13,
47 GlowingWeakRock = 0x14,
48 Grass = 0x20, Snow = 0x21,
51 ArtSnow = 0x22,
53 Earth = 0x30,
55 Sand = 0x31,
56 Wood = 0x40,
58 Leaves = 0x41,
59 GlowingMushroom = 0x42,
60 Ice = 0x43,
61 ArtLeaves = 0x44,
62 Misc = 0xFE,
67 }
68);
69
70impl BlockKind {
71 #[inline]
72 pub const fn is_air(&self) -> bool { matches!(self, BlockKind::Air) }
73
74 #[inline]
78 pub const fn is_fluid(&self) -> bool { *self as u8 & 0xF0 == 0x00 }
79
80 #[inline]
81 pub const fn is_liquid(&self) -> bool { self.is_fluid() && !self.is_air() }
82
83 #[inline]
84 pub const fn liquid_kind(&self) -> Option<LiquidKind> {
85 Some(match self {
86 BlockKind::Water => LiquidKind::Water,
87 BlockKind::Lava => LiquidKind::Lava,
88 _ => return None,
89 })
90 }
91
92 #[inline]
95 pub const fn is_filled(&self) -> bool { !self.is_fluid() }
96
97 #[inline]
100 pub const fn has_color(&self) -> bool { self.is_filled() }
101
102 #[inline]
107 pub const fn is_terrain(&self) -> bool {
108 matches!(
109 self,
110 BlockKind::Rock
111 | BlockKind::WeakRock
112 | BlockKind::GlowingRock
113 | BlockKind::GlowingWeakRock
114 | BlockKind::Grass
115 | BlockKind::Earth
116 | BlockKind::Sand
117 )
118 }
119}
120
121#[derive(Copy, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
141pub struct Block {
142 kind: BlockKind,
143 data: [u8; 3],
144}
145
146impl std::fmt::Debug for Block {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 let mut s = f.debug_struct("Block");
149
150 s.field("kind", &self.kind);
151
152 if let Some(sprite) = super::StructureSprite::from_block(self) {
153 s.field("sprite", &sprite);
154 }
155
156 if self.is_filled() {
157 s.field("color", &self.data);
158 }
159
160 s.finish()
161 }
162}
163
164impl FilledVox for Block {
165 fn default_non_filled() -> Self { Block::air(SpriteKind::Empty) }
166
167 fn is_filled(&self) -> bool { self.kind.is_filled() }
168}
169
170impl Deref for Block {
171 type Target = BlockKind;
172
173 fn deref(&self) -> &Self::Target { &self.kind }
174}
175
176impl Block {
177 pub const MAX_HEIGHT: f32 = 3.0;
178
179 #[inline]
182 pub const fn from_raw(kind: BlockKind, data: [u8; 3]) -> Self { Self { kind, data } }
183
184 #[inline]
186 #[track_caller]
187 pub const fn new(kind: BlockKind, color: Rgb<u8>) -> Self {
188 if kind.is_filled() {
189 Self::from_raw(kind, [color.r, color.g, color.b])
190 } else {
191 let data = (SpriteKind::Empty as u32).to_be_bytes();
193 Self::from_raw(kind, [data[1], data[2], data[3]])
194 }
195 }
196
197 #[inline]
200 pub fn unfilled(kind: BlockKind, sprite: SpriteKind) -> Self {
201 #[cfg(debug_assertions)]
202 assert!(!kind.is_filled());
203
204 Self::from_raw(kind, sprite.to_initial_bytes())
205 }
206
207 #[inline]
208 pub fn air(sprite: SpriteKind) -> Self { Self::unfilled(BlockKind::Air, sprite) }
209
210 #[inline]
211 pub const fn empty() -> Self {
212 let data = (SpriteKind::Empty as u32).to_be_bytes();
214 Self::from_raw(BlockKind::Air, [data[1], data[2], data[3]])
215 }
216
217 #[inline]
218 pub fn water(sprite: SpriteKind) -> Self { Self::unfilled(BlockKind::Water, sprite) }
219
220 #[inline(always)]
223 pub const fn get_sprite(&self) -> Option<SpriteKind> {
224 if !self.kind.is_filled() {
225 SpriteKind::from_block(*self)
226 } else {
227 None
228 }
229 }
230
231 #[inline(always)]
232 pub(super) const fn sprite_category_byte(&self) -> u8 { self.data[0] }
233
234 #[inline(always)]
235 pub const fn sprite_category(&self) -> Option<sprite::Category> {
236 if self.kind.is_filled() {
237 None
238 } else {
239 sprite::Category::from_block(*self)
240 }
241 }
242
243 #[inline]
245 pub fn with_attr<A: sprite::Attribute>(
246 mut self,
247 attr: A,
248 ) -> Result<Self, sprite::AttributeError<core::convert::Infallible>> {
249 self.set_attr(attr)?;
250 Ok(self)
251 }
252
253 #[inline]
255 pub fn set_attr<A: sprite::Attribute>(
256 &mut self,
257 attr: A,
258 ) -> Result<(), sprite::AttributeError<core::convert::Infallible>> {
259 match self.sprite_category() {
260 Some(category) => category.write_attr(self, attr),
261 None => Err(sprite::AttributeError::NotPresent),
262 }
263 }
264
265 #[inline]
267 pub fn get_attr<A: sprite::Attribute>(&self) -> Result<A, sprite::AttributeError<A::Error>> {
268 match self.sprite_category() {
269 Some(category) => category.read_attr(*self),
270 None => Err(sprite::AttributeError::NotPresent),
271 }
272 }
273
274 pub fn rotation_mat(&self) -> Mat3<i32> {
275 let dir = crate::util::Dir2::from_sprite_ori(
276 self.get_attr::<sprite::Ori>().unwrap_or_default().0,
277 )
278 .map(|(d, _)| d)
279 .unwrap_or(crate::util::Dir2::X);
280
281 let mut rot_mat = dir.to_mat3();
282
283 rot_mat.cols *= self.sprite_mirror_vec().map(|f| f as i32);
284
285 rot_mat
286 }
287
288 pub fn sprite_z_rot(&self) -> Option<f32> {
289 self.get_attr::<sprite::Ori>()
290 .ok()
291 .map(|ori| std::f32::consts::PI * 0.25 * ori.0 as f32)
292 }
293
294 pub fn sprite_mirror_vec(&self) -> Vec3<f32> {
295 Vec3::new(
296 self.get_attr::<sprite::MirrorX>().map(|m| m.0),
297 self.get_attr::<sprite::MirrorY>().map(|m| m.0),
298 self.get_attr::<sprite::MirrorZ>().map(|m| m.0),
299 )
300 .map(|b| match b.unwrap_or(false) {
301 true => -1.0,
302 false => 1.0,
303 })
304 }
305
306 #[inline(always)]
307 pub(super) const fn data(&self) -> [u8; 3] { self.data }
308
309 #[inline(always)]
310 pub(super) const fn with_data(mut self, data: [u8; 3]) -> Self {
311 self.data = data;
312 self
313 }
314
315 #[inline(always)]
316 pub(super) const fn to_be_u32(self) -> u32 {
317 u32::from_be_bytes([self.kind as u8, self.data[0], self.data[1], self.data[2]])
318 }
319
320 #[inline]
321 pub fn get_color(&self) -> Option<Rgb<u8>> {
322 if self.has_color() {
323 Some(self.data.into())
324 } else {
325 None
326 }
327 }
328
329 #[inline]
335 pub fn get_rtsim_resource(&self) -> Option<rtsim::TerrainResource> {
336 match self.get_sprite()? {
337 SpriteKind::Stones | SpriteKind::Stones2 => Some(rtsim::TerrainResource::Stone),
338 SpriteKind::Twigs
339 | SpriteKind::Wood
340 | SpriteKind::Bamboo
341 | SpriteKind::Hardwood
342 | SpriteKind::Ironwood
343 | SpriteKind::Frostwood
344 | SpriteKind::Eldwood => Some(rtsim::TerrainResource::Wood),
345 SpriteKind::Amethyst
346 | SpriteKind::Ruby
347 | SpriteKind::Sapphire
348 | SpriteKind::Emerald
349 | SpriteKind::Topaz
350 | SpriteKind::Diamond
351 | SpriteKind::CrystalHigh
352 | SpriteKind::CrystalLow
353 | SpriteKind::Lodestone => Some(rtsim::TerrainResource::Gem),
354 SpriteKind::Bloodstone
355 | SpriteKind::Coal
356 | SpriteKind::Cobalt
357 | SpriteKind::Copper
358 | SpriteKind::Iron
359 | SpriteKind::Tin
360 | SpriteKind::Silver
361 | SpriteKind::Gold => Some(rtsim::TerrainResource::Ore),
362 SpriteKind::LongGrass
363 | SpriteKind::MediumGrass
364 | SpriteKind::ShortGrass
365 | SpriteKind::LargeGrass
366 | SpriteKind::GrassBlue
367 | SpriteKind::SavannaGrass
368 | SpriteKind::TallSavannaGrass
369 | SpriteKind::RedSavannaGrass
370 | SpriteKind::JungleRedGrass
371 | SpriteKind::Fern => Some(rtsim::TerrainResource::Grass),
372 SpriteKind::BlueFlower
373 | SpriteKind::PinkFlower
374 | SpriteKind::PurpleFlower
375 | SpriteKind::RedFlower
376 | SpriteKind::WhiteFlower
377 | SpriteKind::YellowFlower
378 | SpriteKind::Sunflower
379 | SpriteKind::Moonbell
380 | SpriteKind::Pyrebloom => Some(rtsim::TerrainResource::Flower),
381 SpriteKind::Reed
382 | SpriteKind::Flax
383 | SpriteKind::WildFlax
384 | SpriteKind::Cotton
385 | SpriteKind::Corn
386 | SpriteKind::WheatYellow
387 | SpriteKind::WheatGreen => Some(rtsim::TerrainResource::Plant),
388 SpriteKind::Apple
389 | SpriteKind::Pumpkin
390 | SpriteKind::Beehive | SpriteKind::Coconut => Some(rtsim::TerrainResource::Fruit),
392 SpriteKind::Lettuce
393 | SpriteKind::Carrot
394 | SpriteKind::Tomato
395 | SpriteKind::Radish
396 | SpriteKind::Turnip => Some(rtsim::TerrainResource::Vegetable),
397 SpriteKind::Mushroom
398 | SpriteKind::CaveMushroom
399 | SpriteKind::CeilingMushroom
400 | SpriteKind::RockyMushroom
401 | SpriteKind::LushMushroom
402 | SpriteKind::GlowMushroom => Some(rtsim::TerrainResource::Mushroom),
403 s if s.default_loot_spec().is_some_and(|inner| inner.is_some()) => Some(rtsim::TerrainResource::Loot),
405 _ => None,
406 }
407 .filter(|_| matches!(self.get_attr(), Ok(sprite::Collectable(true)) | Err(_)))
411 }
412
413 #[inline]
414 pub fn get_glow(&self) -> Option<u8> {
415 let glow_level = match self.kind() {
416 BlockKind::Lava => 24,
417 BlockKind::GlowingRock | BlockKind::GlowingWeakRock => 10,
418 BlockKind::GlowingMushroom => 20,
419 _ => match self.get_sprite()? {
420 SpriteKind::StreetLamp | SpriteKind::StreetLampTall | SpriteKind::BonfireMLit => 24,
421 SpriteKind::Ember | SpriteKind::FireBlock => 20,
422 SpriteKind::WallLamp
423 | SpriteKind::WallLampSmall
424 | SpriteKind::WallLampWizard
425 | SpriteKind::WallLampMesa
426 | SpriteKind::WallSconce
427 | SpriteKind::FireBowlGround
428 | SpriteKind::MesaLantern
429 | SpriteKind::LampTerracotta
430 | SpriteKind::ChristmasOrnament
431 | SpriteKind::CliffDecorBlock
432 | SpriteKind::Orb
433 | SpriteKind::Candle => 16,
434 SpriteKind::DiamondLight => 30,
435 SpriteKind::VeloriteFrag
436 | SpriteKind::GrassBlueShort
437 | SpriteKind::GrassBlueMedium
438 | SpriteKind::GrassBlueLong
439 | SpriteKind::CavernLillypadBlue
440 | SpriteKind::MycelBlue
441 | SpriteKind::Mold
442 | SpriteKind::CeilingMushroom => 6,
443 SpriteKind::CaveMushroom
444 | SpriteKind::GlowMushroom
445 | SpriteKind::CookingPot
446 | SpriteKind::CrystalHigh
447 | SpriteKind::LanternFlower
448 | SpriteKind::CeilingLanternFlower
449 | SpriteKind::LanternPlant
450 | SpriteKind::CeilingLanternPlant
451 | SpriteKind::CrystalLow => 10,
452 SpriteKind::SewerMushroom => 16,
453 SpriteKind::Lodestone => 3,
454 SpriteKind::Lantern
455 | SpriteKind::LanternpostWoodLantern
456 | SpriteKind::LanternAirshipWallBlackS
457 | SpriteKind::LanternAirshipWallBrownS
458 | SpriteKind::LanternAirshipWallChestnutS
459 | SpriteKind::LanternAirshipWallRedS
460 | SpriteKind::LanternAirshipGroundBlackS
461 | SpriteKind::LanternAirshipGroundBrownS
462 | SpriteKind::LanternAirshipGroundChestnutS
463 | SpriteKind::LanternAirshipGroundRedS
464 | SpriteKind::LampMetalShinglesCyan
465 | SpriteKind::LampMetalShinglesRed => 24,
466 SpriteKind::Velorite | SpriteKind::TerracottaStatue => 8,
467 SpriteKind::SeashellLantern | SpriteKind::GlowIceCrystal => 16,
468 SpriteKind::SeaDecorEmblem => 12,
469 SpriteKind::SeaDecorBlock
470 | SpriteKind::HaniwaKeyDoor
471 | SpriteKind::VampireKeyDoor => 10,
472 _ => return None,
473 },
474 };
475
476 if self
477 .get_attr::<sprite::LightEnabled>()
478 .map_or(true, |l| l.0)
479 {
480 Some(glow_level)
481 } else {
482 None
483 }
484 }
485
486 #[inline]
488 pub fn get_max_sunlight(&self) -> (u8, f32) {
489 match self.kind() {
490 BlockKind::Water => (0, 0.4),
491 BlockKind::Leaves => (9, 255.0),
492 BlockKind::ArtLeaves => (9, 255.0),
493 BlockKind::Wood => (6, 2.0),
494 BlockKind::Snow => (6, 2.0),
495 BlockKind::ArtSnow => (6, 2.0),
496 BlockKind::Ice => (4, 2.0),
497 _ if self.is_opaque() => (0, 255.0),
498 _ => (0, 0.0),
499 }
500 }
501
502 #[inline]
504 pub fn is_solid(&self) -> bool {
505 self.get_sprite()
506 .map(|s| s.solid_height().is_some())
507 .unwrap_or(!matches!(self.kind, BlockKind::Lava))
508 }
509
510 pub fn valid_collision_dir(
511 &self,
512 entity_aabb: Aabb<f32>,
513 block_aabb: Aabb<f32>,
514 move_dir: Vec3<f32>,
515 ) -> bool {
516 self.get_sprite().is_none_or(|sprite| {
517 sprite.valid_collision_dir(entity_aabb, block_aabb, move_dir, self)
518 })
519 }
520
521 #[inline]
525 pub fn explode_power(&self) -> Option<f32> {
526 match self.kind() {
529 BlockKind::Leaves => Some(0.25),
530 BlockKind::ArtLeaves => Some(0.25),
531 BlockKind::Grass => Some(0.5),
532 BlockKind::WeakRock => Some(0.75),
533 BlockKind::Snow => Some(0.1),
534 BlockKind::Ice => Some(0.5),
535 BlockKind::Wood => Some(4.5),
536 BlockKind::Lava => None,
537 _ => self.get_sprite().and_then(|sprite| match sprite {
538 sprite if sprite.is_defined_as_container() => None,
539 SpriteKind::Keyhole
540 | SpriteKind::KeyDoor
541 | SpriteKind::BoneKeyhole
542 | SpriteKind::BoneKeyDoor
543 | SpriteKind::OneWayWall
544 | SpriteKind::KeyholeBars
545 | SpriteKind::DoorBars => None,
546 SpriteKind::Anvil
547 | SpriteKind::Cauldron
548 | SpriteKind::CookingPot
549 | SpriteKind::CraftingBench
550 | SpriteKind::Forge
551 | SpriteKind::Loom
552 | SpriteKind::SpinningWheel
553 | SpriteKind::DismantlingBench
554 | SpriteKind::RepairBench
555 | SpriteKind::TanningRack
556 | SpriteKind::Chest
557 | SpriteKind::DungeonChest0
558 | SpriteKind::DungeonChest1
559 | SpriteKind::DungeonChest2
560 | SpriteKind::DungeonChest3
561 | SpriteKind::DungeonChest4
562 | SpriteKind::DungeonChest5
563 | SpriteKind::CoralChest
564 | SpriteKind::HaniwaUrn
565 | SpriteKind::HaniwaKeyDoor
566 | SpriteKind::HaniwaKeyhole
567 | SpriteKind::VampireKeyDoor
568 | SpriteKind::VampireKeyhole
569 | SpriteKind::MyrmidonKeyDoor
570 | SpriteKind::MyrmidonKeyhole
571 | SpriteKind::MinotaurKeyhole
572 | SpriteKind::HaniwaTrap
573 | SpriteKind::HaniwaTrapTriggered
574 | SpriteKind::ChestBuried
575 | SpriteKind::CommonLockedChest
576 | SpriteKind::TerracottaChest
577 | SpriteKind::SahaginChest
578 | SpriteKind::SeaDecorBlock
579 | SpriteKind::SeaDecorChain
580 | SpriteKind::SeaDecorWindowHor
581 | SpriteKind::SeaDecorWindowVer
582 | SpriteKind::WitchWindow
583 | SpriteKind::Rope
584 | SpriteKind::MetalChain
585 | SpriteKind::IronSpike
586 | SpriteKind::HotSurface
587 | SpriteKind::FireBlock
588 | SpriteKind::GlassBarrier
589 | SpriteKind::GlassKeyhole
590 | SpriteKind::SahaginKeyhole
591 | SpriteKind::SahaginKeyDoor
592 | SpriteKind::TerracottaKeyDoor
593 | SpriteKind::TerracottaKeyhole
594 | SpriteKind::TerracottaStatue
595 | SpriteKind::TerracottaBlock => None,
596 SpriteKind::EnsnaringVines
597 | SpriteKind::EnsnaringWeb
598 | SpriteKind::SeaUrchin
599 | SpriteKind::IceSpike
600 | SpriteKind::DiamondLight => Some(0.1),
601 _ => Some(0.25),
602 }),
603 }
604 }
605
606 #[inline]
611 pub fn is_collectible(&self) -> bool {
612 self.get_sprite()
613 .is_some_and(|s| s.collectible_info().is_some())
614 && matches!(self.get_attr(), Ok(sprite::Collectable(true)) | Err(_))
615 }
616
617 #[inline]
622 pub fn is_directly_collectible(&self) -> bool {
623 self.get_sprite()
626 .is_some_and(|s| s.collectible_info() == Some(None))
627 && matches!(self.get_attr(), Ok(sprite::Collectable(true)) | Err(_))
628 }
629
630 #[inline]
631 pub fn is_mountable(&self) -> bool { self.mount_offset().is_some() }
632
633 pub fn mount_offset(&self) -> Option<(Vec3<f32>, Vec3<f32>)> {
635 self.get_sprite().and_then(|sprite| sprite.mount_offset())
636 }
637
638 pub fn mount_buffs(&self) -> Option<Vec<BuffEffect>> {
639 self.get_sprite().and_then(|sprite| sprite.mount_buffs())
640 }
641
642 pub fn is_controller(&self) -> bool {
643 self.get_sprite()
644 .is_some_and(|sprite| sprite.is_controller())
645 }
646
647 #[inline]
648 pub fn is_bonkable(&self) -> bool {
649 match self.get_sprite() {
650 Some(
651 SpriteKind::Apple | SpriteKind::Beehive | SpriteKind::Coconut | SpriteKind::Bomb,
652 ) => self.is_solid(),
653 _ => false,
654 }
655 }
656
657 #[inline]
658 pub fn is_owned(&self) -> bool {
659 self.get_attr::<sprite::Owned>()
660 .is_ok_and(|sprite::Owned(b)| b)
661 }
662
663 #[inline]
666 pub fn mine_tool(&self) -> Option<ToolKind> {
667 match self.kind() {
668 BlockKind::WeakRock | BlockKind::Ice | BlockKind::GlowingWeakRock => {
669 Some(ToolKind::Pick)
670 },
671 _ => self.get_sprite().and_then(|s| s.mine_tool()),
672 }
673 }
674
675 #[inline]
676 pub fn is_opaque(&self) -> bool {
677 match self.get_sprite() {
678 Some(
679 SpriteKind::Keyhole
680 | SpriteKind::KeyDoor
681 | SpriteKind::KeyholeBars
682 | SpriteKind::DoorBars,
683 ) => true,
684 Some(_) => false,
685 None => self.kind().is_filled(),
686 }
687 }
688
689 #[inline]
690 pub fn solid_height(&self) -> f32 {
691 self.get_sprite()
692 .map(|s| s.solid_height().unwrap_or(0.0))
693 .unwrap_or(1.0)
694 }
695
696 #[inline]
699 pub fn get_friction(&self) -> f32 {
700 match self.kind() {
701 BlockKind::Ice => FRIC_GROUND * 0.1,
702 _ => FRIC_GROUND,
703 }
704 }
705
706 #[inline]
712 pub fn get_traction(&self) -> f32 {
713 match self.kind() {
714 BlockKind::Snow | BlockKind::ArtSnow => 0.8,
715 _ => 1.0,
716 }
717 }
718
719 pub fn with_toggle_light(self, enable: bool) -> Option<Self> {
721 self.with_attr(sprite::LightEnabled(enable)).ok()
722 }
723
724 #[inline]
725 pub fn kind(&self) -> BlockKind { self.kind }
726
727 #[inline]
729 #[must_use]
730 pub fn with_data_of(mut self, other: Block) -> Self {
731 if self.is_filled() == other.is_filled() {
732 self = self.with_data(other.data());
733 }
734 self
735 }
736
737 #[inline]
739 #[must_use]
740 pub fn with_sprite(self, sprite: SpriteKind) -> Self {
741 match self.try_with_sprite(sprite) {
742 Ok(b) => b,
743 Err(b) => b,
744 }
745 }
746
747 #[inline]
751 pub fn try_with_sprite(self, sprite: SpriteKind) -> Result<Self, Self> {
752 if self.is_filled() {
753 Err(self)
754 } else {
755 Ok(Self::unfilled(self.kind, sprite))
756 }
757 }
758
759 #[inline]
761 #[must_use]
762 pub fn with_ori(self, ori: u8) -> Option<Self> { self.with_attr(sprite::Ori(ori)).ok() }
763
764 #[inline]
766 #[must_use]
767 pub fn with_adjacent_type(self, adj: RelativeNeighborPosition) -> Option<Self> {
768 self.with_attr(sprite::AdjacentType(adj as u8)).ok()
769 }
770
771 #[inline]
773 #[must_use]
774 pub fn into_vacant(self) -> Self {
775 if self.is_fluid() {
776 Block::unfilled(self.kind(), SpriteKind::Empty)
777 } else {
778 Block::air(SpriteKind::Empty)
781 }
782 }
783
784 #[inline]
790 #[must_use]
791 pub fn into_collected(self) -> Self {
792 match self.get_sprite() {
793 Some(SpriteKind::Lettuce) => self.with_attr(sprite::Collectable(false)).expect(
794 "Setting collectable will not fail since this sprite has Collectable attribute",
795 ),
796 _ => self.into_vacant(),
797 }
798 }
799
800 #[inline]
802 #[must_use]
803 pub fn from_u32(x: u32) -> Option<Self> {
804 let [bk, r, g, b] = x.to_le_bytes();
805 let block = Self {
806 kind: BlockKind::from_u8(bk)?,
807 data: [r, g, b],
808 };
809
810 (block.kind.is_filled() || SpriteKind::from_block(block).is_some()).then_some(block)
811 }
812
813 #[inline]
814 pub fn to_u32(self) -> u32 {
815 u32::from_le_bytes([self.kind as u8, self.data[0], self.data[1], self.data[2]])
816 }
817}
818
819const _: () = assert!(core::mem::size_of::<BlockKind>() == 1);
820const _: () = assert!(core::mem::size_of::<Block>() == 4);