Added default sprite attribute state

This commit is contained in:
Joshua Barretto 2024-01-21 19:51:37 +00:00
parent ad8965fdd7
commit 4a89d88e9b
8 changed files with 64 additions and 48 deletions

View File

@ -156,50 +156,44 @@ impl Block {
/* Constructors */
// TODO: Rename to `filled`
#[inline]
pub const fn new(kind: BlockKind, color: Rgb<u8>) -> Self {
// TODO: we should probably assert this, overwriting the data fields with a
// colour is bad news
/*
#[cfg(debug_assertions)]
assert!(kind.is_filled());
*/
pub(super) const fn from_raw(kind: BlockKind, data: [u8; 3]) -> Self { Self { kind, data } }
Self {
kind,
// Colours are only valid for non-fluids
data: if kind.is_filled() {
[color.r, color.g, color.b]
} else {
[0; 3]
},
// TODO: Rename to `filled`, make caller guarantees stronger
#[inline]
#[track_caller]
pub const fn new(kind: BlockKind, color: Rgb<u8>) -> Self {
if kind.is_filled() {
Self::from_raw(kind, [color.r, color.g, color.b])
} else {
// Works because `SpriteKind::Empty` has no attributes
let data = (SpriteKind::Empty as u32).to_be_bytes();
Self::from_raw(kind, [data[1], data[2], data[3]])
}
}
// Only valid if `block_kind` is unfilled, so this is just a private utility
// method
#[inline]
const fn unfilled(kind: BlockKind, sprite: SpriteKind) -> Self {
pub fn unfilled(kind: BlockKind, sprite: SpriteKind) -> Self {
#[cfg(debug_assertions)]
assert!(!kind.is_filled());
let sprite_bytes = (sprite as u32).to_be_bytes();
Self {
kind,
data: [sprite_bytes[1], sprite_bytes[2], sprite_bytes[3]],
}
Self::from_raw(kind, sprite.to_initial_bytes())
}
#[inline]
pub const fn air(sprite: SpriteKind) -> Self { Self::unfilled(BlockKind::Air, sprite) }
pub fn air(sprite: SpriteKind) -> Self { Self::unfilled(BlockKind::Air, sprite) }
#[inline]
pub const fn empty() -> Self { Self::air(SpriteKind::Empty) }
pub const fn empty() -> Self {
// Works because `SpriteKind::Empty` has no attributes
let data = (SpriteKind::Empty as u32).to_be_bytes();
Self::from_raw(BlockKind::Air, [data[1], data[2], data[3]])
}
#[inline]
pub const fn water(sprite: SpriteKind) -> Self { Self::unfilled(BlockKind::Water, sprite) }
pub fn water(sprite: SpriteKind) -> Self { Self::unfilled(BlockKind::Water, sprite) }
/* Sprite decoding */
@ -258,6 +252,9 @@ impl Block {
}
}
#[inline(always)]
pub(super) const fn data(&self) -> [u8; 3] { self.data }
#[inline(always)]
pub(super) const fn with_data(mut self, data: [u8; 3]) -> Self {
self.data = data;
@ -428,12 +425,12 @@ impl Block {
};
if self
.get_attr::<sprite::LightDisabled>()
.map_or(false, |l| l.0)
.get_attr::<sprite::LightEnabled>()
.map_or(true, |l| l.0)
{
None
} else {
Some(glow_level)
} else {
None
}
}
@ -636,7 +633,7 @@ impl Block {
/// Apply a light toggle to this block, if possible
pub fn with_toggle_light(self, enable: bool) -> Option<Self> {
self.with_attr(sprite::LightDisabled(!enable)).ok()
self.with_attr(sprite::LightEnabled(enable)).ok()
}
#[inline]
@ -662,7 +659,7 @@ impl Block {
#[must_use]
pub fn into_vacant(self) -> Self {
if self.is_fluid() {
Block::new(self.kind(), Rgb::zero())
Block::unfilled(self.kind(), SpriteKind::Empty)
} else {
// FIXME: Figure out if there's some sensible way to determine what medium to
// replace a filled block with if it's removed.
@ -698,7 +695,7 @@ mod tests {
#[test]
fn convert_u32() {
for bk in BlockKind::iter() {
let block = Block::new(bk, Rgb::new(165, 90, 204)); // Pretty unique bit patterns
let block = Block::from_raw(bk, [165, 90, 204]); // Pretty unique bit patterns
if bk.is_filled() {
assert_eq!(Block::from_u32(block.to_u32()), Some(block));
} else {

View File

@ -286,7 +286,7 @@ impl TerrainChunk {
pub fn water(sea_level: i32) -> TerrainChunk {
TerrainChunk::new(
sea_level,
Block::new(BlockKind::Water, Rgb::zero()),
Block::water(SpriteKind::Empty),
Block::air(SpriteKind::Empty),
TerrainChunkMeta::void(),
)

View File

@ -349,7 +349,7 @@ sprites! {
SeaDecorPillar = 0x1E,
MagicalSeal = 0x1F,
},
Lamp = 7 has Ori, LightDisabled {
Lamp = 7 has Ori, LightEnabled {
// Standalone lights
Lantern = 0,
StreetLamp = 1,
@ -362,20 +362,28 @@ sprites! {
attributes! {
Ori { bits: 4, err: Infallible, from: |bits| Ok(Self(bits as u8)), into: |Ori(x)| x as u16 },
Growth { bits: 4, err: Infallible, from: |bits| Ok(Self(bits as u8)), into: |Growth(x)| x as u16 },
LightDisabled { bits: 1, err: Infallible, from: |bits| Ok(Self(bits == 1)), into: |LightDisabled(x)| x as u16 },
LightEnabled { bits: 1, err: Infallible, from: |bits| Ok(Self(bits == 1)), into: |LightEnabled(x)| x as u16 },
}
// The orientation of the sprite, 0..16
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
pub struct Ori(pub u8);
// The growth of the plant, 0..16
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Growth(pub u8);
// Whether a light has been toggled off.
impl Default for Growth {
fn default() -> Self { Self(15) }
}
// Whether a light has been toggled on or off.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct LightDisabled(pub bool);
pub struct LightEnabled(pub bool);
impl Default for LightEnabled {
fn default() -> Self { Self(true) }
}
impl SpriteKind {
#[inline]

View File

@ -158,6 +158,15 @@ macro_rules! sprites {
},)*
}
}
#[inline] pub(super) fn to_initial_bytes(self) -> [u8; 3] {
let sprite_bytes = (*self as u32).to_be_bytes();
let block = Block::from_raw(super::BlockKind::Air, [sprite_bytes[1], sprite_bytes[2], sprite_bytes[3]]);
match self.category() {
$(Category::$category_name => block$($(.with_attr($attr::default()).unwrap())*)?,)*
}
.data()
}
}
};
}
@ -170,12 +179,14 @@ pub enum AttributeError<E> {
Attribute(E),
}
pub trait Attribute: Sized {
/// The unique index assigned to this attribute, used to index offset arrays
pub trait Attribute: Default + Sized {
/// The unique index assigned to this attribute, used to index offset
/// arrays.
const INDEX: usize;
/// The number of bits required to represent this attribute
/// The number of bits required to represent this attribute.
const BITS: u8;
type Error;
/// The error that might occur when decoding the attribute from bits.
type Error: core::fmt::Debug;
fn from_bits(bits: u16) -> Result<Self, Self::Error>;
fn into_bits(self) -> u16;
}

View File

@ -294,7 +294,7 @@ pub fn generate_mesh<'a>(
let d = d + 2;
let flat = {
let mut volume = vol.cached();
const AIR: Block = Block::air(common::terrain::sprite::SpriteKind::Empty);
const AIR: Block = Block::empty();
// TODO: Once we can manage it sensibly, consider using something like
// Option<Block> instead of just assuming air.
let mut flat = vec![AIR; (w * h * d) as usize];

View File

@ -142,8 +142,8 @@ impl BlocksOfInterest {
_ => {
if let Some(sprite) = block.get_sprite() {
if sprite.category() == sprite::Category::Lamp {
if let Ok(sprite::LightDisabled(disabled)) = block.get_attr() {
interactables.push((pos, Interaction::LightToggle(disabled)));
if let Ok(sprite::LightEnabled(enabled)) = block.get_attr() {
interactables.push((pos, Interaction::LightToggle(!enabled)));
}
}

View File

@ -47,7 +47,7 @@ pub struct Colors {
pub vein: (u8, u8, u8),
}
const EMPTY_AIR: Block = Block::air(SpriteKind::Empty);
const EMPTY_AIR: Block = Block::empty();
pub struct PathLocals {
pub riverless_alt: f32,

View File

@ -167,7 +167,7 @@ impl Archetype for Keep {
make_block(colors.pole.0, colors.pole.1, colors.pole.2).with_priority(important_layer);
let flag =
make_block(flag_color.0, flag_color.1, flag_color.2).with_priority(important_layer);
const AIR: Block = Block::air(SpriteKind::Empty);
const AIR: Block = Block::empty();
const EMPTY: BlockMask = BlockMask::nothing();
let internal = BlockMask::new(AIR, internal_layer);