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