@jgengine/core 0.1.1 → 0.2.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/combat/effects.d.ts +2 -2
- package/dist/input/actionBindings.d.ts +6 -0
- package/dist/input/actionBindings.js +72 -0
- package/dist/runtime/gameContext.d.ts +2 -2
- package/dist/runtime/gameContext.js +3 -3
- package/dist/scene/entityStats.d.ts +13 -13
- package/dist/scene/entityStats.js +5 -5
- package/package.json +1 -1
package/dist/combat/effects.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EntityPosition } from "../scene/entityStore.js";
|
|
2
|
-
import { type
|
|
2
|
+
import { type StatValueMap } from "../scene/entityStats.js";
|
|
3
3
|
export interface ReceiveRule {
|
|
4
4
|
order: string[];
|
|
5
5
|
modifiers?: Record<string, number>;
|
|
@@ -47,7 +47,7 @@ export interface CombatSpatialDeps {
|
|
|
47
47
|
}
|
|
48
48
|
export interface EffectSystemDeps {
|
|
49
49
|
resolveReceive(instanceId: string): ReceiveMap | null | undefined;
|
|
50
|
-
resolveStats(instanceId: string):
|
|
50
|
+
resolveStats(instanceId: string): StatValueMap | undefined;
|
|
51
51
|
getStat(itemId: string, stat: string): number | null;
|
|
52
52
|
spatial: CombatSpatialDeps;
|
|
53
53
|
drainStatByEffect?: Record<string, string>;
|
|
@@ -30,6 +30,12 @@ export type ActionCodes<TCode extends string = string> = readonly TCode[] | {
|
|
|
30
30
|
};
|
|
31
31
|
export type ActionCodesMap<TAction extends string = string, TCode extends string = string> = Record<TAction, ActionCodes<TCode>>;
|
|
32
32
|
export declare function toActionStateBindingMap<TAction extends string, TCode extends string>(map: ActionCodesMap<TAction, TCode>): ActionStateBindingMap<TAction, TCode>;
|
|
33
|
+
export declare function hotbarSlotBindings(count: number, options?: {
|
|
34
|
+
action?: (slot: number) => string;
|
|
35
|
+
}): ActionCodesMap;
|
|
36
|
+
export declare function bindingLabel(code: string): string;
|
|
37
|
+
/** Short display label for an action's first bound code, or null when unbound. */
|
|
38
|
+
export declare function actionLabel(map: ActionCodesMap, action: string): string | null;
|
|
33
39
|
export interface ActionStateTracker<TAction extends string> {
|
|
34
40
|
handleDown(code: string): TAction | null;
|
|
35
41
|
handleUp(code: string): TAction | null;
|
|
@@ -44,6 +44,78 @@ export function toActionStateBindingMap(map) {
|
|
|
44
44
|
}
|
|
45
45
|
return result;
|
|
46
46
|
}
|
|
47
|
+
const HOTBAR_DIGIT_CODES = [
|
|
48
|
+
"Digit1",
|
|
49
|
+
"Digit2",
|
|
50
|
+
"Digit3",
|
|
51
|
+
"Digit4",
|
|
52
|
+
"Digit5",
|
|
53
|
+
"Digit6",
|
|
54
|
+
"Digit7",
|
|
55
|
+
"Digit8",
|
|
56
|
+
"Digit9",
|
|
57
|
+
"Digit0",
|
|
58
|
+
];
|
|
59
|
+
export function hotbarSlotBindings(count, options) {
|
|
60
|
+
if (!Number.isInteger(count) || count < 1 || count > HOTBAR_DIGIT_CODES.length) {
|
|
61
|
+
throw new Error(`hotbarSlotBindings: count must be an integer in [1, ${HOTBAR_DIGIT_CODES.length}]`);
|
|
62
|
+
}
|
|
63
|
+
const action = options?.action ?? ((slot) => `hotbarSlot${slot}`);
|
|
64
|
+
const map = {};
|
|
65
|
+
for (let slot = 1; slot <= count; slot += 1) {
|
|
66
|
+
map[action(slot)] = [HOTBAR_DIGIT_CODES[slot - 1]];
|
|
67
|
+
}
|
|
68
|
+
return map;
|
|
69
|
+
}
|
|
70
|
+
const CODE_LABELS = {
|
|
71
|
+
mouse0: "LMB",
|
|
72
|
+
mouse1: "MMB",
|
|
73
|
+
mouse2: "RMB",
|
|
74
|
+
Space: "Space",
|
|
75
|
+
space: "Space",
|
|
76
|
+
Escape: "Esc",
|
|
77
|
+
Tab: "Tab",
|
|
78
|
+
Enter: "Enter",
|
|
79
|
+
Backspace: "Bksp",
|
|
80
|
+
Shift: "Shift",
|
|
81
|
+
ShiftLeft: "Shift",
|
|
82
|
+
ShiftRight: "Shift",
|
|
83
|
+
Control: "Ctrl",
|
|
84
|
+
ControlLeft: "Ctrl",
|
|
85
|
+
ControlRight: "Ctrl",
|
|
86
|
+
AltLeft: "Alt",
|
|
87
|
+
AltRight: "Alt",
|
|
88
|
+
ArrowUp: "↑",
|
|
89
|
+
ArrowDown: "↓",
|
|
90
|
+
ArrowLeft: "←",
|
|
91
|
+
ArrowRight: "→",
|
|
92
|
+
};
|
|
93
|
+
export function bindingLabel(code) {
|
|
94
|
+
const direct = CODE_LABELS[code];
|
|
95
|
+
if (direct !== undefined)
|
|
96
|
+
return direct;
|
|
97
|
+
if (code.startsWith("Key") && code.length === 4)
|
|
98
|
+
return code.slice(3);
|
|
99
|
+
if (code.startsWith("Digit") && code.length === 6)
|
|
100
|
+
return code.slice(5);
|
|
101
|
+
return code.length === 1 ? code.toUpperCase() : code;
|
|
102
|
+
}
|
|
103
|
+
/** Short display label for an action's first bound code, or null when unbound. */
|
|
104
|
+
export function actionLabel(map, action) {
|
|
105
|
+
const codes = map[action];
|
|
106
|
+
if (codes === undefined)
|
|
107
|
+
return null;
|
|
108
|
+
let list;
|
|
109
|
+
if (Array.isArray(codes)) {
|
|
110
|
+
list = codes;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const modes = codes;
|
|
114
|
+
list = modes.hold ?? modes.toggle ?? [];
|
|
115
|
+
}
|
|
116
|
+
const first = list[0];
|
|
117
|
+
return first === undefined ? null : bindingLabel(first);
|
|
118
|
+
}
|
|
47
119
|
function resolveActionBindingModes(config) {
|
|
48
120
|
if (Array.isArray(config))
|
|
49
121
|
return { hold: config, toggle: [] };
|
|
@@ -17,7 +17,7 @@ import { type ItemUseHandler, type ItemUseInput, type ItemUseRejection, type Ite
|
|
|
17
17
|
import { type WeaponStats } from "../item/weapon.js";
|
|
18
18
|
import { type PoseAllowedStates, type PoseState } from "../movement/poseState.js";
|
|
19
19
|
import type { ModelAssetRef } from "../scene/assetCatalog.js";
|
|
20
|
-
import { type EntityStatsApi, type
|
|
20
|
+
import { type EntityStatsApi, type StatCatalog } from "../scene/entityStats.js";
|
|
21
21
|
import type { EntityPose, SceneEntity, SpawnOptions } from "../scene/entityStore.js";
|
|
22
22
|
import { type ObjectStore } from "../scene/objectStore.js";
|
|
23
23
|
import { type SpatialApi } from "../scene/spatial.js";
|
|
@@ -29,7 +29,7 @@ export interface GameContextItemEntry {
|
|
|
29
29
|
trade?: TradeField;
|
|
30
30
|
}
|
|
31
31
|
export interface GameContextEntityEntry {
|
|
32
|
-
stats?:
|
|
32
|
+
stats?: StatCatalog;
|
|
33
33
|
receive?: ReceiveMap;
|
|
34
34
|
onDeath?: OnDeathSpec;
|
|
35
35
|
movement?: PoseAllowedStates & {
|
|
@@ -16,7 +16,7 @@ import { createInventorySet, putItem, } from "../inventory/inventoryModel.js";
|
|
|
16
16
|
import { createItemUse, } from "../item/use.js";
|
|
17
17
|
import { createWeaponStats } from "../item/weapon.js";
|
|
18
18
|
import { createPoseState } from "../movement/poseState.js";
|
|
19
|
-
import { createEntityStatsApi,
|
|
19
|
+
import { createEntityStatsApi, seedStatValues, setStatValue, } from "../scene/entityStats.js";
|
|
20
20
|
import { createObjectStore } from "../scene/objectStore.js";
|
|
21
21
|
import { createSpatialApi } from "../scene/spatial.js";
|
|
22
22
|
import { createTargeting } from "../scene/targeting.js";
|
|
@@ -166,7 +166,7 @@ export function createGameContext(options) {
|
|
|
166
166
|
});
|
|
167
167
|
function seedUserPool(userId, statId, pool) {
|
|
168
168
|
const map = ensureInstanceStats(userId);
|
|
169
|
-
const next =
|
|
169
|
+
const next = setStatValue(map, statId, pool);
|
|
170
170
|
map[statId] = next[statId];
|
|
171
171
|
}
|
|
172
172
|
const rawQuest = createQuestJournal({
|
|
@@ -226,7 +226,7 @@ export function createGameContext(options) {
|
|
|
226
226
|
: { ...spawnOptions, movement: { ...spawnOptions?.movement, walkSpeed } };
|
|
227
227
|
const instanceId = entities.spawn(name, options);
|
|
228
228
|
death.revive(instanceId);
|
|
229
|
-
statsByInstance.set(instanceId, entry?.stats === undefined ? {} :
|
|
229
|
+
statsByInstance.set(instanceId, entry?.stats === undefined ? {} : seedStatValues(entry.stats));
|
|
230
230
|
return instanceId;
|
|
231
231
|
}
|
|
232
232
|
function despawnEntity(instanceId) {
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface StatValue {
|
|
2
2
|
current: number;
|
|
3
3
|
max: number;
|
|
4
4
|
min: number;
|
|
5
5
|
}
|
|
6
|
-
export interface
|
|
6
|
+
export interface StatValuePatch {
|
|
7
7
|
current?: number;
|
|
8
8
|
max?: number;
|
|
9
9
|
min?: number;
|
|
10
10
|
}
|
|
11
|
-
export type
|
|
12
|
-
export type
|
|
11
|
+
export type StatValueMap = Record<string, StatValue>;
|
|
12
|
+
export type StatCatalog = Record<string, {
|
|
13
13
|
max: number;
|
|
14
14
|
min?: number;
|
|
15
15
|
current?: number;
|
|
16
16
|
}>;
|
|
17
17
|
export type PoolDeltaResult = {
|
|
18
18
|
status: "ok";
|
|
19
|
-
map:
|
|
20
|
-
stat:
|
|
19
|
+
map: StatValueMap;
|
|
20
|
+
stat: StatValue;
|
|
21
21
|
hitMin: boolean;
|
|
22
22
|
hitMax: boolean;
|
|
23
23
|
} | {
|
|
24
24
|
status: "rejected";
|
|
25
25
|
reason: string;
|
|
26
26
|
};
|
|
27
|
-
export declare function
|
|
28
|
-
export declare function
|
|
29
|
-
export declare function applyPoolDelta(map:
|
|
30
|
-
export declare function
|
|
27
|
+
export declare function getStatValue(map: StatValueMap, statId: string): StatValue | null;
|
|
28
|
+
export declare function setStatValue(map: StatValueMap, statId: string, patch: StatValuePatch): StatValueMap;
|
|
29
|
+
export declare function applyPoolDelta(map: StatValueMap, statId: string, amount: number): PoolDeltaResult;
|
|
30
|
+
export declare function seedStatValues(catalogStats: StatCatalog): StatValueMap;
|
|
31
31
|
export interface EntityStatsApi {
|
|
32
|
-
get(instanceId: string, statId: string):
|
|
33
|
-
set(instanceId: string, statId: string, patch:
|
|
32
|
+
get(instanceId: string, statId: string): StatValue | null;
|
|
33
|
+
set(instanceId: string, statId: string, patch: StatValuePatch): boolean;
|
|
34
34
|
delta(instanceId: string, statId: string, amount: number): null | {
|
|
35
35
|
reason: string;
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
-
export declare function createEntityStatsApi(resolve: (instanceId: string) =>
|
|
38
|
+
export declare function createEntityStatsApi(resolve: (instanceId: string) => StatValueMap | undefined): EntityStatsApi;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
function clamp(value, min, max) {
|
|
2
2
|
return Math.min(max, Math.max(min, value));
|
|
3
3
|
}
|
|
4
|
-
export function
|
|
4
|
+
export function getStatValue(map, statId) {
|
|
5
5
|
return map[statId] ?? null;
|
|
6
6
|
}
|
|
7
|
-
export function
|
|
7
|
+
export function setStatValue(map, statId, patch) {
|
|
8
8
|
const existing = map[statId];
|
|
9
9
|
const max = patch.max ?? existing?.max ?? 0;
|
|
10
10
|
const min = patch.min ?? existing?.min ?? 0;
|
|
@@ -26,7 +26,7 @@ export function applyPoolDelta(map, statId, amount) {
|
|
|
26
26
|
hitMax: current === existing.max,
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
-
export function
|
|
29
|
+
export function seedStatValues(catalogStats) {
|
|
30
30
|
const map = {};
|
|
31
31
|
for (const [statId, declaration] of Object.entries(catalogStats)) {
|
|
32
32
|
const min = declaration.min ?? 0;
|
|
@@ -41,13 +41,13 @@ export function createEntityStatsApi(resolve) {
|
|
|
41
41
|
const map = resolve(instanceId);
|
|
42
42
|
if (map === undefined)
|
|
43
43
|
return null;
|
|
44
|
-
return
|
|
44
|
+
return getStatValue(map, statId);
|
|
45
45
|
},
|
|
46
46
|
set(instanceId, statId, patch) {
|
|
47
47
|
const map = resolve(instanceId);
|
|
48
48
|
if (map === undefined)
|
|
49
49
|
return false;
|
|
50
|
-
const next =
|
|
50
|
+
const next = setStatValue(map, statId, patch);
|
|
51
51
|
map[statId] = next[statId];
|
|
52
52
|
return true;
|
|
53
53
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Genre-agnostic TypeScript game engine SDK: game runtime, state store, entity scene, combat, movement, inventory, economy, quests, multiplayer primitives.",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"type": "module",
|