@waica/behaviors 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  import { Component, type Entity } from '@waica/engine';
2
2
  /**
3
- * Collected when the entity with the player role touches
4
- * it: adds its value to a stat, fires onCollect and destroys itself.
5
- * Requires Hitbox on both entities.
3
+ * Collected when its Hitbox mask dispatches an overlap: adds its value to a
4
+ * stat, fires onCollect and destroys itself. Shipped collectibles target the
5
+ * `player` layer; this handler deliberately does not recheck identity.
6
6
  */
7
7
  export declare class Collectible extends Component {
8
8
  static componentName: string;
@@ -22,5 +22,5 @@ export declare class Collectible extends Component {
22
22
  /** Stat receiving the value ('' collects without counting anywhere). */
23
23
  stat: string;
24
24
  onCollect?: (value: number) => void;
25
- onCollide(other: Entity): void;
25
+ onCollide(_other: Entity): void;
26
26
  }
@@ -1,9 +1,8 @@
1
1
  import { Component } from '@waica/engine';
2
- import { isPlayer } from './player-identity.js';
3
2
  /**
4
- * Collected when the entity with the player role touches
5
- * it: adds its value to a stat, fires onCollect and destroys itself.
6
- * Requires Hitbox on both entities.
3
+ * Collected when its Hitbox mask dispatches an overlap: adds its value to a
4
+ * stat, fires onCollect and destroys itself. Shipped collectibles target the
5
+ * `player` layer; this handler deliberately does not recheck identity.
7
6
  */
8
7
  export class Collectible extends Component {
9
8
  static componentName = 'Collectible';
@@ -15,9 +14,7 @@ export class Collectible extends Component {
15
14
  /** Stat receiving the value ('' collects without counting anywhere). */
16
15
  stat = 'points';
17
16
  onCollect;
18
- onCollide(other) {
19
- if (!isPlayer(other))
20
- return;
17
+ onCollide(_other) {
21
18
  this.onCollect?.(this.value);
22
19
  if (this.stat)
23
20
  this.game.stats.add(this.stat, this.value);
package/dist/hazard.d.ts CHANGED
@@ -7,8 +7,9 @@ export type HazardTouch = 'stomp' | 'hurt';
7
7
  */
8
8
  export declare function resolveHazardTouch(playerVy: number, playerBottom: number, hazardY: number, stompable: boolean): HazardTouch;
9
9
  /**
10
- * Hurts the player on contact. If stompable (Mario-style), stomping it
11
- * bounces the player. Requires Hitbox on both entities.
10
+ * Hurts the Entity delivered by its Hitbox mask. Shipped hazards target the
11
+ * `player` layer; this handler deliberately does not recheck identity. If
12
+ * stompable (Mario-style), stomping it bounces the target.
12
13
  *
13
14
  * "Hurts on touch" is all this means — being able to take a hit is Health's
14
15
  * job, so a spike is a Hazard alone and an enemy is Hazard + Health. Either
package/dist/hazard.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { Component } from '@waica/engine';
2
2
  import { Health } from './health.js';
3
3
  import { PlatformerMotor } from './platformer-motor.js';
4
- import { isPlayer } from './player-identity.js';
5
4
  import { Respawnable } from './respawnable.js';
6
5
  /**
7
6
  * Touching a hazard by stomping it from above (falling, with the feet
@@ -14,8 +13,9 @@ export function resolveHazardTouch(playerVy, playerBottom, hazardY, stompable) {
14
13
  return 'hurt';
15
14
  }
16
15
  /**
17
- * Hurts the player on contact. If stompable (Mario-style), stomping it
18
- * bounces the player. Requires Hitbox on both entities.
16
+ * Hurts the Entity delivered by its Hitbox mask. Shipped hazards target the
17
+ * `player` layer; this handler deliberately does not recheck identity. If
18
+ * stompable (Mario-style), stomping it bounces the target.
19
19
  *
20
20
  * "Hurts on touch" is all this means — being able to take a hit is Health's
21
21
  * job, so a spike is a Hazard alone and an enemy is Hazard + Health. Either
@@ -57,8 +57,6 @@ export class Hazard extends Component {
57
57
  */
58
58
  bouncing = false;
59
59
  onCollide(other) {
60
- if (!isPlayer(other))
61
- return;
62
60
  const motor = other.get(PlatformerMotor);
63
61
  if (motor) {
64
62
  const playerBottom = other.position.y - motor.hitboxHeight / 2;
@@ -56,26 +56,16 @@ export function fireInteract(target, initiator) {
56
56
  * radius hides it again. Nearest one wins when several are in range.
57
57
  */
58
58
  export function interactUpdate({ entity, game }) {
59
- let nearest = null;
60
- let nearestEntity = null;
61
- let nearestDistance = Infinity;
62
- for (const other of game.entities) {
63
- if (other === entity)
64
- continue;
65
- const interactable = other.get(Interactable);
66
- if (!interactable)
67
- continue;
68
- const distance = Math.hypot(other.position.x - entity.position.x, other.position.y - entity.position.y);
69
- if (distance <= interactable.radius && distance < nearestDistance) {
70
- nearest = interactable;
71
- nearestEntity = other;
72
- nearestDistance = distance;
73
- }
74
- }
75
- if (!nearest || !nearestEntity) {
59
+ const nearestEntity = game.query.nearest(entity.position.x, entity.position.y, {
60
+ with: [Interactable],
61
+ exclude: entity,
62
+ where: (candidate, { distance }) => distance <= candidate.get(Interactable).radius,
63
+ });
64
+ if (!nearestEntity) {
76
65
  game.ui.hide(INTERACTABLE_UI_PIECE);
77
66
  return;
78
67
  }
68
+ const nearest = nearestEntity.get(Interactable);
79
69
  if (game.input.justPressed('interact') && !game.input.consumed('interact')) {
80
70
  // The press is spent: an input:interact edge needs a NEW press.
81
71
  game.input.consume('interact');
@@ -1,4 +1,4 @@
1
- import { Component, Hitbox, collisionOverlap, } from '@waica/engine';
1
+ import { Component, Hitbox, } from '@waica/engine';
2
2
  import { logicalDirection } from './facing.js';
3
3
  import { Health } from './health.js';
4
4
  /**
@@ -43,25 +43,16 @@ export class MeleeAttack extends Component {
43
43
  this.game.audio.play(this.swingSound, { at: this.entity });
44
44
  const area = this.strikeArea(direction.x, direction.y);
45
45
  const struck = [];
46
- // A copy: a target with no death-handling graph is destroyed on the spot,
47
- // which splices it out of the live array mid-loop (Game does the same).
48
- for (const other of [...this.game.entities]) {
49
- if (other === this.entity || !other.alive)
46
+ const targets = this.game.query.area(area, {
47
+ with: [Health],
48
+ exclude: this.entity,
49
+ });
50
+ for (const other of targets) {
51
+ // Query results are eager live references: an earlier target can destroy
52
+ // a later one while this strike is still consuming its snapshot.
53
+ if (!other.alive)
50
54
  continue;
51
- const hitbox = other.get(Hitbox);
52
55
  const health = other.get(Health);
53
- if (!hitbox || !health)
54
- continue;
55
- const body = {
56
- x: other.position.x + hitbox.offsetX,
57
- y: other.position.y + hitbox.offsetY,
58
- width: hitbox.width,
59
- height: hitbox.height,
60
- shape: hitbox.shape,
61
- points: hitbox.points,
62
- };
63
- if (!collisionOverlap(area, body))
64
- continue;
65
56
  const before = health.current;
66
57
  health.damage(this.damage, this.entity);
67
58
  if (health.current < before)
@@ -6,9 +6,10 @@ import { Component, type Entity } from '@waica/engine';
6
6
  * destination: the incoming scene places its own Player wherever that
7
7
  * scene authored it (no named entry points).
8
8
  *
9
- * With trigger:'overlap' (the default) it fires like Collectible/Hazard:
10
- * the player's Hitbox overlapping its own requires a sibling Hitbox.
11
- * With trigger:'interact' it implements no radius or prompt of its own:
9
+ * With trigger:'overlap' (the default) it fires whenever its Hitbox mask
10
+ * dispatches an overlap. Shipped doors target the `player` layer; this handler
11
+ * deliberately does not recheck identity. With trigger:'interact' it implements
12
+ * no radius or prompt of its own:
12
13
  * it needs a sibling Interactable and fires from the shared nearest-wins
13
14
  * interact scan (interactable.ts's fireInteract), so a door and an NPC in
14
15
  * range arbitrate themselves with no new rule.
@@ -28,7 +29,7 @@ export declare class SceneTransition extends Component {
28
29
  scene: string;
29
30
  trigger: 'overlap' | 'interact';
30
31
  onReady(): void;
31
- onCollide(other: Entity): void;
32
+ onCollide(_other: Entity): void;
32
33
  onInteract(_initiator: Entity): void;
33
34
  private fire;
34
35
  }
@@ -1,6 +1,5 @@
1
1
  import { Component } from '@waica/engine';
2
2
  import { Interactable } from './interactable.js';
3
- import { isPlayer } from './player-identity.js';
4
3
  /**
5
4
  * Replaces the live scene with `scene` (its file's stem, e.g.
6
5
  * "cave.scene.json" -> "cave") when fired — a door, or any entity a player
@@ -8,9 +7,10 @@ import { isPlayer } from './player-identity.js';
8
7
  * destination: the incoming scene places its own Player wherever that
9
8
  * scene authored it (no named entry points).
10
9
  *
11
- * With trigger:'overlap' (the default) it fires like Collectible/Hazard:
12
- * the player's Hitbox overlapping its own requires a sibling Hitbox.
13
- * With trigger:'interact' it implements no radius or prompt of its own:
10
+ * With trigger:'overlap' (the default) it fires whenever its Hitbox mask
11
+ * dispatches an overlap. Shipped doors target the `player` layer; this handler
12
+ * deliberately does not recheck identity. With trigger:'interact' it implements
13
+ * no radius or prompt of its own:
14
14
  * it needs a sibling Interactable and fires from the shared nearest-wins
15
15
  * interact scan (interactable.ts's fireInteract), so a door and an NPC in
16
16
  * range arbitrate themselves with no new rule.
@@ -30,11 +30,9 @@ export class SceneTransition extends Component {
30
30
  'sibling Interactable; it will never fire.');
31
31
  }
32
32
  }
33
- onCollide(other) {
33
+ onCollide(_other) {
34
34
  if (this.trigger !== 'overlap')
35
35
  return;
36
- if (!isPlayer(other))
37
- return;
38
36
  this.fire();
39
37
  }
40
38
  onInteract(_initiator) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waica/behaviors",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Waica built-in behaviors — the curated game-feel library",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,6 +19,6 @@
19
19
  }
20
20
  },
21
21
  "dependencies": {
22
- "@waica/engine": "^0.14.0"
22
+ "@waica/engine": "^0.15.0"
23
23
  }
24
24
  }