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, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
141pub struct Block {
142 kind: BlockKind,
143 data: [u8; 3],
144}
145
146impl FilledVox for Block {
147 fn default_non_filled() -> Self { Block::air(SpriteKind::Empty) }
148
149 fn is_filled(&self) -> bool { self.kind.is_filled() }
150}
151
152impl Deref for Block {
153 type Target = BlockKind;
154
155 fn deref(&self) -> &Self::Target { &self.kind }
156}
157
158impl Block {
159 pub const MAX_HEIGHT: f32 = 3.0;
160
161 #[inline]
164 pub const fn from_raw(kind: BlockKind, data: [u8; 3]) -> Self { Self { kind, data } }
165
166 #[inline]
168 #[track_caller]
169 pub const fn new(kind: BlockKind, color: Rgb<u8>) -> Self {
170 if kind.is_filled() {
171 Self::from_raw(kind, [color.r, color.g, color.b])
172 } else {
173 let data = (SpriteKind::Empty as u32).to_be_bytes();
175 Self::from_raw(kind, [data[1], data[2], data[3]])
176 }
177 }
178
179 #[inline]
182 pub fn unfilled(kind: BlockKind, sprite: SpriteKind) -> Self {
183 #[cfg(debug_assertions)]
184 assert!(!kind.is_filled());
185
186 Self::from_raw(kind, sprite.to_initial_bytes())
187 }
188
189 #[inline]
190 pub fn air(sprite: SpriteKind) -> Self { Self::unfilled(BlockKind::Air, sprite) }
191
192 #[inline]
193 pub const fn empty() -> Self {
194 let data = (SpriteKind::Empty as u32).to_be_bytes();
196 Self::from_raw(BlockKind::Air, [data[1], data[2], data[3]])
197 }
198
199 #[inline]
200 pub fn water(sprite: SpriteKind) -> Self { Self::unfilled(BlockKind::Water, sprite) }
201
202 #[inline(always)]
205 pub const fn get_sprite(&self) -> Option<SpriteKind> {
206 if !self.kind.is_filled() {
207 SpriteKind::from_block(*self)
208 } else {
209 None
210 }
211 }
212
213 #[inline(always)]
214 pub(super) const fn sprite_category_byte(&self) -> u8 { self.data[0] }
215
216 #[inline(always)]
217 pub const fn sprite_category(&self) -> Option<sprite::Category> {
218 if self.kind.is_filled() {
219 None
220 } else {
221 sprite::Category::from_block(*self)
222 }
223 }
224
225 #[inline]
227 pub fn with_attr<A: sprite::Attribute>(
228 mut self,
229 attr: A,
230 ) -> Result<Self, sprite::AttributeError<core::convert::Infallible>> {
231 self.set_attr(attr)?;
232 Ok(self)
233 }
234
235 #[inline]
237 pub fn set_attr<A: sprite::Attribute>(
238 &mut self,
239 attr: A,
240 ) -> Result<(), sprite::AttributeError<core::convert::Infallible>> {
241 match self.sprite_category() {
242 Some(category) => category.write_attr(self, attr),
243 None => Err(sprite::AttributeError::NotPresent),
244 }
245 }
246
247 #[inline]
249 pub fn get_attr<A: sprite::Attribute>(&self) -> Result<A, sprite::AttributeError<A::Error>> {
250 match self.sprite_category() {
251 Some(category) => category.read_attr(*self),
252 None => Err(sprite::AttributeError::NotPresent),
253 }
254 }
255
256 pub fn sprite_z_rot(&self) -> Option<f32> {
257 self.get_attr::<sprite::Ori>()
258 .ok()
259 .map(|ori| std::f32::consts::PI * 0.25 * ori.0 as f32)
260 }
261
262 pub fn sprite_mirror_vec(&self) -> Vec3<f32> {
263 Vec3::new(
264 self.get_attr::<sprite::MirrorX>().map(|m| m.0),
265 self.get_attr::<sprite::MirrorY>().map(|m| m.0),
266 self.get_attr::<sprite::MirrorZ>().map(|m| m.0),
267 )
268 .map(|b| match b.unwrap_or(false) {
269 true => -1.0,
270 false => 1.0,
271 })
272 }
273
274 #[inline(always)]
275 pub(super) const fn data(&self) -> [u8; 3] { self.data }
276
277 #[inline(always)]
278 pub(super) const fn with_data(mut self, data: [u8; 3]) -> Self {
279 self.data = data;
280 self
281 }
282
283 #[inline(always)]
284 pub(super) const fn to_be_u32(self) -> u32 {
285 u32::from_be_bytes([self.kind as u8, self.data[0], self.data[1], self.data[2]])
286 }
287
288 #[inline]
289 pub fn get_color(&self) -> Option<Rgb<u8>> {
290 if self.has_color() {
291 Some(self.data.into())
292 } else {
293 None
294 }
295 }
296
297 #[inline]
303 pub fn get_rtsim_resource(&self) -> Option<rtsim::TerrainResource> {
304 match self.get_sprite()? {
305 SpriteKind::Stones => Some(rtsim::TerrainResource::Stone),
306 SpriteKind::Twigs
307 | SpriteKind::Wood
308 | SpriteKind::Bamboo
309 | SpriteKind::Hardwood
310 | SpriteKind::Ironwood
311 | SpriteKind::Frostwood
312 | SpriteKind::Eldwood => Some(rtsim::TerrainResource::Wood),
313 SpriteKind::Amethyst
314 | SpriteKind::Ruby
315 | SpriteKind::Sapphire
316 | SpriteKind::Emerald
317 | SpriteKind::Topaz
318 | SpriteKind::Diamond
319 | SpriteKind::CrystalHigh
320 | SpriteKind::CrystalLow => Some(rtsim::TerrainResource::Gem),
321 SpriteKind::Bloodstone
322 | SpriteKind::Coal
323 | SpriteKind::Cobalt
324 | SpriteKind::Copper
325 | SpriteKind::Iron
326 | SpriteKind::Tin
327 | SpriteKind::Silver
328 | SpriteKind::Gold => Some(rtsim::TerrainResource::Ore),
329
330 SpriteKind::LongGrass
331 | SpriteKind::MediumGrass
332 | SpriteKind::ShortGrass
333 | SpriteKind::LargeGrass
334 | SpriteKind::GrassBlue
335 | SpriteKind::SavannaGrass
336 | SpriteKind::TallSavannaGrass
337 | SpriteKind::RedSavannaGrass
338 | SpriteKind::JungleRedGrass
339 | SpriteKind::Fern => Some(rtsim::TerrainResource::Grass),
340 SpriteKind::BlueFlower
341 | SpriteKind::PinkFlower
342 | SpriteKind::PurpleFlower
343 | SpriteKind::RedFlower
344 | SpriteKind::WhiteFlower
345 | SpriteKind::YellowFlower
346 | SpriteKind::Sunflower
347 | SpriteKind::Moonbell
348 | SpriteKind::Pyrebloom => Some(rtsim::TerrainResource::Flower),
349 SpriteKind::Reed
350 | SpriteKind::Flax
351 | SpriteKind::WildFlax
352 | SpriteKind::Cotton
353 | SpriteKind::Corn
354 | SpriteKind::WheatYellow
355 | SpriteKind::WheatGreen => Some(rtsim::TerrainResource::Plant),
356 SpriteKind::Apple
357 | SpriteKind::Pumpkin
358 | SpriteKind::Beehive | SpriteKind::Coconut => Some(rtsim::TerrainResource::Fruit),
360 SpriteKind::Lettuce
361 | SpriteKind::Carrot
362 | SpriteKind::Tomato
363 | SpriteKind::Radish
364 | SpriteKind::Turnip => Some(rtsim::TerrainResource::Vegetable),
365 SpriteKind::Mushroom
366 | SpriteKind::CaveMushroom
367 | SpriteKind::CeilingMushroom
368 | SpriteKind::RockyMushroom
369 | SpriteKind::LushMushroom
370 | SpriteKind::GlowMushroom => Some(rtsim::TerrainResource::Mushroom),
371
372 SpriteKind::Chest
373 | SpriteKind::ChestBuried
374 | SpriteKind::PotionMinor
375 | SpriteKind::DungeonChest0
376 | SpriteKind::DungeonChest1
377 | SpriteKind::DungeonChest2
378 | SpriteKind::DungeonChest3
379 | SpriteKind::DungeonChest4
380 | SpriteKind::DungeonChest5
381 | SpriteKind::CoralChest
382 | SpriteKind::HaniwaUrn
383 | SpriteKind::TerracottaChest
384 | SpriteKind::SahaginChest
385 | SpriteKind::Crate
386 | SpriteKind::CommonLockedChest => Some(rtsim::TerrainResource::Loot),
387 _ => None,
388 }
389 .filter(|_| matches!(self.get_attr(), Ok(sprite::Collectable(false)) | Err(_)))
393 }
394
395 #[inline]
396 pub fn get_glow(&self) -> Option<u8> {
397 let glow_level = match self.kind() {
398 BlockKind::Lava => 24,
399 BlockKind::GlowingRock | BlockKind::GlowingWeakRock => 10,
400 BlockKind::GlowingMushroom => 20,
401 _ => match self.get_sprite()? {
402 SpriteKind::StreetLamp | SpriteKind::StreetLampTall | SpriteKind::BonfireMLit => 24,
403 SpriteKind::Ember | SpriteKind::FireBlock => 20,
404 SpriteKind::WallLamp
405 | SpriteKind::WallLampSmall
406 | SpriteKind::WallLampWizard
407 | SpriteKind::WallLampMesa
408 | SpriteKind::WallSconce
409 | SpriteKind::FireBowlGround
410 | SpriteKind::MesaLantern
411 | SpriteKind::LampTerracotta
412 | SpriteKind::ChristmasOrnament
413 | SpriteKind::CliffDecorBlock
414 | SpriteKind::Orb
415 | SpriteKind::Candle => 16,
416 SpriteKind::DiamondLight => 30,
417 SpriteKind::Velorite
418 | SpriteKind::VeloriteFrag
419 | SpriteKind::GrassBlueShort
420 | SpriteKind::GrassBlueMedium
421 | SpriteKind::GrassBlueLong
422 | SpriteKind::CavernLillypadBlue
423 | SpriteKind::MycelBlue
424 | SpriteKind::Mold
425 | SpriteKind::CeilingMushroom => 6,
426 SpriteKind::CaveMushroom
427 | SpriteKind::GlowMushroom
428 | SpriteKind::CookingPot
429 | SpriteKind::CrystalHigh
430 | SpriteKind::LanternFlower
431 | SpriteKind::CeilingLanternFlower
432 | SpriteKind::LanternPlant
433 | SpriteKind::CeilingLanternPlant
434 | SpriteKind::CrystalLow => 10,
435 SpriteKind::SewerMushroom => 16,
436 SpriteKind::Amethyst
437 | SpriteKind::Ruby
438 | SpriteKind::Sapphire
439 | SpriteKind::Diamond
440 | SpriteKind::Emerald
441 | SpriteKind::Topaz => 3,
442 SpriteKind::Lantern
443 | SpriteKind::LanternpostWoodLantern
444 | SpriteKind::LanternAirshipWallBlackS
445 | SpriteKind::LanternAirshipWallBrownS
446 | SpriteKind::LanternAirshipWallChestnutS
447 | SpriteKind::LanternAirshipWallRedS
448 | SpriteKind::LanternAirshipGroundBlackS
449 | SpriteKind::LanternAirshipGroundBrownS
450 | SpriteKind::LanternAirshipGroundChestnutS
451 | SpriteKind::LanternAirshipGroundRedS
452 | SpriteKind::LampMetalShinglesCyan
453 | SpriteKind::LampMetalShinglesRed => 24,
454 SpriteKind::TerracottaStatue => 8,
455 SpriteKind::SeashellLantern | SpriteKind::GlowIceCrystal => 16,
456 SpriteKind::SeaDecorEmblem => 12,
457 SpriteKind::SeaDecorBlock
458 | SpriteKind::HaniwaKeyDoor
459 | SpriteKind::VampireKeyDoor => 10,
460 _ => return None,
461 },
462 };
463
464 if self
465 .get_attr::<sprite::LightEnabled>()
466 .map_or(true, |l| l.0)
467 {
468 Some(glow_level)
469 } else {
470 None
471 }
472 }
473
474 #[inline]
476 pub fn get_max_sunlight(&self) -> (u8, f32) {
477 match self.kind() {
478 BlockKind::Water => (0, 0.4),
479 BlockKind::Leaves => (9, 255.0),
480 BlockKind::ArtLeaves => (9, 255.0),
481 BlockKind::Wood => (6, 2.0),
482 BlockKind::Snow => (6, 2.0),
483 BlockKind::ArtSnow => (6, 2.0),
484 BlockKind::Ice => (4, 2.0),
485 _ if self.is_opaque() => (0, 255.0),
486 _ => (0, 0.0),
487 }
488 }
489
490 #[inline]
492 pub fn is_solid(&self) -> bool {
493 self.get_sprite()
494 .map(|s| s.solid_height().is_some())
495 .unwrap_or(!matches!(self.kind, BlockKind::Lava))
496 }
497
498 pub fn valid_collision_dir(
499 &self,
500 entity_aabb: Aabb<f32>,
501 block_aabb: Aabb<f32>,
502 move_dir: Vec3<f32>,
503 ) -> bool {
504 self.get_sprite().is_none_or(|sprite| {
505 sprite.valid_collision_dir(entity_aabb, block_aabb, move_dir, self)
506 })
507 }
508
509 #[inline]
513 pub fn explode_power(&self) -> Option<f32> {
514 match self.kind() {
517 BlockKind::Leaves => Some(0.25),
518 BlockKind::ArtLeaves => Some(0.25),
519 BlockKind::Grass => Some(0.5),
520 BlockKind::WeakRock => Some(0.75),
521 BlockKind::Snow => Some(0.1),
522 BlockKind::Ice => Some(0.5),
523 BlockKind::Wood => Some(4.5),
524 BlockKind::Lava => None,
525 _ => self.get_sprite().and_then(|sprite| match sprite {
526 sprite if sprite.is_defined_as_container() => None,
527 SpriteKind::Keyhole
528 | SpriteKind::KeyDoor
529 | SpriteKind::BoneKeyhole
530 | SpriteKind::BoneKeyDoor
531 | SpriteKind::OneWayWall
532 | SpriteKind::KeyholeBars
533 | SpriteKind::DoorBars => None,
534 SpriteKind::Anvil
535 | SpriteKind::Cauldron
536 | SpriteKind::CookingPot
537 | SpriteKind::CraftingBench
538 | SpriteKind::Forge
539 | SpriteKind::Loom
540 | SpriteKind::SpinningWheel
541 | SpriteKind::DismantlingBench
542 | SpriteKind::RepairBench
543 | SpriteKind::TanningRack
544 | SpriteKind::Chest
545 | SpriteKind::DungeonChest0
546 | SpriteKind::DungeonChest1
547 | SpriteKind::DungeonChest2
548 | SpriteKind::DungeonChest3
549 | SpriteKind::DungeonChest4
550 | SpriteKind::DungeonChest5
551 | SpriteKind::CoralChest
552 | SpriteKind::HaniwaUrn
553 | SpriteKind::HaniwaKeyDoor
554 | SpriteKind::HaniwaKeyhole
555 | SpriteKind::VampireKeyDoor
556 | SpriteKind::VampireKeyhole
557 | SpriteKind::MyrmidonKeyDoor
558 | SpriteKind::MyrmidonKeyhole
559 | SpriteKind::MinotaurKeyhole
560 | SpriteKind::HaniwaTrap
561 | SpriteKind::HaniwaTrapTriggered
562 | SpriteKind::ChestBuried
563 | SpriteKind::CommonLockedChest
564 | SpriteKind::TerracottaChest
565 | SpriteKind::SahaginChest
566 | SpriteKind::SeaDecorBlock
567 | SpriteKind::SeaDecorChain
568 | SpriteKind::SeaDecorWindowHor
569 | SpriteKind::SeaDecorWindowVer
570 | SpriteKind::WitchWindow
571 | SpriteKind::Rope
572 | SpriteKind::MetalChain
573 | SpriteKind::IronSpike
574 | SpriteKind::HotSurface
575 | SpriteKind::FireBlock
576 | SpriteKind::GlassBarrier
577 | SpriteKind::GlassKeyhole
578 | SpriteKind::SahaginKeyhole
579 | SpriteKind::SahaginKeyDoor
580 | SpriteKind::TerracottaKeyDoor
581 | SpriteKind::TerracottaKeyhole
582 | SpriteKind::TerracottaStatue
583 | SpriteKind::TerracottaBlock => None,
584 SpriteKind::EnsnaringVines
585 | SpriteKind::EnsnaringWeb
586 | SpriteKind::SeaUrchin
587 | SpriteKind::IceSpike
588 | SpriteKind::DiamondLight => Some(0.1),
589 _ => Some(0.25),
590 }),
591 }
592 }
593
594 #[inline]
599 pub fn is_collectible(&self) -> bool {
600 self.get_sprite()
601 .is_some_and(|s| s.collectible_info().is_some())
602 && matches!(self.get_attr(), Ok(sprite::Collectable(true)) | Err(_))
603 }
604
605 #[inline]
610 pub fn is_directly_collectible(&self) -> bool {
611 self.get_sprite()
614 .is_some_and(|s| s.collectible_info() == Some(None))
615 && matches!(self.get_attr(), Ok(sprite::Collectable(true)) | Err(_))
616 }
617
618 #[inline]
619 pub fn is_mountable(&self) -> bool { self.mount_offset().is_some() }
620
621 pub fn mount_offset(&self) -> Option<(Vec3<f32>, Vec3<f32>)> {
623 self.get_sprite().and_then(|sprite| sprite.mount_offset())
624 }
625
626 pub fn mount_buffs(&self) -> Option<Vec<BuffEffect>> {
627 self.get_sprite().and_then(|sprite| sprite.mount_buffs())
628 }
629
630 pub fn is_controller(&self) -> bool {
631 self.get_sprite()
632 .is_some_and(|sprite| sprite.is_controller())
633 }
634
635 #[inline]
636 pub fn is_bonkable(&self) -> bool {
637 match self.get_sprite() {
638 Some(
639 SpriteKind::Apple | SpriteKind::Beehive | SpriteKind::Coconut | SpriteKind::Bomb,
640 ) => self.is_solid(),
641 _ => false,
642 }
643 }
644
645 #[inline]
646 pub fn is_owned(&self) -> bool {
647 self.get_attr::<sprite::Owned>()
648 .is_ok_and(|sprite::Owned(b)| b)
649 }
650
651 #[inline]
654 pub fn mine_tool(&self) -> Option<ToolKind> {
655 match self.kind() {
656 BlockKind::WeakRock | BlockKind::Ice | BlockKind::GlowingWeakRock => {
657 Some(ToolKind::Pick)
658 },
659 _ => self.get_sprite().and_then(|s| s.mine_tool()),
660 }
661 }
662
663 #[inline]
664 pub fn is_opaque(&self) -> bool {
665 match self.get_sprite() {
666 Some(
667 SpriteKind::Keyhole
668 | SpriteKind::KeyDoor
669 | SpriteKind::KeyholeBars
670 | SpriteKind::DoorBars,
671 ) => true,
672 Some(_) => false,
673 None => self.kind().is_filled(),
674 }
675 }
676
677 #[inline]
678 pub fn solid_height(&self) -> f32 {
679 self.get_sprite()
680 .map(|s| s.solid_height().unwrap_or(0.0))
681 .unwrap_or(1.0)
682 }
683
684 #[inline]
687 pub fn get_friction(&self) -> f32 {
688 match self.kind() {
689 BlockKind::Ice => FRIC_GROUND * 0.1,
690 _ => FRIC_GROUND,
691 }
692 }
693
694 #[inline]
700 pub fn get_traction(&self) -> f32 {
701 match self.kind() {
702 BlockKind::Snow | BlockKind::ArtSnow => 0.8,
703 _ => 1.0,
704 }
705 }
706
707 pub fn with_toggle_light(self, enable: bool) -> Option<Self> {
709 self.with_attr(sprite::LightEnabled(enable)).ok()
710 }
711
712 #[inline]
713 pub fn kind(&self) -> BlockKind { self.kind }
714
715 #[inline]
717 #[must_use]
718 pub fn with_data_of(mut self, other: Block) -> Self {
719 if self.is_filled() == other.is_filled() {
720 self = self.with_data(other.data());
721 }
722 self
723 }
724
725 #[inline]
727 #[must_use]
728 pub fn with_sprite(self, sprite: SpriteKind) -> Self {
729 match self.try_with_sprite(sprite) {
730 Ok(b) => b,
731 Err(b) => b,
732 }
733 }
734
735 #[inline]
739 pub fn try_with_sprite(self, sprite: SpriteKind) -> Result<Self, Self> {
740 if self.is_filled() {
741 Err(self)
742 } else {
743 Ok(Self::unfilled(self.kind, sprite))
744 }
745 }
746
747 #[inline]
749 #[must_use]
750 pub fn with_ori(self, ori: u8) -> Option<Self> { self.with_attr(sprite::Ori(ori)).ok() }
751
752 #[inline]
754 #[must_use]
755 pub fn with_adjacent_type(self, adj: RelativeNeighborPosition) -> Option<Self> {
756 self.with_attr(sprite::AdjacentType(adj as u8)).ok()
757 }
758
759 #[inline]
761 #[must_use]
762 pub fn into_vacant(self) -> Self {
763 if self.is_fluid() {
764 Block::unfilled(self.kind(), SpriteKind::Empty)
765 } else {
766 Block::air(SpriteKind::Empty)
769 }
770 }
771
772 #[inline]
778 #[must_use]
779 pub fn into_collected(self) -> Self {
780 match self.get_sprite() {
781 Some(SpriteKind::Lettuce) => self.with_attr(sprite::Collectable(false)).expect(
782 "Setting collectable will not fail since this sprite has Collectable attribute",
783 ),
784 _ => self.into_vacant(),
785 }
786 }
787
788 #[inline]
790 #[must_use]
791 pub fn from_u32(x: u32) -> Option<Self> {
792 let [bk, r, g, b] = x.to_le_bytes();
793 let block = Self {
794 kind: BlockKind::from_u8(bk)?,
795 data: [r, g, b],
796 };
797
798 (block.kind.is_filled() || SpriteKind::from_block(block).is_some()).then_some(block)
799 }
800
801 #[inline]
802 pub fn to_u32(self) -> u32 {
803 u32::from_le_bytes([self.kind as u8, self.data[0], self.data[1], self.data[2]])
804 }
805}
806
807const _: () = assert!(core::mem::size_of::<BlockKind>() == 1);
808const _: () = assert!(core::mem::size_of::<Block>() == 4);