@rpgjs/action-battle 5.0.0-beta.5 → 5.0.0-beta.6

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.
Files changed (63) hide show
  1. package/README.md +115 -0
  2. package/dist/ai.server.d.ts +17 -2
  3. package/dist/client/index.js +13 -8
  4. package/dist/client/index10.js +54 -136
  5. package/dist/client/index11.js +52 -23
  6. package/dist/client/index12.js +101 -1217
  7. package/dist/client/index13.js +139 -42
  8. package/dist/client/index14.js +23 -8
  9. package/dist/client/index15.js +68 -444
  10. package/dist/client/index16.js +1281 -0
  11. package/dist/client/index17.js +13 -0
  12. package/dist/client/index18.js +60 -0
  13. package/dist/client/index19.js +10 -0
  14. package/dist/client/index2.js +25 -87
  15. package/dist/client/index20.js +504 -0
  16. package/dist/client/index3.js +45 -83
  17. package/dist/client/index4.js +98 -297
  18. package/dist/client/index5.js +81 -33
  19. package/dist/client/index6.js +284 -78
  20. package/dist/client/index7.js +33 -74
  21. package/dist/client/index8.js +95 -55
  22. package/dist/client/index9.js +75 -96
  23. package/dist/core/attack-profile.d.ts +9 -0
  24. package/dist/core/attack-runtime.d.ts +20 -0
  25. package/dist/core/enemy-attack-profiles.d.ts +6 -0
  26. package/dist/core/equipment.d.ts +2 -0
  27. package/dist/core/hit-reaction.d.ts +5 -0
  28. package/dist/index.d.ts +6 -1
  29. package/dist/server/index.js +12 -7
  30. package/dist/server/index10.js +1278 -8
  31. package/dist/server/index11.js +37 -0
  32. package/dist/server/index12.js +60 -0
  33. package/dist/server/index13.js +13 -0
  34. package/dist/server/index14.js +503 -0
  35. package/dist/server/index15.js +10 -0
  36. package/dist/server/index3.js +25 -87
  37. package/dist/server/index4.js +45 -141
  38. package/dist/server/index5.js +104 -21
  39. package/dist/server/index6.js +137 -1215
  40. package/dist/server/index7.js +22 -34
  41. package/dist/server/index8.js +70 -44
  42. package/dist/server/index9.js +44 -437
  43. package/dist/server.d.ts +7 -1
  44. package/package.json +5 -5
  45. package/src/ai.server.ts +172 -43
  46. package/src/client.ts +21 -12
  47. package/src/config.ts +17 -2
  48. package/src/core/attack-profile.spec.ts +118 -0
  49. package/src/core/attack-profile.ts +100 -0
  50. package/src/core/attack-runtime.spec.ts +103 -0
  51. package/src/core/attack-runtime.ts +83 -0
  52. package/src/core/contracts.ts +3 -0
  53. package/src/core/enemy-attack-profiles.spec.ts +35 -0
  54. package/src/core/enemy-attack-profiles.ts +103 -0
  55. package/src/core/equipment.spec.ts +37 -0
  56. package/src/core/equipment.ts +17 -0
  57. package/src/core/hit-reaction.spec.ts +43 -0
  58. package/src/core/hit-reaction.ts +70 -0
  59. package/src/core/hit.spec.ts +54 -1
  60. package/src/core/hit.ts +26 -0
  61. package/src/index.ts +36 -0
  62. package/src/server.ts +180 -33
  63. package/src/types.ts +62 -6
@@ -1,307 +1,108 @@
1
- import { actionBattleTargetingState, actionBattleUiOptions, moveTargetingOffset, startTargeting, stopTargeting } from "./index3.js";
2
- import { RpgClientEngine, inject } from "@rpgjs/client";
3
- import { DOMContainer, DOMElement, DOMSprite, computed, cond, effect, h, loop, signal, useDefineProps, useProps } from "canvasengine";
4
- //#region src/components/action-bar.ce
5
- if (typeof document !== "undefined" && !document.getElementById("ce-style--home-runner-work-RPG-JS-RPG-JS-packages-action-battle-src-components-action-bar-ce")) {
6
- const styleElement = document.createElement("style");
7
- styleElement.id = "ce-style--home-runner-work-RPG-JS-RPG-JS-packages-action-battle-src-components-action-bar-ce";
8
- styleElement.textContent = ".action-battle-actionbar-root {\n pointer-events: none;\n }\n\n .action-battle-actionbar {\n position: absolute;\n left: 12px;\n right: 12px;\n bottom: 12px;\n pointer-events: auto;\n display: flex;\n justify-content: center;\n }\n\n .action-battle-actionbar-plate {\n width: min(760px, 100%);\n --rpg-ui-hotbar-size: clamp(38px, 8vw, 52px);\n }\n\n .action-battle-actionbar-track {\n --rpg-ui-hotbar-slots: 10;\n }";
9
- document.head.appendChild(styleElement);
10
- }
11
- function component($$props) {
12
- useProps($$props);
13
- const defineProps = useDefineProps($$props);
14
- const engine = inject(RpgClientEngine);
15
- const keyboardControls = engine.globalConfig.keyboardControls;
16
- const { data, onInteraction, onBack } = defineProps();
17
- const ACTION_BAR_SIZE = 10;
18
- const SLOT_LABELS = [
19
- "1",
20
- "2",
21
- "3",
22
- "4",
23
- "5",
24
- "6",
25
- "7",
26
- "8",
27
- "9",
28
- "0"
29
- ];
30
- const SLOT_CONFIG_KEYS = [
31
- "hotbar1",
32
- "hotbar2",
33
- "hotbar3",
34
- "hotbar4",
35
- "hotbar5",
36
- "hotbar6",
37
- "hotbar7",
38
- "hotbar8",
39
- "hotbar9",
40
- "hotbar0"
41
- ];
42
- const selectedSlotIndex = signal(-1);
43
- const uiOptions = computed(() => actionBattleUiOptions());
44
- const showItems = computed(() => {
45
- const mode = uiOptions().actionBar?.mode || "both";
46
- return mode === "items" || mode === "both";
47
- });
48
- const showSkills = computed(() => {
49
- const mode = uiOptions().actionBar?.mode || "both";
50
- return mode === "skills" || mode === "both";
51
- });
52
- const isTargeting = computed(() => actionBattleTargetingState().active);
53
- const iconSheet = (iconId) => ({
54
- definition: engine.getSpriteSheet(iconId),
55
- playing: "default"
56
- });
57
- const resolveProp = (value) => typeof value === "function" ? value() : value;
58
- const actionBarData = computed(() => resolveProp(data) || {
59
- items: [],
60
- skills: []
61
- });
62
- const actionBarSlots = computed(() => {
63
- const entries = [];
64
- if (showSkills()) (actionBarData().skills || []).forEach((skill, index) => {
65
- entries.push({
66
- type: "skill",
67
- skill,
68
- item: null,
69
- sourceIndex: index
70
- });
71
- });
72
- if (showItems()) (actionBarData().items || []).forEach((item, index) => {
73
- entries.push({
74
- type: "item",
75
- skill: null,
76
- item,
77
- sourceIndex: index
78
- });
79
- });
80
- const slots = entries.slice(0, ACTION_BAR_SIZE).map((entry, index) => ({
81
- ...entry,
82
- label: SLOT_LABELS[index]
83
- }));
84
- while (slots.length < ACTION_BAR_SIZE) slots.push({
85
- type: "empty",
86
- skill: null,
87
- item: null,
88
- sourceIndex: -1,
89
- label: SLOT_LABELS[slots.length]
90
- });
91
- return slots;
92
- });
93
- const hasSlotEntry = (slot) => slot.type === "skill" || slot.type === "item";
94
- const isSlotDisabled = (slot) => {
95
- if (!hasSlotEntry(slot)) return true;
96
- const entry = slot.type === "skill" ? slot.skill : slot.item;
97
- return !entry || !entry.usable;
98
- };
99
- const getItemQuantity = (item) => {
100
- if (!item) return 0;
101
- if (typeof item.quantity === "function") return item.quantity();
102
- return item.quantity || 0;
103
- };
104
- const selectItem = (item) => {
105
- if (!item || !item.usable) return;
106
- if (onInteraction) onInteraction("useItem", { id: item.id });
107
- };
108
- const selectSkill = (skill) => {
109
- if (!skill || !skill.usable) return;
110
- if ((skill.range ?? 0) > 0 || skill.aoeMask) {
111
- startTargeting(skill);
112
- return;
113
- }
114
- if (onInteraction) onInteraction("useSkill", { id: skill.id });
115
- };
116
- const selectSlot = (index) => {
117
- const slot = actionBarSlots()[index];
118
- if (!slot || !hasSlotEntry(slot)) return;
119
- if (slot.type === "skill") {
120
- selectSkill(slot.skill);
121
- return;
1
+ import { normalizeActionBattleAttackProfile } from "./index3.js";
2
+ //#region src/config.ts
3
+ var DEFAULT_ACTION_BATTLE_OPTIONS = {
4
+ ui: {
5
+ actionBar: {
6
+ enabled: false,
7
+ autoOpen: false,
8
+ mode: "both"
9
+ },
10
+ targeting: {
11
+ enabled: true,
12
+ showGrid: true,
13
+ colors: {
14
+ area: 3120887,
15
+ edge: 1796760,
16
+ cursor: 16765286
17
+ }
122
18
  }
123
- selectItem(slot.item);
124
- };
125
- const onSelectSlot = (index) => () => {
126
- selectedSlotIndex.set(index);
127
- selectSlot(index);
19
+ },
20
+ skills: { defaultAoeMask: ["#"] },
21
+ targeting: {
22
+ affects: "events",
23
+ allowEmptyTarget: true
24
+ },
25
+ attack: {
26
+ lockMovement: true,
27
+ lockDurationMs: 350,
28
+ showPreview: true,
29
+ previewDurationMs: 180,
30
+ previewColor: 16774064,
31
+ previewAccentColor: 16777215
32
+ },
33
+ animations: {}
34
+ };
35
+ var currentActionBattleOptions = DEFAULT_ACTION_BATTLE_OPTIONS;
36
+ function normalizeActionBattleOptions(options = {}) {
37
+ const attack = {
38
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.attack,
39
+ ...options.attack
128
40
  };
129
- const getPlayerTile = () => {
130
- const player = engine.scene.getCurrentPlayer();
131
- if (!player) return null;
132
- const hitbox = player.hitbox?.() || {
133
- w: 32,
134
- h: 32
135
- };
136
- const tileWidth = hitbox.w || 32;
137
- const tileHeight = hitbox.h || 32;
138
- return {
139
- x: Math.floor((player.x() + hitbox.w / 2) / tileWidth),
140
- y: Math.floor((player.y() + hitbox.h / 2) / tileHeight)
141
- };
142
- };
143
- const confirmTargeting = () => {
144
- const state = actionBattleTargetingState();
145
- if (!state.skill) return;
146
- const origin = getPlayerTile();
147
- if (!origin) return;
148
- const target = {
149
- x: origin.x + state.offset.x,
150
- y: origin.y + state.offset.y
151
- };
152
- if (onInteraction) onInteraction("useSkill", {
153
- id: state.skill.id,
154
- target
155
- });
156
- stopTargeting();
157
- };
158
- const resolveKeyBind = (key) => {
159
- if (!key) return null;
160
- if (typeof key === "string" && keyboardControls?.[key]) return keyboardControls[key];
161
- return key;
162
- };
163
- const slotBind = (index) => keyboardControls?.[SLOT_CONFIG_KEYS[index]] || SLOT_LABELS[index];
164
- const buildControls = () => {
165
- const hotbarControls = {};
166
- actionBarSlots().forEach((slot, index) => {
167
- if (!hasSlotEntry(slot)) return;
168
- const bind = slotBind(index);
169
- hotbarControls[`slot-${index}`] = {
170
- bind,
171
- keyDown() {
172
- if (isTargeting()) return;
173
- selectedSlotIndex.set(index);
174
- selectSlot(index);
175
- }
176
- };
177
- if (slot.type === "skill") {
178
- const skillBind = resolveKeyBind(slot.skill?.key);
179
- if (!skillBind || skillBind === bind) return;
180
- hotbarControls[`skill-${index}`] = {
181
- bind: skillBind,
182
- keyDown() {
183
- if (isTargeting()) return;
184
- selectedSlotIndex.set(index);
185
- selectSlot(index);
186
- }
187
- };
188
- }
189
- });
190
- return {
191
- left: {
192
- repeat: true,
193
- bind: keyboardControls.left,
194
- throttle: 150,
195
- keyDown() {
196
- if (isTargeting()) moveTargetingOffset(-1, 0);
197
- }
198
- },
199
- right: {
200
- repeat: true,
201
- bind: keyboardControls.right,
202
- throttle: 150,
203
- keyDown() {
204
- if (isTargeting()) moveTargetingOffset(1, 0);
205
- }
206
- },
207
- up: {
208
- repeat: true,
209
- bind: keyboardControls.up,
210
- throttle: 150,
211
- keyDown() {
212
- if (isTargeting()) moveTargetingOffset(0, -1);
213
- }
41
+ const attackProfile = normalizeActionBattleAttackProfile(attack.profile, {
42
+ lockMovement: attack.lockMovement,
43
+ lockDurationMs: attack.lockDurationMs,
44
+ hitboxes: attack.hitboxes
45
+ });
46
+ return {
47
+ ui: {
48
+ actionBar: {
49
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.ui?.actionBar,
50
+ ...options.ui?.actionBar
214
51
  },
215
- down: {
216
- repeat: true,
217
- bind: keyboardControls.down,
218
- throttle: 150,
219
- keyDown() {
220
- if (isTargeting()) moveTargetingOffset(0, 1);
52
+ targeting: {
53
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.ui?.targeting,
54
+ ...options.ui?.targeting,
55
+ colors: {
56
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.ui?.targeting?.colors,
57
+ ...options.ui?.targeting?.colors
221
58
  }
222
- },
223
- action: {
224
- bind: keyboardControls.action,
225
- keyDown() {
226
- if (isTargeting()) confirmTargeting();
59
+ }
60
+ },
61
+ skills: {
62
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.skills,
63
+ ...options.skills
64
+ },
65
+ targeting: {
66
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.targeting,
67
+ ...options.targeting
68
+ },
69
+ debug: {
70
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.debug,
71
+ ...options.debug
72
+ },
73
+ attack: {
74
+ ...attack,
75
+ profile: attackProfile
76
+ },
77
+ animations: {
78
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.animations,
79
+ ...options.animations
80
+ },
81
+ systems: {
82
+ combat: {
83
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.systems?.combat,
84
+ ...options.systems?.combat,
85
+ hooks: {
86
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.systems?.combat?.hooks,
87
+ ...options.systems?.combat?.hooks
227
88
  }
228
89
  },
229
- escape: {
230
- bind: keyboardControls.escape,
231
- keyDown() {
232
- if (isTargeting()) {
233
- stopTargeting();
234
- return;
235
- }
236
- if (onBack) onBack();
90
+ ai: {
91
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.systems?.ai,
92
+ ...options.systems?.ai,
93
+ behaviors: {
94
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.systems?.ai?.behaviors,
95
+ ...options.systems?.ai?.behaviors
237
96
  }
238
- },
239
- ...hotbarControls,
240
- gamepad: { enabled: true }
241
- };
242
- };
243
- const controls = signal(buildControls());
244
- effect(() => {
245
- controls.set(buildControls());
246
- });
247
- return h(DOMContainer, {
248
- width: "100%",
249
- height: "100%",
250
- attrs: { class: "action-battle-actionbar-root" },
251
- controls
252
- }, h(DOMElement, {
253
- element: "div",
254
- attrs: { class: "action-battle-actionbar" }
255
- }, h(DOMElement, {
256
- element: "div",
257
- attrs: { class: "rpg-ui-hotbar action-battle-actionbar-plate" }
258
- }, h(DOMElement, {
259
- element: "div",
260
- attrs: { class: "rpg-ui-hotbar-track action-battle-actionbar-track" }
261
- }, loop(actionBarSlots, (slot, index) => h(DOMElement, {
262
- element: "div",
263
- attrs: {
264
- class: "rpg-ui-hotbar-slot action-battle-actionbar-slot",
265
- "data-selected": computed(() => selectedSlotIndex() === index ? "true" : "false"),
266
- "data-disabled": computed(() => isSlotDisabled(slot) ? "true" : "false"),
267
- "data-empty": computed(() => hasSlotEntry(slot) ? "false" : "true"),
268
- "data-type": slot.type,
269
- tabindex: computed(() => hasSlotEntry(slot) ? index : -1),
270
- click: hasSlotEntry(slot) ? onSelectSlot(index) : void 0
97
+ }
271
98
  }
272
- }, [
273
- h(DOMElement, {
274
- element: "span",
275
- attrs: { class: "rpg-ui-hotbar-key action-battle-actionbar-key" },
276
- textContent: slot.label
277
- }),
278
- cond(computed(() => slot.type === "skill" && slot.skill?.icon), () => h(DOMSprite, {
279
- sheet: computed(() => iconSheet(slot.skill.icon)),
280
- width: "60px",
281
- height: "60px",
282
- scale: 2,
283
- objectFit: "contain"
284
- }), [computed(() => slot.type === "item" && slot.item?.icon), () => h(DOMSprite, {
285
- sheet: computed(() => iconSheet(slot.item.icon)),
286
- width: "60px",
287
- height: "60px",
288
- scale: 2,
289
- objectFit: "contain"
290
- })], [computed(() => slot.type === "skill" && slot.skill), () => h(DOMElement, {
291
- element: "span",
292
- attrs: { class: "rpg-ui-hotbar-text action-battle-actionbar-text" },
293
- textContent: slot.skill.name
294
- })], [computed(() => slot.type === "item" && slot.item), () => h(DOMElement, {
295
- element: "span",
296
- attrs: { class: "rpg-ui-hotbar-text action-battle-actionbar-text" },
297
- textContent: slot.item.name
298
- })]),
299
- cond(computed(() => slot.type === "item" && slot.item), () => h(DOMElement, {
300
- element: "span",
301
- attrs: { class: "rpg-ui-hotbar-count action-battle-actionbar-count" },
302
- textContent: "x" + getItemQuantity(slot.item)
303
- }))
304
- ]))))));
99
+ };
100
+ }
101
+ function setActionBattleOptions(options) {
102
+ currentActionBattleOptions = options;
103
+ }
104
+ function getActionBattleOptions() {
105
+ return currentActionBattleOptions;
305
106
  }
306
107
  //#endregion
307
- export { component as default };
108
+ export { DEFAULT_ACTION_BATTLE_OPTIONS, getActionBattleOptions, normalizeActionBattleOptions, setActionBattleOptions };
@@ -1,37 +1,85 @@
1
- //#region src/targeting.ts
2
- var normalizeMaskRows = (mask) => {
3
- if (!mask) return ["#"];
4
- if (Array.isArray(mask)) return mask;
5
- return mask.trim().split("\n").map((row) => row.replace(/\r/g, ""));
6
- };
7
- var parseAoeMask = (mask) => {
8
- const rows = normalizeMaskRows(mask);
9
- const height = rows.length;
10
- const width = rows.reduce((max, row) => Math.max(max, row.length), 0);
11
- const centerX = Math.floor(width / 2);
12
- const centerY = Math.floor(height / 2);
13
- const cells = [];
14
- rows.forEach((row, y) => {
15
- for (let x = 0; x < row.length; x++) {
16
- const char = row[x];
17
- if (char && char !== "." && char !== " ") cells.push({
18
- dx: x - centerX,
19
- dy: y - centerY
20
- });
21
- }
22
- });
23
- if (cells.length === 0) cells.push({
24
- dx: 0,
25
- dy: 0
1
+ import { DEFAULT_ACTION_BATTLE_OPTIONS, normalizeActionBattleOptions } from "./index4.js";
2
+ import { signal } from "canvasengine";
3
+ //#region src/ui/state.ts
4
+ var defaultTargetingState = {
5
+ active: false,
6
+ skill: null,
7
+ range: 0,
8
+ offset: {
9
+ x: 0,
10
+ y: 0
11
+ },
12
+ aoeMask: DEFAULT_ACTION_BATTLE_OPTIONS.skills?.defaultAoeMask || ["#"]
13
+ };
14
+ var defaultAttackPreviewState = {
15
+ active: false,
16
+ id: 0,
17
+ direction: "down",
18
+ startedAt: 0,
19
+ durationMs: 180,
20
+ color: 16774064,
21
+ accentColor: 16777215
22
+ };
23
+ var actionBattleUiOptions = signal(normalizeActionBattleOptions({}).ui || {});
24
+ var actionBattleSkillOptions = signal(normalizeActionBattleOptions({}).skills || {});
25
+ var actionBattleTargetingState = signal({ ...defaultTargetingState });
26
+ var actionBattleAttackPreviewState = signal({ ...defaultAttackPreviewState });
27
+ var setActionBattleOptions = (options = {}) => {
28
+ const normalized = normalizeActionBattleOptions(options);
29
+ actionBattleUiOptions.set(normalized.ui || {});
30
+ actionBattleSkillOptions.set(normalized.skills || {});
31
+ };
32
+ var startTargeting = (skill) => {
33
+ const skillsOptions = actionBattleSkillOptions();
34
+ const mask = skill.aoeMask || skillsOptions.defaultAoeMask || ["#"];
35
+ actionBattleTargetingState.set({
36
+ active: true,
37
+ skill,
38
+ range: skill.range ?? 0,
39
+ offset: {
40
+ x: 0,
41
+ y: 0
42
+ },
43
+ aoeMask: mask
26
44
  });
27
- return {
28
- width,
29
- height,
30
- centerX,
31
- centerY,
32
- cells
45
+ };
46
+ var stopTargeting = () => {
47
+ actionBattleTargetingState.set({ ...defaultTargetingState });
48
+ };
49
+ var moveTargetingOffset = (dx, dy) => {
50
+ const state = actionBattleTargetingState();
51
+ if (!state.active) return;
52
+ const next = {
53
+ x: state.offset.x + dx,
54
+ y: state.offset.y + dy
33
55
  };
56
+ if (Math.abs(next.x) + Math.abs(next.y) > state.range) return;
57
+ actionBattleTargetingState.set({
58
+ ...state,
59
+ offset: next
60
+ });
61
+ };
62
+ var startAttackPreview = (options) => {
63
+ const id = actionBattleAttackPreviewState().id + 1;
64
+ const durationMs = Math.max(1, options.durationMs ?? defaultAttackPreviewState.durationMs);
65
+ actionBattleAttackPreviewState.set({
66
+ active: true,
67
+ id,
68
+ direction: options.direction,
69
+ startedAt: Date.now(),
70
+ durationMs,
71
+ color: options.color ?? defaultAttackPreviewState.color,
72
+ accentColor: options.accentColor ?? defaultAttackPreviewState.accentColor
73
+ });
74
+ return id;
75
+ };
76
+ var stopAttackPreview = (id) => {
77
+ const current = actionBattleAttackPreviewState();
78
+ if (id !== void 0 && current.id !== id) return;
79
+ actionBattleAttackPreviewState.set({
80
+ ...current,
81
+ active: false
82
+ });
34
83
  };
35
- var manhattanDistance = (a, b) => Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
36
84
  //#endregion
37
- export { manhattanDistance, parseAoeMask };
85
+ export { actionBattleAttackPreviewState, actionBattleTargetingState, actionBattleUiOptions, moveTargetingOffset, setActionBattleOptions, startAttackPreview, startTargeting, stopAttackPreview, stopTargeting };