veloren_common/terrain/
block.rs

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, // Air counts as a fluid
37        Water = 0x01,
38        // 0x02 <= x < 0x10 are reserved for other fluids. These are 2^n aligned to allow bitwise
39        // checking of common conditions. For example, `is_fluid` is just `block_kind &
40        // 0x0F == 0` (this is a very common operation used in meshing that could do with
41        // being *very* fast).
42        Rock = 0x10,
43        WeakRock = 0x11, // Explodable
44        Lava = 0x12,     // TODO: Reevaluate whether this should be in the rock section
45        GlowingRock = 0x13,
46        GlowingWeakRock = 0x14,
47        // 0x12 <= x < 0x20 is reserved for future rocks
48        Grass = 0x20, // Note: *not* the same as grass sprites
49        Snow = 0x21,
50        // Snow to use with sites, to not attract snowfall particles
51        ArtSnow = 0x22,
52        // 0x21 <= x < 0x30 is reserved for future grasses
53        Earth = 0x30,
54        Sand = 0x31,
55        // 0x32 <= x < 0x40 is reserved for future earths/muds/gravels/sands/etc.
56        Wood = 0x40,
57        Leaves = 0x41,
58        GlowingMushroom = 0x42,
59        Ice = 0x43,
60        ArtLeaves = 0x44,
61        // 0x43 <= x < 0x50 is reserved for future tree parts
62        // Covers all other cases (we sometimes have bizarrely coloured misc blocks, and also we
63        // often want to experiment with new kinds of block without allocating them a
64        // dedicated block kind.
65        Misc = 0xFE,
66    }
67);
68
69impl BlockKind {
70    #[inline]
71    pub const fn is_air(&self) -> bool { matches!(self, BlockKind::Air) }
72
73    /// Determine whether the block kind is a gas or a liquid. This does not
74    /// consider any sprites that may occupy the block (the definition of
75    /// fluid is 'a substance that deforms to fit containers')
76    #[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    /// Determine whether the block is filled (i.e: fully solid). Right now,
92    /// this is the opposite of being a fluid.
93    #[inline]
94    pub const fn is_filled(&self) -> bool { !self.is_fluid() }
95
96    /// Determine whether the block has an RGB color stored in the attribute
97    /// fields.
98    #[inline]
99    pub const fn has_color(&self) -> bool { self.is_filled() }
100
101    /// Determine whether the block is 'terrain-like'. This definition is
102    /// arbitrary, but includes things like rocks, soils, sands, grass, and
103    /// other blocks that might be expected to the landscape. Plant matter and
104    /// snow are *not* included.
105    #[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/// # Format
121///
122/// ```ignore
123/// BBBBBBBB CCCCCCCC AAAAAIII IIIIIIII
124/// ```
125/// - `0..8`  : BlockKind
126/// - `8..16` : Category
127/// - `16..N` : Attributes (many fields)
128/// - `N..32` : Sprite ID
129///
130/// `N` is per-category. You can match on the category byte to find the length
131/// of the ID field.
132///
133/// Attributes are also per-category. Each category specifies its own list of
134/// attribute fields.
135///
136/// Why is the sprite ID at the end? Simply put, it makes masking faster and
137/// easier, which is important because extracting the `SpriteKind` is a more
138/// commonly performed operation than extracting attributes.
139#[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    /* Constructors */
161
162    #[inline]
163    pub const fn from_raw(kind: BlockKind, data: [u8; 3]) -> Self { Self { kind, data } }
164
165    // TODO: Rename to `filled`, make caller guarantees stronger
166    #[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            // Works because `SpriteKind::Empty` has no attributes
173            let data = (SpriteKind::Empty as u32).to_be_bytes();
174            Self::from_raw(kind, [data[1], data[2], data[3]])
175        }
176    }
177
178    // Only valid if `block_kind` is unfilled, so this is just a private utility
179    // method
180    #[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        // Works because `SpriteKind::Empty` has no attributes
194        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    /* Sprite decoding */
202
203    #[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    /// Build this block with the given sprite attribute set.
225    #[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    /// Set the given attribute of this block's sprite.
235    #[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    /// Get the given attribute of this block's sprite.
247    #[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    /// Returns the rtsim resource, if any, that this block corresponds to. If
297    /// you want the scarcity of a block to change with rtsim's resource
298    /// depletion tracking, you can do so by editing this function.
299    #[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 // TODO: Not a fruit, but kind of acts like one
356                | 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    // minimum block, attenuation
467    #[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    // Filled blocks or sprites
483    #[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    /// Can this block be exploded? If so, what 'power' is required to do so?
502    /// Note that we don't really define what 'power' is. Consider the units
503    /// arbitrary and only important when compared to one-another.
504    #[inline]
505    pub fn explode_power(&self) -> Option<f32> {
506        // Explodable means that the terrain sprite will get removed anyway,
507        // so all is good for empty fluids.
508        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    /// Whether the block containes a sprite that marked to be collectible
586    ///
587    /// Check docs for [`SpriteKind::default_tool`] for more.
588    #[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    /// Get the position and direction to mount this block if any.
597    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    /// The tool required to mine this block. For blocks that cannot be mined,
623    /// `None` is returned.
624    #[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    /// Get the friction constant used to calculate surface friction when
656    /// walking/climbing. Currently has no units.
657    #[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    /// Get the traction permitted by this block as a proportion of the friction
666    /// applied.
667    ///
668    /// 1.0 = default, 0.0 = completely inhibits movement, > 1.0 = potential for
669    /// infinite acceleration (in a vacuum).
670    #[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    /// Apply a light toggle to this block, if possible
679    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    /// If possible, copy the sprite/color data of the other block.
687    #[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    /// If this block is a fluid, replace its sprite.
697    #[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    /// If this block can have orientation, give it a new orientation.
707    #[inline]
708    #[must_use]
709    pub fn with_ori(self, ori: u8) -> Option<Self> { self.with_attr(sprite::Ori(ori)).ok() }
710
711    /// If this block can have adjacent sprites, give it its AdjacentType
712    #[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    /// Remove the terrain sprite or solid aspects of a block
719    #[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            // FIXME: Figure out if there's some sensible way to determine what medium to
726            // replace a filled block with if it's removed.
727            Block::air(SpriteKind::Empty)
728        }
729    }
730
731    /// Attempt to convert a [`u32`] to a block
732    #[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);