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