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    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, Eq, Hash, PartialEq, Serialize, Deserialize)]
141pub struct Block {
142    kind: BlockKind,
143    data: [u8; 3],
144}
145
146impl std::fmt::Debug for Block {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        let mut s = f.debug_struct("Block");
149
150        s.field("kind", &self.kind);
151
152        if let Some(sprite) = super::StructureSprite::from_block(self) {
153            s.field("sprite", &sprite);
154        }
155
156        if self.is_filled() {
157            s.field("color", &self.data);
158        }
159
160        s.finish()
161    }
162}
163
164impl FilledVox for Block {
165    fn default_non_filled() -> Self { Block::air(SpriteKind::Empty) }
166
167    fn is_filled(&self) -> bool { self.kind.is_filled() }
168}
169
170impl Deref for Block {
171    type Target = BlockKind;
172
173    fn deref(&self) -> &Self::Target { &self.kind }
174}
175
176impl Block {
177    pub const MAX_HEIGHT: f32 = 3.0;
178
179    /* Constructors */
180
181    #[inline]
182    pub const fn from_raw(kind: BlockKind, data: [u8; 3]) -> Self { Self { kind, data } }
183
184    // TODO: Rename to `filled`, make caller guarantees stronger
185    #[inline]
186    #[track_caller]
187    pub const fn new(kind: BlockKind, color: Rgb<u8>) -> Self {
188        if kind.is_filled() {
189            Self::from_raw(kind, [color.r, color.g, color.b])
190        } else {
191            // Works because `SpriteKind::Empty` has no attributes
192            let data = (SpriteKind::Empty as u32).to_be_bytes();
193            Self::from_raw(kind, [data[1], data[2], data[3]])
194        }
195    }
196
197    // Only valid if `block_kind` is unfilled, so this is just a private utility
198    // method
199    #[inline]
200    pub fn unfilled(kind: BlockKind, sprite: SpriteKind) -> Self {
201        #[cfg(debug_assertions)]
202        assert!(!kind.is_filled());
203
204        Self::from_raw(kind, sprite.to_initial_bytes())
205    }
206
207    #[inline]
208    pub fn air(sprite: SpriteKind) -> Self { Self::unfilled(BlockKind::Air, sprite) }
209
210    #[inline]
211    pub const fn empty() -> Self {
212        // Works because `SpriteKind::Empty` has no attributes
213        let data = (SpriteKind::Empty as u32).to_be_bytes();
214        Self::from_raw(BlockKind::Air, [data[1], data[2], data[3]])
215    }
216
217    #[inline]
218    pub fn water(sprite: SpriteKind) -> Self { Self::unfilled(BlockKind::Water, sprite) }
219
220    /* Sprite decoding */
221
222    #[inline(always)]
223    pub const fn get_sprite(&self) -> Option<SpriteKind> {
224        if !self.kind.is_filled() {
225            SpriteKind::from_block(*self)
226        } else {
227            None
228        }
229    }
230
231    #[inline(always)]
232    pub(super) const fn sprite_category_byte(&self) -> u8 { self.data[0] }
233
234    #[inline(always)]
235    pub const fn sprite_category(&self) -> Option<sprite::Category> {
236        if self.kind.is_filled() {
237            None
238        } else {
239            sprite::Category::from_block(*self)
240        }
241    }
242
243    /// Build this block with the given sprite attribute set.
244    #[inline]
245    pub fn with_attr<A: sprite::Attribute>(
246        mut self,
247        attr: A,
248    ) -> Result<Self, sprite::AttributeError<core::convert::Infallible>> {
249        self.set_attr(attr)?;
250        Ok(self)
251    }
252
253    /// Set the given attribute of this block's sprite.
254    #[inline]
255    pub fn set_attr<A: sprite::Attribute>(
256        &mut self,
257        attr: A,
258    ) -> Result<(), sprite::AttributeError<core::convert::Infallible>> {
259        match self.sprite_category() {
260            Some(category) => category.write_attr(self, attr),
261            None => Err(sprite::AttributeError::NotPresent),
262        }
263    }
264
265    /// Get the given attribute of this block's sprite.
266    #[inline]
267    pub fn get_attr<A: sprite::Attribute>(&self) -> Result<A, sprite::AttributeError<A::Error>> {
268        match self.sprite_category() {
269            Some(category) => category.read_attr(*self),
270            None => Err(sprite::AttributeError::NotPresent),
271        }
272    }
273
274    pub fn rotation_mat(&self) -> Mat3<i32> {
275        let dir = crate::util::Dir2::from_sprite_ori(
276            self.get_attr::<sprite::Ori>().unwrap_or_default().0,
277        )
278        .map(|(d, _)| d)
279        .unwrap_or(crate::util::Dir2::X);
280
281        let mut rot_mat = dir.to_mat3();
282
283        rot_mat.cols *= self.sprite_mirror_vec().map(|f| f as i32);
284
285        rot_mat
286    }
287
288    pub fn sprite_z_rot(&self) -> Option<f32> {
289        self.get_attr::<sprite::Ori>()
290            .ok()
291            .map(|ori| std::f32::consts::PI * 0.25 * ori.0 as f32)
292    }
293
294    pub fn sprite_mirror_vec(&self) -> Vec3<f32> {
295        Vec3::new(
296            self.get_attr::<sprite::MirrorX>().map(|m| m.0),
297            self.get_attr::<sprite::MirrorY>().map(|m| m.0),
298            self.get_attr::<sprite::MirrorZ>().map(|m| m.0),
299        )
300        .map(|b| match b.unwrap_or(false) {
301            true => -1.0,
302            false => 1.0,
303        })
304    }
305
306    #[inline(always)]
307    pub(super) const fn data(&self) -> [u8; 3] { self.data }
308
309    #[inline(always)]
310    pub(super) const fn with_data(mut self, data: [u8; 3]) -> Self {
311        self.data = data;
312        self
313    }
314
315    #[inline(always)]
316    pub(super) const fn to_be_u32(self) -> u32 {
317        u32::from_be_bytes([self.kind as u8, self.data[0], self.data[1], self.data[2]])
318    }
319
320    #[inline]
321    pub fn get_color(&self) -> Option<Rgb<u8>> {
322        if self.has_color() {
323            Some(self.data.into())
324        } else {
325            None
326        }
327    }
328
329    /// Returns the rtsim resource, if any, that this block corresponds to. If
330    /// you want the scarcity of a block to change with rtsim's resource
331    /// depletion tracking, you can do so by editing this function.
332    // TODO: Return type should be `Option<&'static [(rtsim::TerrainResource, f32)]>` to allow
333    // fractional quantities and multiple resources per sprite
334    #[inline]
335    pub fn get_rtsim_resource(&self) -> Option<rtsim::TerrainResource> {
336        match self.get_sprite()? {
337            SpriteKind::Stones | SpriteKind::Stones2 => Some(rtsim::TerrainResource::Stone),
338            SpriteKind::Twigs
339            | SpriteKind::Wood
340            | SpriteKind::Bamboo
341            | SpriteKind::Hardwood
342            | SpriteKind::Ironwood
343            | SpriteKind::Frostwood
344            | SpriteKind::Eldwood => Some(rtsim::TerrainResource::Wood),
345            SpriteKind::Amethyst
346            | SpriteKind::Ruby
347            | SpriteKind::Sapphire
348            | SpriteKind::Emerald
349            | SpriteKind::Topaz
350            | SpriteKind::Diamond
351            | SpriteKind::CrystalHigh
352            | SpriteKind::CrystalLow
353            | SpriteKind::Lodestone => Some(rtsim::TerrainResource::Gem),
354            SpriteKind::Bloodstone
355            | SpriteKind::Coal
356            | SpriteKind::Cobalt
357            | SpriteKind::Copper
358            | SpriteKind::Iron
359            | SpriteKind::Tin
360            | SpriteKind::Silver
361            | SpriteKind::Gold => Some(rtsim::TerrainResource::Ore),
362            SpriteKind::LongGrass
363            | SpriteKind::MediumGrass
364            | SpriteKind::ShortGrass
365            | SpriteKind::LargeGrass
366            | SpriteKind::GrassBlue
367            | SpriteKind::SavannaGrass
368            | SpriteKind::TallSavannaGrass
369            | SpriteKind::RedSavannaGrass
370            | SpriteKind::JungleRedGrass
371            | SpriteKind::Fern => Some(rtsim::TerrainResource::Grass),
372            SpriteKind::BlueFlower
373            | SpriteKind::PinkFlower
374            | SpriteKind::PurpleFlower
375            | SpriteKind::RedFlower
376            | SpriteKind::WhiteFlower
377            | SpriteKind::YellowFlower
378            | SpriteKind::Sunflower
379            | SpriteKind::Moonbell
380            | SpriteKind::Pyrebloom => Some(rtsim::TerrainResource::Flower),
381            SpriteKind::Reed
382            | SpriteKind::Flax
383            | SpriteKind::WildFlax
384            | SpriteKind::Cotton
385            | SpriteKind::Corn
386            | SpriteKind::WheatYellow
387            | SpriteKind::WheatGreen => Some(rtsim::TerrainResource::Plant),
388            SpriteKind::Apple
389            | SpriteKind::Pumpkin
390            | SpriteKind::Beehive // TODO: Not a fruit, but kind of acts like one
391            | SpriteKind::Coconut => Some(rtsim::TerrainResource::Fruit),
392            SpriteKind::Lettuce
393            | SpriteKind::Carrot
394            | SpriteKind::Tomato
395            | SpriteKind::Radish
396            | SpriteKind::Turnip => Some(rtsim::TerrainResource::Vegetable),
397            SpriteKind::Mushroom
398            | SpriteKind::CaveMushroom
399            | SpriteKind::CeilingMushroom
400            | SpriteKind::RockyMushroom
401            | SpriteKind::LushMushroom
402            | SpriteKind::GlowMushroom => Some(rtsim::TerrainResource::Mushroom),
403            // Catch all for other things that give items, but aren't specified above.
404            s if s.default_loot_spec().is_some_and(|inner| inner.is_some()) => Some(rtsim::TerrainResource::Loot),
405            _ => None,
406        }
407        // Don't count collected sprites.
408        // TODO: we may want to have rtsim still spawn these sprites when depleted by spawning them
409        // in the "collected" state, see `into_collected` for sprites that would need this.
410        .filter(|_|  matches!(self.get_attr(), Ok(sprite::Collectable(true)) | Err(_)))
411    }
412
413    #[inline]
414    pub fn get_glow(&self) -> Option<u8> {
415        let glow_level = match self.kind() {
416            BlockKind::Lava => 24,
417            BlockKind::GlowingRock | BlockKind::GlowingWeakRock => 10,
418            BlockKind::GlowingMushroom => 20,
419            _ => match self.get_sprite()? {
420                SpriteKind::StreetLamp | SpriteKind::StreetLampTall | SpriteKind::BonfireMLit => 24,
421                SpriteKind::Ember | SpriteKind::FireBlock => 20,
422                SpriteKind::WallLamp
423                | SpriteKind::WallLampSmall
424                | SpriteKind::WallLampWizard
425                | SpriteKind::WallLampMesa
426                | SpriteKind::WallSconce
427                | SpriteKind::FireBowlGround
428                | SpriteKind::MesaLantern
429                | SpriteKind::LampTerracotta
430                | SpriteKind::ChristmasOrnament
431                | SpriteKind::CliffDecorBlock
432                | SpriteKind::Orb
433                | SpriteKind::Candle => 16,
434                SpriteKind::DiamondLight => 30,
435                SpriteKind::VeloriteFrag
436                | SpriteKind::GrassBlueShort
437                | SpriteKind::GrassBlueMedium
438                | SpriteKind::GrassBlueLong
439                | SpriteKind::CavernLillypadBlue
440                | SpriteKind::MycelBlue
441                | SpriteKind::Mold
442                | SpriteKind::CeilingMushroom => 6,
443                SpriteKind::CaveMushroom
444                | SpriteKind::GlowMushroom
445                | SpriteKind::CookingPot
446                | SpriteKind::CrystalHigh
447                | SpriteKind::LanternFlower
448                | SpriteKind::CeilingLanternFlower
449                | SpriteKind::LanternPlant
450                | SpriteKind::CeilingLanternPlant
451                | SpriteKind::CrystalLow => 10,
452                SpriteKind::SewerMushroom => 16,
453                SpriteKind::Lodestone => 3,
454                SpriteKind::Lantern
455                | SpriteKind::LanternpostWoodLantern
456                | SpriteKind::LanternAirshipWallBlackS
457                | SpriteKind::LanternAirshipWallBrownS
458                | SpriteKind::LanternAirshipWallChestnutS
459                | SpriteKind::LanternAirshipWallRedS
460                | SpriteKind::LanternAirshipGroundBlackS
461                | SpriteKind::LanternAirshipGroundBrownS
462                | SpriteKind::LanternAirshipGroundChestnutS
463                | SpriteKind::LanternAirshipGroundRedS
464                | SpriteKind::LampMetalShinglesCyan
465                | SpriteKind::LampMetalShinglesRed => 24,
466                SpriteKind::Velorite | SpriteKind::TerracottaStatue => 8,
467                SpriteKind::SeashellLantern | SpriteKind::GlowIceCrystal => 16,
468                SpriteKind::SeaDecorEmblem => 12,
469                SpriteKind::SeaDecorBlock
470                | SpriteKind::HaniwaKeyDoor
471                | SpriteKind::VampireKeyDoor => 10,
472                _ => return None,
473            },
474        };
475
476        if self
477            .get_attr::<sprite::LightEnabled>()
478            .map_or(true, |l| l.0)
479        {
480            Some(glow_level)
481        } else {
482            None
483        }
484    }
485
486    // minimum block, attenuation
487    #[inline]
488    pub fn get_max_sunlight(&self) -> (u8, f32) {
489        match self.kind() {
490            BlockKind::Water => (0, 0.4),
491            BlockKind::Leaves => (9, 255.0),
492            BlockKind::ArtLeaves => (9, 255.0),
493            BlockKind::Wood => (6, 2.0),
494            BlockKind::Snow => (6, 2.0),
495            BlockKind::ArtSnow => (6, 2.0),
496            BlockKind::Ice => (4, 2.0),
497            _ if self.is_opaque() => (0, 255.0),
498            _ => (0, 0.0),
499        }
500    }
501
502    // Filled blocks or sprites
503    #[inline]
504    pub fn is_solid(&self) -> bool {
505        self.get_sprite()
506            .map(|s| s.solid_height().is_some())
507            .unwrap_or(!matches!(self.kind, BlockKind::Lava))
508    }
509
510    pub fn valid_collision_dir(
511        &self,
512        entity_aabb: Aabb<f32>,
513        block_aabb: Aabb<f32>,
514        move_dir: Vec3<f32>,
515    ) -> bool {
516        self.get_sprite().is_none_or(|sprite| {
517            sprite.valid_collision_dir(entity_aabb, block_aabb, move_dir, self)
518        })
519    }
520
521    /// Can this block be exploded? If so, what 'power' is required to do so?
522    /// Note that we don't really define what 'power' is. Consider the units
523    /// arbitrary and only important when compared to one-another.
524    #[inline]
525    pub fn explode_power(&self) -> Option<f32> {
526        // Explodable means that the terrain sprite will get removed anyway,
527        // so all is good for empty fluids.
528        match self.kind() {
529            BlockKind::Leaves => Some(0.25),
530            BlockKind::ArtLeaves => Some(0.25),
531            BlockKind::Grass => Some(0.5),
532            BlockKind::WeakRock => Some(0.75),
533            BlockKind::Snow => Some(0.1),
534            BlockKind::Ice => Some(0.5),
535            BlockKind::Wood => Some(4.5),
536            BlockKind::Lava => None,
537            _ => self.get_sprite().and_then(|sprite| match sprite {
538                sprite if sprite.is_defined_as_container() => None,
539                SpriteKind::Keyhole
540                | SpriteKind::KeyDoor
541                | SpriteKind::BoneKeyhole
542                | SpriteKind::BoneKeyDoor
543                | SpriteKind::OneWayWall
544                | SpriteKind::KeyholeBars
545                | SpriteKind::DoorBars => None,
546                SpriteKind::Anvil
547                | SpriteKind::Cauldron
548                | SpriteKind::CookingPot
549                | SpriteKind::CraftingBench
550                | SpriteKind::Forge
551                | SpriteKind::Loom
552                | SpriteKind::SpinningWheel
553                | SpriteKind::DismantlingBench
554                | SpriteKind::RepairBench
555                | SpriteKind::TanningRack
556                | SpriteKind::Chest
557                | SpriteKind::DungeonChest0
558                | SpriteKind::DungeonChest1
559                | SpriteKind::DungeonChest2
560                | SpriteKind::DungeonChest3
561                | SpriteKind::DungeonChest4
562                | SpriteKind::DungeonChest5
563                | SpriteKind::CoralChest
564                | SpriteKind::HaniwaUrn
565                | SpriteKind::HaniwaKeyDoor
566                | SpriteKind::HaniwaKeyhole
567                | SpriteKind::VampireKeyDoor
568                | SpriteKind::VampireKeyhole
569                | SpriteKind::MyrmidonKeyDoor
570                | SpriteKind::MyrmidonKeyhole
571                | SpriteKind::MinotaurKeyhole
572                | SpriteKind::HaniwaTrap
573                | SpriteKind::HaniwaTrapTriggered
574                | SpriteKind::ChestBuried
575                | SpriteKind::CommonLockedChest
576                | SpriteKind::TerracottaChest
577                | SpriteKind::SahaginChest
578                | SpriteKind::SeaDecorBlock
579                | SpriteKind::SeaDecorChain
580                | SpriteKind::SeaDecorWindowHor
581                | SpriteKind::SeaDecorWindowVer
582                | SpriteKind::WitchWindow
583                | SpriteKind::Rope
584                | SpriteKind::MetalChain
585                | SpriteKind::IronSpike
586                | SpriteKind::HotSurface
587                | SpriteKind::FireBlock
588                | SpriteKind::GlassBarrier
589                | SpriteKind::GlassKeyhole
590                | SpriteKind::SahaginKeyhole
591                | SpriteKind::SahaginKeyDoor
592                | SpriteKind::TerracottaKeyDoor
593                | SpriteKind::TerracottaKeyhole
594                | SpriteKind::TerracottaStatue
595                | SpriteKind::TerracottaBlock => None,
596                SpriteKind::EnsnaringVines
597                | SpriteKind::EnsnaringWeb
598                | SpriteKind::SeaUrchin
599                | SpriteKind::IceSpike
600                | SpriteKind::DiamondLight => Some(0.1),
601                _ => Some(0.25),
602            }),
603        }
604    }
605
606    /// Whether the block containes a sprite that is collectible.
607    ///
608    /// Note, this is based on [`SpriteKind::collectible_info`] and accounts for
609    /// if the [`Collectable`][`sprite::Collectable`] sprite attr is `false`.
610    #[inline]
611    pub fn is_collectible(&self) -> bool {
612        self.get_sprite()
613            .is_some_and(|s| s.collectible_info().is_some())
614            && matches!(self.get_attr(), Ok(sprite::Collectable(true)) | Err(_))
615    }
616
617    /// Can this sprite be picked up to yield an item without a tool?
618    ///
619    /// Note, this is based on [`SpriteKind::collectible_info`] and accounts for
620    /// if the [`Collectable`][`sprite::Collectable`] sprite attr is `false`.
621    #[inline]
622    pub fn is_directly_collectible(&self) -> bool {
623        // NOTE: This doesn't require `SpriteCfg` because `SpriteCfg::loot_table` is
624        // only expected to be set for `collectible_info.is_some()` sprites!
625        self.get_sprite()
626            .is_some_and(|s| s.collectible_info() == Some(None))
627            && matches!(self.get_attr(), Ok(sprite::Collectable(true)) | Err(_))
628    }
629
630    #[inline]
631    pub fn is_mountable(&self) -> bool { self.mount_offset().is_some() }
632
633    /// Get the position and direction to mount this block if any.
634    pub fn mount_offset(&self) -> Option<(Vec3<f32>, Vec3<f32>)> {
635        self.get_sprite().and_then(|sprite| sprite.mount_offset())
636    }
637
638    pub fn mount_buffs(&self) -> Option<Vec<BuffEffect>> {
639        self.get_sprite().and_then(|sprite| sprite.mount_buffs())
640    }
641
642    pub fn is_controller(&self) -> bool {
643        self.get_sprite()
644            .is_some_and(|sprite| sprite.is_controller())
645    }
646
647    #[inline]
648    pub fn is_bonkable(&self) -> bool {
649        match self.get_sprite() {
650            Some(
651                SpriteKind::Apple | SpriteKind::Beehive | SpriteKind::Coconut | SpriteKind::Bomb,
652            ) => self.is_solid(),
653            _ => false,
654        }
655    }
656
657    #[inline]
658    pub fn is_owned(&self) -> bool {
659        self.get_attr::<sprite::Owned>()
660            .is_ok_and(|sprite::Owned(b)| b)
661    }
662
663    /// The tool required to mine this block. For blocks that cannot be mined,
664    /// `None` is returned.
665    #[inline]
666    pub fn mine_tool(&self) -> Option<ToolKind> {
667        match self.kind() {
668            BlockKind::WeakRock | BlockKind::Ice | BlockKind::GlowingWeakRock => {
669                Some(ToolKind::Pick)
670            },
671            _ => self.get_sprite().and_then(|s| s.mine_tool()),
672        }
673    }
674
675    #[inline]
676    pub fn is_opaque(&self) -> bool {
677        match self.get_sprite() {
678            Some(
679                SpriteKind::Keyhole
680                | SpriteKind::KeyDoor
681                | SpriteKind::KeyholeBars
682                | SpriteKind::DoorBars,
683            ) => true,
684            Some(_) => false,
685            None => self.kind().is_filled(),
686        }
687    }
688
689    #[inline]
690    pub fn solid_height(&self) -> f32 {
691        self.get_sprite()
692            .map(|s| s.solid_height().unwrap_or(0.0))
693            .unwrap_or(1.0)
694    }
695
696    /// Get the friction constant used to calculate surface friction when
697    /// walking/climbing. Currently has no units.
698    #[inline]
699    pub fn get_friction(&self) -> f32 {
700        match self.kind() {
701            BlockKind::Ice => FRIC_GROUND * 0.1,
702            _ => FRIC_GROUND,
703        }
704    }
705
706    /// Get the traction permitted by this block as a proportion of the friction
707    /// applied.
708    ///
709    /// 1.0 = default, 0.0 = completely inhibits movement, > 1.0 = potential for
710    /// infinite acceleration (in a vacuum).
711    #[inline]
712    pub fn get_traction(&self) -> f32 {
713        match self.kind() {
714            BlockKind::Snow | BlockKind::ArtSnow => 0.8,
715            _ => 1.0,
716        }
717    }
718
719    /// Apply a light toggle to this block, if possible
720    pub fn with_toggle_light(self, enable: bool) -> Option<Self> {
721        self.with_attr(sprite::LightEnabled(enable)).ok()
722    }
723
724    #[inline]
725    pub fn kind(&self) -> BlockKind { self.kind }
726
727    /// If possible, copy the sprite/color data of the other block.
728    #[inline]
729    #[must_use]
730    pub fn with_data_of(mut self, other: Block) -> Self {
731        if self.is_filled() == other.is_filled() {
732            self = self.with_data(other.data());
733        }
734        self
735    }
736
737    /// If this block is a fluid, replace its sprite.
738    #[inline]
739    #[must_use]
740    pub fn with_sprite(self, sprite: SpriteKind) -> Self {
741        match self.try_with_sprite(sprite) {
742            Ok(b) => b,
743            Err(b) => b,
744        }
745    }
746
747    /// If this block is a fluid, replace its sprite.
748    ///
749    /// Returns block in `Err` if the sprite was not replaced.
750    #[inline]
751    pub fn try_with_sprite(self, sprite: SpriteKind) -> Result<Self, Self> {
752        if self.is_filled() {
753            Err(self)
754        } else {
755            Ok(Self::unfilled(self.kind, sprite))
756        }
757    }
758
759    /// If this block can have orientation, give it a new orientation.
760    #[inline]
761    #[must_use]
762    pub fn with_ori(self, ori: u8) -> Option<Self> { self.with_attr(sprite::Ori(ori)).ok() }
763
764    /// If this block can have adjacent sprites, give it its AdjacentType
765    #[inline]
766    #[must_use]
767    pub fn with_adjacent_type(self, adj: RelativeNeighborPosition) -> Option<Self> {
768        self.with_attr(sprite::AdjacentType(adj as u8)).ok()
769    }
770
771    /// Remove the terrain sprite or solid aspects of a block
772    #[inline]
773    #[must_use]
774    pub fn into_vacant(self) -> Self {
775        if self.is_fluid() {
776            Block::unfilled(self.kind(), SpriteKind::Empty)
777        } else {
778            // FIXME: Figure out if there's some sensible way to determine what medium to
779            // replace a filled block with if it's removed.
780            Block::air(SpriteKind::Empty)
781        }
782    }
783
784    /// Apply the effect of collecting the sprite in this block.
785    ///
786    /// This sets the `Collectable` attribute to `false` for some sprites like
787    /// `Lettuce`. Other sprites will simply be removed via
788    /// [`into_vacant`][Self::into_vacant].
789    #[inline]
790    #[must_use]
791    pub fn into_collected(self) -> Self {
792        match self.get_sprite() {
793            Some(SpriteKind::Lettuce) => self.with_attr(sprite::Collectable(false)).expect(
794                "Setting collectable will not fail since this sprite has Collectable attribute",
795            ),
796            _ => self.into_vacant(),
797        }
798    }
799
800    /// Attempt to convert a [`u32`] to a block
801    #[inline]
802    #[must_use]
803    pub fn from_u32(x: u32) -> Option<Self> {
804        let [bk, r, g, b] = x.to_le_bytes();
805        let block = Self {
806            kind: BlockKind::from_u8(bk)?,
807            data: [r, g, b],
808        };
809
810        (block.kind.is_filled() || SpriteKind::from_block(block).is_some()).then_some(block)
811    }
812
813    #[inline]
814    pub fn to_u32(self) -> u32 {
815        u32::from_le_bytes([self.kind as u8, self.data[0], self.data[1], self.data[2]])
816    }
817}
818
819const _: () = assert!(core::mem::size_of::<BlockKind>() == 1);
820const _: () = assert!(core::mem::size_of::<Block>() == 4);