1use super::{
2 SpriteKind,
3 sprite::{self, RelativeNeighborPosition},
4};
5use crate::{
6 comp::{fluid_dynamics::LiquidKind, tool::ToolKind},
7 consts::FRIC_GROUND,
8 lottery::LootSpec,
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 match self.sprite_category() {
232 Some(category) => category.write_attr(&mut self, attr)?,
233 None => return Err(sprite::AttributeError::NotPresent),
234 }
235 Ok(self)
236 }
237
238 #[inline]
240 pub fn set_attr<A: sprite::Attribute>(
241 &mut self,
242 attr: A,
243 ) -> Result<(), sprite::AttributeError<core::convert::Infallible>> {
244 match self.sprite_category() {
245 Some(category) => category.write_attr(self, attr),
246 None => Err(sprite::AttributeError::NotPresent),
247 }
248 }
249
250 #[inline]
252 pub fn get_attr<A: sprite::Attribute>(&self) -> Result<A, sprite::AttributeError<A::Error>> {
253 match self.sprite_category() {
254 Some(category) => category.read_attr(*self),
255 None => Err(sprite::AttributeError::NotPresent),
256 }
257 }
258
259 #[inline(always)]
260 pub(super) const fn data(&self) -> [u8; 3] { self.data }
261
262 #[inline(always)]
263 pub(super) const fn with_data(mut self, data: [u8; 3]) -> Self {
264 self.data = data;
265 self
266 }
267
268 #[inline(always)]
269 pub(super) const fn to_be_u32(self) -> u32 {
270 u32::from_be_bytes([self.kind as u8, self.data[0], self.data[1], self.data[2]])
271 }
272
273 #[inline]
274 pub fn get_color(&self) -> Option<Rgb<u8>> {
275 if self.has_color() {
276 Some(self.data.into())
277 } else {
278 None
279 }
280 }
281
282 #[inline]
284 pub fn get_ori(&self) -> Option<u8> { self.get_attr::<sprite::Ori>().ok().map(|ori| ori.0) }
285
286 #[inline]
290 pub fn get_rtsim_resource(&self) -> Option<rtsim::ChunkResource> {
291 match self.get_sprite()? {
292 SpriteKind::Stones => Some(rtsim::ChunkResource::Stone),
293 SpriteKind::Twigs
294 | SpriteKind::Wood
295 | SpriteKind::Bamboo
296 | SpriteKind::Hardwood
297 | SpriteKind::Ironwood
298 | SpriteKind::Frostwood
299 | SpriteKind::Eldwood => Some(rtsim::ChunkResource::Wood),
300 SpriteKind::Amethyst
301 | SpriteKind::Ruby
302 | SpriteKind::Sapphire
303 | SpriteKind::Emerald
304 | SpriteKind::Topaz
305 | SpriteKind::Diamond
306 | SpriteKind::CrystalHigh
307 | SpriteKind::CrystalLow => Some(rtsim::ChunkResource::Gem),
308 SpriteKind::Bloodstone
309 | SpriteKind::Coal
310 | SpriteKind::Cobalt
311 | SpriteKind::Copper
312 | SpriteKind::Iron
313 | SpriteKind::Tin
314 | SpriteKind::Silver
315 | SpriteKind::Gold => Some(rtsim::ChunkResource::Ore),
316
317 SpriteKind::LongGrass
318 | SpriteKind::MediumGrass
319 | SpriteKind::ShortGrass
320 | SpriteKind::LargeGrass
321 | SpriteKind::GrassBlue
322 | SpriteKind::SavannaGrass
323 | SpriteKind::TallSavannaGrass
324 | SpriteKind::RedSavannaGrass
325 | SpriteKind::JungleRedGrass
326 | SpriteKind::Fern => Some(rtsim::ChunkResource::Grass),
327 SpriteKind::BlueFlower
328 | SpriteKind::PinkFlower
329 | SpriteKind::PurpleFlower
330 | SpriteKind::RedFlower
331 | SpriteKind::WhiteFlower
332 | SpriteKind::YellowFlower
333 | SpriteKind::Sunflower
334 | SpriteKind::Moonbell
335 | SpriteKind::Pyrebloom => Some(rtsim::ChunkResource::Flower),
336 SpriteKind::Reed
337 | SpriteKind::Flax
338 | SpriteKind::WildFlax
339 | SpriteKind::Cotton
340 | SpriteKind::Corn
341 | SpriteKind::WheatYellow
342 | SpriteKind::WheatGreen => Some(rtsim::ChunkResource::Plant),
343 SpriteKind::Apple
344 | SpriteKind::Pumpkin
345 | SpriteKind::Beehive | SpriteKind::Coconut => Some(rtsim::ChunkResource::Fruit),
347 SpriteKind::Cabbage
348 | SpriteKind::Carrot
349 | SpriteKind::Tomato
350 | SpriteKind::Radish
351 | SpriteKind::Turnip => Some(rtsim::ChunkResource::Vegetable),
352 SpriteKind::Mushroom
353 | SpriteKind::CaveMushroom
354 | SpriteKind::CeilingMushroom
355 | SpriteKind::RockyMushroom
356 | SpriteKind::LushMushroom
357 | SpriteKind::GlowMushroom => Some(rtsim::ChunkResource::Mushroom),
358
359 SpriteKind::Chest
360 | SpriteKind::ChestBuried
361 | SpriteKind::PotionMinor
362 | SpriteKind::DungeonChest0
363 | SpriteKind::DungeonChest1
364 | SpriteKind::DungeonChest2
365 | SpriteKind::DungeonChest3
366 | SpriteKind::DungeonChest4
367 | SpriteKind::DungeonChest5
368 | SpriteKind::CoralChest
369 | SpriteKind::HaniwaUrn
370 | SpriteKind::TerracottaChest
371 | SpriteKind::SahaginChest
372 | SpriteKind::Crate => Some(rtsim::ChunkResource::Loot),
373 _ => None,
374 }
375 }
376
377 #[inline]
378 pub fn get_glow(&self) -> Option<u8> {
379 let glow_level = match self.kind() {
380 BlockKind::Lava => 24,
381 BlockKind::GlowingRock | BlockKind::GlowingWeakRock => 10,
382 BlockKind::GlowingMushroom => 20,
383 _ => match self.get_sprite()? {
384 SpriteKind::StreetLamp | SpriteKind::StreetLampTall | SpriteKind::BonfireMLit => 24,
385 SpriteKind::Ember | SpriteKind::FireBlock => 20,
386 SpriteKind::WallLamp
387 | SpriteKind::WallLampSmall
388 | SpriteKind::WallLampWizard
389 | SpriteKind::WallLampMesa
390 | SpriteKind::WallSconce
391 | SpriteKind::FireBowlGround
392 | SpriteKind::MesaLantern
393 | SpriteKind::LampTerracotta
394 | SpriteKind::ChristmasOrnament
395 | SpriteKind::CliffDecorBlock
396 | SpriteKind::Orb
397 | SpriteKind::Candle => 16,
398 SpriteKind::DiamondLight => 30,
399 SpriteKind::Velorite
400 | SpriteKind::VeloriteFrag
401 | SpriteKind::GrassBlueShort
402 | SpriteKind::GrassBlueMedium
403 | SpriteKind::GrassBlueLong
404 | SpriteKind::CavernLillypadBlue
405 | SpriteKind::MycelBlue
406 | SpriteKind::Mold
407 | SpriteKind::CeilingMushroom => 6,
408 SpriteKind::CaveMushroom
409 | SpriteKind::GlowMushroom
410 | SpriteKind::CookingPot
411 | SpriteKind::CrystalHigh
412 | SpriteKind::LanternFlower
413 | SpriteKind::CeilingLanternFlower
414 | SpriteKind::LanternPlant
415 | SpriteKind::CeilingLanternPlant
416 | SpriteKind::CrystalLow => 10,
417 SpriteKind::SewerMushroom => 16,
418 SpriteKind::Amethyst
419 | SpriteKind::Ruby
420 | SpriteKind::Sapphire
421 | SpriteKind::Diamond
422 | SpriteKind::Emerald
423 | SpriteKind::Topaz => 3,
424 SpriteKind::Lantern | SpriteKind::LanternpostWoodLantern => 24,
425 SpriteKind::TerracottaStatue => 8,
426 SpriteKind::SeashellLantern | SpriteKind::GlowIceCrystal => 16,
427 SpriteKind::SeaDecorEmblem => 12,
428 SpriteKind::SeaDecorBlock
429 | SpriteKind::HaniwaKeyDoor
430 | SpriteKind::VampireKeyDoor => 10,
431 _ => return None,
432 },
433 };
434
435 if self
436 .get_attr::<sprite::LightEnabled>()
437 .map_or(true, |l| l.0)
438 {
439 Some(glow_level)
440 } else {
441 None
442 }
443 }
444
445 #[inline]
447 pub fn get_max_sunlight(&self) -> (u8, f32) {
448 match self.kind() {
449 BlockKind::Water => (0, 0.4),
450 BlockKind::Leaves => (9, 255.0),
451 BlockKind::ArtLeaves => (9, 255.0),
452 BlockKind::Wood => (6, 2.0),
453 BlockKind::Snow => (6, 2.0),
454 BlockKind::ArtSnow => (6, 2.0),
455 BlockKind::Ice => (4, 2.0),
456 _ if self.is_opaque() => (0, 255.0),
457 _ => (0, 0.0),
458 }
459 }
460
461 #[inline]
463 pub fn is_solid(&self) -> bool {
464 self.get_sprite()
465 .map(|s| s.solid_height().is_some())
466 .unwrap_or(!matches!(self.kind, BlockKind::Lava))
467 }
468
469 pub fn valid_collision_dir(
470 &self,
471 entity_aabb: Aabb<f32>,
472 block_aabb: Aabb<f32>,
473 move_dir: Vec3<f32>,
474 ) -> bool {
475 self.get_sprite().is_none_or(|sprite| {
476 sprite.valid_collision_dir(entity_aabb, block_aabb, move_dir, self)
477 })
478 }
479
480 #[inline]
484 pub fn explode_power(&self) -> Option<f32> {
485 match self.kind() {
488 BlockKind::Leaves => Some(0.25),
489 BlockKind::ArtLeaves => Some(0.25),
490 BlockKind::Grass => Some(0.5),
491 BlockKind::WeakRock => Some(0.75),
492 BlockKind::Snow => Some(0.1),
493 BlockKind::Ice => Some(0.5),
494 BlockKind::Wood => Some(4.5),
495 BlockKind::Lava => None,
496 _ => self.get_sprite().and_then(|sprite| match sprite {
497 sprite if sprite.is_container() => None,
498 SpriteKind::Keyhole
499 | SpriteKind::KeyDoor
500 | SpriteKind::BoneKeyhole
501 | SpriteKind::BoneKeyDoor
502 | SpriteKind::OneWayWall
503 | SpriteKind::KeyholeBars
504 | SpriteKind::DoorBars => None,
505 SpriteKind::Anvil
506 | SpriteKind::Cauldron
507 | SpriteKind::CookingPot
508 | SpriteKind::CraftingBench
509 | SpriteKind::Forge
510 | SpriteKind::Loom
511 | SpriteKind::SpinningWheel
512 | SpriteKind::DismantlingBench
513 | SpriteKind::RepairBench
514 | SpriteKind::TanningRack
515 | SpriteKind::Chest
516 | SpriteKind::DungeonChest0
517 | SpriteKind::DungeonChest1
518 | SpriteKind::DungeonChest2
519 | SpriteKind::DungeonChest3
520 | SpriteKind::DungeonChest4
521 | SpriteKind::DungeonChest5
522 | SpriteKind::CoralChest
523 | SpriteKind::HaniwaUrn
524 | SpriteKind::HaniwaKeyDoor
525 | SpriteKind::HaniwaKeyhole
526 | SpriteKind::VampireKeyDoor
527 | SpriteKind::VampireKeyhole
528 | SpriteKind::MyrmidonKeyDoor
529 | SpriteKind::MyrmidonKeyhole
530 | SpriteKind::MinotaurKeyhole
531 | SpriteKind::HaniwaTrap
532 | SpriteKind::HaniwaTrapTriggered
533 | SpriteKind::ChestBuried
534 | SpriteKind::TerracottaChest
535 | SpriteKind::SahaginChest
536 | SpriteKind::SeaDecorBlock
537 | SpriteKind::SeaDecorChain
538 | SpriteKind::SeaDecorWindowHor
539 | SpriteKind::SeaDecorWindowVer
540 | SpriteKind::WitchWindow
541 | SpriteKind::Rope
542 | SpriteKind::MetalChain
543 | SpriteKind::IronSpike
544 | SpriteKind::HotSurface
545 | SpriteKind::FireBlock
546 | SpriteKind::GlassBarrier
547 | SpriteKind::GlassKeyhole
548 | SpriteKind::SahaginKeyhole
549 | SpriteKind::SahaginKeyDoor
550 | SpriteKind::TerracottaKeyDoor
551 | SpriteKind::TerracottaKeyhole
552 | SpriteKind::TerracottaStatue
553 | SpriteKind::TerracottaBlock => None,
554 SpriteKind::EnsnaringVines
555 | SpriteKind::EnsnaringWeb
556 | SpriteKind::SeaUrchin
557 | SpriteKind::IceSpike
558 | SpriteKind::DiamondLight => Some(0.1),
559 _ => Some(0.25),
560 }),
561 }
562 }
563
564 #[inline]
565 pub fn collectible_id(&self) -> Option<Option<LootSpec<&'static str>>> {
566 self.get_sprite()
567 .map(|s| s.collectible_id())
568 .unwrap_or(None)
569 }
570
571 #[inline]
572 pub fn is_collectible(&self) -> bool {
573 self.collectible_id().is_some() && self.mine_tool().is_none()
574 }
575
576 #[inline]
577 pub fn is_mountable(&self) -> bool { self.mount_offset().is_some() }
578
579 pub fn mount_offset(&self) -> Option<(Vec3<f32>, Vec3<f32>)> {
581 self.get_sprite().and_then(|sprite| sprite.mount_offset())
582 }
583
584 pub fn is_controller(&self) -> bool {
585 self.get_sprite()
586 .is_some_and(|sprite| sprite.is_controller())
587 }
588
589 #[inline]
590 pub fn is_bonkable(&self) -> bool {
591 match self.get_sprite() {
592 Some(
593 SpriteKind::Apple | SpriteKind::Beehive | SpriteKind::Coconut | SpriteKind::Bomb,
594 ) => self.is_solid(),
595 _ => false,
596 }
597 }
598
599 #[inline]
600 pub fn is_owned(&self) -> bool {
601 self.get_attr::<sprite::Owned>()
602 .is_ok_and(|sprite::Owned(b)| b)
603 }
604
605 #[inline]
608 pub fn mine_tool(&self) -> Option<ToolKind> {
609 match self.kind() {
610 BlockKind::WeakRock | BlockKind::Ice | BlockKind::GlowingWeakRock => {
611 Some(ToolKind::Pick)
612 },
613 _ => self.get_sprite().and_then(|s| s.mine_tool()),
614 }
615 }
616
617 #[inline]
618 pub fn is_opaque(&self) -> bool {
619 match self.get_sprite() {
620 Some(
621 SpriteKind::Keyhole
622 | SpriteKind::KeyDoor
623 | SpriteKind::KeyholeBars
624 | SpriteKind::DoorBars,
625 ) => true,
626 Some(_) => false,
627 None => self.kind().is_filled(),
628 }
629 }
630
631 #[inline]
632 pub fn solid_height(&self) -> f32 {
633 self.get_sprite()
634 .map(|s| s.solid_height().unwrap_or(0.0))
635 .unwrap_or(1.0)
636 }
637
638 #[inline]
641 pub fn get_friction(&self) -> f32 {
642 match self.kind() {
643 BlockKind::Ice => FRIC_GROUND * 0.1,
644 _ => FRIC_GROUND,
645 }
646 }
647
648 #[inline]
654 pub fn get_traction(&self) -> f32 {
655 match self.kind() {
656 BlockKind::Snow | BlockKind::ArtSnow => 0.8,
657 _ => 1.0,
658 }
659 }
660
661 pub fn with_toggle_light(self, enable: bool) -> Option<Self> {
663 self.with_attr(sprite::LightEnabled(enable)).ok()
664 }
665
666 #[inline]
667 pub fn kind(&self) -> BlockKind { self.kind }
668
669 #[inline]
671 #[must_use]
672 pub fn with_data_of(mut self, other: Block) -> Self {
673 if self.is_filled() == other.is_filled() {
674 self = self.with_data(other.data());
675 }
676 self
677 }
678
679 #[inline]
681 #[must_use]
682 pub fn with_sprite(mut self, sprite: SpriteKind) -> Self {
683 if !self.is_filled() {
684 self = Self::unfilled(self.kind, sprite);
685 }
686 self
687 }
688
689 #[inline]
691 #[must_use]
692 pub fn with_ori(self, ori: u8) -> Option<Self> { self.with_attr(sprite::Ori(ori)).ok() }
693
694 #[inline]
696 #[must_use]
697 pub fn with_adjacent_type(self, adj: RelativeNeighborPosition) -> Option<Self> {
698 self.with_attr(sprite::AdjacentType(adj as u8)).ok()
699 }
700
701 #[inline]
703 #[must_use]
704 pub fn into_vacant(self) -> Self {
705 if self.is_fluid() {
706 Block::unfilled(self.kind(), SpriteKind::Empty)
707 } else {
708 Block::air(SpriteKind::Empty)
711 }
712 }
713
714 #[inline]
716 #[must_use]
717 pub fn from_u32(x: u32) -> Option<Self> {
718 let [bk, r, g, b] = x.to_le_bytes();
719 let block = Self {
720 kind: BlockKind::from_u8(bk)?,
721 data: [r, g, b],
722 };
723
724 (block.kind.is_filled() || SpriteKind::from_block(block).is_some()).then_some(block)
725 }
726
727 #[inline]
728 pub fn to_u32(self) -> u32 {
729 u32::from_le_bytes([self.kind as u8, self.data[0], self.data[1], self.data[2]])
730 }
731}
732
733const _: () = assert!(core::mem::size_of::<BlockKind>() == 1);
734const _: () = assert!(core::mem::size_of::<Block>() == 4);