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