@waica/behaviors 0.15.0 → 0.16.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 +34 -9
- package/dist/health.js +62 -38
- package/package.json +2 -2
package/dist/health.d.ts
CHANGED
|
@@ -65,10 +65,21 @@ export declare class Health extends Component {
|
|
|
65
65
|
current: number;
|
|
66
66
|
/** Whoever dealt the last accepted hit, for the state that reacts to it. */
|
|
67
67
|
lastDamageSource: Entity | undefined;
|
|
68
|
-
/**
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
/**
|
|
69
|
+
* The open invulnerability window, on `game.time`. Null once
|
|
70
|
+
* `closeWindow()` has run. A window ended by owner destroy or scene
|
|
71
|
+
* unload instead of a natural close never runs `closeWindow()` — cancellation
|
|
72
|
+
* runs no callback — so this can also be present but inactive; `blinking`
|
|
73
|
+
* and `inspectState().invulnerable` both read through `.active`/`.remaining`,
|
|
74
|
+
* so that case still reports correctly.
|
|
75
|
+
*/
|
|
76
|
+
private windowHandle;
|
|
77
|
+
/**
|
|
78
|
+
* The 10 Hz blink toggle, on `game.time`. Null once `closeWindow()` has
|
|
79
|
+
* run, same asymmetry as `windowHandle`: a window cancelled by owner
|
|
80
|
+
* destroy or scene unload leaves this present but inactive instead.
|
|
81
|
+
*/
|
|
82
|
+
private blinkHandle;
|
|
72
83
|
/** Whether the node is being flashed: exactly while a window is open. */
|
|
73
84
|
get blinking(): boolean;
|
|
74
85
|
/** Whether the signalled death must be checked on this component's next update. */
|
|
@@ -82,7 +93,7 @@ export declare class Health extends Component {
|
|
|
82
93
|
*/
|
|
83
94
|
inspectState(): Record<string, unknown>;
|
|
84
95
|
onReady(): void;
|
|
85
|
-
onUpdate(
|
|
96
|
+
onUpdate(): void;
|
|
86
97
|
/**
|
|
87
98
|
* Makes good on a death the graph said it would handle. Signalling is
|
|
88
99
|
* fire-and-forget, so "it declared an edge" is a promise, not a receipt:
|
|
@@ -103,11 +114,25 @@ export declare class Health extends Component {
|
|
|
103
114
|
/** Mirrors current into the named stat, when one is named. */
|
|
104
115
|
private publish;
|
|
105
116
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
117
|
+
* Opens a fresh invulnerability window on `game.time`, starting visible:
|
|
118
|
+
* `after(invulnerability)` closes it, and `every(0.1)` flashes the node
|
|
119
|
+
* at 10 Hz for as long as it stays open (CA-11). `invulnerability <= 0`
|
|
120
|
+
* schedules nothing and touches no `game.time` at all — the window never
|
|
121
|
+
* opens. Both are owned by this entity, so `Entity.destroy()` cancels
|
|
122
|
+
* them immediately mid-window.
|
|
123
|
+
*
|
|
124
|
+
* The `invulnerability <= 0` check below runs once, here, before either
|
|
125
|
+
* handle is touched — it is not re-checked later. So lowering
|
|
126
|
+
* `invulnerability` to 0 while a window is already open does not close it
|
|
127
|
+
* early: that window keeps blinking and rejecting damage until it runs
|
|
128
|
+
* its normal course. Deliberate, not a bug: nothing re-reads
|
|
129
|
+
* `invulnerability` once a window is open.
|
|
109
130
|
*/
|
|
110
|
-
private
|
|
131
|
+
private openWindow;
|
|
132
|
+
/** Cancels the blink and restores visibility the moment the window closes. */
|
|
133
|
+
private closeWindow;
|
|
134
|
+
/** The 10 Hz flash while a window is open: openWindow() starts it visible, so a plain toggle lands on the right phase every time. */
|
|
135
|
+
private toggleBlink;
|
|
111
136
|
/**
|
|
112
137
|
* Announces the death, then lets the role decide — but only if it says it
|
|
113
138
|
* can: a graph with no death edge would swallow the signal silently, so
|
package/dist/health.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Component,
|
|
1
|
+
import { Component, 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
|
|
@@ -51,11 +51,11 @@ export class Health extends Component {
|
|
|
51
51
|
};
|
|
52
52
|
static transient = [
|
|
53
53
|
'current',
|
|
54
|
-
'invulnerable',
|
|
55
54
|
'deathPending',
|
|
56
55
|
'expectedStates',
|
|
57
56
|
'lastDamageSource',
|
|
58
|
-
'
|
|
57
|
+
'windowHandle',
|
|
58
|
+
'blinkHandle',
|
|
59
59
|
];
|
|
60
60
|
max = 3;
|
|
61
61
|
/** Seconds of immunity granted by taking a hit. 0 disables i-frames. */
|
|
@@ -71,13 +71,24 @@ export class Health extends Component {
|
|
|
71
71
|
current = 0;
|
|
72
72
|
/** Whoever dealt the last accepted hit, for the state that reacts to it. */
|
|
73
73
|
lastDamageSource;
|
|
74
|
-
/**
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
/**
|
|
75
|
+
* The open invulnerability window, on `game.time`. Null once
|
|
76
|
+
* `closeWindow()` has run. A window ended by owner destroy or scene
|
|
77
|
+
* unload instead of a natural close never runs `closeWindow()` — cancellation
|
|
78
|
+
* runs no callback — so this can also be present but inactive; `blinking`
|
|
79
|
+
* and `inspectState().invulnerable` both read through `.active`/`.remaining`,
|
|
80
|
+
* so that case still reports correctly.
|
|
81
|
+
*/
|
|
82
|
+
windowHandle = null;
|
|
83
|
+
/**
|
|
84
|
+
* The 10 Hz blink toggle, on `game.time`. Null once `closeWindow()` has
|
|
85
|
+
* run, same asymmetry as `windowHandle`: a window cancelled by owner
|
|
86
|
+
* destroy or scene unload leaves this present but inactive instead.
|
|
87
|
+
*/
|
|
88
|
+
blinkHandle = null;
|
|
78
89
|
/** Whether the node is being flashed: exactly while a window is open. */
|
|
79
90
|
get blinking() {
|
|
80
|
-
return this.
|
|
91
|
+
return this.windowHandle?.active ?? false;
|
|
81
92
|
}
|
|
82
93
|
/** Whether the signalled death must be checked on this component's next update. */
|
|
83
94
|
deathPending = false;
|
|
@@ -94,7 +105,7 @@ export class Health extends Component {
|
|
|
94
105
|
invulnerability: this.invulnerability,
|
|
95
106
|
stat: this.stat,
|
|
96
107
|
current: this.current,
|
|
97
|
-
invulnerable: this.
|
|
108
|
+
invulnerable: this.windowHandle?.remaining ?? 0,
|
|
98
109
|
blinking: this.blinking,
|
|
99
110
|
lastDamageSource: this.lastDamageSource?.name ?? null,
|
|
100
111
|
};
|
|
@@ -110,17 +121,9 @@ export class Health extends Component {
|
|
|
110
121
|
if (this.current === 0)
|
|
111
122
|
this.die();
|
|
112
123
|
}
|
|
113
|
-
onUpdate(
|
|
114
|
-
|
|
115
|
-
|
|
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;
|
|
122
|
-
this.blink(dt);
|
|
123
|
-
}
|
|
124
|
+
onUpdate() {
|
|
125
|
+
// The invulnerability window and its blink now run entirely on
|
|
126
|
+
// game.time (ADR 0017, CA-11): this no longer drives either.
|
|
124
127
|
if (this.deathPending) {
|
|
125
128
|
this.deathPending = false;
|
|
126
129
|
this.settleDeath();
|
|
@@ -154,7 +157,7 @@ export class Health extends Component {
|
|
|
154
157
|
// too — every NaN comparison is false, so amount <= 0 lets it through
|
|
155
158
|
// and poisons current (NaN - anything is NaN, and every guard against
|
|
156
159
|
// it is false forever after).
|
|
157
|
-
if (!(amount > 0) || this.current <= 0 || this.
|
|
160
|
+
if (!(amount > 0) || this.current <= 0 || this.blinking)
|
|
158
161
|
return;
|
|
159
162
|
this.current = Math.max(0, this.current - amount);
|
|
160
163
|
if (this.current < DEATH_EPSILON)
|
|
@@ -169,8 +172,7 @@ export class Health extends Component {
|
|
|
169
172
|
});
|
|
170
173
|
if (this.hurtSound)
|
|
171
174
|
this.game.audio.play(this.hurtSound, { at: this.entity });
|
|
172
|
-
this.
|
|
173
|
-
this.blinkClock = 0;
|
|
175
|
+
this.openWindow();
|
|
174
176
|
if (this.current === 0) {
|
|
175
177
|
this.die();
|
|
176
178
|
return;
|
|
@@ -192,31 +194,53 @@ export class Health extends Component {
|
|
|
192
194
|
this.game.stats.set(this.stat, this.current);
|
|
193
195
|
}
|
|
194
196
|
/**
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
197
|
+
* Opens a fresh invulnerability window on `game.time`, starting visible:
|
|
198
|
+
* `after(invulnerability)` closes it, and `every(0.1)` flashes the node
|
|
199
|
+
* at 10 Hz for as long as it stays open (CA-11). `invulnerability <= 0`
|
|
200
|
+
* schedules nothing and touches no `game.time` at all — the window never
|
|
201
|
+
* opens. Both are owned by this entity, so `Entity.destroy()` cancels
|
|
202
|
+
* them immediately mid-window.
|
|
203
|
+
*
|
|
204
|
+
* The `invulnerability <= 0` check below runs once, here, before either
|
|
205
|
+
* handle is touched — it is not re-checked later. So lowering
|
|
206
|
+
* `invulnerability` to 0 while a window is already open does not close it
|
|
207
|
+
* early: that window keeps blinking and rejecting damage until it runs
|
|
208
|
+
* its normal course. Deliberate, not a bug: nothing re-reads
|
|
209
|
+
* `invulnerability` once a window is open.
|
|
198
210
|
*/
|
|
199
|
-
|
|
200
|
-
if (this.
|
|
201
|
-
this.blinkClock += dt;
|
|
202
|
-
this.entity.node.visible = Math.floor(this.blinkClock / BLINK_PERIOD) % 2 === 0;
|
|
211
|
+
openWindow() {
|
|
212
|
+
if (this.invulnerability <= 0)
|
|
203
213
|
return;
|
|
204
|
-
|
|
214
|
+
this.entity.node.visible = true;
|
|
215
|
+
this.windowHandle = this.game.time.after(this.invulnerability, () => this.closeWindow(), { owner: this.entity });
|
|
216
|
+
this.blinkHandle = this.game.time.every(BLINK_PERIOD, () => this.toggleBlink(), { owner: this.entity });
|
|
217
|
+
}
|
|
218
|
+
/** Cancels the blink and restores visibility the moment the window closes. */
|
|
219
|
+
closeWindow() {
|
|
220
|
+
this.blinkHandle?.cancel();
|
|
221
|
+
this.blinkHandle = null;
|
|
222
|
+
this.windowHandle = null;
|
|
205
223
|
this.entity.node.visible = true;
|
|
206
224
|
}
|
|
225
|
+
/** The 10 Hz flash while a window is open: openWindow() starts it visible, so a plain toggle lands on the right phase every time. */
|
|
226
|
+
toggleBlink() {
|
|
227
|
+
this.entity.node.visible = !this.entity.node.visible;
|
|
228
|
+
}
|
|
207
229
|
/**
|
|
208
230
|
* Announces the death, then lets the role decide — but only if it says it
|
|
209
231
|
* can: a graph with no death edge would swallow the signal silently, so
|
|
210
232
|
* destroying is the fallback rather than the exception.
|
|
211
233
|
*/
|
|
212
234
|
die() {
|
|
213
|
-
// The killing blow opened a window like any other hit;
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
this.
|
|
219
|
-
this.
|
|
235
|
+
// The killing blow opened a window like any other hit; cancel it here
|
|
236
|
+
// (running no callback, per game.time's cancel contract) rather than
|
|
237
|
+
// let it run its course. The death pose must hold steady, and a
|
|
238
|
+
// revived entity starts without leftover immunity.
|
|
239
|
+
this.windowHandle?.cancel();
|
|
240
|
+
this.windowHandle = null;
|
|
241
|
+
this.blinkHandle?.cancel();
|
|
242
|
+
this.blinkHandle = null;
|
|
243
|
+
this.entity.node.visible = true;
|
|
220
244
|
this.game.events.emit('death', { entity: this.entity });
|
|
221
245
|
const machine = this.entity.get(StateMachine);
|
|
222
246
|
const targets = machine ? deathTargets(machine.states, machine.current) : [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waica/behaviors",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.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.
|
|
22
|
+
"@waica/engine": "^0.16.0"
|
|
23
23
|
}
|
|
24
24
|
}
|