@jgengine/core 0.1.0 → 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.
@@ -1,5 +1,5 @@
1
1
  import type { EntityPosition } from "../scene/entityStore.js";
2
- import { type PoolStatMap } from "../scene/entityStats.js";
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): PoolStatMap | undefined;
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>;
@@ -41,6 +41,12 @@ export interface GameCameraConfig {
41
41
  dragTargetSmoothing?: number;
42
42
  distanceSmoothing?: number;
43
43
  }
44
+ export interface EntitySpriteConfig {
45
+ url: string;
46
+ width: number;
47
+ height: number;
48
+ y: number;
49
+ }
44
50
  export interface PlayableGame<TUi = unknown, TWorldOverlay = unknown> {
45
51
  game: GameDefinition;
46
52
  content: GameContextContent;
@@ -48,6 +54,8 @@ export interface PlayableGame<TUi = unknown, TWorldOverlay = unknown> {
48
54
  GameUI: TUi;
49
55
  /** Optional canvas-layer VFX component (e.g. traveling projectiles). */
50
56
  WorldOverlay?: TWorldOverlay;
57
+ /** Billboard sprites keyed by entity kind name; unmatched entities get primitive markers. */
58
+ entitySprites?: Record<string, EntitySpriteConfig>;
51
59
  /** Optional scroll-selected hotbar index for primary ability (mouse0). */
52
60
  hotbarSelection?: () => number;
53
61
  /** Third-person orbit camera tuning for the dev game player shell. */
@@ -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 PoolStatCatalog } from "../scene/entityStats.js";
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?: PoolStatCatalog;
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, seedPoolStats, setPoolStat, } from "../scene/entityStats.js";
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 = setPoolStat(map, statId, pool);
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 ? {} : seedPoolStats(entry.stats));
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 PoolStat {
1
+ export interface StatValue {
2
2
  current: number;
3
3
  max: number;
4
4
  min: number;
5
5
  }
6
- export interface PoolStatPatch {
6
+ export interface StatValuePatch {
7
7
  current?: number;
8
8
  max?: number;
9
9
  min?: number;
10
10
  }
11
- export type PoolStatMap = Record<string, PoolStat>;
12
- export type PoolStatCatalog = Record<string, {
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: PoolStatMap;
20
- stat: PoolStat;
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 getPoolStat(map: PoolStatMap, statId: string): PoolStat | null;
28
- export declare function setPoolStat(map: PoolStatMap, statId: string, patch: PoolStatPatch): PoolStatMap;
29
- export declare function applyPoolDelta(map: PoolStatMap, statId: string, amount: number): PoolDeltaResult;
30
- export declare function seedPoolStats(catalogStats: PoolStatCatalog): PoolStatMap;
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): PoolStat | null;
33
- set(instanceId: string, statId: string, patch: PoolStatPatch): boolean;
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) => PoolStatMap | undefined): EntityStatsApi;
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 getPoolStat(map, statId) {
4
+ export function getStatValue(map, statId) {
5
5
  return map[statId] ?? null;
6
6
  }
7
- export function setPoolStat(map, statId, patch) {
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 seedPoolStats(catalogStats) {
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 getPoolStat(map, statId);
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 = setPoolStat(map, statId, patch);
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.1.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",