veloren_common/terrain/
block.rs

1use super::{
2    SpriteCfg, 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, // Air counts as a fluid
38        Water = 0x01,
39        // 0x02 <= x < 0x10 are reserved for other fluids. These are 2^n aligned to allow bitwise
40        // checking of common conditions. For example, `is_fluid` is just `block_kind &
41        // 0x0F == 0` (this is a very common operation used in meshing that could do with
42        // being *very* fast).
43        Rock = 0x10,
44        WeakRock = 0x11, // Explodable
45        Lava = 0x12,     // TODO: Reevaluate whether this should be in the rock section
46        GlowingRock = 0x13,
47        GlowingWeakRock = 0x14,
48        // 0x12 <= x < 0x20 is reserved for future rocks
49        Grass = 0x20, // Note: *not* the same as grass sprites
50        Snow = 0x21,
51        // Snow to use with sites, to not attract snowfall particles
52        ArtSnow = 0x22,
53        // 0x21 <= x < 0x30 is reserved for future grasses
54        Earth = 0x30,
55        Sand = 0x31,
56        // 0x32 <= x < 0x40 is reserved for future earths/muds/gravels/sands/etc.
57        Wood = 0x40,
58        Leaves = 0x41,
59        GlowingMushroom = 0x42,
60        Ice = 0x43,
61        ArtLeaves = 0x44,
62        // 0x43 <= x < 0x50 is reserved for future tree parts
63        // Covers all other cases (we sometimes have bizarrely coloured misc blocks, and also we
64        // often want to experiment with new kinds of block without allocating them a
65        // dedicated block kind.
66        Misc = 0xFE,
67    }
68);
69
70impl BlockKind {
71    #[inline]
72    pub const fn is_air(&self) -> bool { matches!(self, BlockKind::Air) }
73
74    /// Determine whether the block kind is a gas or a liquid. This does not
75    /// consider any sprites that may occupy the block (the definition of
76    /// fluid is 'a substance that deforms to fit containers')
77    #[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    /// Determine whether the block is filled (i.e: fully solid). Right now,
93    /// this is the opposite of being a fluid.
94    #[inline]
95    pub const fn is_filled(&self) -> bool { !self.is_fluid() }
96
97    /// Determine whether the block has an RGB color stored in the attribute
98    /// fields.
99    #[inline]
100    pub const fn has_color(&self) -> bool { self.is_filled() }
101
102    /// Determine whether the block is 'terrain-like'. This definition is
103    /// arbitrary, but includes things like rocks, soils, sands, grass, and
104    /// other blocks that might be expected to the landscape. Plant matter and
105    /// snow are *not* included.
106    #[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/// # Format
122///
123/// ```ignore
124/// BBBBBBBB CCCCCCCC AAAAAIII IIIIIIII
125/// ```
126/// - `0..8`  : BlockKind
127/// - `8..16` : Category
128/// - `16..N` : Attributes (many fields)
129/// - `N..32` : Sprite ID
130///
131/// `N` is per-category. You can match on the category byte to find the length
132/// of the ID field.
133///
134/// Attributes are also per-category. Each category specifies its own list of
135/// attribute fields.
136///
137/// Why is the sprite ID at the end? Simply put, it makes masking faster and
138/// easier, which is important because extracting the `SpriteKind` is a more
139/// commonly performed operation than extracting attributes.
140#[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    /* Constructors */
162
163    #[inline]
164    pub const fn from_raw(kind: BlockKind, data: [u8; 3]) -> Self { Self { kind, data } }
165
166    // TODO: Rename to `filled`, make caller guarantees stronger
167    #[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            // Works because `SpriteKind::Empty` has no attributes
174            let data = (SpriteKind::Empty as u32).to_be_bytes();
175            Self::from_raw(kind, [data[1], data[2], data[3]])
176        }
177    }
178
179    // Only valid if `block_kind` is unfilled, so this is just a private utility
180    // method
181    #[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        // Works because `SpriteKind::Empty` has no attributes
195        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    /* Sprite decoding */
203
204    #[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    /// Build this block with the given sprite attribute set.
226    #[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    /// Set the given attribute of this block's sprite.
236    #[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    /// Get the given attribute of this block's sprite.
248    #[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    /// Returns the rtsim resource, if any, that this block corresponds to. If
298    /// you want the scarcity of a block to change with rtsim's resource
299    /// depletion tracking, you can do so by editing this function.
300    #[inline]
301    pub fn get_rtsim_resource(&self) -> Option<rtsim::ChunkResource> {
302        match self.get_sprite()? {
303            SpriteKind::Stones => Some(rtsim::ChunkResource::Stone),
304            SpriteKind::Twigs
305                | SpriteKind::Wood
306                | SpriteKind::Bamboo
307                | SpriteKind::Hardwood
308                | SpriteKind::Ironwood
309                | SpriteKind::Frostwood
310                | SpriteKind::Eldwood => Some(rtsim::ChunkResource::Wood),
311            SpriteKind::Amethyst
312                | SpriteKind::Ruby
313                | SpriteKind::Sapphire
314                | SpriteKind::Emerald
315                | SpriteKind::Topaz
316                | SpriteKind::Diamond
317                | SpriteKind::CrystalHigh
318                | SpriteKind::CrystalLow => Some(rtsim::ChunkResource::Gem),
319            SpriteKind::Bloodstone
320                | SpriteKind::Coal
321                | SpriteKind::Cobalt
322                | SpriteKind::Copper
323                | SpriteKind::Iron
324                | SpriteKind::Tin
325                | SpriteKind::Silver
326                | SpriteKind::Gold => Some(rtsim::ChunkResource::Ore),
327
328            SpriteKind::LongGrass
329                | SpriteKind::MediumGrass
330                | SpriteKind::ShortGrass
331                | SpriteKind::LargeGrass
332                | SpriteKind::GrassBlue
333                | SpriteKind::SavannaGrass
334                | SpriteKind::TallSavannaGrass
335                | SpriteKind::RedSavannaGrass
336                | SpriteKind::JungleRedGrass
337                | SpriteKind::Fern => Some(rtsim::ChunkResource::Grass),
338            SpriteKind::BlueFlower
339                | SpriteKind::PinkFlower
340                | SpriteKind::PurpleFlower
341                | SpriteKind::RedFlower
342                | SpriteKind::WhiteFlower
343                | SpriteKind::YellowFlower
344                | SpriteKind::Sunflower
345                | SpriteKind::Moonbell
346                | SpriteKind::Pyrebloom => Some(rtsim::ChunkResource::Flower),
347            SpriteKind::Reed
348                | SpriteKind::Flax
349                | SpriteKind::WildFlax
350                | SpriteKind::Cotton
351                | SpriteKind::Corn
352                | SpriteKind::WheatYellow
353                | SpriteKind::WheatGreen => Some(rtsim::ChunkResource::Plant),
354            SpriteKind::Apple
355                | SpriteKind::Pumpkin
356                | SpriteKind::Beehive // TODO: Not a fruit, but kind of acts like one
357                | SpriteKind::Coconut => Some(rtsim::ChunkResource::Fruit),
358            SpriteKind::Lettuce
359                | SpriteKind::Carrot
360                | SpriteKind::Tomato
361                | SpriteKind::Radish
362                | SpriteKind::Turnip => Some(rtsim::ChunkResource::Vegetable),
363            SpriteKind::Mushroom
364                | SpriteKind::CaveMushroom
365                | SpriteKind::CeilingMushroom
366                | SpriteKind::RockyMushroom
367                | SpriteKind::LushMushroom
368                | SpriteKind::GlowMushroom => Some(rtsim::ChunkResource::Mushroom),
369
370            SpriteKind::Chest
371                | SpriteKind::ChestBuried
372                | SpriteKind::PotionMinor
373                | SpriteKind::DungeonChest0
374                | SpriteKind::DungeonChest1
375                | SpriteKind::DungeonChest2
376                | SpriteKind::DungeonChest3
377                | SpriteKind::DungeonChest4
378                | SpriteKind::DungeonChest5
379                | SpriteKind::CoralChest
380                | SpriteKind::HaniwaUrn
381                | SpriteKind::TerracottaChest
382                | SpriteKind::SahaginChest
383                | SpriteKind::Crate
384                | SpriteKind::CommonLockedChest => Some(rtsim::ChunkResource::Loot),
385            _ => None,
386        }
387    }
388
389    #[inline]
390    pub fn get_glow(&self) -> Option<u8> {
391        let glow_level = match self.kind() {
392            BlockKind::Lava => 24,
393            BlockKind::GlowingRock | BlockKind::GlowingWeakRock => 10,
394            BlockKind::GlowingMushroom => 20,
395            _ => match self.get_sprite()? {
396                SpriteKind::StreetLamp | SpriteKind::StreetLampTall | SpriteKind::BonfireMLit => 24,
397                SpriteKind::Ember | SpriteKind::FireBlock => 20,
398                SpriteKind::WallLamp
399                | SpriteKind::WallLampSmall
400                | SpriteKind::WallLampWizard
401                | SpriteKind::WallLampMesa
402                | SpriteKind::WallSconce
403                | SpriteKind::FireBowlGround
404                | SpriteKind::MesaLantern
405                | SpriteKind::LampTerracotta
406                | SpriteKind::ChristmasOrnament
407                | SpriteKind::CliffDecorBlock
408                | SpriteKind::Orb
409                | SpriteKind::Candle => 16,
410                SpriteKind::DiamondLight => 30,
411                SpriteKind::Velorite
412                | SpriteKind::VeloriteFrag
413                | SpriteKind::GrassBlueShort
414                | SpriteKind::GrassBlueMedium
415                | SpriteKind::GrassBlueLong
416                | SpriteKind::CavernLillypadBlue
417                | SpriteKind::MycelBlue
418                | SpriteKind::Mold
419                | SpriteKind::CeilingMushroom => 6,
420                SpriteKind::CaveMushroom
421                | SpriteKind::GlowMushroom
422                | SpriteKind::CookingPot
423                | SpriteKind::CrystalHigh
424                | SpriteKind::LanternFlower
425                | SpriteKind::CeilingLanternFlower
426                | SpriteKind::LanternPlant
427                | SpriteKind::CeilingLanternPlant
428                | SpriteKind::CrystalLow => 10,
429                SpriteKind::SewerMushroom => 16,
430                SpriteKind::Amethyst
431                | SpriteKind::Ruby
432                | SpriteKind::Sapphire
433                | SpriteKind::Diamond
434                | SpriteKind::Emerald
435                | SpriteKind::Topaz => 3,
436                SpriteKind::Lantern
437                | SpriteKind::LanternpostWoodLantern
438                | SpriteKind::LanternAirshipWallBlackS
439                | SpriteKind::LanternAirshipWallBrownS
440                | SpriteKind::LanternAirshipWallChestnutS
441                | SpriteKind::LanternAirshipWallRedS
442                | SpriteKind::LanternAirshipGroundBlackS
443                | SpriteKind::LanternAirshipGroundBrownS
444                | SpriteKind::LanternAirshipGroundChestnutS
445                | SpriteKind::LanternAirshipGroundRedS
446                | SpriteKind::LampMetalShinglesCyan
447                | SpriteKind::LampMetalShinglesRed => 24,
448                SpriteKind::TerracottaStatue => 8,
449                SpriteKind::SeashellLantern | SpriteKind::GlowIceCrystal => 16,
450                SpriteKind::SeaDecorEmblem => 12,
451                SpriteKind::SeaDecorBlock
452                | SpriteKind::HaniwaKeyDoor
453                | SpriteKind::VampireKeyDoor => 10,
454                _ => return None,
455            },
456        };
457
458        if self
459            .get_attr::<sprite::LightEnabled>()
460            .map_or(true, |l| l.0)
461        {
462            Some(glow_level)
463        } else {
464            None
465        }
466    }
467
468    // minimum block, attenuation
469    #[inline]
470    pub fn get_max_sunlight(&self) -> (u8, f32) {
471        match self.kind() {
472            BlockKind::Water => (0, 0.4),
473            BlockKind::Leaves => (9, 255.0),
474            BlockKind::ArtLeaves => (9, 255.0),
475            BlockKind::Wood => (6, 2.0),
476            BlockKind::Snow => (6, 2.0),
477            BlockKind::ArtSnow => (6, 2.0),
478            BlockKind::Ice => (4, 2.0),
479            _ if self.is_opaque() => (0, 255.0),
480            _ => (0, 0.0),
481        }
482    }
483
484    // Filled blocks or sprites
485    #[inline]
486    pub fn is_solid(&self) -> bool {
487        self.get_sprite()
488            .map(|s| s.solid_height().is_some())
489            .unwrap_or(!matches!(self.kind, BlockKind::Lava))
490    }
491
492    pub fn valid_collision_dir(
493        &self,
494        entity_aabb: Aabb<f32>,
495        block_aabb: Aabb<f32>,
496        move_dir: Vec3<f32>,
497    ) -> bool {
498        self.get_sprite().is_none_or(|sprite| {
499            sprite.valid_collision_dir(entity_aabb, block_aabb, move_dir, self)
500        })
501    }
502
503    /// Can this block be exploded? If so, what 'power' is required to do so?
504    /// Note that we don't really define what 'power' is. Consider the units
505    /// arbitrary and only important when compared to one-another.
506    #[inline]
507    pub fn explode_power(&self) -> Option<f32> {
508        // Explodable means that the terrain sprite will get removed anyway,
509        // so all is good for empty fluids.
510        match self.kind() {
511            BlockKind::Leaves => Some(0.25),
512            BlockKind::ArtLeaves => Some(0.25),
513            BlockKind::Grass => Some(0.5),
514            BlockKind::WeakRock => Some(0.75),
515            BlockKind::Snow => Some(0.1),
516            BlockKind::Ice => Some(0.5),
517            BlockKind::Wood => Some(4.5),
518            BlockKind::Lava => None,
519            _ => self.get_sprite().and_then(|sprite| match sprite {
520                sprite if sprite.is_defined_as_container() => None,
521                SpriteKind::Keyhole
522                | SpriteKind::KeyDoor
523                | SpriteKind::BoneKeyhole
524                | SpriteKind::BoneKeyDoor
525                | SpriteKind::OneWayWall
526                | SpriteKind::KeyholeBars
527                | SpriteKind::DoorBars => None,
528                SpriteKind::Anvil
529                | SpriteKind::Cauldron
530                | SpriteKind::CookingPot
531                | SpriteKind::CraftingBench
532                | SpriteKind::Forge
533                | SpriteKind::Loom
534                | SpriteKind::SpinningWheel
535                | SpriteKind::DismantlingBench
536                | SpriteKind::RepairBench
537                | SpriteKind::TanningRack
538                | SpriteKind::Chest
539                | SpriteKind::DungeonChest0
540                | SpriteKind::DungeonChest1
541                | SpriteKind::DungeonChest2
542                | SpriteKind::DungeonChest3
543                | SpriteKind::DungeonChest4
544                | SpriteKind::DungeonChest5
545                | SpriteKind::CoralChest
546                | SpriteKind::HaniwaUrn
547                | SpriteKind::HaniwaKeyDoor
548                | SpriteKind::HaniwaKeyhole
549                | SpriteKind::VampireKeyDoor
550                | SpriteKind::VampireKeyhole
551                | SpriteKind::MyrmidonKeyDoor
552                | SpriteKind::MyrmidonKeyhole
553                | SpriteKind::MinotaurKeyhole
554                | SpriteKind::HaniwaTrap
555                | SpriteKind::HaniwaTrapTriggered
556                | SpriteKind::ChestBuried
557                | SpriteKind::CommonLockedChest
558                | SpriteKind::TerracottaChest
559                | SpriteKind::SahaginChest
560                | SpriteKind::SeaDecorBlock
561                | SpriteKind::SeaDecorChain
562                | SpriteKind::SeaDecorWindowHor
563                | SpriteKind::SeaDecorWindowVer
564                | SpriteKind::WitchWindow
565                | SpriteKind::Rope
566                | SpriteKind::MetalChain
567                | SpriteKind::IronSpike
568                | SpriteKind::HotSurface
569                | SpriteKind::FireBlock
570                | SpriteKind::GlassBarrier
571                | SpriteKind::GlassKeyhole
572                | SpriteKind::SahaginKeyhole
573                | SpriteKind::SahaginKeyDoor
574                | SpriteKind::TerracottaKeyDoor
575                | SpriteKind::TerracottaKeyhole
576                | SpriteKind::TerracottaStatue
577                | SpriteKind::TerracottaBlock => None,
578                SpriteKind::EnsnaringVines
579                | SpriteKind::EnsnaringWeb
580                | SpriteKind::SeaUrchin
581                | SpriteKind::IceSpike
582                | SpriteKind::DiamondLight => Some(0.1),
583                _ => Some(0.25),
584            }),
585        }
586    }
587
588    /// Whether the block containes a sprite that marked to be collectible
589    ///
590    /// Check docs for [`SpriteKind::default_tool`] for more.
591    #[inline]
592    pub fn default_tool(&self) -> Option<Option<ToolKind>> {
593        self.get_sprite().and_then(|s| s.default_tool())
594    }
595
596    #[inline]
597    pub fn is_collectible(&self, sprite_cfg: Option<&SpriteCfg>) -> bool {
598        self.get_sprite().is_some_and(|s| {
599            sprite_cfg.and_then(|cfg| cfg.loot_table.as_ref()).is_some()
600                || s.default_tool() == Some(None)
601        }) && matches!(self.get_attr(), Ok(sprite::Collectable(true)) | Err(_))
602    }
603
604    #[inline]
605    pub fn is_mountable(&self) -> bool { self.mount_offset().is_some() }
606
607    /// Get the position and direction to mount this block if any.
608    pub fn mount_offset(&self) -> Option<(Vec3<f32>, Vec3<f32>)> {
609        self.get_sprite().and_then(|sprite| sprite.mount_offset())
610    }
611
612    pub fn mount_buffs(&self) -> Option<Vec<BuffEffect>> {
613        self.get_sprite().and_then(|sprite| sprite.mount_buffs())
614    }
615
616    pub fn is_controller(&self) -> bool {
617        self.get_sprite()
618            .is_some_and(|sprite| sprite.is_controller())
619    }
620
621    #[inline]
622    pub fn is_bonkable(&self) -> bool {
623        match self.get_sprite() {
624            Some(
625                SpriteKind::Apple | SpriteKind::Beehive | SpriteKind::Coconut | SpriteKind::Bomb,
626            ) => self.is_solid(),
627            _ => false,
628        }
629    }
630
631    #[inline]
632    pub fn is_owned(&self) -> bool {
633        self.get_attr::<sprite::Owned>()
634            .is_ok_and(|sprite::Owned(b)| b)
635    }
636
637    /// The tool required to mine this block. For blocks that cannot be mined,
638    /// `None` is returned.
639    #[inline]
640    pub fn mine_tool(&self) -> Option<ToolKind> {
641        match self.kind() {
642            BlockKind::WeakRock | BlockKind::Ice | BlockKind::GlowingWeakRock => {
643                Some(ToolKind::Pick)
644            },
645            _ => self.get_sprite().and_then(|s| s.mine_tool()),
646        }
647    }
648
649    #[inline]
650    pub fn is_opaque(&self) -> bool {
651        match self.get_sprite() {
652            Some(
653                SpriteKind::Keyhole
654                | SpriteKind::KeyDoor
655                | SpriteKind::KeyholeBars
656                | SpriteKind::DoorBars,
657            ) => true,
658            Some(_) => false,
659            None => self.kind().is_filled(),
660        }
661    }
662
663    #[inline]
664    pub fn solid_height(&self) -> f32 {
665        self.get_sprite()
666            .map(|s| s.solid_height().unwrap_or(0.0))
667            .unwrap_or(1.0)
668    }
669
670    /// Get the friction constant used to calculate surface friction when
671    /// walking/climbing. Currently has no units.
672    #[inline]
673    pub fn get_friction(&self) -> f32 {
674        match self.kind() {
675            BlockKind::Ice => FRIC_GROUND * 0.1,
676            _ => FRIC_GROUND,
677        }
678    }
679
680    /// Get the traction permitted by this block as a proportion of the friction
681    /// applied.
682    ///
683    /// 1.0 = default, 0.0 = completely inhibits movement, > 1.0 = potential for
684    /// infinite acceleration (in a vacuum).
685    #[inline]
686    pub fn get_traction(&self) -> f32 {
687        match self.kind() {
688            BlockKind::Snow | BlockKind::ArtSnow => 0.8,
689            _ => 1.0,
690        }
691    }
692
693    /// Apply a light toggle to this block, if possible
694    pub fn with_toggle_light(self, enable: bool) -> Option<Self> {
695        self.with_attr(sprite::LightEnabled(enable)).ok()
696    }
697
698    #[inline]
699    pub fn kind(&self) -> BlockKind { self.kind }
700
701    /// If possible, copy the sprite/color data of the other block.
702    #[inline]
703    #[must_use]
704    pub fn with_data_of(mut self, other: Block) -> Self {
705        if self.is_filled() == other.is_filled() {
706            self = self.with_data(other.data());
707        }
708        self
709    }
710
711    /// If this block is a fluid, replace its sprite.
712    #[inline]
713    #[must_use]
714    pub fn with_sprite(mut self, sprite: SpriteKind) -> Self {
715        if !self.is_filled() {
716            self = Self::unfilled(self.kind, sprite);
717        }
718        self
719    }
720
721    /// If this block can have orientation, give it a new orientation.
722    #[inline]
723    #[must_use]
724    pub fn with_ori(self, ori: u8) -> Option<Self> { self.with_attr(sprite::Ori(ori)).ok() }
725
726    /// If this block can have adjacent sprites, give it its AdjacentType
727    #[inline]
728    #[must_use]
729    pub fn with_adjacent_type(self, adj: RelativeNeighborPosition) -> Option<Self> {
730        self.with_attr(sprite::AdjacentType(adj as u8)).ok()
731    }
732
733    /// Remove the terrain sprite or solid aspects of a block
734    #[inline]
735    #[must_use]
736    pub fn into_vacant(self) -> Self {
737        if self.is_fluid() {
738            Block::unfilled(self.kind(), SpriteKind::Empty)
739        } else {
740            // FIXME: Figure out if there's some sensible way to determine what medium to
741            // replace a filled block with if it's removed.
742            Block::air(SpriteKind::Empty)
743        }
744    }
745
746    /// Attempt to convert a [`u32`] to a block
747    #[inline]
748    #[must_use]
749    pub fn from_u32(x: u32) -> Option<Self> {
750        let [bk, r, g, b] = x.to_le_bytes();
751        let block = Self {
752            kind: BlockKind::from_u8(bk)?,
753            data: [r, g, b],
754        };
755
756        (block.kind.is_filled() || SpriteKind::from_block(block).is_some()).then_some(block)
757    }
758
759    #[inline]
760    pub fn to_u32(self) -> u32 {
761        u32::from_le_bytes([self.kind as u8, self.data[0], self.data[1], self.data[2]])
762    }
763}
764
765const _: () = assert!(core::mem::size_of::<BlockKind>() == 1);
766const _: () = assert!(core::mem::size_of::<Block>() == 4);