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