Skip to main content

pixel8/physics/
member.rs

1//! The handle a cart keeps — which seat in the cast, and which occupancy of it — and the two
2//! views the world lends out for it: one to read the seat, one to change it.
3
4use core::fmt;
5
6use super::{
7    wire,
8    world::{corner, mirrored, span_byte},
9    Bounds, Contacts, Force, Velocity, World,
10};
11use crate::{motion::floor_i16, BitFlags, SpriteFlag, SpriteId};
12
13/// One member of a [`World`](super::World)'s cast: a seat, and the right to ask the world to
14/// borrow whoever is in it.
15///
16/// What [`MemberBuilder::enlist`] hands back and the cart keeps beside its own game data.
17/// Two bytes, [`Copy`], and nothing else: the position, the velocity, the rectangle, the contacts
18/// and the look are all the world's, and every one of them is asked of the seat this id names,
19/// borrowed from the world it was enlisted into — [`world.member(hero)`](World::member) to read
20/// it, [`world.member_mut(hero)`](World::member_mut) to change it.
21///
22/// ```no_run
23/// # use pixel8::physics::{Member, MemberId, World};
24/// struct Hero {
25///     /// Where the hero is, how fast, and what it last ran into: all of it the world's.
26///     member: MemberId,
27///     /// And what is the cart's own, which the world has never heard of.
28///     coins: u16,
29/// }
30///
31/// # fn f(world: &mut World<8>) -> Option<Hero> {
32/// let hero = Hero {
33///     member: Member::builder(16.0, 80.0, 8, 8).enlist(world)?,
34///     coins: 0,
35/// };
36/// # Some(hero) }
37/// ```
38///
39/// A member is only ever as good as its seat. [`retire`](MemberMut::retire) empties the seat and
40/// the id to it goes stale on the spot: asking the world to borrow anything with a stale one is a
41/// bug in the cart, and it is answered with a panic naming the seat rather than with somebody
42/// else's position. A cart that would rather ask than know asks
43/// [`get_member`](World::get_member).
44///
45/// Ids are the world's own: one from a different [`World`] means the seat of that number in
46/// *this* one, which is a member the cart never meant. Carts with two scenes going at once keep
47/// their ids with the world they came from.
48///
49/// A seat's ids come round again: one let for the hundred and twenty-ninth time is handed the id
50/// it was handed the first time, and an id kept unasked-about across all of them would answer for
51/// whoever holds the seat now. Which is a way of saying: retire a member and forget it, the way a
52/// cart does anyway.
53///
54/// [`World`]: super::World
55#[derive(Clone, Copy, PartialEq, Eq, Debug)]
56#[must_use = "a seat with no id kept to it can never be retired"]
57pub struct MemberId {
58    /// Which of the world's `N` seats.
59    pub(super) slot: u8,
60    /// Which occupancy of it: the seat's count as it was taken. The count moves on as the seat
61    /// is taken and again as it is emptied, so it is odd for exactly as long as somebody sits
62    /// there: no id is ever an empty seat's count, and an id to a member that has left can be
63    /// told from an id to whoever was seated there next.
64    pub(super) generation: u8,
65}
66
67impl MemberId {
68    /// An id for nobody: what a cart's state holds for somebody not yet enlisted.
69    ///
70    /// The seat it names is past the sixty-four the wire carries, so no world has it and no
71    /// [`enlist`](MemberBuilder::enlist) can ever hand it out. It is a `const`, which is the whole
72    /// point of it: a cart whose state is [placed rather than built](crate::game) writes its actors
73    /// down as constants and gives them their seats in [`Game::boot`](crate::Game::boot), and this
74    /// is what they hold until it does.
75    ///
76    /// Asking the world to borrow anything with it is the same bug as asking with a retired id,
77    /// and panics the same way — an actor that was never seated is not standing anywhere.
78    ///
79    /// ```no_run
80    /// # use pixel8::physics::MemberId;
81    /// struct Hero {
82    ///     member: MemberId,
83    ///     coins: u16,
84    /// }
85    ///
86    /// impl Hero {
87    ///     /// The hero as the cart ships: everything about it but a seat.
88    ///     const fn waiting() -> Self {
89    ///         Self { member: MemberId::NOBODY, coins: 0 }
90    ///     }
91    /// }
92    /// ```
93    pub const NOBODY: Self = Self {
94        slot: u8::MAX,
95        generation: 0,
96    };
97
98    /// Which seat of the cast this is, counting from zero.
99    ///
100    /// The stepping order is seat order — see [`enlist`](MemberBuilder::enlist) — so this is
101    /// the one thing a cart can read off an id: who moves before whom.
102    pub const fn seat(self) -> usize {
103        self.slot as usize
104    }
105}
106
107/// A member borrowed from its world, to be read.
108///
109/// What [`World::member`] and [`World::get_member`] hand back: the seat's whole story, without a
110/// call back to the world for each question asked of it. Borrowed shared, so a cart may hold as
111/// many of these at once as it has questions — a stomp told from a ram by comparing two members'
112/// [`bounds`](Self::bounds).
113#[derive(Clone, Copy)]
114pub struct Member<'a> {
115    /// The id the member was borrowed with.
116    pub(super) id: MemberId,
117    /// The seat's record: everything the step and the draw read of the member.
118    pub(super) record: &'a wire::Record,
119    /// What the member weighs, which never crosses the wire and so is kept beside the record.
120    pub(super) mass: f32,
121    /// Whether what means *wall* to the member is a rule of its own rather than the scene's.
122    pub(super) own_solid: bool,
123}
124
125// Every method a view has, bar `builder`, is `#[inline]`, like the borrows that hand the views out:
126// carts are built for size, where a getter left out of line is a call, and one taking `&self`
127// makes the cart write the whole view to memory first. Inlined, it is the loads it reads.
128impl Member<'_> {
129    /// Describes a member `width` x `height` pixels at (`x`, `y`), covering that rectangle from
130    /// the pixel it will draw at, and hands back the [`MemberBuilder`] the rest of it is
131    /// described through.
132    ///
133    /// The position is exact and sub-pixel, like everything else that moves here; the rectangle
134    /// is whole pixels, and it is the one rectangle a member has — what the walls stop, what the
135    /// rest of the cast meets, and what the edge of the world holds. A hurtbox narrower than the
136    /// sprite says so with [`offset`](MemberBuilder::offset).
137    ///
138    /// That is a whole member already: standing still, wearing nothing, stopped by whatever the
139    /// scene calls solid, told about everything it meets, free to walk off the map and of the
140    /// weight nobody has to think about. [`MemberBuilder`]'s own builders say the rest, and
141    /// [`enlist`](MemberBuilder::enlist) closes the description, seats it in a world and hands
142    /// over the id the cart asks the world about the seat with.
143    ///
144    /// ```no_run
145    /// # use pixel8::physics::{Member, World};
146    /// # fn f(world: &mut World<8>) {
147    /// let Some(spark) = Member::builder(64.0, 64.0, 2, 2).moving(0.0, -1.5).enlist(world) else {
148    ///     // Every seat is taken; this one waits for the next explosion.
149    ///     return;
150    /// };
151    /// # }
152    /// ```
153    pub fn builder(x: f32, y: f32, width: u16, height: u16) -> MemberBuilder {
154        let (rx, ry) = (floor_i16(x), floor_i16(y));
155
156        MemberBuilder {
157            record: wire::Record {
158                x,
159                y,
160                rx,
161                ry,
162                bx: rx,
163                by: ry,
164                bw: width,
165                bh: height,
166                heeds: BitFlags::<SpriteFlag>::all().bits(),
167                ..wire::EMPTY
168            },
169            solid: None,
170            mass: 1.0,
171            offset: (0, 0),
172        }
173    }
174
175    /// The [`MemberId`] this member was borrowed with — what the cart keeps to ask the world for
176    /// it again.
177    #[inline]
178    pub fn id(&self) -> MemberId {
179        self.id
180    }
181
182    /// Where the member is: its exact sub-pixel position.
183    ///
184    /// The truth for a cart's own arithmetic — which tile it is over, how far it is from something.
185    /// What to *draw* at is [`draw_pos`](Self::draw_pos).
186    #[inline]
187    pub fn pos(&self) -> (f32, f32) {
188        (self.record.x, self.record.y)
189    }
190
191    /// The coherent pixel the member draws at.
192    ///
193    /// Where [`draw`](World::draw) puts the member — the top left of the block it is drawn from —
194    /// and the pixel for anything a cart draws at it on its own: a rotor, a shadow, a name over its
195    /// head. It is [`Body`](crate::Body)'s phase-coherent pixel — a sub-pixel diagonal climbs a
196    /// clean staircase through it instead of shimmering — and the step keeps it coherent across the
197    /// wire, so a running jump climbs that same staircase in the console.
198    #[inline]
199    pub fn draw_pos(&self) -> (i16, i16) {
200        (self.record.rx, self.record.ry)
201    }
202
203    /// What the member is travelling at, in pixels per update.
204    ///
205    /// After a step, what survived it: an axis that ran into something has been spent, so a fall
206    /// that landed reads zero and something that walked into a wall is not still walking.
207    #[inline]
208    pub fn velocity(&self) -> Velocity {
209        Velocity::new(self.record.dx, self.record.dy)
210    }
211
212    /// What the member's last step ran into: the sides it was stopped at, and the flags of
213    /// everything it met.
214    ///
215    /// The whole answer, walls and the edge of the world together, so a cart standing a member on
216    /// the bottom of the level, on a floor tile and on a moving platform reads all three the same
217    /// way. A [prop](MemberBuilder::prop) is never given contacts: the cart drives it, and there
218    /// is nobody home to tell.
219    #[inline]
220    pub fn contacts(&self) -> Contacts {
221        Contacts::from_wire(self.record.sides, self.record.touched)
222    }
223
224    /// The rectangle the member covers, where it now stands.
225    ///
226    /// The one rectangle a member has: what the walls stopped, what the rest of the cast met, and
227    /// what the edge of the world held. It follows the body through every step, so this is always
228    /// the rectangle that was collided with.
229    ///
230    /// The step says *the hero met a badie*; which badie, and what that costs, is the cart's, and
231    /// this is what it settles it with — a stomp told from a ram by comparing two rectangles the
232    /// world has just moved.
233    ///
234    /// ```no_run
235    /// # use pixel8::physics::{MemberId, World};
236    /// # fn f(world: &World<4>, hero: MemberId, badie: MemberId) -> bool {
237    /// // Level with the badie is a ram; anything else is the hero coming down on it.
238    /// world.member(hero).bounds().y() == world.member(badie).bounds().y()
239    /// # }
240    /// ```
241    #[inline]
242    pub fn bounds(&self) -> Bounds {
243        Bounds::new(
244            self.record.bx,
245            self.record.by,
246            self.record.bw,
247            self.record.bh,
248        )
249    }
250
251    /// The rectangle the member may not leave, if it named one — see
252    /// [`MemberBuilder::confined_to`].
253    #[inline]
254    pub fn confines(&self) -> Option<Bounds> {
255        (self.record.meta & wire::CONFINED != 0).then(|| {
256            Bounds::new(
257                self.record.cx,
258                self.record.cy,
259                self.record.cw,
260                self.record.ch,
261            )
262        })
263    }
264
265    /// The cell the member wears, if any — see [`MemberBuilder::wearing`].
266    ///
267    /// One cell for both halves of a member's part in the scene: [`draw`](World::draw) draws the
268    /// member from it and [`step`](World::step) steps it by the flags on it, so what is drawn and
269    /// what is met can never be two different sprites.
270    #[inline]
271    pub fn sprite(&self) -> Option<SpriteId> {
272        match self.record.sprite {
273            wire::UNWORN => None,
274            id => Some(SpriteId(id as u8)),
275        }
276    }
277
278    /// The member's own answer to what means *wall* to it, where it gave one — see
279    /// [`MemberBuilder::stopped_by`].
280    ///
281    /// `None` is a member that goes by the scene's word, whatever
282    /// [`with_solid`](World::with_solid) declared it to be.
283    #[inline]
284    pub fn solid(&self) -> Option<BitFlags<SpriteFlag>> {
285        self.own_solid.then(|| {
286            BitFlags::from_bits(self.record.solid)
287                .expect("a seat's solid was written from real flags")
288        })
289    }
290
291    /// Which flags the member cares to be told about — see [`MemberBuilder::heeding`].
292    #[inline]
293    pub fn heeds(&self) -> BitFlags<SpriteFlag> {
294        BitFlags::from_bits(self.record.heeds).expect("a seat's heeds was written from real flags")
295    }
296
297    /// What the member weighs — see [`MemberBuilder::weighing`].
298    #[inline]
299    pub fn mass(&self) -> f32 {
300        self.mass
301    }
302
303    /// Which way round the member is drawn: mirrored across, and mirrored up and down — see
304    /// [`MemberBuilder::flipped`].
305    #[inline]
306    pub fn flip(&self) -> (bool, bool) {
307        (
308            self.record.meta & wire::FLIP_X != 0,
309            self.record.meta & wire::FLIP_Y != 0,
310        )
311    }
312
313    /// How many cells the member is drawn from, across and down — see [`MemberBuilder::spanning`].
314    #[inline]
315    pub fn span(&self) -> (u8, u8) {
316        ((self.record.span & 0x0f) + 1, (self.record.span >> 4) + 1)
317    }
318
319    /// Whether the member is left off the screen — see [`MemberBuilder::hidden`].
320    #[inline]
321    pub fn hidden(&self) -> bool {
322        self.record.meta & wire::HIDDEN != 0
323    }
324}
325
326/// Everything the member's getters answer, in their order, and whether it is a
327/// [prop](MemberBuilder::prop).
328impl fmt::Debug for Member<'_> {
329    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330        f.debug_struct("Member")
331            .field("id", &self.id)
332            .field("pos", &self.pos())
333            .field("draw_pos", &self.draw_pos())
334            .field("velocity", &self.velocity())
335            .field("contacts", &self.contacts())
336            .field("bounds", &self.bounds())
337            .field("confines", &self.confines())
338            .field("sprite", &self.sprite())
339            .field("solid", &self.solid())
340            .field("heeds", &self.heeds())
341            .field("mass", &self.mass())
342            .field("flip", &self.flip())
343            .field("span", &self.span())
344            .field("hidden", &self.hidden())
345            .field("prop", &(self.record.meta & wire::PROP != 0))
346            .finish()
347    }
348}
349
350/// A member borrowed from its world, to be changed.
351///
352/// What [`World::member_mut`] and [`World::get_member_mut`] hand back: every setter a cart might
353/// write into the seat, and [`retire`](Self::retire) to empty it. Borrowed exclusively, so it is
354/// held for the few lines that change one member and let go before the world
355/// [steps](World::step) or another member is borrowed.
356pub struct MemberMut<'a> {
357    /// The id the member was borrowed with, and so the seat whose bit is its own in the words.
358    pub(super) id: MemberId,
359    /// The seat's record: everything the step and the draw read of the member.
360    pub(super) record: &'a mut wire::Record,
361    /// What the member weighs.
362    pub(super) mass: &'a mut f32,
363    /// Where the member's rectangle sits relative to the pixel it draws at.
364    pub(super) offset: &'a mut (i16, i16),
365    /// The world's word of which seats are taken, a bit apiece.
366    pub(super) seated: &'a mut u64,
367    /// The world's word of which members answered what is solid with a rule of their own.
368    pub(super) own_solid: &'a mut u64,
369    /// The seat's count, which retiring moves on.
370    pub(super) generation: &'a mut u8,
371    /// The scene's word for *wall*, which a member handed back to it goes by.
372    pub(super) scene_solid: BitFlags<SpriteFlag>,
373}
374
375// Inlined throughout, for the reason given at `impl Member`.
376impl MemberMut<'_> {
377    /// The [`MemberId`] this member was borrowed with — see [`Member::id`].
378    #[inline]
379    pub fn id(&self) -> MemberId {
380        self.read().id()
381    }
382
383    /// Where the member is — see [`Member::pos`].
384    #[inline]
385    pub fn pos(&self) -> (f32, f32) {
386        self.read().pos()
387    }
388
389    /// The coherent pixel the member draws at — see [`Member::draw_pos`].
390    #[inline]
391    pub fn draw_pos(&self) -> (i16, i16) {
392        self.read().draw_pos()
393    }
394
395    /// What the member is travelling at — see [`Member::velocity`].
396    #[inline]
397    pub fn velocity(&self) -> Velocity {
398        self.read().velocity()
399    }
400
401    /// What the member's last step ran into — see [`Member::contacts`].
402    #[inline]
403    pub fn contacts(&self) -> Contacts {
404        self.read().contacts()
405    }
406
407    /// The rectangle the member covers — see [`Member::bounds`].
408    #[inline]
409    pub fn bounds(&self) -> Bounds {
410        self.read().bounds()
411    }
412
413    /// The rectangle the member may not leave, if it named one — see [`Member::confines`].
414    #[inline]
415    pub fn confines(&self) -> Option<Bounds> {
416        self.read().confines()
417    }
418
419    /// The cell the member wears, if any — see [`Member::sprite`].
420    #[inline]
421    pub fn sprite(&self) -> Option<SpriteId> {
422        self.read().sprite()
423    }
424
425    /// The member's own answer to what means *wall* to it, where it gave one — see
426    /// [`Member::solid`].
427    #[inline]
428    pub fn solid(&self) -> Option<BitFlags<SpriteFlag>> {
429        self.read().solid()
430    }
431
432    /// Which flags the member cares to be told about — see [`Member::heeds`].
433    #[inline]
434    pub fn heeds(&self) -> BitFlags<SpriteFlag> {
435        self.read().heeds()
436    }
437
438    /// What the member weighs — see [`Member::mass`].
439    #[inline]
440    pub fn mass(&self) -> f32 {
441        self.read().mass()
442    }
443
444    /// Which way round the member is drawn — see [`Member::flip`].
445    #[inline]
446    pub fn flip(&self) -> (bool, bool) {
447        self.read().flip()
448    }
449
450    /// How many cells the member is drawn from, across and down — see [`Member::span`].
451    #[inline]
452    pub fn span(&self) -> (u8, u8) {
453        self.read().span()
454    }
455
456    /// Whether the member is left off the screen — see [`Member::hidden`].
457    #[inline]
458    pub fn hidden(&self) -> bool {
459        self.read().hidden()
460    }
461
462    /// Puts the member at (`x`, `y`) — a teleport, not a movement.
463    ///
464    /// The drawn pixel is re-snapped to the floor of the new position rather than eased towards it,
465    /// because this is a jump: a respawn, a room the player has walked into, the rails a
466    /// [prop](MemberBuilder::prop) is driven along. The rectangle goes with it, keeping whatever
467    /// [offset](MemberBuilder::offset) it was given.
468    ///
469    /// Ordinary movement is not this. A member is moved by having a velocity
470    /// ([`set_velocity`](Self::set_velocity)) and being stepped: that is what is stopped by walls,
471    /// held inside limits and reported in contacts, and none of it happens here.
472    #[inline]
473    pub fn set_pos(&mut self, x: f32, y: f32) {
474        (self.record.x, self.record.y) = (x, y);
475        (self.record.rx, self.record.ry) = (floor_i16(x), floor_i16(y));
476        (self.record.bx, self.record.by) = corner((self.record.rx, self.record.ry), *self.offset);
477    }
478
479    /// Sets what the member is travelling at: what the cart means it to do this update.
480    ///
481    /// Where the buttons, the patrol and the jump all end up. It is written before
482    /// [`step`](World::step), which is what turns it into movement — and written afresh every
483    /// update by anything that leans on a wall, since the step spends the speed that ran into one.
484    #[inline]
485    pub fn set_velocity(&mut self, velocity: Velocity) {
486        (self.record.dx, self.record.dy) = (velocity.dx, velocity.dy);
487    }
488
489    /// Sets how big the member's rectangle is.
490    ///
491    /// For a hitbox that follows the animation — a crouch, a blast that grows, a hurtbox switched
492    /// off by giving it no size at all, which is a member nothing resolves and everything lets
493    /// through. Where the rectangle sits on the body is [`set_offset`](Self::set_offset).
494    #[inline]
495    pub fn resize(&mut self, width: u16, height: u16) {
496        (self.record.bw, self.record.bh) = (width, height);
497    }
498
499    /// Sets where the member's rectangle sits relative to the pixel it draws at — see
500    /// [`MemberBuilder::offset`].
501    #[inline]
502    pub fn set_offset(&mut self, dx: i16, dy: i16) {
503        *self.offset = (dx, dy);
504        (self.record.bx, self.record.by) = corner((self.record.rx, self.record.ry), (dx, dy));
505    }
506
507    /// Sets the rectangle the member may not leave, or takes its limits away — see
508    /// [`MemberBuilder::confined_to`].
509    ///
510    /// The room the player has just walked into, an arena closing in, a level that grows. `None`
511    /// is a member let go: free to walk off the map, which is what a bullet or a spent enemy wants.
512    #[inline]
513    pub fn set_confines(&mut self, confines: Option<Bounds>) {
514        match confines {
515            Some(limits) => {
516                self.record.meta |= wire::CONFINED;
517                (self.record.cx, self.record.cy) = (limits.x(), limits.y());
518                (self.record.cw, self.record.ch) = (limits.width(), limits.height());
519            }
520            None => self.record.meta &= !wire::CONFINED,
521        }
522    }
523
524    /// Sets the cell the member wears, or takes it off — see [`MemberBuilder::wearing`].
525    ///
526    /// How an animation is shown: the next frame of a walk cycle, written in the update that took
527    /// the step. Cells carrying the same flags change how the member looks and nothing about what
528    /// everybody meets, which is what a walk cycle wants; a badie that turns into a puff of smoke
529    /// changes both.
530    #[inline]
531    pub fn set_sprite(&mut self, sprite: Option<SpriteId>) {
532        self.record.sprite = match sprite {
533            Some(sprite) => sprite.0 as u16,
534            None => wire::UNWORN,
535        };
536    }
537
538    /// Sets what means *wall* to the member, or hands it back to the scene's word — see
539    /// [`MemberBuilder::stopped_by`].
540    ///
541    /// `None` is the scene's word as it stands now ([`with_solid`](World::with_solid)), and the
542    /// member follows it from here on.
543    #[inline]
544    pub fn set_solid(&mut self, solid: Option<BitFlags<SpriteFlag>>) {
545        let slot = self.id.seat();
546        let word = match solid {
547            Some(solid) => {
548                *self.own_solid |= 1 << slot;
549                solid
550            }
551            None => {
552                *self.own_solid &= !(1 << slot);
553                self.scene_solid
554            }
555        };
556        self.record.solid = word.bits();
557    }
558
559    /// Sets which flags the member cares to be told about — see [`MemberBuilder::heeding`].
560    #[inline]
561    pub fn set_heeds(&mut self, heeds: impl Into<BitFlags<SpriteFlag>>) {
562        self.record.heeds = heeds.into().bits();
563    }
564
565    /// Sets what the member weighs: a crate that fills with water, a ship that burns its fuel off.
566    #[inline]
567    pub fn set_mass(&mut self, mass: f32) {
568        *self.mass = mass;
569    }
570
571    /// Sets which way round the member is drawn — see [`MemberBuilder::flipped`].
572    ///
573    /// The walker turning round: written in the update that turned it, beside the velocity that
574    /// sends it back the way it came.
575    #[inline]
576    pub fn set_flip(&mut self, flip_x: bool, flip_y: bool) {
577        self.record.meta = mirrored(self.record.meta, flip_x, flip_y);
578    }
579
580    /// Sets how many cells the member is drawn from, across and down — see
581    /// [`MemberBuilder::spanning`].
582    ///
583    /// The pose that needs more room than the rest: a sword swung out a cell in front, a stretch a
584    /// cell taller. It is the look alone — the rectangle the member is met by is
585    /// [`resize`](Self::resize)'s, if it changes at all — and a block no sheet holds panics here as
586    /// it does in [`spanning`](MemberBuilder::spanning).
587    #[inline]
588    pub fn set_span(&mut self, width: u8, height: u8) {
589        self.record.span = span_byte(width, height);
590    }
591
592    /// Hides the member, or shows it again — see [`MemberBuilder::hidden`].
593    ///
594    /// The blink: a hero flickering through the frames after a hit is hidden on every other one
595    /// of them, and stepped, met and told on all of them alike.
596    #[inline]
597    pub fn set_hidden(&mut self, hidden: bool) {
598        if hidden {
599            self.record.meta |= wire::HIDDEN;
600        } else {
601            self.record.meta &= !wire::HIDDEN;
602        }
603    }
604
605    /// Empties the member's seat: it leaves the cast, and the id to it goes stale on the spot.
606    ///
607    /// What a cart does with a bullet that has left the screen, a badie that has been stomped, an
608    /// explosion that has burned out. The seat is the next one [`enlist`](MemberBuilder::enlist)
609    /// fills, and until it is filled it is nothing to anybody — the step carries it as a prop
610    /// covering no pixels, which nothing meets and no force reaches.
611    ///
612    /// The member is gone the moment this returns, so the meeting it died of has already been
613    /// reported to whoever it met: the whole cast is stepped where it stands, and nothing is
614    /// waiting on a picture of it.
615    ///
616    /// Retiring a member twice, or asking the world to borrow it afterwards, is a bug in the cart
617    /// and panics saying so.
618    #[inline]
619    pub fn retire(self) {
620        let slot = self.id.seat();
621        *self.seated &= !(1 << slot);
622        *self.own_solid &= !(1 << slot);
623        *self.record = wire::VACANT;
624        *self.mass = 1.0;
625        *self.offset = (0, 0);
626        // Even again, like every empty seat's count, so no id handed out answers to it.
627        *self.generation = self.generation.wrapping_add(1);
628    }
629
630    /// This member, read rather than changed — what every getter above answers through.
631    #[inline]
632    fn read(&self) -> Member<'_> {
633        Member {
634            id: self.id,
635            record: self.record,
636            mass: *self.mass,
637            own_solid: *self.own_solid & (1 << self.id.seat()) != 0,
638        }
639    }
640}
641
642/// The member as [`Member`] prints it, wrapped in `MemberMut(..)`.
643impl fmt::Debug for MemberMut<'_> {
644    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
645        f.debug_tuple("MemberMut").field(&self.read()).finish()
646    }
647}
648
649/// A member described and not yet seated: everything it will be, kept here until
650/// [`enlist`](Self::enlist) takes a seat and writes the whole of it in at once.
651///
652/// [`Member::builder`] starts one at (`x`, `y`), covering `width` x `height` pixels from the
653/// pixel it will draw at: standing still, wearing nothing, stopped by whatever the scene calls
654/// solid, told about everything it meets, free to walk off the map and of the weight nobody has
655/// to think about — a whole member already, in everything but a seat. Every builder here says one
656/// more thing about it, and [`enlist`](Self::enlist) closes the description, claims the lowest
657/// empty seat and hands back the [`MemberId`].
658///
659/// `Copy`, so one description seats as many members as a cart enlists from it: a gun that fires
660/// the same shot over and over keeps one description and enlists a fresh bullet from it every
661/// time.
662///
663/// ```no_run
664/// # use pixel8::{physics::{Bounds, Member, World}, SpriteId};
665/// # const BADIE_SPRITE: SpriteId = SpriteId(6);
666/// # const LEVEL: Bounds = Bounds::new(0, 0, 256, 128);
667/// # fn f(world: &mut World<4>) {
668/// // The badie: one sprite's worth of it, wearing the cell its flag is written on, patrolling
669/// // inside the level and never let out of it.
670/// let badie = Member::builder(200.0, 104.0, 8, 8)
671///     .wearing(BADIE_SPRITE)
672///     .confined_to(LEVEL)
673///     .enlist(world)
674///     .expect("a seat for the badie");
675/// # }
676/// ```
677#[derive(Clone, Copy)]
678#[must_use = "a member described and never enlisted is nobody: `enlist` seats it"]
679pub struct MemberBuilder {
680    /// Everything a step or a draw reads out of a seat, built up field by field and written into
681    /// the seat whole, in one move, when [`enlist`](Self::enlist) claims one.
682    record: wire::Record,
683    /// The member's own rule for what means *wall* to it, if [`stopped_by`](Self::stopped_by)
684    /// gave one — `None` until then, which is what asks the scene's word at the moment it is
685    /// enlisted.
686    solid: Option<BitFlags<SpriteFlag>>,
687    /// What the member will weigh — see [`weighing`](Self::weighing).
688    mass: f32,
689    /// Where the member's rectangle will sit relative to the pixel it draws at — see
690    /// [`offset`](Self::offset).
691    offset: (i16, i16),
692}
693
694impl MemberBuilder {
695    /// The same member, already travelling.
696    ///
697    /// Standing still is the default, and what most members want: a velocity is written afresh
698    /// every update with [`set_velocity`](MemberMut::set_velocity), out of the buttons or a patrol
699    /// or whatever else the cart is thinking.
700    pub fn moving(mut self, dx: f32, dy: f32) -> Self {
701        (self.record.dx, self.record.dy) = (dx, dy);
702
703        self
704    }
705
706    /// The same member, wearing `sprite`: the cell whose flags everybody else meets in it, and the
707    /// cell the world draws it from.
708    ///
709    /// The other side of [`stopped_by`](Self::stopped_by). That says which flags stop *me*; this
710    /// says which flags I carry, and they are the flags the cart wrote on that cell in the sprite
711    /// editor — the same one vocabulary the map's tiles already speak. So a badie is a badie
712    /// because its cell is flagged `BADIE`, and everything that meets it is told `BADIE` in
713    /// [`Contacts::touched`](super::Contacts::touched).
714    ///
715    /// It is the member's look as well: [`World::draw`] draws it from this very cell, mirrored or
716    /// spanning more cells as the builders below say, so what is drawn and what is met are one
717    /// sprite.
718    ///
719    /// Wearing nothing — the default — is a member nobody is stopped by, nobody is told about, and
720    /// nobody sees. It is still stopped by everything, and still told everything: a sensor needs no
721    /// flag of its own. A member whose look changes with its state changes what it wears with
722    /// [`set_sprite`](MemberMut::set_sprite); two walk-cycle cells carrying the same flag change
723    /// nothing anybody meets, which is the usual case.
724    pub fn wearing(mut self, sprite: SpriteId) -> Self {
725        self.record.sprite = sprite.0 as u16;
726
727        self
728    }
729
730    /// The same member, drawn mirrored: across for `flip_x`, and up and down for `flip_y`.
731    ///
732    /// Which way a member faces is part of how it looks, and the world [draws](World::draw) it
733    /// that way. Nothing about the step reads it: the rectangle stays where it is, and the flags
734    /// everybody else meets are the worn cell's either way round. A member that turns as it walks
735    /// turns with [`set_flip`](MemberMut::set_flip), in the update that turned it.
736    pub fn flipped(mut self, flip_x: bool, flip_y: bool) -> Self {
737        self.record.meta = mirrored(self.record.meta, flip_x, flip_y);
738
739        self
740    }
741
742    /// The same member, drawn from a block of `width` x `height` cells of the sheet, with the cell
743    /// it wears at its top left.
744    ///
745    /// The way [`Graphics::sprite_ext`](crate::Graphics::sprite_ext) draws a block: a 16x16 hero is
746    /// `spanning(2, 2)`, wearing the cell at the top left of its picture. One cell is the default,
747    /// and what most of a cast is drawn from.
748    ///
749    /// It is the look and only the look. The rectangle the member is stepped by is still the one
750    /// [`Member::builder`] gave it, and what everybody meets in it is still the flags of the one
751    /// cell it wears.
752    ///
753    /// One to sixteen cells each way, the sheet being sixteen cells across; anything else is a bug
754    /// in the cart, and panics saying so.
755    pub fn spanning(mut self, width: u8, height: u8) -> Self {
756        self.record.span = span_byte(width, height);
757
758        self
759    }
760
761    /// The same member, with rules of its own about what means *wall* to it.
762    ///
763    /// The scene's word — [`World::with_solid`] — is what a member is stopped by unless it says
764    /// otherwise here, and most of a cast never says otherwise, because what is a wall is usually a
765    /// fact about the scene rather than about anybody in it. What is said here *replaces* the
766    /// scene's word for this member alone, and the emptiest rule of all — `BitFlags::empty()` — is
767    /// a member nothing anywhere stops, whatever the scene declares: a bullet, a bird, anything a
768    /// cart wants told about the world rather than stopped by it.
769    ///
770    /// A member's *own* kind belongs here as readily as anything else, and is the usual reason to
771    /// have rules of one's own at all: the world knows who is who and never asks a member about
772    /// itself, so two crates wearing `CRATE`, each with `CRATE` solid to it, block each other and
773    /// neither is ever its own wall.
774    ///
775    /// ```no_run
776    /// # use pixel8::{physics::Member, SpriteFlag, SpriteId};
777    /// # const SOLID: SpriteFlag = SpriteFlag::Flag0;
778    /// # const CRATE: SpriteFlag = SpriteFlag::Flag1;
779    /// # const CRATE_SPRITE: SpriteId = SpriteId(9);
780    /// # fn f(world: &mut pixel8::physics::World<4>) {
781    /// // The walls stop a crate like they stop everybody — and so does another crate.
782    /// let crated = Member::builder(0.0, 0.0, 8, 8)
783    ///     .wearing(CRATE_SPRITE)
784    ///     .stopped_by(SOLID | CRATE)
785    ///     .enlist(world)
786    ///     .expect("a seat for the crate");
787    /// # }
788    /// ```
789    pub fn stopped_by(mut self, solid: impl Into<BitFlags<SpriteFlag>>) -> Self {
790        self.solid = Some(solid.into());
791
792        self
793    }
794
795    /// The same member, told about `heeds` and nothing else.
796    ///
797    /// [`stopped_by`](Self::stopped_by) says what stops the member; this says what it wants to hear
798    /// about, and everything else the world meets on its behalf it throws away without ever working
799    /// out whether it was met. Everything is the default, and it is the honest one — a member that
800    /// has not said otherwise is told about every flag it meets.
801    ///
802    /// Narrowing it is a promise the cart makes and the world takes at its word: a neighbour
803    /// carrying nothing this member heeds is skipped before a single edge of it is worked out, and
804    /// a tile's flags are dropped before they are collected. In a scene where everything is in one
805    /// cast that is most of the work of an update, and it is spent on answers nobody was going to
806    /// read.
807    ///
808    /// It cannot cost a member a wall: solid is heeded whatever this says, so a wall it never asked
809    /// to hear about still stops it, and being stopped by it still reports it.
810    pub fn heeding(mut self, heeds: impl Into<BitFlags<SpriteFlag>>) -> Self {
811        self.record.heeds = heeds.into().bits();
812
813        self
814    }
815
816    /// The same member, never let out of `confines`.
817    ///
818    /// The edge of the world, which is not a wall and is nowhere on the map: nothing else stops a
819    /// member walking off the last tile and falling for ever.
820    /// [`Bounds::screen`](super::Bounds::screen) is what most carts that want one mean; a level
821    /// bigger than the screen hands over the level. The sides it is held at arrive in the same
822    /// [`Contacts`](super::Contacts) as the walls, so a hold at the bottom of the level reads
823    /// [`below`](super::Contacts::below) as a floor tile does.
824    ///
825    /// Saying nothing — the default — is a member free to leave, which is what a bullet or a spent
826    /// enemy wants: it walks off the map, and the cart retires it when
827    /// [`Bounds::on_screen`](super::Bounds::on_screen) says it has gone. A room the player walks
828    /// into changes the limits with [`set_confines`](MemberMut::set_confines).
829    pub fn confined_to(mut self, confines: Bounds) -> Self {
830        self.record.meta |= wire::CONFINED;
831        (self.record.cx, self.record.cy) = (confines.x(), confines.y());
832        (self.record.cw, self.record.ch) = (confines.width(), confines.height());
833
834        self
835    }
836
837    /// The same member, with its rectangle sitting `dx`, `dy` pixels from the pixel it draws at.
838    ///
839    /// For a hurtbox narrower than the sprite: the member is drawn from one corner and judged from
840    /// another. The rectangle keeps that seat on the body wherever the step carries it, so what
841    /// stops the member stops the rectangle, exactly where a cart drew it.
842    ///
843    /// `(0, 0)` — the default — is the rectangle over the sprite, which is what most of a cast
844    /// wants.
845    pub fn offset(mut self, dx: i16, dy: i16) -> Self {
846        self.offset = (dx, dy);
847        (self.record.bx, self.record.by) = corner((self.record.rx, self.record.ry), (dx, dy));
848
849        self
850    }
851
852    /// The same member as a prop: in the cast to be met, never to be moved.
853    ///
854    /// A prop stands in everybody's way exactly as any member does — the rectangle it covers and
855    /// the flags on the cell it wears — and is otherwise left alone: no force reaches it, nothing
856    /// resolves it, and its contacts are never written. The cart drives it wherever it likes, on
857    /// whatever rails it likes, with [`set_pos`](MemberMut::set_pos) before the world steps. A
858    /// hazard patrolling a fixed beat, a lift on a track, a door: things the world must know about
859    /// without being asked to drive them. It is [drawn](World::draw) like anybody else, wherever
860    /// the cart last put it.
861    pub fn prop(mut self) -> Self {
862        self.record.meta |= wire::PROP;
863
864        self
865    }
866
867    /// The same member, hidden: in the cast to be met, never to be drawn.
868    ///
869    /// The other half of what a [prop](Self::prop) is: a prop is met and never moved, and a hidden
870    /// member is met and never drawn. It is in the cast like anybody else — stepped, stopping
871    /// whoever its cell is a wall to, and told what it meets — and [`World::draw`] leaves it off
872    /// the screen. An invisible wall, a trigger wearing a flagged cell, a hero blinking through the
873    /// frames after a hit, with [`set_hidden`](MemberMut::set_hidden) on every other one of them.
874    pub fn hidden(mut self) -> Self {
875        self.record.meta |= wire::HIDDEN;
876
877        self
878    }
879
880    /// The same member, weighing `mass`.
881    ///
882    /// How hard it is to push, relative to everything else in the scene: `1.0` is the default
883    /// nobody has to think about, `4.0` takes four times the shove for the same movement and `0.25`
884    /// a quarter of it. What to make of it is each [`Force`]'s business — [`Wind`](super::Wind) and
885    /// [`Atmosphere`](super::Atmosphere) divide their grip by it, and [`Gravity`](super::Gravity)
886    /// never reads it at all. See the [module docs](super#mass).
887    pub fn weighing(mut self, mass: f32) -> Self {
888        self.mass = mass;
889
890        self
891    }
892
893    /// Closes the description, claims the lowest empty seat of `world`'s cast for it — standing
894    /// where it was described to, covering the rectangle it was given, and looking and answering
895    /// exactly as it was told to — and hands back the [`MemberId`] the cart asks the world about
896    /// the seat with.
897    ///
898    /// The lowest empty seat, always: a cast seated once, in the order the scene works, keeps that
899    /// order — and it is the order the step goes in, so a lift enlisted before its rider carries it
900    /// the same update. A seat freed by [`retire`](MemberMut::retire) is the next one filled.
901    ///
902    /// `None` is a full house: all `N` seats are taken, and the scene has to make room before it
903    /// can take anybody else on. A cart that spawns as it goes — bullets, sparks — either sizes `N`
904    /// for its worst frame or takes `None` as *not this frame*.
905    #[must_use = "a seat with no id kept to it can never be retired"]
906    pub fn enlist<const N: usize, F>(self, world: &mut World<N, F>) -> Option<MemberId>
907    where
908        F: Force,
909    {
910        world.claim(self.record, self.solid, self.mass, self.offset)
911    }
912}