game_toolkit_platformer/sim/ability.rs
1//! [`Ability`]: a move of your game's own (an attack lunge, a ledge grab, a grapple, a
2//! pogo), hooked into [`World::step`](super::World::step) without forking the movement.
3//!
4//! An ability runs twice per step: [`Ability::before`] ahead of the built-in moves, and
5//! [`Ability::after`] once they moved the body. It may change anything public on the
6//! [`Player`] (speed, facing, refills, [`Player::launch`] and [`Player::teleport`]), read
7//! the [`Intent`] (your own buttons arrive in [`Intent::custom`]), read and change the
8//! terrain (your own type through [`find_mut`](trait.Terrain.html#method.find_mut): break a
9//! block, flip a switch, use up a pickup; the movement sees the change at once; it answers
10//! in its own pixels under a `Builder::scale`, so scale the body's rects by
11//! [`Step::scale`] first), and report
12//! [`Event::Custom`]s (with up to four numbers of data: a knockback, a hit target).
13//!
14//! Name your modes and events with a [`CustomId`](super::CustomId) after your crate
15//! (`CustomId::new("my_crate::lunge")`): abilities from different crates then never have
16//! to agree on numbers. Custom buttons ([`Intent::custom`]) belong to the game, which binds
17//! them to keys: an ability meant for others takes its button's bit as a parameter.
18//!
19//! To replace a built-in move, rule it out and do your own in its place. To react to
20//! collisions, read [`Player::contacts`] in [`Ability::after`]. In a [`crate::Party`],
21//! [`Step::others`] holds the other characters' bodies.
22//!
23//! To rule out single built-in moves for a step (no dash while attacking, no wall grab
24//! while carrying, slow walking while pushing), clear them in [`Step::moves`] in
25//! [`Ability::before`].
26//!
27//! To take the body over for a while, put it in [`Mode::Custom`](super::Mode::Custom):
28//! no built-in move starts or runs then (no running, no gravity, no jumps or dashes), but
29//! the body still moves by its speed through the collision, lands and is carried by moving
30//! ground. The mode's clock counts up on its own; set [`Mode::Normal`](super::Mode::Normal)
31//! to hand the body back.
32//!
33//! Abilities are part of the world: a cloned world clones them, and it must play out
34//! identically, so keep their state in the ability (no clocks or randomness from outside)
35//! and derive `Clone`.
36//!
37//! ```
38//! use game_toolkit_platformer::sim::{Ability, CustomId, Event, Intent, Mode, Preset, Spot, Step, World};
39//!
40//! /// Custom button 0 lunges forward on the ground.
41//! #[derive(Clone, Debug)]
42//! struct Lunge;
43//!
44//! /// The lunge's mode and event id, named after the crate so no other ability's collides.
45//! const LUNGE: CustomId = CustomId::new("my_game::lunge");
46//!
47//! impl Ability for Lunge {
48//! fn before(&mut self, s: &mut Step<'_>) {
49//! let p = &mut *s.player;
50//! match p.mode {
51//! Mode::Normal if s.intent.pressed(0) && p.grounded() => {
52//! p.mode = Mode::Custom { id: LUNGE, t: 0.0 };
53//! p.vx = 240.0 * p.facing as f32;
54//! p.vy = 0.0;
55//! s.events.push(Event::custom(LUNGE).with([p.vx, 0.0, 0.0, 0.0]));
56//! }
57//! Mode::Custom { id: LUNGE, t } if t >= 0.15 => p.mode = Mode::Normal,
58//! _ => {}
59//! }
60//! // No dash out of the lunge's recovery, while custom button 0 is held.
61//! s.moves.dash = !s.intent.held(0);
62//! }
63//! }
64//!
65//! let mut world = World::new(Preset::Hybrid.tuning(), Spot::TopLeft(20, 88));
66//! world.abilities.push(Box::new(Lunge));
67//! let mut floor = |r: game_toolkit_platformer::sim::Rect| r.bottom() > 100;
68//! world.step(&mut floor, &Intent { custom_pressed: 1, ..Intent::default() });
69//! assert!(world.events.iter().any(|e| e.custom_data(LUNGE).is_some()));
70//! ```
71
72use std::any::Any;
73use std::fmt::Debug;
74
75use super::{Event, Intent, Moves, Player, Rect, Terrain, Tuning};
76
77/// What an [`Ability`] gets to work with in one step.
78pub struct Step<'a> {
79 /// The body.
80 pub player: &'a mut Player,
81 /// This step's input.
82 pub intent: &'a Intent,
83 /// The world around it; changes count from here on, in this step too.
84 pub terrain: &'a mut dyn Terrain,
85 /// The movement feel.
86 pub tuning: &'a Tuning,
87 /// The built-in moves allowed this step: clear some in [`Ability::before`]. Changes in
88 /// [`Ability::after`] come too late for this step (and the next starts from
89 /// [`Moves::ALL`] again).
90 pub moves: &'a mut Moves,
91 /// This step's events so far; push yours ([`Event::Custom`]).
92 pub events: &'a mut Vec<Event>,
93 /// The other characters' bodies on the same terrain (a [`crate::Party`]), as the step
94 /// started for those yet to move and as they ended up for those already moved; empty
95 /// for a character on its own.
96 pub others: &'a [Rect],
97 /// The step's length in seconds ([`super::DT`]).
98 pub dt: f32,
99 /// Pixels of the game's terrain per unit ([`super::World::scale`]; 1 without a
100 /// `Builder::scale`). Everything in the step is in units, but a terrain of the game's
101 /// own reached through [`find_mut`](trait.Terrain.html#method.find_mut) answers in its
102 /// pixels: ask it with `rect.scaled(s.scale)`.
103 pub scale: i32,
104}
105
106/// A move of your own, run in every [`World::step`](super::World::step). Derive `Clone`
107/// and `Debug`; both hooks default to nothing.
108pub trait Ability: AbilityClone + Debug {
109 /// Ahead of the built-in moves: start your move, steer it, end it; rule out built-in
110 /// moves ([`Step::moves`]).
111 fn before(&mut self, _s: &mut Step<'_>) {}
112
113 /// After the built-in moves, collision and landing: react to where the body ended up
114 /// and what it ran into ([`Player::contacts`]: a wall, a ceiling, the floor).
115 fn after(&mut self, _s: &mut Step<'_>) {}
116}
117
118/// Cloning a boxed [`Ability`], so a world with abilities stays `Clone`, and reaching its
119/// concrete type ([`super::World::ability`]). Implemented for every `Ability + Clone`; you
120/// don't implement it yourself.
121pub trait AbilityClone {
122 /// A boxed copy.
123 fn clone_box(&self) -> Box<dyn Ability>;
124 /// Itself, to downcast.
125 fn as_any(&self) -> &dyn Any;
126 /// Itself, to downcast and change.
127 fn as_any_mut(&mut self) -> &mut dyn Any;
128}
129
130impl<T: Ability + Clone + 'static> AbilityClone for T {
131 fn clone_box(&self) -> Box<dyn Ability> {
132 Box::new(self.clone())
133 }
134
135 fn as_any(&self) -> &dyn Any {
136 self
137 }
138
139 fn as_any_mut(&mut self) -> &mut dyn Any {
140 self
141 }
142}
143
144impl Clone for Box<dyn Ability> {
145 fn clone(&self) -> Self {
146 self.clone_box()
147 }
148}