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}