Skip to main content

yog_registry/
particles.rs

1//! A particle a mod adds (`.yparticle`): how it looks, how it moves and
2//! how it's drawn — what the hosts draw it by, and Yog-IDLE's designer
3//! previews. Its colour is each spawn's.
4
5use yog_data::kinds::Texture;
6use yog_data::with::{List, RefTo};
7
8/// The game's own particles' defaults, where a particle doesn't say.
9mod game {
10    /// Speed kept each tick (`Particle.friction`).
11    pub const FRICTION: f32 = 0.98;
12    /// Ticks a particle lives, about (`Particle.lifetime`'s base).
13    pub const LIFETIME: u32 = 20;
14}
15
16/// A particle type (`hexcasting:conjure_particle`): its id, and the rest.
17#[derive(yog_data::Schema, Debug, Clone, PartialEq)]
18pub struct ParticleDef {
19    pub id: String,
20    pub look: Particle,
21}
22
23/// A `.yparticle`: everything but its id (its path).
24#[derive(yog_data::Schema, Debug, Clone, PartialEq)]
25pub struct Particle {
26    /// Its pictures, in order: the mod's textures of the particles' atlas
27    /// (`@texture:mymod:particle/cloud`).
28    #[data(with = "List<RefTo<Texture>>")]
29    pub textures: Vec<String>,
30    /// Which of them it shows.
31    #[data(default)]
32    pub frames: Frames,
33    /// How much faster than its life its frames go by age (`Frames::Age`).
34    #[data(default = "one")]
35    pub frame_speed: f32,
36    /// Once that faster run is past its life, its frames go by its age as it
37    /// is (else they stay on the last).
38    #[data(default)]
39    pub frame_settle: bool,
40    /// Ticks it lives: between `min` and `max`.
41    #[data(default = "lifetime")]
42    pub lifetime: Lifetime,
43    /// Its size, times the game's particles' (which varies a little).
44    #[data(default = "one")]
45    pub size: f32,
46    /// Its size times this each tick (1: it keeps it).
47    #[data(default = "one")]
48    pub size_per_tick: f32,
49    /// Its opacity at first (0–1).
50    #[data(default = "one", range = "0..=1")]
51    pub alpha: f32,
52    /// It fades out over its life, from `alpha` to none.
53    #[data(default)]
54    pub fade: bool,
55    /// Its speed kept each tick.
56    #[data(default = "friction")]
57    pub friction: f32,
58    /// Blocks a tick² it falls (negative: rises).
59    #[data(default)]
60    pub gravity: f32,
61    /// It falls (`gravity`) only when spawned moving along every axis.
62    #[data(default)]
63    pub gravity_needs_motion: bool,
64    /// It meets blocks (it doesn't go through them).
65    #[data(default)]
66    pub physics: bool,
67    /// It speeds up sideways when something stops it rising or falling.
68    #[data(default)]
69    pub slide_when_blocked: bool,
70    /// It starts turned at random (else upright).
71    #[data(default)]
72    pub random_roll: bool,
73    /// How it's drawn.
74    #[data(default)]
75    pub render: ParticleRender,
76}
77
78/// Which picture a particle shows.
79#[derive(yog_data::Schema, Debug, Clone, Copy, PartialEq, Eq, Default)]
80pub enum Frames {
81    /// One at random, for its whole life.
82    Random,
83    /// One after another as it ages (`frame_speed` times as fast as it lives).
84    #[default]
85    Age,
86}
87
88/// The ticks a particle lives.
89#[derive(yog_data::Schema, Debug, Clone, Copy, PartialEq, Eq)]
90pub struct Lifetime {
91    pub min: u32,
92    pub max: u32,
93}
94
95/// How a particle is drawn.
96#[derive(yog_data::Schema, Debug, Clone, Copy, PartialEq, Eq)]
97pub struct ParticleRender {
98    #[data(default)]
99    pub blend: Blend,
100    /// It hides what's behind it from later particles (off: they show through).
101    #[data(default = "yes")]
102    pub depth_write: bool,
103    /// It glows: at full light whatever the light where it is.
104    #[data(default)]
105    pub glow: bool,
106}
107
108impl Default for ParticleRender {
109    fn default() -> Self {
110        ParticleRender {
111            blend: Blend::default(),
112            depth_write: true,
113            glow: false,
114        }
115    }
116}
117
118/// How a particle's colour meets what's behind it.
119#[derive(yog_data::Schema, Debug, Clone, Copy, PartialEq, Eq, Default)]
120pub enum Blend {
121    /// It covers it (its picture's see-through pixels left out).
122    Opaque,
123    /// See-through as its alpha says.
124    #[default]
125    Translucent,
126    /// Its light added to it: it glows over what's behind.
127    Additive,
128}
129
130fn one() -> f32 {
131    1.0
132}
133
134fn yes() -> bool {
135    true
136}
137
138fn friction() -> f32 {
139    game::FRICTION
140}
141
142fn lifetime() -> Lifetime {
143    Lifetime {
144        min: game::LIFETIME,
145        max: game::LIFETIME,
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn a_particle_says_only_what_isnt_the_games() {
155        let p = yog_data::from_text::<Particle>(
156            r#"{ textures: ["@texture:m:particle/cloud"], render: { blend: "additive" } }"#,
157        )
158        .unwrap()
159        .value;
160        assert_eq!(p.friction, game::FRICTION);
161        assert_eq!(p.lifetime, lifetime());
162        assert_eq!(p.frames, Frames::Age);
163        assert_eq!(p.render.blend, Blend::Additive);
164        assert!(p.render.depth_write && !p.render.glow);
165    }
166}