Skip to main content

game_toolkit_platformer/hero/
driver.rs

1//! [`HeroDriver`]: the simulation's facts and events as the hero graph's parameters.
2
3use crate::anim::{Driver, ParamId, ParamKind, Params, SpecError};
4use crate::sim::{Event, JumpKind, Mode, World};
5
6/// Parameter ids of the hero graph, looked up once.
7#[derive(Clone, Debug)]
8pub struct HeroDriver {
9    /// `grounded`
10    grounded: ParamId,
11    /// `skid`
12    skid: ParamId,
13    /// `rolling`
14    rolling: ParamId,
15    /// `dashing`
16    dashing: ParamId,
17    /// `climbing`
18    climbing: ParamId,
19    /// `slipping`
20    slipping: ParamId,
21    /// `wall_slide`
22    wall_slide: ParamId,
23    /// `hard_landing`
24    hard_landing: ParamId,
25    /// `wings`
26    wings: ParamId,
27    /// `wall_push`
28    wall_push: ParamId,
29    /// `gliding`
30    gliding: ParamId,
31    /// `super_bonk`
32    super_bonk: ParamId,
33    /// `super_launch`
34    super_launch: ParamId,
35    /// `super_flying`
36    super_flying: ParamId,
37    /// `charged`
38    charged: ParamId,
39    /// `charging`
40    charging: ParamId,
41    /// `speed`
42    speed: ParamId,
43    /// `vy`
44    vy: ParamId,
45    /// `dash_y`
46    dash_y: ParamId,
47    /// `look`
48    look: ParamId,
49    /// `jump`
50    jump: ParamId,
51    /// `double`
52    double: ParamId,
53    /// `wall_jump`
54    wall_jump: ParamId,
55    /// `hop`
56    hop: ParamId,
57    /// `drop`
58    drop: ParamId,
59    /// `land`
60    land: ParamId,
61    /// `roll`
62    roll: ParamId,
63    /// `hard`
64    hard: ParamId,
65    /// `dash`
66    dash: ParamId,
67    /// `grab`
68    grab: ParamId,
69    /// `dive`
70    dive: ParamId,
71    /// `impact`
72    impact: ParamId,
73}
74
75/// Skidding takes at least this much speed against the facing direction.
76const SKID_SPEED: f32 = 30.0;
77
78impl HeroDriver {
79    /// Look up the hero graph's parameters in `params`. Panics on a missing one: the
80    /// driver and [`super::spec`] belong together. For a graph of your own, use
81    /// [`HeroDriver::try_new`].
82    pub fn new(params: &Params) -> Self {
83        Self::try_new(params).unwrap_or_else(|e| panic!("{e}"))
84    }
85
86    /// Look up the parameters this driver writes in `params`, or say which one a graph
87    /// doesn't declare. A custom graph has to declare all of them (it may ignore any);
88    /// see [`HeroDriver::declaring`] for one that doesn't.
89    pub fn try_new(params: &Params) -> Result<Self, SpecError> {
90        Self::lookup(&mut params.clone(), false)
91    }
92
93    /// Look up the parameters this driver writes in `params`, declaring the ones a graph
94    /// of your own left out. The graph never reads those, so it can leave out whatever it
95    /// doesn't animate.
96    pub fn declaring(params: &mut Params) -> Self {
97        Self::lookup(params, true).expect("missing parameters are declared")
98    }
99
100    /// Find (or, with `declare`, add) every parameter this driver writes.
101    fn lookup(params: &mut Params, declare: bool) -> Result<Self, SpecError> {
102        const B: ParamKind = ParamKind::Bool;
103        const F: ParamKind = ParamKind::Float;
104        const T: ParamKind = ParamKind::Trigger { ttl: 0.0 };
105        let mut id = |n: &'static str, kind: ParamKind| match params.id(n) {
106            Some(id) => Ok(id),
107            None if declare => Ok(params.declare(n, kind)),
108            None => Err(SpecError::UnknownParam(n.into())),
109        };
110        Ok(Self {
111            grounded: id("grounded", B)?,
112            skid: id("skid", B)?,
113            rolling: id("rolling", B)?,
114            dashing: id("dashing", B)?,
115            climbing: id("climbing", B)?,
116            slipping: id("slipping", B)?,
117            wall_slide: id("wall_slide", B)?,
118            hard_landing: id("hard_landing", B)?,
119            wings: id("wings", B)?,
120            wall_push: id("wall_push", B)?,
121            gliding: id("gliding", B)?,
122            super_bonk: id("super_bonk", T)?,
123            super_launch: id("super_launch", T)?,
124            super_flying: id("super_flying", B)?,
125            charged: id("charged", B)?,
126            charging: id("charging", B)?,
127            speed: id("speed", F)?,
128            vy: id("vy", F)?,
129            dash_y: id("dash_y", F)?,
130            look: id("look", F)?,
131            jump: id("jump", T)?,
132            double: id("double", T)?,
133            wall_jump: id("wall_jump", T)?,
134            hop: id("hop", T)?,
135            drop: id("drop", T)?,
136            land: id("land", T)?,
137            roll: id("roll", T)?,
138            hard: id("hard", T)?,
139            dash: id("dash", T)?,
140            grab: id("grab", T)?,
141            dive: id("dive", T)?,
142            impact: id("impact", T)?,
143        })
144    }
145}
146
147impl Driver<World> for HeroDriver {
148    fn drive(&mut self, w: &World, p: &mut Params) {
149        let pl = &w.player;
150        p.set_bool(self.grounded, pl.grounded);
151        p.set_bool(
152            self.skid,
153            pl.grounded
154                && pl.mode == Mode::Normal
155                && pl.vx.abs() > SKID_SPEED
156                && pl.vx.signum() as i32 != pl.facing,
157        );
158        p.set_bool(self.rolling, matches!(pl.mode, Mode::Roll { .. }));
159        p.set_bool(self.dashing, matches!(pl.mode, Mode::Dash { .. }));
160        p.set_bool(self.climbing, pl.mode == Mode::Climb);
161        p.set_bool(self.slipping, pl.slipping);
162        p.set_bool(self.wall_slide, pl.wall_slide != 0);
163        p.set_bool(self.hard_landing, matches!(pl.mode, Mode::HardLand { .. }));
164        p.set_bool(self.slipping, pl.slipping);
165        p.set_bool(self.wings, w.tuning.wings);
166        // On the ground, running into a wall (the movement keeps the speed at zero there).
167        p.set_bool(
168            self.wall_push,
169            pl.grounded && pl.mode == Mode::Normal && pl.contacts.wall != 0,
170        );
171        p.set_bool(self.gliding, pl.gliding);
172        let charge = match pl.mode {
173            Mode::SuperCharge { t, .. } => Some(t),
174            _ => None,
175        };
176        p.set_bool(self.charging, charge.is_some());
177        p.set_bool(
178            self.charged,
179            charge.is_some_and(|t| t >= w.tuning.super_charge),
180        );
181        p.set_bool(self.super_flying, matches!(pl.mode, Mode::SuperDash { .. }));
182        // Signed seconds held: negative looking up, positive looking down.
183        p.set_float(self.look, pl.look as f32 * pl.look_time);
184        p.set_float(self.speed, pl.vx.abs());
185        p.set_float(self.vy, pl.vy);
186        if let Mode::Dash { dy, .. } = pl.mode {
187            p.set_float(self.dash_y, dy);
188        }
189        for e in &w.events {
190            let t = match e {
191                Event::Jumped {
192                    kind: JumpKind::Double,
193                    ..
194                } => self.double,
195                Event::Jumped {
196                    kind: JumpKind::Wall,
197                    ..
198                } => self.wall_jump,
199                Event::Jumped { .. } => self.jump,
200                Event::Landed { .. } => self.land,
201                Event::RollStarted => self.roll,
202                Event::HardLanded => self.hard,
203                Event::Dashed { .. } => self.dash,
204                Event::Grabbed => self.grab,
205                Event::ClimbHop => self.hop,
206                Event::DroppedThrough => self.drop,
207                Event::DiveStarted => self.dive,
208                Event::DiveImpact { .. } => self.impact,
209                Event::SuperDashStarted { .. } => self.super_launch,
210                Event::SuperDashEnded { bonk: true } => self.super_bonk,
211                _ => continue,
212            };
213            p.fire(t);
214        }
215    }
216}