veloren_common/terrain/sprite/
mod.rs

1//! Here's the deal.
2//!
3//! Blocks are always 4 bytes. The first byte is the [`BlockKind`]. For filled
4//! blocks, the remaining 3 sprites are the block colour. For unfilled sprites
5//! (air, water, etc.) the remaining 3 bytes correspond to sprite data. That's
6//! not a lot to work with! As a result, we're pulling every rabbit out of the
7//! bit-twiddling hat to squash as much information as possible into those 3
8//! bytes.
9//!
10//! Fundamentally, sprites are composed of one or more elements: the
11//! [`SpriteKind`], which tells us what the sprite *is*, and a list of
12//! attributes that define extra properties that the sprite has. Some examples
13//! of attributes might include:
14//!
15//! - the orientation of the sprite (with respect to the volume it sits within)
16//! - whether the sprite has snow cover on it
17//! - a 'variation seed' that allows frontends to pseudorandomly customise the
18//!   appearance of the sprite in a manner that's consistent across clients
19//! - Whether doors are open, closed, or permanently locked
20//! - The stage of growth of a plant
21//! - The kind of plant that sits in pots/planters/vessels
22//! - The colour of the sprite
23//! - The material of the sprite
24//!
25//! # Category
26//!
27//! The first of the three bytes is the sprite 'category'. As much as possible,
28//! we should try to have the properties of each sprite within a category be
29//! consistent with others in the category, to improve performance.
30//!
31//! Since a single byte is not enough to disambiguate the [`SpriteKind`] (we
32//! have more than 256 kinds, so there's not enough space), the category also
33//! corresponds to a 'kind mask': a bitmask that, when applied to the first two
34//! of the three bytes gives us the [`SpriteKind`].
35//!
36//! [`BlockKind`]: crate::terrain::block::BlockKind
37//! [`SpriteKind`]: crate::terrain::sprite::SpriteKind
38
39mod magic;
40//use inline_tweak::tweak_fn;
41pub use self::magic::{Attribute, AttributeError};
42use crate::{
43    attributes,
44    comp::{BuffData, BuffKind, item::ItemDefinitionIdOwned, tool::ToolKind},
45    effect::BuffEffect,
46    lottery::LootSpec,
47    make_case_elim,
48    resources::Secs,
49    sprites,
50    terrain::Block,
51};
52use common_i18n::Content;
53use hashbrown::HashMap;
54use lazy_static::lazy_static;
55use num_derive::FromPrimitive;
56use serde::{Deserialize, Serialize};
57use std::{
58    borrow::Cow,
59    convert::{Infallible, TryFrom},
60    fmt,
61};
62use strum::EnumIter;
63use vek::*;
64
65/// A sprite that can be deserialized with all its attributes.
66///
67/// Say we have created the sprites:
68/// ```ignore
69/// sprites! {
70///    Furniture = 0 has Ori, MirrorX {
71///       Chair,
72///       Table,
73///    }
74/// }
75/// ```
76/// And given we're deserializing from ron we could deserialize an array
77/// of `StructureSprite` that look like this:
78/// ```ignore
79/// [
80///    // This will be a `SpriteKind::Chair` with default attributes
81///    Chair(),
82///    // This will be a `SpriteKind::Chair` with the given attributes `Ori(2)` and `MirrorX(true)`.
83///    Chair(Ori(2), MirrorX(true)),
84///    // This will be a `SpriteKind::Table` with the given attribute `Ori(2)` and the rest of its
85///    // attributes set to default.
86///    Table(Ori(4)),
87/// ]
88/// ```
89#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize)]
90#[serde(transparent)]
91pub struct StructureSprite(StructureSpriteKind);
92
93impl StructureSprite {
94    /// Assigns this structure sprite to a block.
95    ///
96    /// Returns error if [`Block::try_with_sprite`] fails.
97    pub fn apply_to_block(self, block: Block) -> Result<Block, Block> {
98        self.0.apply_to_block(block)
99    }
100}
101
102sprites! {
103    Void = 0 {
104        Empty = 0,
105    },
106    // Generic collection of sprites, no attributes but anything goes
107    Misc = 1 {
108        Ember      = 0x00,
109        SmokeDummy = 0x01,
110        Bomb       = 0x02,
111        FireBlock  = 0x03, // FireBlock for Burning Buff
112        HotSurface = 0x04,
113        Stones2    = 0x05, // Same as `Stones` but not collectible
114        TrainSmoke = 0x06,
115    },
116    // Furniture. In the future, we might add an attribute to customise material
117    // TODO: Remove sizes and variants, represent with attributes
118    Furniture = 2 has Ori, MirrorX {
119        // Indoor
120        BookshelfArabic    = 0x0D,
121        WallTableArabic    = 0x0E,
122        TableArabicLarge   = 0x0F,
123        TableArabicSmall   = 0x10,
124        CupboardArabic     = 0x11,
125        OvenArabic         = 0x12,
126        CushionArabic      = 0x13,
127        CanapeArabic       = 0x14,
128        Shelf              = 0x15,
129        Planter            = 0x16,
130        BedMesa            = 0x18,
131        WallTableMesa      = 0x19,
132        MirrorMesa         = 0x1A,
133        WardrobeSingleMesa = 0x1B,
134        WardrobeDoubleMesa = 0x1C,
135        CupboardMesa       = 0x1D,
136        TableCoastalLarge  = 0x1E,
137        BenchCoastal       = 0x1F,
138        // Crafting
139        CraftingBench    = 0x20,
140        Forge            = 0x21,
141        Cauldron         = 0x22,
142        Anvil            = 0x23,
143        CookingPot       = 0x24,
144        SpinningWheel    = 0x25,
145        TanningRack      = 0x26,
146        Loom             = 0x27,
147        DismantlingBench = 0x28,
148        RepairBench      = 0x29,
149        // Uncollectable containers
150        Barrel            = 0x30,
151        CrateBlock        = 0x31,
152        // Wall
153        HangingBasket     = 0x50,
154        HangingSign       = 0x51,
155        ChristmasOrnament = 0x52,
156        ChristmasWreath   = 0x53,
157        WallLampWizard    = 0x54,
158        WallLamp          = 0x55,
159        WallLampSmall     = 0x56,
160        WallSconce        = 0x57,
161        DungeonWallDecor  = 0x58,
162        WallLampMesa      = 0x59,
163        // Outdoor
164        Tent          = 0x60,
165        Bedroll       = 0x61,
166        BedrollSnow   = 0x62,
167        BedrollPirate = 0x63,
168        Sign          = 0x64,
169        Helm          = 0x65,
170        // Misc
171        Scarecrow      = 0x70,
172        FountainArabic = 0x71,
173        Hearth         = 0x72,
174        ChestWoodDouble= 0x73,
175        LanternpostWoodUpper = 0x74,
176        LanternpostWoodBase = 0x75,
177        LampMetalBase = 0x76,
178        BlacksmithBellows = 0x77,
179        CarpenterTable = 0x78,
180        CarpenterCrateWoodS = 0x79,
181        CarpenterCrateWoodL = 0x7A,
182        CarpenterToolsWall = 0x7B,
183        CarpenterLogCutter = 0x7C,
184        BarrelWoodCoal = 0x7D,
185        BarrelWoodWater = 0x7E,
186        BasketWovenL = 0x7F,
187        BasketWovenM = 0x80,
188        BasketWovenS = 0x81,
189        BonfireMLit = 0x82,
190        BonfireMUnlit = 0x83,
191        BucketWoodM = 0x84,
192        MirrorWoodM = 0x85,
193        SackLeatherM = 0x86,
194        TrophyframeWoodBear = 0x87,
195        TrophyframeWoodDeer = 0x88,
196        JugClayM = 0x89,
197        LogsWoodBranchS = 0x8A,
198        DiningtableWoodCorner = 0x8B,
199        DiningtableWoodBody = 0x8C,
200        BenchWoodEnd = 0x8D,
201        BenchWoodMiddle = 0x8E,
202        LogsWoodCoreEnd = 0x8F,
203        LogsWoodCoreMiddle = 0x90,
204        LogsWoodBarkEnd = 0x91,
205        LogsWoodBarkMiddle = 0x92,
206        LogsWoodBranchEnd = 0x93,
207        LogsWoodBranchMiddle = 0x94,
208        SeatWoodBlueMiddle = 0x95,
209        SeatWoodBlueSide = 0x96,
210        RopeCoilM = 0x97,
211        BedWoodWoodlandHead = 0x99,
212        BedWoodWoodlandMiddle = 0x9A,
213        BedWoodWoodlandTail = 0x9B,
214        BenchWoodWoodlandGreen1 = 0x9C,
215        BenchWoodWoodlandGreen2 = 0x9D,
216        BenchWoodWoodlandGreen3 = 0x9E,
217        BenchWoodWoodland = 0xA0,
218        ChairWoodWoodland = 0xA1,
219        ChairWoodWoodland2 = 0xA2,
220        CoatrackMetalWoodland = 0xA3,
221        CoatrackWoodWoodland = 0xA4,
222        DrawerWoodWoodlandL1 = 0xA5,
223        DrawerWoodWoodlandL2 = 0xA6,
224        DrawerWoodWoodlandM1 = 0xA7,
225        DrawerWoodWoodlandM2 = 0xA8,
226        DrawerWoodWoodlandS = 0xA9,
227        HandCartWoodHead = 0xAA,
228        HandCartWoodMiddle = 0xAB,
229        HandCartWoodTail = 0xAC,
230        FlowerpotWoodWoodlandS = 0xAD,
231        DiningtableWoodWoodlandRound = 0xAE,
232        DiningtableWoodWoodlandSquare = 0xAF,
233        TableWoodFancyWoodlandCorner = 0xB0,
234        TableWoodFancyWoodlandBody = 0xB1,
235        WardrobedoubleWoodWoodland = 0xB2,
236        WardrobedoubleWoodWoodland2 = 0xB3,
237        WardrobesingleWoodWoodland = 0xB4,
238        WardrobesingleWoodWoodland2 = 0xB5,
239        BedCliffHead = 0xB6,
240        BedCliffMiddle = 0xB7,
241        BedCliffTail = 0xB8,
242        BedCoastalHead = 0xB9,
243        BedCoastalMiddle = 0xBA,
244        BedCoastalTail = 0xBB,
245        BedDesertHead = 0xBC,
246        BedDesertMiddle = 0xBD,
247        BedDesertTail = 0xBE,
248        BedSavannahHead = 0xBF,
249        BedSavannahMiddle = 0xC0,
250        BedSavannahTail = 0xC1,
251        Ladder = 0xC2,
252        BookshelfEnd = 0xC3,
253        BookshelfMiddle = 0xC4,
254        HandrailWoodWoodlandBase = 0xC5,
255        HandrailWoodWoodlandMiddle = 0xC6,
256        HandrailWoodWoodlandTop = 0xC7,
257        BroomWoodWoodlandBlue = 0xC8,
258        ShovelWoodWoodlandGreen = 0xC9,
259        PitchforkWoodWoodlandGreen = 0xCA,
260        RakeWoodWoodland = 0xCB,
261        FenceWoodGateWoodland = 0xCC,
262        Hay = 0xCD,
263        CrystalBall = 0xCE,
264        BenchWood2Middle = 0xD0,
265        BenchWood2Side = 0xD1,
266        BenchWood2Middle2 = 0xD2,
267    },
268    // Sprites representing plants that may grow over time (this does not include plant parts, like fruit).
269    Plant = 3 has Growth, Owned, SnowCovered, Collectable {
270        // Cacti
271        BarrelCactus    = 0x00,
272        RoundCactus     = 0x01,
273        ShortCactus     = 0x02,
274        MedFlatCactus   = 0x03,
275        ShortFlatCactus = 0x04,
276        LargeCactus     = 0x05,
277        TallCactus      = 0x06,
278        // Flowers
279        BlueFlower    = 0x10,
280        PinkFlower    = 0x11,
281        PurpleFlower  = 0x12,
282        RedFlower     = 0x13,
283        WhiteFlower   = 0x14,
284        YellowFlower  = 0x15,
285        Sunflower     = 0x16,
286        Moonbell      = 0x17,
287        Pyrebloom     = 0x18,
288        LushFlower    = 0x19,
289        LanternFlower = 0x1A,
290        // Grasses, ferns, and other 'wild' plants/fungi
291        // TODO: remove sizes, make part of the `Growth` attribute
292        LongGrass          = 0x20,
293        MediumGrass        = 0x21,
294        ShortGrass         = 0x22,
295        Fern               = 0x23,
296        LargeGrass         = 0x24,
297        Reed               = 0x25,
298        TaigaGrass         = 0x26,
299        GrassBlue          = 0x27,
300        SavannaGrass       = 0x28,
301        TallSavannaGrass   = 0x29,
302        RedSavannaGrass    = 0x2A,
303        SavannaBush        = 0x2B,
304        Welwitch           = 0x2C,
305        LeafyPlant         = 0x2D,
306        DeadBush           = 0x2E,
307        JungleFern         = 0x2F,
308        GrassBlueShort     = 0x30,
309        GrassBlueMedium    = 0x31,
310        GrassBlueLong      = 0x32,
311        CavernLillypadBlue = 0x33,
312        EnsnaringVines     = 0x34,
313        LillyPads          = 0x35,
314        JungleLeafyPlant   = 0x36,
315        JungleRedGrass     = 0x37,
316        LanternPlant       = 0x38,
317        SporeReed          = 0x39,
318        DeadPlant          = 0x3A,
319        // Crops, berries, and fungi
320        Corn          = 0x41,
321        WheatYellow   = 0x42,
322        WheatGreen    = 0x43, // TODO: Remove `WheatGreen`, make part of the `Growth` attribute
323        LingonBerry   = 0x44,
324        Blueberry     = 0x45,
325        Lettuce       = 0x46,
326        Pumpkin       = 0x47,
327        Carrot        = 0x48,
328        Tomato        = 0x49,
329        Radish        = 0x4A,
330        Turnip        = 0x4B,
331        Flax          = 0x4C,
332        Mushroom      = 0x4D,
333        CaveMushroom  = 0x4E,
334        Cotton        = 0x4F,
335        WildFlax      = 0x50,
336        SewerMushroom = 0x51,
337        LushMushroom  = 0x52,
338        RockyMushroom = 0x53,
339        GlowMushroom  = 0x54,
340        // Seaweeds, corals, and other underwater plants
341        StonyCoral       = 0x61,
342        SoftCoral        = 0x62,
343        SeaweedTemperate = 0x63,
344        SeaweedTropical  = 0x64,
345        GiantKelp        = 0x65,
346        BullKelp         = 0x66,
347        WavyAlgae        = 0x67,
348        SeaGrapes        = 0x68,
349        MermaidsFan      = 0x69,
350        SeaAnemone       = 0x6A,
351        Seagrass         = 0x6B,
352        RedAlgae         = 0x6C,
353        // Danglying ceiling plants/fungi
354        Liana                   = 0x71,
355        MycelBlue               = 0x72,
356        CeilingMushroom         = 0x73,
357        Mold                    = 0x74,
358        Root                    = 0x75,
359        CeilingLanternPlant     = 0x76,
360        CeilingLanternFlower    = 0x77,
361        CeilingJungleLeafyPlant = 0x78,
362    },
363    // Solid resources
364    // TODO: Remove small variants, make deposit size be an attribute
365    Resource = 4 has Owned, SnowCovered {
366        // Gems and ores
367        // Woods and twigs
368        Twigs     = 0x00,
369        Wood      = 0x01,
370        Bamboo    = 0x02,
371        Hardwood  = 0x03,
372        Ironwood  = 0x04,
373        Frostwood = 0x05,
374        Eldwood   = 0x06,
375        // Other
376        Apple       = 0x20,
377        Coconut     = 0x21,
378        Stones      = 0x22,
379        Seashells   = 0x23,
380        Beehive     = 0x24,
381        Bowl        = 0x25,
382        PotionMinor = 0x26,
383        //= 0x27,
384        VialEmpty   = 0x28,
385    },
386    MineableResource = 5 has Damage {
387        Amethyst      = 0x00,
388        Ruby          = 0x01,
389        Sapphire      = 0x02,
390        Emerald       = 0x03,
391        Topaz         = 0x04,
392        Diamond       = 0x05,
393        Bloodstone    = 0x06,
394        Coal          = 0x07,
395        Cobalt        = 0x08,
396        Copper        = 0x09,
397        Iron          = 0x0A,
398        Tin           = 0x0B,
399        Silver        = 0x0C,
400        Gold          = 0x0D,
401        Velorite      = 0x0E,
402        VeloriteFrag  = 0x0F,
403        Mud           = 0x10,
404        Grave         = 0x11,
405        Lodestone     = 0x12,
406    },
407    // Structural elements including doors and building parts
408    Structural = 6 has Ori {
409        // Doors and keyholes
410        Door         = 0x00,
411        DoorDark     = 0x01,
412        DoorWide     = 0x02,
413        BoneKeyhole  = 0x03,
414        BoneKeyDoor  = 0x04,
415        Keyhole      = 0x05,
416        KeyDoor      = 0x06,
417        GlassKeyhole = 0x07,
418        KeyholeBars  = 0x08,
419        HaniwaKeyDoor = 0x09,
420        HaniwaKeyhole = 0x0A,
421        TerracottaKeyDoor = 0x0B,
422        TerracottaKeyhole = 0x0C,
423        SahaginKeyhole = 0x0D,
424        SahaginKeyDoor = 0x0E,
425        VampireKeyDoor = 0x0F,
426        VampireKeyhole = 0x10,
427        MyrmidonKeyDoor = 0x11,
428        MyrmidonKeyhole = 0x12,
429        MinotaurKeyhole = 0x13,
430
431        // Windows
432        Window1      = 0x14,
433        Window2      = 0x15,
434        Window3      = 0x16,
435        Window4      = 0x17,
436        WitchWindow  = 0x18,
437        WindowArabic = 0x19,
438        // Walls
439        GlassBarrier    = 0x20,
440        SeaDecorBlock   = 0x21,
441        CliffDecorBlock = 0x22,
442        MagicalBarrier  = 0x23,
443        OneWayWall      = 0x24,
444        // Gates and grates
445        SeaDecorWindowHor = 0x30,
446        SeaDecorWindowVer = 0x31,
447        DropGate          = 0x32,
448        DropGateBottom    = 0x33,
449        WoodBarricades    = 0x34,
450        // Misc
451        Rope          = 0x40,
452        SeaDecorChain = 0x41,
453        IronSpike     = 0x42,
454        DoorBars      = 0x43,
455        HaniwaTrap    = 0x44,
456        HaniwaTrapTriggered = 0x45,
457        TerracottaStatue = 0x46,
458        TerracottaBlock = 0x47,
459        MetalChain = 0x48,
460        Bell = 0x49,
461        DoorWideAirship = 0x4A,
462    },
463    // Decorative items, both natural and artificial
464    Decor = 7 has Ori {
465        // Natural
466        Bones          = 0x00,
467        IceCrystal     = 0x01,
468        GlowIceCrystal = 0x02,
469        CrystalHigh    = 0x03,
470        CrystalLow     = 0x04,
471        UnderwaterVent = 0x05,
472        SeaUrchin      = 0x06,
473        IceSpike       = 0x07,
474        Orb            = 0x08,
475        EnsnaringWeb   = 0x09,
476        DiamondLight   = 0x0A,
477
478        // Artificial
479        Gravestone        = 0x10,
480        Melon             = 0x11,
481        ForgeTools        = 0x12,
482        JugAndBowlArabic  = 0x13,
483        JugArabic         = 0x14,
484        DecorSetArabic    = 0x15,
485        SepareArabic      = 0x16,
486        Candle            = 0x17,
487        SmithingTable     = 0x18,
488        Forge0            = 0x19,
489        GearWheel0        = 0x1A,
490        Quench0           = 0x1B,
491        SeaDecorEmblem    = 0x1C,
492        SeaDecorPillar    = 0x1D,
493        MagicalSeal       = 0x1E,
494        JugAndCupsCoastal = 0x1F,
495    },
496    Lamp = 8 has Ori, LightEnabled {
497        // Standalone lights
498        Lantern         = 0x00,
499        StreetLamp      = 0x01,
500        StreetLampTall  = 0x02,
501        SeashellLantern = 0x03,
502        FireBowlGround  = 0x04,
503        MesaLantern     = 0x05,
504        LanternpostWoodLantern = 0x06,
505        LampMetalShinglesRed = 0x07,
506        LampTerracotta = 0x08,
507        LampMetalShinglesCyan = 0x09,
508        LanternAirshipWallBlackS = 0x0A,
509        LanternAirshipWallBrownS = 0x0B,
510        LanternAirshipWallChestnutS = 0x0C,
511        LanternAirshipWallRedS = 0x0D,
512        LanternAirshipGroundBlackS = 0x0E,
513        LanternAirshipGroundBrownS = 0x0F,
514        LanternAirshipGroundChestnutS = 0x10,
515        LanternAirshipGroundRedS = 0x11,
516    },
517    // These are all expected to return `Some` for `collectible_info`
518    //
519    // NOTE: Collectable attr currently unused, plan is to add collected models for at least some
520    // of these.
521    Container = 9 has Ori, Owned, Collectable {
522        Chest             = 0x00,
523        DungeonChest0     = 0x01,
524        DungeonChest1     = 0x02,
525        DungeonChest2     = 0x03,
526        DungeonChest3     = 0x04,
527        DungeonChest4     = 0x05,
528        DungeonChest5     = 0x06,
529        CoralChest        = 0x07,
530        HaniwaUrn         = 0x08,
531        TerracottaChest   = 0x09,
532        SahaginChest      = 0x0A,
533        CommonLockedChest = 0x0B,
534        ChestBuried       = 0x0C,
535        Crate             = 0x0D,
536        // SLOT           = 0x0E,
537        // SLOT           = 0x0F,
538        WitchChest        = 0x10,
539        PirateChest       = 0x11,
540    },
541    Modular = 10 has Ori, AdjacentType {
542        FenceWoodWoodland = 0x00,
543    }
544}
545
546attributes! {
547    Ori { bits: 3, err: Infallible, from: |bits| Ok(Self(bits as u8)), into: |Ori(x)| x as u16 },
548    MirrorX { bits: 1, err: Infallible, from: |bits| Ok(Self(bits == 1)), into: |MirrorX(x)| x as u16 },
549    MirrorY { bits: 1, err: Infallible, from: |bits| Ok(Self(bits == 1)), into: |MirrorY(x)| x as u16 },
550    MirrorZ { bits: 1, err: Infallible, from: |bits| Ok(Self(bits == 1)), into: |MirrorZ(x)| x as u16 },
551    Growth { bits: 4, err: Infallible, from: |bits| Ok(Self(bits as u8)), into: |Growth(x)| x as u16 },
552    LightEnabled { bits: 1, err: Infallible, from: |bits| Ok(Self(bits == 1)), into: |LightEnabled(x)| x as u16 },
553    Collectable { bits: 1, err: Infallible, from: |bits| Ok(Self(bits == 1)), into: |Collectable(x)| x as u16 },
554    Damage { bits: 3, err: Infallible, from: |bits| Ok(Self(bits as u8)), into: |Damage(x)| x as u16 },
555    Owned { bits: 1, err: Infallible, from: |bits| Ok(Self(bits == 1)), into: |Owned(x)| x as u16 },
556    AdjacentType { bits: 3, err: Infallible, from: |bits| Ok(Self(bits as u8)), into: |AdjacentType(x)| x as u16 },
557    SnowCovered { bits: 1, err: Infallible, from: |bits| Ok(Self(bits == 1)), into: |SnowCovered(x)| x as u16 },
558}
559
560// The orientation of the sprite, 0..16
561#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Deserialize)]
562pub struct Ori(pub u8);
563
564#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Deserialize)]
565pub struct MirrorX(pub bool);
566
567#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Deserialize)]
568pub struct MirrorY(pub bool);
569
570#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Deserialize)]
571pub struct MirrorZ(pub bool);
572
573// The growth of the plant, 0..16
574#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
575pub struct Growth(pub u8);
576
577impl Default for Growth {
578    fn default() -> Self { Self(15) }
579}
580
581// Whether a light has been toggled on or off.
582#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
583pub struct LightEnabled(pub bool);
584
585impl Default for LightEnabled {
586    fn default() -> Self { Self(true) }
587}
588
589#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
590pub struct Collectable(pub bool);
591
592impl Default for Collectable {
593    fn default() -> Self { Self(true) }
594}
595
596#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
597pub struct Owned(pub bool);
598
599/** Relative Neighbor Position:
600    an enum to determine the exact sprite for AdjacentType sprites
601    I - Straight - 0
602    L - Corner - 1
603    T - Junction - 2
604    X - Intersection - 3
605    End - single connection - 4
606**/
607
608#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Deserialize, FromPrimitive, Hash)]
609#[repr(u8)]
610pub enum RelativeNeighborPosition {
611    #[default]
612    I,
613    L,
614    T,
615    X,
616    End,
617}
618
619#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
620#[serde(from = "RelativeNeighborPosition")]
621pub struct AdjacentType(pub u8);
622
623impl From<RelativeNeighborPosition> for AdjacentType {
624    fn from(value: RelativeNeighborPosition) -> Self { Self(value as u8) }
625}
626
627impl Default for AdjacentType {
628    fn default() -> Self { Self::from(RelativeNeighborPosition::I) }
629}
630
631// Damage of an ore
632#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Deserialize)]
633pub struct Damage(pub u8);
634
635// Whether a sprite has snow on it
636#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Deserialize)]
637pub struct SnowCovered(pub bool);
638
639impl SpriteKind {
640    #[inline]
641    //#[tweak_fn]
642    pub fn solid_height(&self) -> Option<f32> {
643        // Beware: the height *must* be <= `MAX_HEIGHT` or the collision system will not
644        // properly detect it!
645        Some(match self {
646            SpriteKind::Bedroll => 0.3,
647            SpriteKind::BedrollSnow => 0.4,
648            SpriteKind::BedrollPirate => 0.3,
649            SpriteKind::Tomato => 1.65,
650            SpriteKind::BarrelCactus => 0.909,
651            SpriteKind::LargeCactus => 3.0,
652            SpriteKind::TallCactus => 2.63,
653            SpriteKind::Scarecrow => 3.0,
654            SpriteKind::Turnip => 0.36,
655            SpriteKind::Pumpkin => 0.81,
656            SpriteKind::Chest => 1.09,
657            SpriteKind::CommonLockedChest
658            | SpriteKind::DungeonChest0
659            | SpriteKind::DungeonChest1
660            | SpriteKind::DungeonChest2
661            | SpriteKind::DungeonChest3
662            | SpriteKind::DungeonChest4
663            | SpriteKind::DungeonChest5
664            | SpriteKind::CoralChest
665            | SpriteKind::HaniwaUrn
666            | SpriteKind::SahaginChest
667            | SpriteKind::TerracottaChest
668            | SpriteKind::WitchChest
669            | SpriteKind::PirateChest => 1.09,
670            SpriteKind::TerracottaStatue => 5.29,
671            SpriteKind::TerracottaBlock => 1.00,
672            // Fence is more than 1.0 to prevent auto block-hopping onto the fence.
673            SpriteKind::FenceWoodWoodland => 1.09,
674            SpriteKind::SeaDecorChain => 1.09,
675            SpriteKind::SeaDecorBlock => 1.00,
676            SpriteKind::SeaDecorWindowHor => 0.55,
677            SpriteKind::SeaDecorWindowVer => 1.09,
678            SpriteKind::SeaDecorPillar => 2.55,
679            SpriteKind::SeashellLantern => 2.09,
680            SpriteKind::MesaLantern => 1.3,
681            SpriteKind::Rope => 1.09,
682            SpriteKind::MetalChain => 1.09,
683            SpriteKind::StreetLamp => 2.65,
684            SpriteKind::Carrot => 0.18,
685            SpriteKind::Radish => 0.18,
686            SpriteKind::FireBowlGround => 0.55,
687            SpriteKind::BedMesa => 0.82,
688            SpriteKind::DungeonWallDecor => 1.0,
689            SpriteKind::Planter => 1.09,
690            SpriteKind::WardrobeSingleMesa => 2.0,
691            SpriteKind::WardrobeDoubleMesa => 2.0,
692            SpriteKind::MirrorMesa => 2.0,
693            SpriteKind::Mud => 0.36,
694            SpriteKind::ChestBuried => 0.91,
695            SpriteKind::StonyCoral => 1.4,
696            SpriteKind::CraftingBench => 1.18,
697            SpriteKind::Forge => 1.818,
698            SpriteKind::Cauldron => 1.27,
699            SpriteKind::SpinningWheel => 1.454,
700            SpriteKind::TanningRack => 1.363,
701            SpriteKind::Loom => 1.545,
702            SpriteKind::Anvil => 1.18,
703            SpriteKind::CookingPot => 1.091,
704            SpriteKind::DismantlingBench => 1.091,
705            SpriteKind::IceSpike => 1.0,
706            SpriteKind::RepairBench => 1.2,
707            SpriteKind::RoundCactus => 0.72,
708            SpriteKind::ShortCactus => 1.36,
709            SpriteKind::MedFlatCactus => 1.36,
710            SpriteKind::ShortFlatCactus => 0.91,
711            SpriteKind::Bell => 1.0,
712            // TODO: Find suitable heights.
713            SpriteKind::Apple
714            | SpriteKind::Beehive
715            | SpriteKind::Velorite
716            | SpriteKind::VeloriteFrag
717            | SpriteKind::Coconut
718            | SpriteKind::StreetLampTall
719            | SpriteKind::Window1
720            | SpriteKind::Window2
721            | SpriteKind::Window3
722            | SpriteKind::Window4
723            | SpriteKind::DropGate
724            | SpriteKind::WitchWindow
725            | SpriteKind::SeaUrchin
726            | SpriteKind::IronSpike
727            | SpriteKind::GlassBarrier
728            | SpriteKind::GlassKeyhole
729            | SpriteKind::Keyhole
730            | SpriteKind::KeyDoor
731            | SpriteKind::BoneKeyhole
732            | SpriteKind::BoneKeyDoor
733            | SpriteKind::HaniwaKeyhole
734            | SpriteKind::HaniwaKeyDoor
735            | SpriteKind::SahaginKeyhole
736            | SpriteKind::SahaginKeyDoor
737            | SpriteKind::VampireKeyhole
738            | SpriteKind::VampireKeyDoor
739            | SpriteKind::HaniwaTrap
740            | SpriteKind::HaniwaTrapTriggered
741            | SpriteKind::TerracottaKeyDoor
742            | SpriteKind::TerracottaKeyhole
743            | SpriteKind::MyrmidonKeyDoor
744            | SpriteKind::MyrmidonKeyhole
745            | SpriteKind::MinotaurKeyhole
746            | SpriteKind::Bomb
747            | SpriteKind::OneWayWall
748            | SpriteKind::DoorBars
749            | SpriteKind::KeyholeBars
750            | SpriteKind::WoodBarricades
751            | SpriteKind::DiamondLight
752            | SpriteKind::CrystalBall
753            | SpriteKind::Ladder => 1.0,
754            // TODO: Figure out if this should be solid or not.
755            SpriteKind::Shelf => 1.0,
756            SpriteKind::Lantern => 0.9,
757            SpriteKind::CrystalHigh | SpriteKind::CrystalLow => 1.5,
758            SpriteKind::Bloodstone
759            | SpriteKind::Coal
760            | SpriteKind::Cobalt
761            | SpriteKind::Copper
762            | SpriteKind::Iron
763            | SpriteKind::Tin
764            | SpriteKind::Silver
765            | SpriteKind::Gold
766            | SpriteKind::Lodestone => 0.6,
767            SpriteKind::EnsnaringVines
768            | SpriteKind::CavernLillypadBlue
769            | SpriteKind::EnsnaringWeb => 0.15,
770            SpriteKind::LillyPads => 0.1,
771            SpriteKind::WindowArabic | SpriteKind::BookshelfArabic => 1.9,
772            SpriteKind::DecorSetArabic => 2.6,
773            SpriteKind::SepareArabic => 2.2,
774            SpriteKind::CushionArabic => 0.4,
775            SpriteKind::JugArabic => 1.4,
776            SpriteKind::TableArabicSmall => 0.9,
777            SpriteKind::TableArabicLarge => 1.0,
778            SpriteKind::TableCoastalLarge => 1.0,
779            SpriteKind::BenchCoastal => 1.0,
780            SpriteKind::CanapeArabic => 1.2,
781            SpriteKind::CupboardArabic => 4.5,
782            SpriteKind::WallTableArabic => 2.3,
783            SpriteKind::JugAndBowlArabic => 1.4,
784            SpriteKind::JugAndCupsCoastal => 1.4,
785            SpriteKind::Melon => 0.7,
786            SpriteKind::OvenArabic => 3.2,
787            SpriteKind::FountainArabic => 2.4,
788            SpriteKind::Hearth => 2.3,
789            SpriteKind::ForgeTools => 2.8,
790            SpriteKind::CliffDecorBlock | SpriteKind::FireBlock => 1.0,
791            SpriteKind::Wood
792            | SpriteKind::Hardwood
793            | SpriteKind::Ironwood
794            | SpriteKind::Frostwood
795            | SpriteKind::Eldwood => 7.0 / 11.0,
796            SpriteKind::Bamboo => 9.0 / 11.0,
797            SpriteKind::MagicalBarrier => 3.0,
798            SpriteKind::MagicalSeal => 1.0,
799            SpriteKind::Helm => 1.909,
800            SpriteKind::Sign => 16.0 / 11.0,
801            SpriteKind::SmithingTable => 13.0 / 11.0,
802            SpriteKind::Forge0 => 17.0 / 11.0,
803            SpriteKind::GearWheel0 => 3.0 / 11.0,
804            SpriteKind::Quench0 => 8.0 / 11.0,
805            SpriteKind::HotSurface => 0.01,
806            SpriteKind::Barrel => 1.0,
807            SpriteKind::CrateBlock => 1.0,
808            SpriteKind::BarrelWoodWater | SpriteKind::BarrelWoodCoal => 1.545,
809            SpriteKind::LanternpostWoodLantern | SpriteKind::LanternpostWoodUpper => 2.000,
810            SpriteKind::LanternpostWoodBase => 3.000,
811            SpriteKind::LampMetalShinglesRed => 1.000,
812            SpriteKind::LampMetalShinglesCyan => 1.000,
813            SpriteKind::LampMetalBase => 2.818,
814            SpriteKind::LampTerracotta => 1.727,
815            SpriteKind::BlacksmithBellows => 0.545,
816            SpriteKind::CarpenterTable => 2.000,
817            SpriteKind::CarpenterCrateWoodS => 0.727,
818            SpriteKind::CarpenterCrateWoodL => 1.273,
819            SpriteKind::CarpenterLogCutter => 1.545,
820            SpriteKind::BasketWovenL | SpriteKind::JugClayM => 1.000,
821            SpriteKind::BasketWovenM => 0.909,
822            SpriteKind::BasketWovenS => 0.818,
823            SpriteKind::BonfireMLit | SpriteKind::BonfireMUnlit => 2.273,
824            SpriteKind::BucketWoodM | SpriteKind::SackLeatherM => 1.091,
825            SpriteKind::MirrorWoodM => 1.364,
826            SpriteKind::TrophyframeWoodBear => 1.455,
827            SpriteKind::TrophyframeWoodDeer => 1.727,
828            SpriteKind::ChestWoodDouble => 1.182,
829            SpriteKind::DiningtableWoodCorner => 1.273,
830            SpriteKind::DiningtableWoodBody => 1.273,
831            SpriteKind::BenchWoodEnd => 0.636,
832            SpriteKind::BenchWoodMiddle => 0.636,
833            SpriteKind::LogsWoodCoreEnd => 0.818,
834            SpriteKind::LogsWoodCoreMiddle => 0.818,
835            SpriteKind::LogsWoodBarkEnd => 1.091,
836            SpriteKind::LogsWoodBarkMiddle => 1.091,
837            SpriteKind::LogsWoodBranchEnd => 1.091,
838            SpriteKind::LogsWoodBranchMiddle => 1.091,
839            SpriteKind::LogsWoodBranchS => 1.091,
840            SpriteKind::SeatWoodBlueMiddle => 1.818,
841            SpriteKind::SeatWoodBlueSide => 1.818,
842            SpriteKind::LanternAirshipWallBlackS
843            | SpriteKind::LanternAirshipWallBrownS
844            | SpriteKind::LanternAirshipWallChestnutS
845            | SpriteKind::LanternAirshipWallRedS => 1.182,
846            SpriteKind::LanternAirshipGroundBlackS
847            | SpriteKind::LanternAirshipGroundBrownS
848            | SpriteKind::LanternAirshipGroundChestnutS
849            | SpriteKind::LanternAirshipGroundRedS => 0.909,
850            SpriteKind::RopeCoilM => 0.363,
851            SpriteKind::BedCliffHead => 0.636,
852            SpriteKind::BedCliffMiddle => 0.636,
853            SpriteKind::BedCliffTail => 0.636,
854            SpriteKind::BedCoastalHead => 0.636,
855            SpriteKind::BedCoastalMiddle => 0.636,
856            SpriteKind::BedCoastalTail => 0.636,
857            SpriteKind::BedDesertHead => 0.545,
858            SpriteKind::BedDesertMiddle => 0.545,
859            SpriteKind::BedDesertTail => 0.545,
860            SpriteKind::BedSavannahHead => 0.545,
861            SpriteKind::BedSavannahMiddle => 0.545,
862            SpriteKind::BedSavannahTail => 0.545,
863            SpriteKind::BedWoodWoodlandHead => 0.727,
864            SpriteKind::BedWoodWoodlandMiddle => 0.727,
865            SpriteKind::BedWoodWoodlandTail => 0.727,
866            SpriteKind::BookshelfEnd => 3.0,
867            SpriteKind::BookshelfMiddle => 3.0,
868            SpriteKind::BenchWoodWoodlandGreen1 => 1.545,
869            SpriteKind::BenchWoodWoodlandGreen2 => 1.545,
870            SpriteKind::BenchWoodWoodlandGreen3 => 1.545,
871            SpriteKind::BenchWoodWoodland => 1.545,
872            SpriteKind::ChairWoodWoodland => 1.636,
873            SpriteKind::ChairWoodWoodland2 => 1.727,
874            SpriteKind::CoatrackMetalWoodland => 2.364,
875            SpriteKind::CoatrackWoodWoodland => 2.364,
876            SpriteKind::Crate => 0.909,
877            SpriteKind::DrawerWoodWoodlandS => 1.000,
878            SpriteKind::DrawerWoodWoodlandM1 => 0.909,
879            SpriteKind::DrawerWoodWoodlandM2 => 0.909,
880            SpriteKind::DrawerWoodWoodlandL1 => 1.273,
881            SpriteKind::DrawerWoodWoodlandL2 => 1.273,
882            SpriteKind::DiningtableWoodWoodlandRound => 1.273,
883            SpriteKind::DiningtableWoodWoodlandSquare => 1.273,
884            SpriteKind::TableWoodFancyWoodlandCorner => 1.273,
885            SpriteKind::TableWoodFancyWoodlandBody => 1.273,
886            SpriteKind::WardrobesingleWoodWoodland => 2.364,
887            SpriteKind::WardrobesingleWoodWoodland2 => 2.364,
888            SpriteKind::WardrobedoubleWoodWoodland => 2.364,
889            SpriteKind::WardrobedoubleWoodWoodland2 => 2.364,
890            SpriteKind::FlowerpotWoodWoodlandS => 0.455,
891            SpriteKind::HandCartWoodHead => 1.091,
892            SpriteKind::HandCartWoodMiddle => 1.091,
893            SpriteKind::HandCartWoodTail => 1.091,
894            SpriteKind::HandrailWoodWoodlandBase | SpriteKind::HandrailWoodWoodlandMiddle => 1.727,
895            SpriteKind::HandrailWoodWoodlandTop => 1.181,
896            SpriteKind::Hay => 1.09,
897            SpriteKind::BenchWood2Middle
898            | SpriteKind::BenchWood2Side
899            | SpriteKind::BenchWood2Middle2 => 0.636,
900            _ => return None,
901        })
902    }
903
904    pub fn valid_collision_dir(
905        &self,
906        entity_aabb: Aabb<f32>,
907        block_aabb: Aabb<f32>,
908        move_dir: Vec3<f32>,
909        parent: &Block,
910    ) -> bool {
911        // Find the intrusion vector of the collision
912        let dir = entity_aabb.collision_vector_with_aabb(block_aabb);
913
914        // Determine an appropriate resolution vector (i.e: the minimum distance
915        // needed to push out of the block)
916        let max_axis = dir.map(|e| e.abs()).reduce_partial_min();
917
918        let resolve_dir = -dir.map(|e| {
919            if e.abs().to_bits() == max_axis.to_bits() {
920                e.signum()
921            } else {
922                0.0
923            }
924        });
925
926        let is_moving_into = move_dir.dot(resolve_dir) <= 0.0;
927
928        match self {
929            SpriteKind::LillyPads | SpriteKind::CavernLillypadBlue => {
930                is_moving_into && resolve_dir.z > 0.0
931            },
932            SpriteKind::OneWayWall => {
933                is_moving_into
934                    && parent.get_attr().is_ok_and(|Ori(ori)| {
935                        Vec2::new(
936                            0.0,
937                            parent.get_attr::<MirrorY>().map_or(1.0, |m| match m.0 {
938                                true => -1.0,
939                                false => 1.0,
940                            }),
941                        )
942                        .rotated_z(std::f32::consts::PI * 0.25 * ori as f32)
943                        .with_z(0.0)
944                        .map2(resolve_dir, |e, r| (e - r).abs() < 0.1)
945                        .reduce_and()
946                    })
947            },
948            _ => true,
949        }
950    }
951
952    /// What loot table would collecting this sprite draw from, by default?
953    ///
954    /// NOTE: `Item::try_reclaim_from_block` is what you probably looking for
955    /// instead.
956    ///
957    /// `None` = block cannot be collected.
958    /// `Some(None)` = block can be collected, but does not give back an item.
959    /// `Some(Some(_))` = block can be collected and gives back an item.
960    #[inline]
961    pub fn default_loot_spec(&self) -> Option<Option<LootSpec<&'static str>>> {
962        let item = LootSpec::Item;
963        let table = LootSpec::LootTable;
964        Some(Some(match self {
965            SpriteKind::Apple => item("common.items.food.apple"),
966            SpriteKind::Mushroom => item("common.items.food.mushroom"),
967            SpriteKind::Velorite => item("common.items.mineral.ore.velorite"),
968            SpriteKind::VeloriteFrag => item("common.items.mineral.ore.veloritefrag"),
969            SpriteKind::Lodestone => item("common.items.mineral.ore.lodestone"),
970            //SpriteKind::BlueFlower => item("common.items.flowers.blue"),
971            //SpriteKind::PinkFlower => item("common.items.flowers.pink"),
972            //SpriteKind::PurpleFlower => item("common.items.flowers.purple"),
973            SpriteKind::RedFlower => item("common.items.flowers.red"),
974            //SpriteKind::WhiteFlower => item("common.items.flowers.white"),
975            //SpriteKind::YellowFlower => item("common.items.flowers.yellow"),
976            SpriteKind::Sunflower => item("common.items.flowers.sunflower"),
977            //SpriteKind::LongGrass => item("common.items.grasses.long"),
978            //SpriteKind::MediumGrass => item("common.items.grasses.medium"),
979            //SpriteKind::ShortGrass => item("common.items.grasses.short"),
980            SpriteKind::Lettuce => item("common.items.food.lettuce"),
981            SpriteKind::Coconut => item("common.items.food.coconut"),
982            SpriteKind::Beehive => item("common.items.crafting_ing.honey"),
983            SpriteKind::Stones => item("common.items.crafting_ing.stones"),
984            SpriteKind::Twigs => item("common.items.crafting_ing.twigs"),
985            SpriteKind::VialEmpty => item("common.items.crafting_ing.empty_vial"),
986            SpriteKind::Bowl => item("common.items.crafting_ing.bowl"),
987            SpriteKind::PotionMinor => item("common.items.consumable.potion_minor"),
988            SpriteKind::Amethyst => item("common.items.mineral.gem.amethyst"),
989            SpriteKind::Ruby => item("common.items.mineral.gem.ruby"),
990            SpriteKind::Diamond => item("common.items.mineral.gem.diamond"),
991            SpriteKind::Sapphire => item("common.items.mineral.gem.sapphire"),
992            SpriteKind::Topaz => item("common.items.mineral.gem.topaz"),
993            SpriteKind::Emerald => item("common.items.mineral.gem.emerald"),
994            SpriteKind::Bloodstone => item("common.items.mineral.ore.bloodstone"),
995            SpriteKind::Coal => item("common.items.mineral.ore.coal"),
996            SpriteKind::Cobalt => item("common.items.mineral.ore.cobalt"),
997            SpriteKind::Copper => item("common.items.mineral.ore.copper"),
998            SpriteKind::Iron => item("common.items.mineral.ore.iron"),
999            SpriteKind::Tin => item("common.items.mineral.ore.tin"),
1000            SpriteKind::Silver => item("common.items.mineral.ore.silver"),
1001            SpriteKind::Gold => item("common.items.mineral.ore.gold"),
1002            SpriteKind::Cotton => item("common.items.crafting_ing.cotton_boll"),
1003            SpriteKind::Moonbell => item("common.items.flowers.moonbell"),
1004            SpriteKind::Pyrebloom => item("common.items.flowers.pyrebloom"),
1005            SpriteKind::WildFlax => item("common.items.flowers.wild_flax"),
1006            SpriteKind::Seashells => item("common.items.crafting_ing.seashells"),
1007            SpriteKind::RoundCactus => item("common.items.crafting_ing.cactus"),
1008            SpriteKind::ShortFlatCactus => item("common.items.crafting_ing.cactus"),
1009            SpriteKind::MedFlatCactus => item("common.items.crafting_ing.cactus"),
1010            SpriteKind::Bomb => item("common.items.utility.bomb"),
1011            SpriteKind::Chest => table("common.loot_tables.sprite.chest"),
1012            SpriteKind::DungeonChest0 => table("common.loot_tables.dungeon.gnarling.chest"),
1013            SpriteKind::DungeonChest1 => table("common.loot_tables.dungeon.adlet.chest"),
1014            SpriteKind::DungeonChest2 => table("common.loot_tables.dungeon.sahagin.chest"),
1015            SpriteKind::DungeonChest3 => table("common.loot_tables.dungeon.haniwa.chest"),
1016            SpriteKind::DungeonChest4 => table("common.loot_tables.dungeon.myrmidon.chest"),
1017            SpriteKind::DungeonChest5 => table("common.loot_tables.dungeon.cultist.chest"),
1018            SpriteKind::CoralChest => table("common.loot_tables.dungeon.sea_chapel.chest_coral"),
1019            SpriteKind::HaniwaUrn => table("common.loot_tables.dungeon.haniwa.key"),
1020            SpriteKind::TerracottaChest => {
1021                table("common.loot_tables.dungeon.terracotta.chest_terracotta")
1022            },
1023            SpriteKind::SahaginChest => table("common.loot_tables.dungeon.sahagin.key_chest"),
1024            SpriteKind::CommonLockedChest => table("common.loot_tables.dungeon.sahagin.chest"),
1025            SpriteKind::ChestBuried => table("common.loot_tables.sprite.chest-buried"),
1026            SpriteKind::Crate => table("common.loot_tables.sprite.crate"),
1027            SpriteKind::Mud => table("common.loot_tables.sprite.mud"),
1028            SpriteKind::Grave => table("common.loot_tables.sprite.mud"),
1029            SpriteKind::Wood => item("common.items.log.wood"),
1030            SpriteKind::Bamboo => item("common.items.log.bamboo"),
1031            SpriteKind::Hardwood => item("common.items.log.hardwood"),
1032            SpriteKind::Ironwood => item("common.items.log.ironwood"),
1033            SpriteKind::Frostwood => item("common.items.log.frostwood"),
1034            SpriteKind::Eldwood => item("common.items.log.eldwood"),
1035            // TODO: why does this have a loot table?
1036            SpriteKind::MagicalBarrier => table("common.loot_tables.sprite.chest"),
1037            SpriteKind::WitchChest => table("common.loot_tables.spot.witch"),
1038            SpriteKind::PirateChest => table("common.loot_tables.spot.buccaneer"),
1039            SpriteKind::Keyhole
1040            | SpriteKind::BoneKeyhole
1041            | SpriteKind::HaniwaKeyhole
1042            | SpriteKind::VampireKeyhole
1043            | SpriteKind::GlassKeyhole
1044            | SpriteKind::KeyholeBars
1045            | SpriteKind::SahaginKeyhole
1046            | SpriteKind::TerracottaKeyhole
1047            | SpriteKind::MyrmidonKeyhole
1048            | SpriteKind::MinotaurKeyhole => {
1049                return Some(None);
1050            },
1051            _ => return None,
1052        }))
1053    }
1054
1055    /// Is this sprite *expected* to be picked up?
1056    ///
1057    /// Note, this will `Some(_)` even when the `Collectable` attr is `false`.
1058    ///
1059    /// * `None` means sprite can't be collected.
1060    /// * `Some(None)` means sprite can be collected without any mine tool.
1061    /// * `Some(Some(_))` means sprite can be collected but requires a tool.
1062    #[inline]
1063    pub fn collectible_info(&self) -> Option<Option<ToolKind>> {
1064        self.default_loot_spec().map(|_| self.mine_tool())
1065    }
1066
1067    /// Should the sprite behave like a container?
1068    ///
1069    /// That means:
1070    /// * The sprite is collectible (checked by test).
1071    /// * The sprite is not explodable.
1072    /// * `SpriteInteractKind::Chest` is used in the interaction character state
1073    ///   when collecting.
1074    /// * `should_drop_mystery` returns `true`.
1075    /// * Structure tests allows SpriteCfg.loot_table to be set for this sprite.
1076    ///
1077    /// If you just asking where you can collect this sprite without any tool,
1078    /// use [`Block::is_directly_collectible`].
1079    ///
1080    /// Implicit invariant of this method is that only sprites listed here
1081    /// are expected to use SpriteCfg.loot_table because that needs
1082    /// [`SpriteKind::should_drop_mystery`] to be `true` to avoid displaying the
1083    /// `default_loot_spec` items.
1084    #[inline]
1085    pub fn is_defined_as_container(&self) -> bool { self.category() == Category::Container }
1086
1087    /// Does this drop random items or potentially have a a custom loot_table in
1088    /// the SpriteCfg.
1089    ///
1090    /// This acts as a hint to avoid displaying the items from
1091    /// `Item::try_reclaim_from_block`.
1092    ///
1093    /// Some items may drop random items, yet aren't containers. So
1094    /// [`SpriteKind::is_defined_as_container()`] alone is insufficient for
1095    /// this.
1096    #[inline]
1097    pub fn should_drop_mystery(&self) -> bool {
1098        self.is_defined_as_container()
1099            || matches!(
1100                self.default_loot_spec(),
1101                Some(Some(LootSpec::LootTable { .. } | LootSpec::Lottery { .. }))
1102            )
1103    }
1104
1105    /// Get the position and direction to mount this sprite if any.
1106    #[inline]
1107    //#[tweak_fn]
1108    pub fn mount_offset(&self) -> Option<(Vec3<f32>, Vec3<f32>)> {
1109        match self {
1110            SpriteKind::ChairWoodWoodland
1111            | SpriteKind::ChairWoodWoodland2
1112            | SpriteKind::BenchWoodWoodlandGreen1
1113            | SpriteKind::BenchWoodWoodlandGreen2
1114            | SpriteKind::BenchWoodWoodlandGreen3
1115            | SpriteKind::BenchWoodWoodland
1116            | SpriteKind::BenchWoodEnd
1117            | SpriteKind::BenchWoodMiddle
1118            | SpriteKind::BenchCoastal => Some((Vec3::new(0.0, 0.0, 0.5), Vec3::unit_x())),
1119            SpriteKind::SeatWoodBlueMiddle | SpriteKind::SeatWoodBlueSide => {
1120                Some((Vec3::new(0.4, 0.0, 0.5), Vec3::unit_x()))
1121            },
1122            SpriteKind::BenchWood2Middle
1123            | SpriteKind::BenchWood2Side
1124            | SpriteKind::BenchWood2Middle2 => Some((Vec3::new(0.4, 0.0, 0.62), Vec3::unit_x())),
1125            SpriteKind::Helm => Some((Vec3::new(0.0, -1.1, 0.0), Vec3::unit_y())),
1126            SpriteKind::BedWoodWoodlandHead
1127            | SpriteKind::BedCliffHead
1128            | SpriteKind::BedDesertHead
1129            | SpriteKind::BedCoastalHead
1130            | SpriteKind::BedSavannahHead => Some((Vec3::new(1.4, 0.0, 0.5), Vec3::unit_x())),
1131            SpriteKind::BedMesa => Some((Vec3::new(0.0, 0.0, 0.6), -Vec3::unit_y())),
1132            SpriteKind::BedrollSnow | SpriteKind::BedrollPirate => {
1133                Some((Vec3::new(0.0, 0.0, 0.1), -Vec3::unit_x()))
1134            },
1135            SpriteKind::Bedroll => Some((Vec3::new(0.0, 0.0, 0.1), Vec3::unit_y())),
1136            _ => None,
1137        }
1138    }
1139
1140    pub fn is_bed(&self) -> bool {
1141        matches!(
1142            self,
1143            SpriteKind::BedWoodWoodlandHead
1144                | SpriteKind::BedMesa
1145                | SpriteKind::BedCliffHead
1146                | SpriteKind::BedCoastalHead
1147                | SpriteKind::BedDesertHead
1148                | SpriteKind::BedSavannahHead
1149                | SpriteKind::Bedroll
1150                | SpriteKind::BedrollSnow
1151                | SpriteKind::BedrollPirate
1152        )
1153    }
1154
1155    #[inline]
1156    pub fn is_mountable(&self) -> bool { self.mount_offset().is_some() }
1157
1158    /// Get the buff provided by the block (currently used for mounting)
1159    #[inline]
1160    pub fn mount_buffs(&self) -> Option<Vec<BuffEffect>> {
1161        match self {
1162            sprite if sprite.is_bed() => Some(vec![BuffEffect {
1163                kind: BuffKind::RestingHeal,
1164                data: BuffData::new(0.02, Some(Secs(1.0))),
1165                cat_ids: Vec::new(),
1166            }]),
1167            _ => None,
1168        }
1169    }
1170
1171    #[inline]
1172    pub fn is_controller(&self) -> bool { matches!(self, SpriteKind::Helm) }
1173
1174    #[inline]
1175    pub fn is_door(&self) -> bool {
1176        matches!(
1177            self,
1178            SpriteKind::Door
1179                | SpriteKind::DoorWide
1180                | SpriteKind::DoorDark
1181                | SpriteKind::DoorWideAirship
1182        )
1183    }
1184
1185    /// Which tool (if any) is needed to collect this sprite?
1186    #[inline]
1187    pub fn mine_tool(&self) -> Option<ToolKind> {
1188        match self {
1189            SpriteKind::Velorite
1190            | SpriteKind::VeloriteFrag
1191            // Gems
1192            | SpriteKind::Amethyst
1193            | SpriteKind::Ruby
1194            | SpriteKind::Diamond
1195            | SpriteKind::Sapphire
1196            | SpriteKind::Emerald
1197            | SpriteKind::Topaz
1198            | SpriteKind::Bloodstone
1199            | SpriteKind::Coal
1200            | SpriteKind::Cobalt
1201            | SpriteKind::Copper
1202            | SpriteKind::Iron
1203            | SpriteKind::Tin
1204            | SpriteKind::Silver
1205            | SpriteKind::Gold
1206            | SpriteKind::Lodestone => Some(ToolKind::Pick),
1207            SpriteKind::Grave | SpriteKind::Mud => Some(ToolKind::Shovel),
1208            _ => None,
1209        }
1210    }
1211
1212    pub fn required_mine_damage(&self) -> Option<u8> {
1213        Some(match self {
1214            SpriteKind::Gold => 6,
1215            SpriteKind::Silver => 6,
1216            SpriteKind::Bloodstone => 6,
1217            SpriteKind::Cobalt => 6,
1218            SpriteKind::Coal => 4,
1219            SpriteKind::Iron => 4,
1220            SpriteKind::Copper => 3,
1221            SpriteKind::Tin => 3,
1222            SpriteKind::Amethyst => 3,
1223            SpriteKind::Ruby => 3,
1224            SpriteKind::Sapphire => 3,
1225            SpriteKind::Emerald => 3,
1226            SpriteKind::Topaz => 3,
1227            SpriteKind::Diamond => 3,
1228            SpriteKind::Velorite => 3,
1229            SpriteKind::VeloriteFrag => 2,
1230            SpriteKind::Lodestone => 2,
1231            _ => return None,
1232        })
1233    }
1234
1235    /// Defines how much damage it takes for a mined resource to possibly
1236    /// make an extra drop.
1237    pub fn mine_drop_interval(&self) -> u8 {
1238        match self {
1239            SpriteKind::Gold => 3,
1240            SpriteKind::Silver => 3,
1241            SpriteKind::Bloodstone => 3,
1242            SpriteKind::Cobalt => 3,
1243            SpriteKind::Coal => 2,
1244            SpriteKind::Iron => 2,
1245            SpriteKind::Copper => 1,
1246            SpriteKind::Tin => 1,
1247            SpriteKind::Emerald => 3,
1248            SpriteKind::Sapphire => 3,
1249            SpriteKind::Amethyst => 3,
1250            SpriteKind::Topaz => 3,
1251            SpriteKind::Diamond => 3,
1252            SpriteKind::Ruby => 3,
1253            SpriteKind::Velorite => 3,
1254            SpriteKind::VeloriteFrag => 2,
1255            SpriteKind::Lodestone => 2,
1256            _ => 1,
1257        }
1258    }
1259
1260    /// Requires this item in the inventory to harvest, uses item_definition_id
1261    // TODO: Do we want to consolidate this with mine_tool at all? Main differences
1262    // are that mine tool requires item to be an equippable tool, be equipped, and
1263    // does not consume item while required_item requires that the item be in the
1264    // inventory and will consume the item on collecting the sprite.
1265    pub fn unlock_condition(self, cfg: Option<&SpriteCfg>) -> Option<Cow<'_, UnlockKind>> {
1266        let kind = if let Some(unlock) = cfg.and_then(|cfg| cfg.unlock.as_ref()) {
1267            Cow::Borrowed(unlock)
1268        } else {
1269            Cow::Owned(match self {
1270                SpriteKind::CommonLockedChest => {
1271                    UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(String::from(
1272                        "common.items.utility.lockpick.lockpick_copper",
1273                    )))
1274                },
1275                SpriteKind::SahaginKeyhole => UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
1276                    String::from("common.items.keys.sahagin_key"),
1277                )),
1278                SpriteKind::BoneKeyhole => UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
1279                    String::from("common.items.keys.bone_key"),
1280                )),
1281                SpriteKind::HaniwaKeyhole => UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
1282                    String::from("common.items.keys.haniwa_key"),
1283                )),
1284                SpriteKind::VampireKeyhole => UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
1285                    String::from("common.items.keys.vampire_key"),
1286                )),
1287                SpriteKind::GlassKeyhole => UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
1288                    String::from("common.items.keys.glass_key"),
1289                )),
1290                SpriteKind::TerracottaChest => UnlockKind::Consumes(
1291                    ItemDefinitionIdOwned::Simple(String::from(
1292                        "common.items.keys.terracotta_key_chest",
1293                    ))
1294                    .to_owned(),
1295                ),
1296                SpriteKind::TerracottaKeyhole => UnlockKind::Consumes(
1297                    ItemDefinitionIdOwned::Simple(String::from(
1298                        "common.items.keys.terracotta_key_door",
1299                    ))
1300                    .to_owned(),
1301                ),
1302                SpriteKind::MyrmidonKeyhole => UnlockKind::Consumes(
1303                    ItemDefinitionIdOwned::Simple(String::from("common.items.keys.myrmidon_key"))
1304                        .to_owned(),
1305                ),
1306                SpriteKind::MinotaurKeyhole => UnlockKind::Consumes(
1307                    ItemDefinitionIdOwned::Simple(String::from("common.items.keys.minotaur_key"))
1308                        .to_owned(),
1309                ),
1310                SpriteKind::WitchChest => UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
1311                    String::from("common.items.utility.lockpick.lockpick_cobalt"),
1312                )),
1313                SpriteKind::PirateChest => UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
1314                    String::from("common.items.utility.lockpick.lockpick_iron"),
1315                )),
1316                _ => return None,
1317            })
1318        };
1319        Some(kind)
1320    }
1321
1322    /// Get the [`Content`] that this sprite is labelled with.
1323    pub fn content(&self, cfg: Option<SpriteCfg>) -> Option<Content> {
1324        cfg.and_then(|cfg| cfg.content)
1325    }
1326
1327    // TODO: phase out use of this method in favour of `sprite.has_attr::<Ori>()`
1328    #[inline]
1329    pub fn has_ori(&self) -> bool { self.category().has_attr::<Ori>() }
1330}
1331
1332impl fmt::Display for SpriteKind {
1333    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) }
1334}
1335
1336use strum::IntoEnumIterator;
1337
1338lazy_static! {
1339    pub static ref SPRITE_KINDS: HashMap<String, SpriteKind> =
1340        SpriteKind::iter().map(|sk| (sk.to_string(), sk)).collect();
1341}
1342
1343impl<'a> TryFrom<&'a str> for SpriteKind {
1344    type Error = ();
1345
1346    #[inline]
1347    fn try_from(s: &'a str) -> Result<Self, Self::Error> { SPRITE_KINDS.get(s).copied().ok_or(()) }
1348}
1349
1350// TODO: Free and Requires are currently unused.
1351#[derive(Clone, Debug, Serialize, Deserialize)]
1352pub enum UnlockKind {
1353    /// The sprite can be freely unlocked without any conditions
1354    Free,
1355    /// The sprite requires that the opening character has a given item in their
1356    /// inventory
1357    Requires(ItemDefinitionIdOwned),
1358    /// The sprite will consume the given item from the opening character's
1359    /// inventory
1360    Consumes(ItemDefinitionIdOwned),
1361}
1362
1363#[derive(Default, Clone, Debug, Serialize, Deserialize)]
1364pub struct SpriteCfg {
1365    /// Signifies that this sprite needs an item to be unlocked.
1366    pub unlock: Option<UnlockKind>,
1367    /// Signifies a text associated with this sprite.
1368    /// This also allows (but not requires) internationalization.
1369    ///
1370    /// Notice boards are an example of sprite that uses this.
1371    pub content: Option<Content>,
1372    /// Signifies a loot table associated with this sprite. The string referes
1373    /// to the loot table asset identifier.
1374    ///
1375    /// Chests are an example of sprite that can use this. For simple sprites
1376    /// like flowers using [`SpriteKind::default_loot_spec`] method is
1377    /// recommended instead.
1378    ///
1379    /// If you place a custom loot table on a sprite, make sure it's listed in
1380    /// [`SpriteKind::is_defined_as_container`], which should be enforced in
1381    /// tests, if possible.
1382    ///
1383    ///`collectible_info` must be `Some` for a sprite to be collectible.
1384    /// Adding a loot table to other sprites will not enable collecting
1385    /// them. `is_defined_as_container` is necessary to avoid displaying
1386    /// items from the default loot table (and all containers are included in
1387    /// `collectible_info`).
1388    ///
1389    /// NOTE: this is sent to the clients, we may potentionally strip this info
1390    /// on sending.
1391    pub loot_table: Option<String>,
1392}
1393
1394#[cfg(test)]
1395mod tests {
1396    use super::*;
1397
1398    #[test]
1399    fn sprite_containers_are_collectible() {
1400        for sprite in SpriteKind::all() {
1401            if sprite.is_defined_as_container() {
1402                assert!(sprite.collectible_info().is_some());
1403            }
1404        }
1405    }
1406
1407    #[test]
1408    fn sprite_conv_kind() {
1409        for sprite in SpriteKind::all() {
1410            let block = Block::air(*sprite);
1411            assert_eq!(block.sprite_category(), Some(sprite.category()));
1412            assert_eq!(block.get_sprite(), Some(*sprite));
1413        }
1414    }
1415
1416    #[test]
1417    fn sprite_attr() {
1418        for category in Category::all() {
1419            if category.has_attr::<Ori>() {
1420                for sprite in category.all_sprites() {
1421                    for i in 0..4 {
1422                        let block = Block::air(*sprite).with_attr(Ori(i)).unwrap();
1423                        assert_eq!(block.get_attr::<Ori>().unwrap(), Ori(i));
1424                        assert_eq!(block.get_sprite(), Some(*sprite));
1425                    }
1426                }
1427            }
1428            if category.has_attr::<Growth>() {
1429                for sprite in category.all_sprites() {
1430                    for i in 0..16 {
1431                        let block = Block::air(*sprite).with_attr(Growth(i)).unwrap();
1432                        assert_eq!(block.get_attr::<Growth>().unwrap(), Growth(i));
1433                        assert_eq!(block.get_sprite(), Some(*sprite));
1434                    }
1435                }
1436            }
1437        }
1438    }
1439}