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