@auraindustry/aurajs 0.1.3 → 0.1.5
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/README.md +7 -0
- package/benchmarks/perf-thresholds.json +27 -0
- package/package.json +6 -1
- package/src/ai-guidance.mjs +302 -0
- package/src/authored-project.mjs +498 -2
- package/src/build-contract/capabilities.mjs +87 -1
- package/src/build-contract/constants.mjs +1 -0
- package/src/build-contract.mjs +2 -0
- package/src/bundler.mjs +143 -13
- package/src/cli.mjs +681 -13
- package/src/commands/packs.mjs +741 -0
- package/src/commands/project-authoring.mjs +128 -1
- package/src/conformance/cases/app-and-ui-runtime-cases.mjs +1 -2
- package/src/conformance/cases/core-runtime-cases.mjs +6 -2
- package/src/conformance/cases/scene3d-and-media-cases.mjs +238 -0
- package/src/conformance/cases/systems-and-gameplay-cases.mjs +265 -4
- package/src/conformance-mobile.mjs +166 -0
- package/src/conformance.mjs +89 -30
- package/src/evidence-bundle.mjs +242 -0
- package/src/headless-test/runtime-coordinator.mjs +186 -33
- package/src/headless-test.mjs +2 -0
- package/src/helpers/2d/index.mjs +183 -0
- package/src/helpers/index.mjs +26 -0
- package/src/helpers/starter-utils/adventure-objectives.js +102 -0
- package/src/helpers/starter-utils/adventure-world-2d.js +221 -0
- package/src/helpers/starter-utils/animation-2d.js +337 -0
- package/src/helpers/starter-utils/animation-packaging-2d.js +203 -0
- package/src/helpers/starter-utils/atlas-assets-2d.js +111 -0
- package/src/helpers/starter-utils/autoplay-debug-2d.js +215 -0
- package/src/helpers/starter-utils/avatar-3d.js +404 -0
- package/src/helpers/starter-utils/combat-feedback-2d.js +320 -0
- package/src/helpers/starter-utils/combat-runtime-2d.js +290 -0
- package/src/helpers/starter-utils/core.js +150 -0
- package/src/helpers/starter-utils/dialogue-2d.js +351 -0
- package/src/helpers/starter-utils/enemy-archetypes-2d.js +68 -0
- package/src/helpers/starter-utils/index.js +26 -0
- package/src/helpers/starter-utils/inventory-2d.js +268 -0
- package/src/helpers/starter-utils/journal-2d.js +267 -0
- package/src/helpers/starter-utils/platformer-3d.js +132 -0
- package/src/helpers/starter-utils/scene-audio-2d.js +236 -0
- package/src/helpers/starter-utils/streamed-world-2d.js +378 -0
- package/src/helpers/starter-utils/tilemap-nav-2d.js +499 -0
- package/src/helpers/starter-utils/tilemap-world-2d.js +205 -0
- package/src/helpers/starter-utils/triggers.js +662 -0
- package/src/helpers/starter-utils/tween-2d.js +615 -0
- package/src/helpers/starter-utils/wave-director.js +101 -0
- package/src/helpers/starter-utils/world-compositor-2d.js +253 -0
- package/src/helpers/starter-utils/world-persistence-2d.js +180 -0
- package/src/mobile/android/build.mjs +606 -0
- package/src/mobile/android/host-artifact.mjs +280 -0
- package/src/mobile/ios/build.mjs +1323 -0
- package/src/mobile/ios/host-artifact.mjs +819 -0
- package/src/mobile/shared/capabilities.mjs +174 -0
- package/src/packs/catalog.mjs +259 -0
- package/src/perf-benchmark-runner.mjs +17 -12
- package/src/perf-benchmark.mjs +408 -4
- package/src/publish-command.mjs +303 -6
- package/src/replay-runtime.mjs +257 -0
- package/src/scaffold/config.mjs +2 -0
- package/src/scaffold/fs.mjs +8 -1
- package/src/scaffold/project-docs.mjs +43 -1
- package/src/scaffold.mjs +4 -0
- package/src/session-runtime.mjs +4 -3
- package/src/web-conformance.mjs +0 -36
- package/templates/create/2d-adventure/config/gameplay/adventure.config.js +9 -6
- package/templates/create/2d-adventure/content/gameplay/dialogue.js +85 -0
- package/templates/create/2d-adventure/content/gameplay/world.js +32 -36
- package/templates/create/2d-adventure/content/gameplay/world.tilemap.json +273 -0
- package/templates/create/2d-adventure/docs/design/loop.md +4 -3
- package/templates/create/2d-adventure/prefabs/relic.prefab.js +10 -10
- package/templates/create/2d-adventure/prefabs/world.prefab.js +127 -74
- package/templates/create/2d-adventure/scenes/gameplay.scene.js +603 -112
- package/templates/create/2d-adventure/src/runtime/capabilities.js +16 -0
- package/templates/create/2d-adventure/ui/hud.screen.js +187 -4
- package/templates/create/2d-adventure/ui/journal.screen.js +183 -0
- package/templates/create/3d/scenes/gameplay.scene.js +30 -3
- package/templates/create/3d/src/runtime/capabilities.js +5 -0
- package/templates/create/3d/src/runtime/materials.js +10 -0
- package/templates/create/3d-adventure/scenes/gameplay.scene.js +30 -3
- package/templates/create/3d-adventure/src/runtime/capabilities.js +5 -0
- package/templates/create/3d-adventure/src/runtime/materials.js +11 -0
- package/templates/create/3d-collectathon/scenes/gameplay.scene.js +30 -3
- package/templates/create/3d-collectathon/src/runtime/capabilities.js +5 -0
- package/templates/create/3d-collectathon/src/runtime/materials.js +10 -0
- package/templates/create/shared/src/runtime/ui-forms.js +552 -0
- package/templates/create/shared/src/starter-utils/adventure-world-2d.js +221 -0
- package/templates/create/shared/src/starter-utils/animation-packaging-2d.js +203 -0
- package/templates/create/shared/src/starter-utils/atlas-assets-2d.js +111 -0
- package/templates/create/shared/src/starter-utils/autoplay-debug-2d.js +215 -0
- package/templates/create/shared/src/starter-utils/combat-runtime-2d.js +290 -0
- package/templates/create/shared/src/starter-utils/dialogue-2d.js +351 -0
- package/templates/create/shared/src/starter-utils/index.js +15 -1
- package/templates/create/shared/src/starter-utils/inventory-2d.js +268 -0
- package/templates/create/shared/src/starter-utils/journal-2d.js +267 -0
- package/templates/create/shared/src/starter-utils/scene-audio-2d.js +236 -0
- package/templates/create/shared/src/starter-utils/streamed-world-2d.js +378 -0
- package/templates/create/shared/src/starter-utils/tilemap-nav-2d.js +499 -0
- package/templates/create/shared/src/starter-utils/tilemap-world-2d.js +205 -0
- package/templates/create/shared/src/starter-utils/world-compositor-2d.js +253 -0
- package/templates/create/shared/src/starter-utils/world-persistence-2d.js +180 -0
- package/templates/create-bin/play.js +36 -7
- package/templates/skills/auramaxx/SKILL.md +46 -0
- package/templates/skills/auramaxx/project-requirements.md +68 -0
- package/templates/skills/auramaxx/starter-recipes.md +104 -0
- package/templates/skills/auramaxx/validation-checklist.md +49 -0
- package/templates/skills/aurajs/SKILL.md +0 -96
- package/templates/skills/aurajs/api-contract-3d.md +0 -7
- package/templates/skills/aurajs/api-contract.md +0 -7
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
function finite(value, fallback = 0) {
|
|
2
|
+
const numeric = Number(value);
|
|
3
|
+
return Number.isFinite(numeric) ? numeric : fallback;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function keyName(value, fallback) {
|
|
7
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : fallback;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function clamp(value, min, max) {
|
|
11
|
+
return Math.max(min, Math.min(max, value));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeAutoplayAxis(axis) {
|
|
15
|
+
return {
|
|
16
|
+
x: clamp(finite(axis?.x, 0), -1, 1),
|
|
17
|
+
y: clamp(finite(axis?.y, 0), -1, 1),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeAutoplayControl(control) {
|
|
22
|
+
return {
|
|
23
|
+
axis: normalizeAutoplayAxis(control?.axis),
|
|
24
|
+
aimAngle: Number.isFinite(Number(control?.aimAngle)) ? Number(control.aimAngle) : null,
|
|
25
|
+
fire: control?.fire === true,
|
|
26
|
+
dash: control?.dash === true,
|
|
27
|
+
extract: control?.extract === true,
|
|
28
|
+
weaponIndex: Number.isFinite(Number(control?.weaponIndex))
|
|
29
|
+
? Math.max(0, Math.floor(Number(control.weaponIndex)))
|
|
30
|
+
: null,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createAutoplayController2D(options = {}) {
|
|
35
|
+
return {
|
|
36
|
+
enabled: options.enabled === true,
|
|
37
|
+
frameCount: Math.max(0, Math.floor(finite(options.frameCount, 0))),
|
|
38
|
+
lastReasonCode: keyName(options.lastReasonCode, 'autoplay_idle'),
|
|
39
|
+
lastControl: options.lastControl ? normalizeAutoplayControl(options.lastControl) : null,
|
|
40
|
+
buildControl: typeof options.buildControl === 'function' ? options.buildControl : null,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function setAutoplayEnabled2D(controller, enabled = true) {
|
|
45
|
+
if (!controller || typeof controller !== 'object') {
|
|
46
|
+
throw new Error('autoplay controller is required.');
|
|
47
|
+
}
|
|
48
|
+
controller.enabled = enabled === true;
|
|
49
|
+
controller.lastReasonCode = controller.enabled ? 'autoplay_enabled' : 'autoplay_disabled';
|
|
50
|
+
return {
|
|
51
|
+
ok: true,
|
|
52
|
+
enabled: controller.enabled,
|
|
53
|
+
reasonCode: controller.lastReasonCode,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function toggleAutoplayEnabled2D(controller) {
|
|
58
|
+
if (!controller || typeof controller !== 'object') {
|
|
59
|
+
throw new Error('autoplay controller is required.');
|
|
60
|
+
}
|
|
61
|
+
return setAutoplayEnabled2D(controller, controller.enabled !== true);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function resolveAutoplayControl2D(controller, context = {}, buildControl = null) {
|
|
65
|
+
if (!controller || typeof controller !== 'object') {
|
|
66
|
+
throw new Error('autoplay controller is required.');
|
|
67
|
+
}
|
|
68
|
+
if (controller.enabled !== true) {
|
|
69
|
+
controller.lastReasonCode = 'autoplay_disabled';
|
|
70
|
+
return {
|
|
71
|
+
ok: true,
|
|
72
|
+
enabled: false,
|
|
73
|
+
reasonCode: controller.lastReasonCode,
|
|
74
|
+
frameCount: controller.frameCount,
|
|
75
|
+
control: null,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const builder = typeof buildControl === 'function' ? buildControl : controller.buildControl;
|
|
80
|
+
if (typeof builder !== 'function') {
|
|
81
|
+
controller.lastReasonCode = 'autoplay_builder_missing';
|
|
82
|
+
return {
|
|
83
|
+
ok: false,
|
|
84
|
+
enabled: true,
|
|
85
|
+
reasonCode: controller.lastReasonCode,
|
|
86
|
+
frameCount: controller.frameCount,
|
|
87
|
+
control: null,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
controller.frameCount += 1;
|
|
93
|
+
const control = normalizeAutoplayControl(builder(context, controller) || {});
|
|
94
|
+
controller.lastControl = control;
|
|
95
|
+
controller.lastReasonCode = 'autoplay_control_ready';
|
|
96
|
+
return {
|
|
97
|
+
ok: true,
|
|
98
|
+
enabled: true,
|
|
99
|
+
reasonCode: controller.lastReasonCode,
|
|
100
|
+
frameCount: controller.frameCount,
|
|
101
|
+
control,
|
|
102
|
+
};
|
|
103
|
+
} catch (error) {
|
|
104
|
+
controller.lastReasonCode = 'autoplay_builder_failed';
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
enabled: true,
|
|
108
|
+
reasonCode: controller.lastReasonCode,
|
|
109
|
+
frameCount: controller.frameCount,
|
|
110
|
+
control: controller.lastControl,
|
|
111
|
+
error: String(error && error.message ? error.message : error),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function createRenderTargetExportState2D(options = {}) {
|
|
117
|
+
return {
|
|
118
|
+
queued: options.queued === true,
|
|
119
|
+
path: keyName(options.path, 'aura-export.png'),
|
|
120
|
+
lastExportReason: keyName(options.lastExportReason, 'draw2d_export_idle'),
|
|
121
|
+
lastExportOk: options.lastExportOk !== false,
|
|
122
|
+
exportCount: Math.max(0, Math.floor(finite(options.exportCount, 0))),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function queueRenderTargetExport2D(exportState, options = {}) {
|
|
127
|
+
if (!exportState || typeof exportState !== 'object') {
|
|
128
|
+
throw new Error('render target export state is required.');
|
|
129
|
+
}
|
|
130
|
+
exportState.queued = true;
|
|
131
|
+
exportState.path = keyName(options.path, exportState.path || 'aura-export.png');
|
|
132
|
+
return {
|
|
133
|
+
ok: true,
|
|
134
|
+
queued: true,
|
|
135
|
+
path: exportState.path,
|
|
136
|
+
reasonCode: 'draw2d_render_target_export_queued',
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function flushRenderTargetExport2D(exportState, renderTarget, aura, options = {}) {
|
|
141
|
+
if (!exportState || typeof exportState !== 'object') {
|
|
142
|
+
throw new Error('render target export state is required.');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const path = keyName(options.path, exportState.path || 'aura-export.png');
|
|
146
|
+
exportState.path = path;
|
|
147
|
+
|
|
148
|
+
if (exportState.queued !== true) {
|
|
149
|
+
return {
|
|
150
|
+
ok: true,
|
|
151
|
+
exported: false,
|
|
152
|
+
queued: false,
|
|
153
|
+
path,
|
|
154
|
+
reasonCode: 'draw2d_render_target_export_not_queued',
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
exportState.queued = false;
|
|
159
|
+
|
|
160
|
+
if (!renderTarget || typeof renderTarget !== 'object') {
|
|
161
|
+
exportState.lastExportReason = 'invalid_export_target';
|
|
162
|
+
exportState.lastExportOk = false;
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
exported: false,
|
|
166
|
+
queued: false,
|
|
167
|
+
path,
|
|
168
|
+
reasonCode: exportState.lastExportReason,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!aura?.draw2d || typeof aura.draw2d.exportRenderTarget !== 'function') {
|
|
173
|
+
exportState.lastExportReason = 'draw2d_export_unavailable';
|
|
174
|
+
exportState.lastExportOk = false;
|
|
175
|
+
return {
|
|
176
|
+
ok: false,
|
|
177
|
+
exported: false,
|
|
178
|
+
queued: false,
|
|
179
|
+
path,
|
|
180
|
+
reasonCode: exportState.lastExportReason,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const result = aura.draw2d.exportRenderTarget(renderTarget, path) || null;
|
|
186
|
+
const reasonCode = keyName(result?.reasonCode, 'draw2d_render_target_export_queued');
|
|
187
|
+
const ok = result?.ok !== false
|
|
188
|
+
&& reasonCode !== 'unsupported_export_format'
|
|
189
|
+
&& reasonCode !== 'invalid_export_target';
|
|
190
|
+
exportState.lastExportReason = reasonCode;
|
|
191
|
+
exportState.lastExportOk = ok;
|
|
192
|
+
if (ok) {
|
|
193
|
+
exportState.exportCount += 1;
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
ok,
|
|
197
|
+
exported: ok,
|
|
198
|
+
queued: false,
|
|
199
|
+
path: keyName(result?.path, path),
|
|
200
|
+
reasonCode,
|
|
201
|
+
result,
|
|
202
|
+
};
|
|
203
|
+
} catch (error) {
|
|
204
|
+
exportState.lastExportReason = 'draw2d_render_target_export_failed';
|
|
205
|
+
exportState.lastExportOk = false;
|
|
206
|
+
return {
|
|
207
|
+
ok: false,
|
|
208
|
+
exported: false,
|
|
209
|
+
queued: false,
|
|
210
|
+
path,
|
|
211
|
+
reasonCode: exportState.lastExportReason,
|
|
212
|
+
error: String(error && error.message ? error.message : error),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
function finite(value, fallback = 0) {
|
|
2
|
+
const numeric = Number(value);
|
|
3
|
+
return Number.isFinite(numeric) ? numeric : fallback;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function positiveNumber(value, fallback, label) {
|
|
7
|
+
const numeric = finite(value, fallback);
|
|
8
|
+
if (!(numeric > 0)) {
|
|
9
|
+
throw new Error(`${label} must be a positive number.`);
|
|
10
|
+
}
|
|
11
|
+
return numeric;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function positiveInteger(value, fallback, label) {
|
|
15
|
+
const numeric = Math.floor(finite(value, fallback));
|
|
16
|
+
if (!(numeric >= 1)) {
|
|
17
|
+
throw new Error(`${label} must be a positive integer.`);
|
|
18
|
+
}
|
|
19
|
+
return numeric;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function ratio01(value) {
|
|
23
|
+
return Math.max(0, Math.min(1, finite(value, 0)));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function keyName(value, fallback) {
|
|
27
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : fallback;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function applyShieldedDamage2D(target, amount, options = {}) {
|
|
31
|
+
if (!target || typeof target !== 'object') {
|
|
32
|
+
throw new Error('shielded damage target is required.');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const hpKey = keyName(options.hpKey, 'hp');
|
|
36
|
+
const shieldKey = keyName(options.shieldKey, 'shield');
|
|
37
|
+
const invulnKey = keyName(options.invulnKey, 'invuln');
|
|
38
|
+
const rechargeDelayKey = keyName(options.rechargeDelayKey, 'shieldRechargeDelay');
|
|
39
|
+
const rechargeTimerKey = keyName(options.rechargeTimerKey, 'shieldRechargeTimer');
|
|
40
|
+
const damageAmount = Math.max(0, finite(amount, 0));
|
|
41
|
+
const invulnDuration = Math.max(0, finite(options.invulnDuration, 0));
|
|
42
|
+
|
|
43
|
+
if (damageAmount <= 0) {
|
|
44
|
+
return {
|
|
45
|
+
ok: true,
|
|
46
|
+
applied: false,
|
|
47
|
+
reasonCode: 'no_damage',
|
|
48
|
+
defeated: finite(target[hpKey], 0) <= 0,
|
|
49
|
+
absorbed: 0,
|
|
50
|
+
hpLost: 0,
|
|
51
|
+
hp: finite(target[hpKey], 0),
|
|
52
|
+
shield: finite(target[shieldKey], 0),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (finite(target[invulnKey], 0) > 0 && options.ignoreInvuln !== true) {
|
|
57
|
+
return {
|
|
58
|
+
ok: true,
|
|
59
|
+
applied: false,
|
|
60
|
+
reasonCode: 'invulnerable',
|
|
61
|
+
defeated: finite(target[hpKey], 0) <= 0,
|
|
62
|
+
absorbed: 0,
|
|
63
|
+
hpLost: 0,
|
|
64
|
+
hp: finite(target[hpKey], 0),
|
|
65
|
+
shield: finite(target[shieldKey], 0),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
target[rechargeTimerKey] = finite(target[rechargeDelayKey], 0);
|
|
70
|
+
|
|
71
|
+
let remaining = damageAmount;
|
|
72
|
+
const shield = Math.max(0, finite(target[shieldKey], 0));
|
|
73
|
+
const absorbed = Math.min(shield, remaining);
|
|
74
|
+
if (absorbed > 0) {
|
|
75
|
+
target[shieldKey] = shield - absorbed;
|
|
76
|
+
remaining -= absorbed;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const hpBefore = Math.max(0, finite(target[hpKey], 0));
|
|
80
|
+
const hpLost = Math.min(hpBefore, remaining);
|
|
81
|
+
if (hpLost > 0) {
|
|
82
|
+
target[hpKey] = hpBefore - hpLost;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (invulnDuration > 0) {
|
|
86
|
+
target[invulnKey] = Math.max(finite(target[invulnKey], 0), invulnDuration);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
ok: true,
|
|
91
|
+
applied: absorbed > 0 || hpLost > 0,
|
|
92
|
+
reasonCode: null,
|
|
93
|
+
defeated: finite(target[hpKey], 0) <= 0,
|
|
94
|
+
absorbed,
|
|
95
|
+
hpLost,
|
|
96
|
+
hp: Math.max(0, finite(target[hpKey], 0)),
|
|
97
|
+
shield: Math.max(0, finite(target[shieldKey], 0)),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function stepShieldRecharge2D(target, dt, options = {}) {
|
|
102
|
+
if (!target || typeof target !== 'object') {
|
|
103
|
+
throw new Error('shield recharge target is required.');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const shieldKey = keyName(options.shieldKey, 'shield');
|
|
107
|
+
const maxShieldKey = keyName(options.maxShieldKey, 'maxShield');
|
|
108
|
+
const rechargeTimerKey = keyName(options.rechargeTimerKey, 'shieldRechargeTimer');
|
|
109
|
+
const rate = Math.max(0, finite(options.rate, 0));
|
|
110
|
+
const stepDt = Math.max(0, finite(dt, 0));
|
|
111
|
+
|
|
112
|
+
target[rechargeTimerKey] = Math.max(0, finite(target[rechargeTimerKey], 0) - stepDt);
|
|
113
|
+
if (target[rechargeTimerKey] > 0 || rate <= 0) {
|
|
114
|
+
return {
|
|
115
|
+
ok: true,
|
|
116
|
+
recharged: 0,
|
|
117
|
+
shield: Math.max(0, finite(target[shieldKey], 0)),
|
|
118
|
+
timer: target[rechargeTimerKey],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const maxShield = Math.max(0, finite(target[maxShieldKey], 0));
|
|
123
|
+
const shield = Math.max(0, finite(target[shieldKey], 0));
|
|
124
|
+
const nextShield = Math.min(maxShield, shield + (rate * stepDt));
|
|
125
|
+
const recharged = nextShield - shield;
|
|
126
|
+
target[shieldKey] = nextShield;
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
ok: true,
|
|
130
|
+
recharged,
|
|
131
|
+
shield: nextShield,
|
|
132
|
+
timer: target[rechargeTimerKey],
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function restoreShieldedHealth2D(target, options = {}) {
|
|
137
|
+
if (!target || typeof target !== 'object') {
|
|
138
|
+
throw new Error('shield restore target is required.');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const hpKey = keyName(options.hpKey, 'hp');
|
|
142
|
+
const maxHpKey = keyName(options.maxHpKey, 'maxHp');
|
|
143
|
+
const shieldKey = keyName(options.shieldKey, 'shield');
|
|
144
|
+
const maxShieldKey = keyName(options.maxShieldKey, 'maxShield');
|
|
145
|
+
const hpAmount = Math.max(0, finite(options.hp, 0));
|
|
146
|
+
const shieldAmount = Math.max(0, finite(options.shield, 0));
|
|
147
|
+
|
|
148
|
+
if (hpAmount > 0) {
|
|
149
|
+
target[hpKey] = Math.min(Math.max(0, finite(target[maxHpKey], 0)), Math.max(0, finite(target[hpKey], 0)) + hpAmount);
|
|
150
|
+
}
|
|
151
|
+
if (shieldAmount > 0) {
|
|
152
|
+
target[shieldKey] = Math.min(Math.max(0, finite(target[maxShieldKey], 0)), Math.max(0, finite(target[shieldKey], 0)) + shieldAmount);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
ok: true,
|
|
157
|
+
hp: Math.max(0, finite(target[hpKey], 0)),
|
|
158
|
+
shield: Math.max(0, finite(target[shieldKey], 0)),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function normalizeDropEntries(entries = []) {
|
|
163
|
+
let runningWeight = 0;
|
|
164
|
+
return (Array.isArray(entries) ? entries : [])
|
|
165
|
+
.map((entry, index) => {
|
|
166
|
+
const raw = entry && typeof entry === 'object' && !Array.isArray(entry) ? entry : null;
|
|
167
|
+
if (!raw) return null;
|
|
168
|
+
const id = keyName(raw.id, `drop-${index + 1}`);
|
|
169
|
+
const weight = positiveNumber(raw.weight, 1, `drop "${id}" weight`);
|
|
170
|
+
runningWeight += weight;
|
|
171
|
+
return {
|
|
172
|
+
...raw,
|
|
173
|
+
id,
|
|
174
|
+
weight,
|
|
175
|
+
cumulativeWeight: runningWeight,
|
|
176
|
+
};
|
|
177
|
+
})
|
|
178
|
+
.filter(Boolean);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function createWeightedDropTable2D(entries = []) {
|
|
182
|
+
const normalized = normalizeDropEntries(entries);
|
|
183
|
+
return {
|
|
184
|
+
entries: normalized,
|
|
185
|
+
totalWeight: normalized.length > 0 ? normalized[normalized.length - 1].cumulativeWeight : 0,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function rollWeightedDrop2D(table, sample = Math.random()) {
|
|
190
|
+
if (!table || !Array.isArray(table.entries) || table.entries.length === 0 || !(table.totalWeight > 0)) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const threshold = ratio01(sample) * table.totalWeight;
|
|
195
|
+
for (const entry of table.entries) {
|
|
196
|
+
if (threshold <= entry.cumulativeWeight) {
|
|
197
|
+
return entry;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return table.entries[table.entries.length - 1] || null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function createEncounterPressure2D(options = {}) {
|
|
204
|
+
return {
|
|
205
|
+
kills: Math.max(0, Math.floor(finite(options.kills, 0))),
|
|
206
|
+
wave: Math.max(1, Math.floor(finite(options.wave, 1))),
|
|
207
|
+
combo: Math.max(0, Math.floor(finite(options.combo, 0))),
|
|
208
|
+
comboTimer: Math.max(0, finite(options.comboTimer, 0)),
|
|
209
|
+
comboWindow: positiveNumber(options.comboWindow, 2.1, 'combat pressure comboWindow'),
|
|
210
|
+
waveEveryKills: positiveInteger(options.waveEveryKills, 10, 'combat pressure waveEveryKills'),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function recordEncounterKill2D(pressure, options = {}) {
|
|
215
|
+
if (!pressure || typeof pressure !== 'object') {
|
|
216
|
+
throw new Error('encounter pressure state is required.');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const comboWindow = positiveNumber(options.comboWindow, pressure.comboWindow, 'combat pressure comboWindow');
|
|
220
|
+
const waveEveryKills = positiveInteger(options.waveEveryKills, pressure.waveEveryKills, 'combat pressure waveEveryKills');
|
|
221
|
+
|
|
222
|
+
pressure.comboWindow = comboWindow;
|
|
223
|
+
pressure.waveEveryKills = waveEveryKills;
|
|
224
|
+
pressure.kills = Math.max(0, Math.floor(finite(pressure.kills, 0))) + 1;
|
|
225
|
+
pressure.combo = Math.max(0, Math.floor(finite(pressure.combo, 0))) + 1;
|
|
226
|
+
pressure.comboTimer = comboWindow;
|
|
227
|
+
|
|
228
|
+
const waveAdvanced = pressure.kills % waveEveryKills === 0;
|
|
229
|
+
if (waveAdvanced) {
|
|
230
|
+
pressure.wave = Math.max(1, Math.floor(finite(pressure.wave, 1))) + 1;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
ok: true,
|
|
235
|
+
waveAdvanced,
|
|
236
|
+
kills: pressure.kills,
|
|
237
|
+
combo: pressure.combo,
|
|
238
|
+
comboTimer: pressure.comboTimer,
|
|
239
|
+
wave: pressure.wave,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function stepEncounterPressure2D(pressure, dt) {
|
|
244
|
+
if (!pressure || typeof pressure !== 'object') {
|
|
245
|
+
throw new Error('encounter pressure state is required.');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
pressure.comboTimer = Math.max(0, finite(pressure.comboTimer, 0) - Math.max(0, finite(dt, 0)));
|
|
249
|
+
if (pressure.comboTimer <= 0) {
|
|
250
|
+
pressure.combo = 0;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
ok: true,
|
|
255
|
+
combo: pressure.combo,
|
|
256
|
+
comboTimer: pressure.comboTimer,
|
|
257
|
+
wave: Math.max(1, Math.floor(finite(pressure.wave, 1))),
|
|
258
|
+
kills: Math.max(0, Math.floor(finite(pressure.kills, 0))),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function advanceBossPhase2D(entity, healthRatio, thresholds = [], options = {}) {
|
|
263
|
+
if (!entity || typeof entity !== 'object') {
|
|
264
|
+
throw new Error('boss phase entity is required.');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const phaseKey = keyName(options.phaseKey, 'phaseStage');
|
|
268
|
+
const normalizedThresholds = [...(Array.isArray(thresholds) ? thresholds : [])]
|
|
269
|
+
.map((entry) => ratio01(entry))
|
|
270
|
+
.sort((left, right) => right - left);
|
|
271
|
+
const currentPhase = Math.max(0, Math.floor(finite(entity[phaseKey], 0)));
|
|
272
|
+
let nextPhase = currentPhase;
|
|
273
|
+
|
|
274
|
+
for (let index = 0; index < normalizedThresholds.length; index += 1) {
|
|
275
|
+
if (ratio01(healthRatio) <= normalizedThresholds[index]) {
|
|
276
|
+
nextPhase = Math.max(nextPhase, index + 1);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (options.mutate === true) {
|
|
281
|
+
entity[phaseKey] = nextPhase;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
ok: true,
|
|
286
|
+
shifted: nextPhase > currentPhase,
|
|
287
|
+
phaseStage: nextPhase,
|
|
288
|
+
previousPhaseStage: currentPhase,
|
|
289
|
+
};
|
|
290
|
+
}
|