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