@waica/behaviors 0.12.0 → 0.14.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.
package/dist/health.d.ts CHANGED
@@ -45,6 +45,10 @@ export declare class Health extends Component {
45
45
  label: string;
46
46
  ref: 'stat';
47
47
  };
48
+ hurtSound: {
49
+ label: string;
50
+ ref: 'sound';
51
+ };
48
52
  };
49
53
  static transient: string[];
50
54
  max: number;
@@ -55,6 +59,8 @@ export declare class Health extends Component {
55
59
  * publishes nothing: the stat is the project's to declare.
56
60
  */
57
61
  stat: string;
62
+ /** Sound played at this entity's position on every accepted hit. Empty plays nothing (CA-12). */
63
+ hurtSound: string;
58
64
  /** Health left; 0 is dead. Filled in from max on ready. */
59
65
  current: number;
60
66
  /** Whoever dealt the last accepted hit, for the state that reacts to it. */
package/dist/health.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Component, StateMachine } from '@waica/engine';
1
+ import { Component, SIMULATION_TIME_EPSILON, StateMachine, } from '@waica/engine';
2
2
  /**
3
3
  * Below this, current is treated as exactly zero. Repeated fractional
4
4
  * damage (e.g. ten hits of 0.1 against max: 1) can leave a floating-point
@@ -47,6 +47,7 @@ export class Health extends Component {
47
47
  max: { label: 'Max health', min: 1, max: 20, step: 1 },
48
48
  invulnerability: { label: 'Invulnerability', min: 0, max: 5, step: 0.1 },
49
49
  stat: { label: 'Stat', ref: 'stat' },
50
+ hurtSound: { label: 'Hurt sound', ref: 'sound' },
50
51
  };
51
52
  static transient = [
52
53
  'current',
@@ -64,6 +65,8 @@ export class Health extends Component {
64
65
  * publishes nothing: the stat is the project's to declare.
65
66
  */
66
67
  stat = '';
68
+ /** Sound played at this entity's position on every accepted hit. Empty plays nothing (CA-12). */
69
+ hurtSound = '';
67
70
  /** Health left; 0 is dead. Filled in from max on ready. */
68
71
  current = 0;
69
72
  /** Whoever dealt the last accepted hit, for the state that reacts to it. */
@@ -110,6 +113,12 @@ export class Health extends Component {
110
113
  onUpdate(dt) {
111
114
  if (this.invulnerable > 0) {
112
115
  this.invulnerable = Math.max(0, this.invulnerable - dt);
116
+ // this.invulnerable is a countdown of SIMULATION_STEP-sized dts, which
117
+ // float error can leave as a tiny positive residual instead of
118
+ // exactly 0 on the step that should close the window; without this,
119
+ // `> 0` above stays true for one whole extra step.
120
+ if (this.invulnerable <= SIMULATION_TIME_EPSILON)
121
+ this.invulnerable = 0;
113
122
  this.blink(dt);
114
123
  }
115
124
  if (this.deathPending) {
@@ -158,6 +167,8 @@ export class Health extends Component {
158
167
  current: this.current,
159
168
  source,
160
169
  });
170
+ if (this.hurtSound)
171
+ this.game.audio.play(this.hurtSound, { at: this.entity });
161
172
  this.invulnerable = this.invulnerability;
162
173
  this.blinkClock = 0;
163
174
  if (this.current === 0) {
package/dist/lifetime.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Component } from '@waica/engine';
1
+ import { Component, SIMULATION_TIME_EPSILON } from '@waica/engine';
2
2
  /** Destroys its entity after a configurable amount of simulated time. */
3
3
  export class Lifetime extends Component {
4
4
  static componentName = 'Lifetime';
@@ -14,7 +14,10 @@ export class Lifetime extends Component {
14
14
  if (!this.entity.alive)
15
15
  return;
16
16
  this.elapsed += dt;
17
- if (this.elapsed >= this.seconds)
17
+ // this.elapsed is a sum of SIMULATION_STEP-sized dts, which float error
18
+ // can leave a hair under an exact multiple; the epsilon keeps a bare
19
+ // `>=` from waiting one whole step too long to destroy.
20
+ if (this.elapsed + SIMULATION_TIME_EPSILON >= this.seconds)
18
21
  this.entity.destroy();
19
22
  }
20
23
  }
@@ -29,12 +29,18 @@ export declare class MeleeAttack extends Component {
29
29
  max: number;
30
30
  step: number;
31
31
  };
32
+ swingSound: {
33
+ label: string;
34
+ ref: 'sound';
35
+ };
32
36
  };
33
37
  damage: number;
34
38
  /** How far in front of the attacker the blow reaches, in world units. */
35
39
  range: number;
36
40
  /** How wide the blow is, across the facing. */
37
41
  width: number;
42
+ /** Sound played on every swing. Empty plays nothing (CA-12). */
43
+ swingSound: string;
38
44
  /**
39
45
  * Lands one blow along `facing`: every other living entity with a Hitbox
40
46
  * and a Health inside the strike area takes `damage` once, with this
@@ -16,12 +16,15 @@ export class MeleeAttack extends Component {
16
16
  damage: { label: 'Damage', min: 0, max: 20, step: 1 },
17
17
  range: { label: 'Range', min: 0.5, max: 5, step: 0.5 },
18
18
  width: { label: 'Width', min: 0.5, max: 5, step: 0.5 },
19
+ swingSound: { label: 'Swing sound', ref: 'sound' },
19
20
  };
20
21
  damage = 1;
21
22
  /** How far in front of the attacker the blow reaches, in world units. */
22
23
  range = 1;
23
24
  /** How wide the blow is, across the facing. */
24
25
  width = 1;
26
+ /** Sound played on every swing. Empty plays nothing (CA-12). */
27
+ swingSound = '';
25
28
  /**
26
29
  * Lands one blow along `facing`: every other living entity with a Hitbox
27
30
  * and a Health inside the strike area takes `damage` once, with this
@@ -33,6 +36,11 @@ export class MeleeAttack extends Component {
33
36
  const direction = logicalDirection(facing, this.game.projection);
34
37
  if (!direction)
35
38
  return [];
39
+ // Positional like Health's hurtSound (CA-8): an off-camera NPC's swing
40
+ // should attenuate and pan like the hit it causes, not play flat and
41
+ // dead-centre while the damage it lands sounds correctly placed.
42
+ if (this.swingSound)
43
+ this.game.audio.play(this.swingSound, { at: this.entity });
36
44
  const area = this.strikeArea(direction.x, direction.y);
37
45
  const struck = [];
38
46
  // A copy: a target with no death-handling graph is destroyed on the spot,
package/dist/patrol.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Component, installedDirectionalAnimation, projectIsometric, } from '@waica/engine';
1
+ import { Component, installedDirectionalAnimation, projectIsometric, SIMULATION_TIME_EPSILON, } from '@waica/engine';
2
2
  import { facingForInput } from './facing.js';
3
3
  /** Seconds a hit stops the rail. */
4
4
  const HURT_SECONDS = 0.25;
@@ -105,7 +105,10 @@ export const PATROLLER_ROLE = {
105
105
  // Holds the death pose for a beat, then the body goes away for good.
106
106
  dead: {
107
107
  onUpdate({ entity, fsm }) {
108
- if (fsm.elapsed >= DEATH_SECONDS)
108
+ // fsm.elapsed is a sum of SIMULATION_STEP-sized dts, which float
109
+ // error can leave a hair under an exact multiple; the epsilon keeps
110
+ // a bare `>=` from waiting one whole step too long to destroy.
111
+ if (fsm.elapsed + SIMULATION_TIME_EPSILON >= DEATH_SECONDS)
109
112
  entity.destroy();
110
113
  },
111
114
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waica/behaviors",
3
- "version": "0.12.0",
3
+ "version": "0.14.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.12.0"
22
+ "@waica/engine": "^0.14.0"
23
23
  }
24
24
  }