@jgengine/core 0.2.0 → 0.3.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,3 +1,4 @@
1
+ import type { PositionedPrompt } from "../interaction/proximityPrompt.js";
1
2
  import type { GameContext, GameContextContent } from "../runtime/gameContext.js";
2
3
  import type { GameDefinition, GameLoop } from "./defineGame.js";
3
4
  export interface CameraFollowState {
@@ -58,6 +59,8 @@ export interface PlayableGame<TUi = unknown, TWorldOverlay = unknown> {
58
59
  entitySprites?: Record<string, EntitySpriteConfig>;
59
60
  /** Optional scroll-selected hotbar index for primary ability (mouse0). */
60
61
  hotbarSelection?: () => number;
62
+ /** Positioned proximity prompts for the interact key + HUD; single source shared with useActivePrompt. */
63
+ prompts?: (ctx: GameContext) => readonly PositionedPrompt[];
61
64
  /** Third-person orbit camera tuning for the dev game player shell. */
62
65
  camera?: GameCameraConfig;
63
66
  }
@@ -33,6 +33,16 @@ export declare function toActionStateBindingMap<TAction extends string, TCode ex
33
33
  export declare function hotbarSlotBindings(count: number, options?: {
34
34
  action?: (slot: number) => string;
35
35
  }): ActionCodesMap;
36
+ /** 0-based slot index for a hotbar-slot action name (`slot1`, `hotbarSlot1`), or null. */
37
+ export declare function hotbarSlotActionIndex(action: string): number | null;
38
+ export declare function isHotbarSlotAction(action: string): boolean;
39
+ /**
40
+ * Convention mapping a bound input action to the command it fires: the action's
41
+ * own name when a command by that name exists, otherwise a `ui.<action>`
42
+ * fallback. Reserved actions (consumed natively by the shell) and hotbar slots
43
+ * yield null so the shell can handle them itself.
44
+ */
45
+ export declare function resolveActionCommand(action: string, has: (command: string) => boolean, reserved: ReadonlySet<string>): string | null;
36
46
  export declare function bindingLabel(code: string): string;
37
47
  /** Short display label for an action's first bound code, or null when unbound. */
38
48
  export declare function actionLabel(map: ActionCodesMap, action: string): string | null;
@@ -67,6 +67,29 @@ export function hotbarSlotBindings(count, options) {
67
67
  }
68
68
  return map;
69
69
  }
70
+ const HOTBAR_SLOT_ACTION = /^(?:hotbar)?slot(\d+)$/i;
71
+ /** 0-based slot index for a hotbar-slot action name (`slot1`, `hotbarSlot1`), or null. */
72
+ export function hotbarSlotActionIndex(action) {
73
+ const match = HOTBAR_SLOT_ACTION.exec(action);
74
+ return match === null ? null : Number(match[1]) - 1;
75
+ }
76
+ export function isHotbarSlotAction(action) {
77
+ return HOTBAR_SLOT_ACTION.test(action);
78
+ }
79
+ /**
80
+ * Convention mapping a bound input action to the command it fires: the action's
81
+ * own name when a command by that name exists, otherwise a `ui.<action>`
82
+ * fallback. Reserved actions (consumed natively by the shell) and hotbar slots
83
+ * yield null so the shell can handle them itself.
84
+ */
85
+ export function resolveActionCommand(action, has, reserved) {
86
+ if (reserved.has(action) || isHotbarSlotAction(action))
87
+ return null;
88
+ if (has(action))
89
+ return action;
90
+ const uiCommand = `ui.${action}`;
91
+ return has(uiCommand) ? uiCommand : null;
92
+ }
70
93
  const CODE_LABELS = {
71
94
  mouse0: "LMB",
72
95
  mouse1: "MMB",
@@ -12,7 +12,8 @@ import { type QuestJournal } from "../game/quest.js";
12
12
  import { type Social } from "../game/social.js";
13
13
  import { type TradeField, type TradeSystem } from "../game/trade.js";
14
14
  import { type Unlocks } from "../game/unlocks.js";
15
- import { type InventorySet } from "../inventory/inventoryModel.js";
15
+ import { type InventoryLayout, type InventorySet } from "../inventory/inventoryModel.js";
16
+ import type { ProximityPrompt } from "../interaction/proximityPrompt.js";
16
17
  import { type ItemUseHandler, type ItemUseInput, type ItemUseRejection, type ItemUseResult } from "../item/use.js";
17
18
  import { type WeaponStats } from "../item/weapon.js";
18
19
  import { type PoseAllowedStates, type PoseState } from "../movement/poseState.js";
@@ -28,6 +29,7 @@ export interface GameContextItemEntry {
28
29
  weapon?: Record<string, unknown>;
29
30
  trade?: TradeField;
30
31
  }
32
+ export type CatalogEntityRole = "player" | "enemy" | "hostile" | "npc" | "vehicle";
31
33
  export interface GameContextEntityEntry {
32
34
  stats?: StatCatalog;
33
35
  receive?: ReceiveMap;
@@ -35,11 +37,19 @@ export interface GameContextEntityEntry {
35
37
  movement?: PoseAllowedStates & {
36
38
  walkSpeed?: number;
37
39
  };
38
- role?: string;
40
+ role?: CatalogEntityRole;
41
+ }
42
+ export interface GameContextObjectEntry {
43
+ proximityPrompt?: ProximityPrompt;
44
+ breakable?: false | {
45
+ baseBreakTime: number;
46
+ };
47
+ slotInventory?: InventoryLayout;
39
48
  }
40
49
  export interface GameContextContent {
41
50
  itemById?(itemId: string): GameContextItemEntry | null | undefined;
42
51
  entityById?(catalogId: string): GameContextEntityEntry | null | undefined;
52
+ objectById?(catalogId: string): GameContextObjectEntry | null | undefined;
43
53
  }
44
54
  export interface GameContextOptions<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> {
45
55
  definition: GameDefinition<TAssetRef, TMultiplayer>;
@@ -50,6 +60,9 @@ export interface GameContextOptions<TAssetRef extends ModelAssetRef = ModelAsset
50
60
  };
51
61
  now?: () => number;
52
62
  }
63
+ export interface SceneObjectContext extends ObjectStore {
64
+ catalog(instanceId: string): GameContextObjectEntry | null;
65
+ }
53
66
  export interface SceneEntityContext {
54
67
  spawn(name: string, options?: SpawnOptions): string;
55
68
  despawn(instanceId: string): boolean;
@@ -102,7 +115,7 @@ export interface GameContextItemUse {
102
115
  }
103
116
  export interface GameContext {
104
117
  scene: {
105
- object: ObjectStore;
118
+ object: SceneObjectContext;
106
119
  entity: SceneEntityContext;
107
120
  };
108
121
  game: {
@@ -44,6 +44,10 @@ export function createGameContext(options) {
44
44
  const entity = entities.get(instanceId);
45
45
  return entity === null ? undefined : content.entityById?.(entity.name);
46
46
  }
47
+ function catalogObject(instanceId) {
48
+ const object = objects.get(instanceId);
49
+ return object === null ? undefined : content.objectById?.(object.catalogId);
50
+ }
47
51
  const spatial = createSpatialApi({
48
52
  resolvePosition: (instanceId) => entities.get(instanceId)?.position,
49
53
  candidates: () => entities.list().map((entity) => entity.id),
@@ -282,9 +286,13 @@ export function createGameContext(options) {
282
286
  getStat: weapon.getStat,
283
287
  now,
284
288
  }), ["fireProjectile", "settleProjectile"], signal.notify);
289
+ const sceneObjects = {
290
+ ...objects,
291
+ catalog: (instanceId) => catalogObject(instanceId) ?? null,
292
+ };
285
293
  const ctx = {
286
294
  scene: {
287
- object: objects,
295
+ object: sceneObjects,
288
296
  entity: {
289
297
  spawn: spawnEntity,
290
298
  despawn: despawnEntity,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jgengine/core",
3
- "version": "0.2.0",
3
+ "version": "0.3.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",