@jgengine/core 0.6.0 → 0.7.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.
Files changed (222) hide show
  1. package/CHANGELOG.md +167 -2
  2. package/dist/ai/crowd.d.ts +36 -0
  3. package/dist/ai/crowd.js +213 -0
  4. package/dist/ai/jobBoard.d.ts +49 -0
  5. package/dist/ai/jobBoard.js +155 -0
  6. package/dist/ai/spawnDirector.d.ts +56 -0
  7. package/dist/ai/spawnDirector.js +150 -0
  8. package/dist/ai/threat.d.ts +25 -0
  9. package/dist/ai/threat.js +67 -0
  10. package/dist/audio/audioFalloff.d.ts +38 -0
  11. package/dist/audio/audioFalloff.js +32 -0
  12. package/dist/board/laneBoard.d.ts +63 -0
  13. package/dist/board/laneBoard.js +114 -0
  14. package/dist/board/timelineBoard.d.ts +36 -0
  15. package/dist/board/timelineBoard.js +70 -0
  16. package/dist/cards/cardPile.d.ts +63 -0
  17. package/dist/cards/cardPile.js +166 -0
  18. package/dist/cards/modifierPipeline.d.ts +33 -0
  19. package/dist/cards/modifierPipeline.js +48 -0
  20. package/dist/combat/abilityKit.d.ts +42 -0
  21. package/dist/combat/abilityKit.js +129 -0
  22. package/dist/combat/animationState.d.ts +47 -0
  23. package/dist/combat/animationState.js +101 -0
  24. package/dist/combat/attackTags.d.ts +14 -0
  25. package/dist/combat/attackTags.js +26 -0
  26. package/dist/combat/breakMeters.d.ts +32 -0
  27. package/dist/combat/breakMeters.js +51 -0
  28. package/dist/combat/comboString.d.ts +35 -0
  29. package/dist/combat/comboString.js +59 -0
  30. package/dist/combat/death.d.ts +8 -0
  31. package/dist/combat/death.js +5 -2
  32. package/dist/combat/defensiveWindow.d.ts +34 -0
  33. package/dist/combat/defensiveWindow.js +66 -0
  34. package/dist/combat/downed.d.ts +35 -0
  35. package/dist/combat/downed.js +104 -0
  36. package/dist/combat/effects.d.ts +11 -0
  37. package/dist/combat/effects.js +22 -13
  38. package/dist/combat/hitReaction.d.ts +23 -0
  39. package/dist/combat/hitReaction.js +20 -0
  40. package/dist/combat/resistance.d.ts +13 -0
  41. package/dist/combat/resistance.js +46 -0
  42. package/dist/combat/telegraph.d.ts +27 -0
  43. package/dist/combat/telegraph.js +37 -0
  44. package/dist/crafting/crop.d.ts +59 -0
  45. package/dist/crafting/crop.js +181 -0
  46. package/dist/crafting/production.d.ts +64 -0
  47. package/dist/crafting/production.js +130 -0
  48. package/dist/crafting/recipe.d.ts +62 -0
  49. package/dist/crafting/recipe.js +101 -0
  50. package/dist/economy/sharedWallet.d.ts +28 -0
  51. package/dist/economy/sharedWallet.js +62 -0
  52. package/dist/economy/techTree.d.ts +40 -0
  53. package/dist/economy/techTree.js +66 -0
  54. package/dist/game/cosmetics.d.ts +25 -0
  55. package/dist/game/cosmetics.js +51 -0
  56. package/dist/game/defineGame.d.ts +2 -0
  57. package/dist/game/events.d.ts +62 -0
  58. package/dist/game/lootFilter.d.ts +33 -0
  59. package/dist/game/lootFilter.js +48 -0
  60. package/dist/game/ping.d.ts +67 -0
  61. package/dist/game/ping.js +86 -0
  62. package/dist/game/playableGame.d.ts +281 -4
  63. package/dist/game/race.d.ts +95 -0
  64. package/dist/game/race.js +216 -0
  65. package/dist/game/runDraft.d.ts +34 -0
  66. package/dist/game/runDraft.js +97 -0
  67. package/dist/game/social.d.ts +27 -0
  68. package/dist/game/social.js +16 -0
  69. package/dist/game/worldItem.d.ts +92 -0
  70. package/dist/game/worldItem.js +132 -0
  71. package/dist/input/axisInput.d.ts +43 -0
  72. package/dist/input/axisInput.js +70 -0
  73. package/dist/input/pointer.d.ts +24 -0
  74. package/dist/input/pointer.js +17 -0
  75. package/dist/interaction/contextMenu.d.ts +30 -0
  76. package/dist/interaction/contextMenu.js +22 -0
  77. package/dist/interaction/qte.d.ts +20 -0
  78. package/dist/interaction/qte.js +24 -0
  79. package/dist/interaction/skillCheck.d.ts +20 -0
  80. package/dist/interaction/skillCheck.js +26 -0
  81. package/dist/inventory/shapedGrid.d.ts +56 -0
  82. package/dist/inventory/shapedGrid.js +176 -0
  83. package/dist/inventory/storageTier.d.ts +52 -0
  84. package/dist/inventory/storageTier.js +77 -0
  85. package/dist/item/affix.d.ts +54 -0
  86. package/dist/item/affix.js +133 -0
  87. package/dist/item/durability.d.ts +51 -0
  88. package/dist/item/durability.js +78 -0
  89. package/dist/item/modularItem.d.ts +46 -0
  90. package/dist/item/modularItem.js +75 -0
  91. package/dist/meta/changelog.d.ts +1 -1
  92. package/dist/meta/changelog.js +35 -1
  93. package/dist/movement/dash.d.ts +37 -0
  94. package/dist/movement/dash.js +65 -0
  95. package/dist/multiplayer/combatSnapshot.d.ts +45 -0
  96. package/dist/multiplayer/combatSnapshot.js +89 -0
  97. package/dist/multiplayer/lagCompensation.d.ts +43 -0
  98. package/dist/multiplayer/lagCompensation.js +154 -0
  99. package/dist/multiplayer/matchmaking.d.ts +33 -0
  100. package/dist/multiplayer/matchmaking.js +60 -0
  101. package/dist/multiplayer/simultaneousCommit.d.ts +34 -0
  102. package/dist/multiplayer/simultaneousCommit.js +53 -0
  103. package/dist/nav/navGrid.d.ts +46 -0
  104. package/dist/nav/navGrid.js +248 -0
  105. package/dist/nav/pathFollow.d.ts +29 -0
  106. package/dist/nav/pathFollow.js +67 -0
  107. package/dist/physics/buoyancy.d.ts +49 -0
  108. package/dist/physics/buoyancy.js +112 -0
  109. package/dist/physics/carryable.d.ts +46 -0
  110. package/dist/physics/carryable.js +110 -0
  111. package/dist/physics/damageZones.d.ts +56 -0
  112. package/dist/physics/damageZones.js +107 -0
  113. package/dist/physics/forceVolume.d.ts +48 -0
  114. package/dist/physics/forceVolume.js +119 -0
  115. package/dist/physics/physicsWorld.d.ts +83 -0
  116. package/dist/physics/physicsWorld.js +261 -1
  117. package/dist/physics/ragdoll.d.ts +49 -0
  118. package/dist/physics/ragdoll.js +103 -0
  119. package/dist/physics/spatialGrid.d.ts +41 -0
  120. package/dist/physics/spatialGrid.js +138 -0
  121. package/dist/physics/structure.d.ts +83 -0
  122. package/dist/physics/structure.js +0 -0
  123. package/dist/physics/traversal.d.ts +75 -0
  124. package/dist/physics/traversal.js +138 -0
  125. package/dist/physics/vehicleBody.d.ts +93 -0
  126. package/dist/physics/vehicleBody.js +234 -0
  127. package/dist/runtime/gameContext.d.ts +64 -2
  128. package/dist/runtime/gameContext.js +154 -4
  129. package/dist/runtime/hostPersistence.d.ts +18 -0
  130. package/dist/runtime/hostPersistence.js +4 -0
  131. package/dist/runtime/persistenceScope.d.ts +32 -0
  132. package/dist/runtime/persistenceScope.js +64 -0
  133. package/dist/scene/autoTarget.d.ts +15 -0
  134. package/dist/scene/autoTarget.js +54 -0
  135. package/dist/scene/behaviors.d.ts +13 -1
  136. package/dist/scene/behaviors.js +3 -0
  137. package/dist/scene/captureCheck.d.ts +7 -0
  138. package/dist/scene/captureCheck.js +11 -0
  139. package/dist/scene/entityStore.d.ts +1 -1
  140. package/dist/scene/form.d.ts +40 -0
  141. package/dist/scene/form.js +56 -0
  142. package/dist/scene/mount.d.ts +62 -0
  143. package/dist/scene/mount.js +107 -0
  144. package/dist/scene/possession.d.ts +30 -0
  145. package/dist/scene/possession.js +51 -0
  146. package/dist/scene/roster.d.ts +25 -0
  147. package/dist/scene/roster.js +57 -0
  148. package/dist/scene/selection.d.ts +30 -0
  149. package/dist/scene/selection.js +38 -0
  150. package/dist/scene/stationClaim.d.ts +43 -0
  151. package/dist/scene/stationClaim.js +109 -0
  152. package/dist/sensor/frustumSensor.d.ts +60 -0
  153. package/dist/sensor/frustumSensor.js +104 -0
  154. package/dist/sensor/hiddenStateProbe.d.ts +31 -0
  155. package/dist/sensor/hiddenStateProbe.js +40 -0
  156. package/dist/sensor/recordingBuffer.d.ts +29 -0
  157. package/dist/sensor/recordingBuffer.js +61 -0
  158. package/dist/sensor/revealQuery.d.ts +22 -0
  159. package/dist/sensor/revealQuery.js +36 -0
  160. package/dist/session/contestedChannel.d.ts +36 -0
  161. package/dist/session/contestedChannel.js +100 -0
  162. package/dist/session/extraction.d.ts +54 -0
  163. package/dist/session/extraction.js +108 -0
  164. package/dist/session/ring.d.ts +36 -0
  165. package/dist/session/ring.js +88 -0
  166. package/dist/session/roundState.d.ts +55 -0
  167. package/dist/session/roundState.js +129 -0
  168. package/dist/stats/accumulatorMeter.d.ts +31 -0
  169. package/dist/stats/accumulatorMeter.js +81 -0
  170. package/dist/stats/eventMeter.d.ts +34 -0
  171. package/dist/stats/eventMeter.js +76 -0
  172. package/dist/stats/rollCheck.d.ts +15 -0
  173. package/dist/stats/rollCheck.js +15 -0
  174. package/dist/survival/decayMeter.d.ts +59 -0
  175. package/dist/survival/decayMeter.js +95 -0
  176. package/dist/survival/moodle.d.ts +48 -0
  177. package/dist/survival/moodle.js +91 -0
  178. package/dist/survival/regionHealth.d.ts +72 -0
  179. package/dist/survival/regionHealth.js +129 -0
  180. package/dist/tactics/predictiveQuery.d.ts +34 -0
  181. package/dist/tactics/predictiveQuery.js +44 -0
  182. package/dist/tactics/snapshot.d.ts +18 -0
  183. package/dist/tactics/snapshot.js +65 -0
  184. package/dist/tactics/surface.d.ts +52 -0
  185. package/dist/tactics/surface.js +135 -0
  186. package/dist/tactics/tacticalGrid.d.ts +57 -0
  187. package/dist/tactics/tacticalGrid.js +244 -0
  188. package/dist/time/beatClock.d.ts +40 -0
  189. package/dist/time/beatClock.js +73 -0
  190. package/dist/turn/commit.d.ts +27 -0
  191. package/dist/turn/commit.js +72 -0
  192. package/dist/turn/turnLoop.d.ts +58 -0
  193. package/dist/turn/turnLoop.js +216 -0
  194. package/dist/world/buildPermissions.d.ts +47 -0
  195. package/dist/world/buildPermissions.js +134 -0
  196. package/dist/world/carve.d.ts +105 -0
  197. package/dist/world/carve.js +175 -0
  198. package/dist/world/connectors.d.ts +40 -0
  199. package/dist/world/connectors.js +67 -0
  200. package/dist/world/envField.d.ts +71 -0
  201. package/dist/world/envField.js +96 -0
  202. package/dist/world/fog.d.ts +40 -0
  203. package/dist/world/fog.js +137 -0
  204. package/dist/world/markers.d.ts +52 -0
  205. package/dist/world/markers.js +96 -0
  206. package/dist/world/minimap.d.ts +42 -0
  207. package/dist/world/minimap.js +72 -0
  208. package/dist/world/placedStructureStore.d.ts +39 -0
  209. package/dist/world/placedStructureStore.js +95 -0
  210. package/dist/world/placementController.d.ts +48 -0
  211. package/dist/world/placementController.js +97 -0
  212. package/dist/world/realm.d.ts +61 -0
  213. package/dist/world/realm.js +66 -0
  214. package/dist/world/support.d.ts +27 -0
  215. package/dist/world/support.js +75 -0
  216. package/dist/world/terraform.d.ts +54 -0
  217. package/dist/world/terraform.js +186 -0
  218. package/dist/world/walls.d.ts +61 -0
  219. package/dist/world/walls.js +221 -0
  220. package/dist/world/weather.d.ts +92 -0
  221. package/dist/world/weather.js +169 -0
  222. package/package.json +1 -1
@@ -1,7 +1,10 @@
1
1
  import { type OnDeathSpec } from "../combat/death.js";
2
- import { type EffectInput, type EffectResult, type ReceiveMap, type SingleTargetEffectInput } from "../combat/effects.js";
2
+ import { type EffectInput, type EffectResult, type EffectVia, type ReceiveMap, type SingleTargetEffectInput } from "../combat/effects.js";
3
3
  import { type ProjectileSystem } from "../combat/projectiles.js";
4
+ import { type HitReaction, type HitReactionConfig } from "../combat/hitReaction.js";
5
+ import { type TelegraphShape } from "../combat/telegraph.js";
4
6
  import { type CommandDefinition, type CommandResult } from "../commands/commandRegistry.js";
7
+ import { type Cosmetics } from "../game/cosmetics.js";
5
8
  import type { GameDefinition } from "../game/defineGame.js";
6
9
  import { type GameEventMap, type GameEvents } from "../game/events.js";
7
10
  import { type GameFeed } from "../game/feed.js";
@@ -9,18 +12,23 @@ import { type Leaderboard } from "../game/leaderboard.js";
9
12
  import { type Loadouts } from "../game/loadout.js";
10
13
  import { type Drop, type LootTableDef } from "../game/lootTable.js";
11
14
  import { type QuestJournal } from "../game/quest.js";
15
+ import { type WorldItemRecord, type WorldItemSpawnInput } from "../game/worldItem.js";
12
16
  import { type Social } from "../game/social.js";
13
17
  import { type TradeField, type TradeSystem } from "../game/trade.js";
14
18
  import { type Unlocks } from "../game/unlocks.js";
15
19
  import { type InventoryLayout, type InventorySet } from "../inventory/inventoryModel.js";
20
+ import type { ContextVerb } from "../interaction/contextMenu.js";
16
21
  import type { ProximityPrompt } from "../interaction/proximityPrompt.js";
17
22
  import { type ItemUseHandler, type ItemUseInput, type ItemUseRejection, type ItemUseResult } from "../item/use.js";
18
23
  import { type WeaponStats } from "../item/weapon.js";
19
24
  import { type PoseAllowedStates, type PoseState } from "../movement/poseState.js";
20
25
  import type { ModelAssetRef } from "../scene/assetCatalog.js";
21
26
  import { type EntityStatsApi, type StatCatalog } from "../scene/entityStats.js";
22
- import type { EntityPose, SceneEntity, SpawnOptions } from "../scene/entityStore.js";
27
+ import type { EntityPose, EntityPosition, SceneEntity, SpawnOptions } from "../scene/entityStore.js";
28
+ import { type Forms } from "../scene/form.js";
23
29
  import { type ObjectStore } from "../scene/objectStore.js";
30
+ import { type Roster } from "../scene/roster.js";
31
+ import { type Possession } from "../scene/possession.js";
24
32
  import { type SpatialApi } from "../scene/spatial.js";
25
33
  import { type CycleTargetOptions } from "../scene/targeting.js";
26
34
  import { type Stats } from "../stats/statModifiers.js";
@@ -29,6 +37,10 @@ export interface GameContextItemEntry {
29
37
  use?: string;
30
38
  weapon?: Record<string, unknown>;
31
39
  trade?: TradeField;
40
+ /** Rarity id read by the `worldItem` loot-filter/render binding when this item drops to the ground (#32/#33). */
41
+ rarity?: string;
42
+ /** Base item type read by the loot-filter rule evaluator (#33); defaults to the item id when absent. */
43
+ baseType?: string;
32
44
  }
33
45
  export type CatalogEntityRole = "player" | "enemy" | "hostile" | "npc" | "vehicle";
34
46
  export interface GameContextEntityEntry {
@@ -39,6 +51,8 @@ export interface GameContextEntityEntry {
39
51
  walkSpeed?: number;
40
52
  };
41
53
  role?: CatalogEntityRole;
54
+ /** Right-click context-menu verbs for this entity (#31). */
55
+ verbs?: readonly ContextVerb[];
42
56
  }
43
57
  export interface GameContextObjectEntry {
44
58
  proximityPrompt?: ProximityPrompt;
@@ -46,6 +60,8 @@ export interface GameContextObjectEntry {
46
60
  baseBreakTime: number;
47
61
  };
48
62
  slotInventory?: InventoryLayout;
63
+ /** Right-click context-menu verbs for this object (#31). */
64
+ verbs?: readonly ContextVerb[];
49
65
  }
50
66
  export interface GameContextContent {
51
67
  itemById?(itemId: string): GameContextItemEntry | null | undefined;
@@ -70,6 +86,31 @@ export interface FloatTextInput {
70
86
  text?: string;
71
87
  kind?: string;
72
88
  amount?: number;
89
+ hitType?: string;
90
+ element?: string;
91
+ crit?: boolean;
92
+ scale?: number;
93
+ }
94
+ export interface TelegraphInput {
95
+ from: string;
96
+ shape: TelegraphShape;
97
+ at: [number, number, number];
98
+ dir?: number;
99
+ windupMs: number;
100
+ kind?: string;
101
+ effect?: {
102
+ effect: string;
103
+ via?: EffectVia;
104
+ radius?: number;
105
+ falloff?: "linear" | "none";
106
+ los?: boolean;
107
+ };
108
+ }
109
+ export interface HitReactionInput {
110
+ from: string;
111
+ to: string;
112
+ config: HitReactionConfig;
113
+ power?: number;
73
114
  }
74
115
  export interface SceneEntityContext {
75
116
  spawn(name: string, options?: SpawnOptions): string;
@@ -79,6 +120,8 @@ export interface SceneEntityContext {
79
120
  list(): readonly SceneEntity[];
80
121
  stats: EntityStatsApi;
81
122
  floatText(input: FloatTextInput): void;
123
+ telegraph(input: TelegraphInput): () => void;
124
+ hitReaction(input: HitReactionInput): HitReaction | null;
82
125
  setTarget(fromId: string, toId: string | null): void;
83
126
  getTarget(fromId: string): string | null;
84
127
  cycleTarget(fromId: string, options?: CycleTargetOptions): string | null;
@@ -93,6 +136,21 @@ export interface SceneEntityContext {
93
136
  hasLineOfSight: SpatialApi["hasLineOfSight"];
94
137
  queryArc: SpatialApi["queryArc"];
95
138
  moveToward: SpatialApi["moveToward"];
139
+ form: Forms;
140
+ }
141
+ export type WorldItemPickupResult = {
142
+ status: "ok";
143
+ record: WorldItemRecord;
144
+ } | {
145
+ status: "rejected";
146
+ reason: string;
147
+ };
148
+ export interface SceneWorldItemContext {
149
+ spawn(input: WorldItemSpawnInput): WorldItemRecord;
150
+ get(instanceId: string): WorldItemRecord | null;
151
+ list(): readonly WorldItemRecord[];
152
+ nearestInRadius(from: EntityPosition, radius: number, filter?: (record: WorldItemRecord) => boolean): string | null;
153
+ pickup(instanceId: string, userId: string): WorldItemPickupResult;
96
154
  }
97
155
  export interface GameContextCommands {
98
156
  define<TInput>(name: string, definition: CommandDefinition<GameContext, TInput>): void;
@@ -126,6 +184,7 @@ export interface GameContext {
126
184
  scene: {
127
185
  object: SceneObjectContext;
128
186
  entity: SceneEntityContext;
187
+ worldItem: SceneWorldItemContext;
129
188
  };
130
189
  game: {
131
190
  commands: GameContextCommands;
@@ -138,6 +197,7 @@ export interface GameContext {
138
197
  unlocks: Unlocks;
139
198
  economy: GameContextEconomy;
140
199
  leaderboard: Leaderboard;
200
+ roster: Roster;
141
201
  };
142
202
  player: {
143
203
  userId: string;
@@ -149,6 +209,8 @@ export interface GameContext {
149
209
  reason: string;
150
210
  } | null;
151
211
  movement: PoseState;
212
+ possession: Possession;
213
+ cosmetics: Cosmetics;
152
214
  };
153
215
  item: {
154
216
  use: GameContextItemUse;
@@ -1,14 +1,18 @@
1
- import { createDeathSystem, deathReasonFromEffect } from "../combat/death.js";
1
+ import { createDeathSystem, deathReasonFromEffect, normalizeOnDeath } from "../combat/death.js";
2
2
  import { createEffectSystem, } from "../combat/effects.js";
3
3
  import { createProjectileSystem } from "../combat/projectiles.js";
4
+ import { resolveHitReaction, } from "../combat/hitReaction.js";
5
+ import { pointInTelegraph, } from "../combat/telegraph.js";
4
6
  import { createCommandRegistry, } from "../commands/commandRegistry.js";
5
7
  import { balance as walletBalance, canAfford as walletCanAfford, charge as walletCharge, chargeAll as walletChargeAll, createEmptyWallet, grant as walletGrant, } from "../economy/wallet.js";
8
+ import { createCosmetics } from "../game/cosmetics.js";
6
9
  import { createGameEvents } from "../game/events.js";
7
10
  import { createGameFeed } from "../game/feed.js";
8
11
  import { createLeaderboard } from "../game/leaderboard.js";
9
12
  import { createLoadouts } from "../game/loadout.js";
10
13
  import { createLootRegistry, grantDrops } from "../game/lootTable.js";
11
14
  import { createQuestJournal } from "../game/quest.js";
15
+ import { createWorldItemStore, resolveDeathDrops, DEFAULT_RARITY, WORLD_ITEM_ENTITY_NAME, } from "../game/worldItem.js";
12
16
  import { createSocial } from "../game/social.js";
13
17
  import { createTradeSystem } from "../game/trade.js";
14
18
  import { createUnlocks } from "../game/unlocks.js";
@@ -17,7 +21,10 @@ import { createItemUse, } from "../item/use.js";
17
21
  import { createWeaponStats } from "../item/weapon.js";
18
22
  import { createPoseState } from "../movement/poseState.js";
19
23
  import { createEntityStatsApi, seedStatValues, setStatValue, } from "../scene/entityStats.js";
24
+ import { createForms } from "../scene/form.js";
20
25
  import { createObjectStore } from "../scene/objectStore.js";
26
+ import { createRoster } from "../scene/roster.js";
27
+ import { createPossession } from "../scene/possession.js";
21
28
  import { createSpatialApi } from "../scene/spatial.js";
22
29
  import { createTargeting } from "../scene/targeting.js";
23
30
  import { createStats } from "../stats/statModifiers.js";
@@ -80,17 +87,29 @@ export function createGameContext(options) {
80
87
  const feed = createGameFeed();
81
88
  const lootRegistry = createLootRegistry();
82
89
  const unlocks = notifyAfter(createUnlocks(), ["grant", "hydrate"], signal.notify);
83
- const rawSocial = createSocial({ events, now });
90
+ const rawSocial = createSocial({
91
+ events,
92
+ now,
93
+ emotes: {
94
+ entities: { get: (id) => entities.get(id) },
95
+ spatial: { inRadius: (center, radius, filter) => spatial.inRadius(center, radius, filter) },
96
+ },
97
+ });
84
98
  const social = {
85
99
  friends: notifyAfter(rawSocial.friends, ["request", "accept", "remove", "block", "hydrate"], signal.notify),
86
100
  party: notifyAfter(rawSocial.party, ["invite", "accept", "kick", "leave", "promote"], signal.notify),
87
101
  presence: rawSocial.presence,
102
+ emotes: rawSocial.emotes,
88
103
  };
89
104
  const leaderboard = notifyAfter(createLeaderboard(), ["increment", "hydrate"], signal.notify);
105
+ const roster = notifyAfter(createRoster({ now }), ["capture", "release", "setEquipped", "hydrate"], signal.notify);
90
106
  const playerStats = createStats({});
91
107
  const pose = createPoseState((instanceId) => catalogEntry(instanceId)?.movement);
92
108
  const commandRegistry = createCommandRegistry();
93
109
  const itemUse = createItemUse((itemId) => content.itemById?.(itemId)?.use);
110
+ const possession = notifyAfter(createPossession({ entities, events }), ["possess", "own", "disown"], signal.notify);
111
+ const forms = notifyAfter(createForms({ entities, time, events }), ["shapeshift", "revert"], signal.notify);
112
+ const cosmetics = notifyAfter(createCosmetics({ events }), ["apply", "equip", "hydrate"], signal.notify);
94
113
  const inventoryDeclarations = definition.inventories ?? {};
95
114
  const inventoryIds = Object.keys(inventoryDeclarations);
96
115
  const layouts = {};
@@ -242,6 +261,37 @@ export function createGameContext(options) {
242
261
  pose.clear(instanceId);
243
262
  return existed;
244
263
  }
264
+ const worldItems = notifyAfter(createWorldItemStore({
265
+ spawnEntity: (position) => entities.spawn(WORLD_ITEM_ENTITY_NAME, { position, role: "prop" }),
266
+ despawnEntity,
267
+ resolvePosition: (instanceId) => entities.get(instanceId)?.position,
268
+ }), ["spawn", "take"], signal.notify);
269
+ function spawnWorldItem(input) {
270
+ const record = worldItems.spawn(input);
271
+ events.emit("worldItem.dropped", {
272
+ instanceId: record.instanceId,
273
+ itemId: record.itemId,
274
+ rarity: record.rarity,
275
+ count: record.count,
276
+ position: [input.position[0], input.position[1], input.position[2]],
277
+ ...(record.source !== undefined ? { source: record.source } : {}),
278
+ });
279
+ return record;
280
+ }
281
+ function pickupWorldItem(instanceId, userId) {
282
+ const record = worldItems.take(instanceId);
283
+ if (record === null)
284
+ return { status: "rejected", reason: "not-found" };
285
+ loot.grantToPlayer(userId, [{ item: record.itemId, count: record.count }], "worldItem.pickup");
286
+ events.emit("worldItem.picked_up", {
287
+ instanceId,
288
+ userId,
289
+ itemId: record.itemId,
290
+ rarity: record.rarity,
291
+ count: record.count,
292
+ });
293
+ return { status: "ok", record };
294
+ }
245
295
  const death = createDeathSystem({
246
296
  resolveOnDeath: (instanceId) => catalogEntry(instanceId)?.onDeath,
247
297
  resolveIdentity(instanceId) {
@@ -268,7 +318,10 @@ export function createGameContext(options) {
268
318
  getStat: weapon.getStat,
269
319
  spatial: combatSpatial,
270
320
  onLethal(instanceId, lethalCtx) {
271
- const catalogId = entities.get(instanceId)?.name;
321
+ const dyingEntity = entities.get(instanceId);
322
+ const catalogId = dyingEntity?.name;
323
+ const position = dyingEntity?.position;
324
+ const normalizedOnDeath = normalizeOnDeath(catalogEntry(instanceId)?.onDeath);
272
325
  const reason = deathReasonFromEffect({
273
326
  ...lethalCtx,
274
327
  userIdOf: (id) => (id === player.userId ? player.userId : undefined),
@@ -278,7 +331,23 @@ export function createGameContext(options) {
278
331
  resolution.drops.length > 0 &&
279
332
  reason.kind === "player_kill" &&
280
333
  reason.killerUserId === player.userId) {
281
- loot.grantToPlayer(player.userId, resolution.drops, catalogId);
334
+ if (normalizedOnDeath.dropMode === "world" && position !== undefined) {
335
+ const resolved = resolveDeathDrops(resolution.drops, {
336
+ mode: "world",
337
+ origin: position,
338
+ resolveRarity: (itemId) => content.itemById?.(itemId)?.rarity ?? DEFAULT_RARITY,
339
+ resolveBaseType: (itemId) => content.itemById?.(itemId)?.baseType ?? itemId,
340
+ scatter: normalizedOnDeath.scatter,
341
+ ...(catalogId !== undefined ? { source: catalogId } : {}),
342
+ });
343
+ for (const spawn of resolved.worldSpawns)
344
+ spawnWorldItem(spawn);
345
+ if (resolved.grants.length > 0)
346
+ loot.grantToPlayer(player.userId, resolved.grants, catalogId);
347
+ }
348
+ else {
349
+ loot.grantToPlayer(player.userId, resolution.drops, catalogId);
350
+ }
282
351
  }
283
352
  },
284
353
  }), ["applyEffect"], signal.notify);
@@ -297,8 +366,76 @@ export function createGameContext(options) {
297
366
  event.instanceId = input.instanceId;
298
367
  if (input.amount !== undefined)
299
368
  event.amount = input.amount;
369
+ if (input.hitType !== undefined)
370
+ event.hitType = input.hitType;
371
+ if (input.element !== undefined)
372
+ event.element = input.element;
373
+ if (input.crit !== undefined)
374
+ event.crit = input.crit;
375
+ if (input.scale !== undefined)
376
+ event.scale = input.scale;
300
377
  events.emit("entity.floatText", event);
301
378
  }
379
+ let telegraphSeq = 0;
380
+ function fireTelegraph(input) {
381
+ const id = telegraphSeq++;
382
+ const telegraphEvent = {
383
+ id,
384
+ shape: input.shape,
385
+ position: [input.at[0], input.at[1], input.at[2]],
386
+ windupMs: input.windupMs,
387
+ kind: input.kind ?? "danger",
388
+ };
389
+ if (input.dir !== undefined)
390
+ telegraphEvent.dir = input.dir;
391
+ events.emit("combat.telegraph", telegraphEvent);
392
+ const bound = input.effect;
393
+ if (bound === undefined)
394
+ return () => { };
395
+ const config = { shape: input.shape, at: input.at, windupMs: input.windupMs };
396
+ if (input.dir !== undefined)
397
+ config.dir = input.dir;
398
+ return time.after(input.windupMs / 1000, () => {
399
+ const targets = entities.list().filter((entity) => pointInTelegraph(config, entity.position));
400
+ for (const target of targets) {
401
+ applyEffectAndFloat({
402
+ from: input.from,
403
+ to: target.id,
404
+ effect: bound.effect,
405
+ ...(bound.via === undefined ? {} : { via: bound.via }),
406
+ });
407
+ }
408
+ });
409
+ }
410
+ function applyHitReaction(input) {
411
+ const attacker = entities.get(input.from);
412
+ const target = entities.get(input.to);
413
+ if (target === null)
414
+ return null;
415
+ const attackerPos = attacker?.position ?? target.position;
416
+ const reaction = resolveHitReaction(input.config, {
417
+ attackerPos,
418
+ targetPos: target.position,
419
+ ...(input.power === undefined ? {} : { power: input.power }),
420
+ });
421
+ entities.setPose(input.to, {
422
+ position: [
423
+ target.position[0] + reaction.impulse[0],
424
+ target.position[1] + reaction.impulse[1],
425
+ target.position[2] + reaction.impulse[2],
426
+ ],
427
+ rotationY: target.rotationY,
428
+ });
429
+ const reactionEvent = {
430
+ instanceId: input.to,
431
+ position: [target.position[0], target.position[1], target.position[2]],
432
+ hitstopMs: reaction.hitstopMs,
433
+ };
434
+ if (reaction.shake !== null)
435
+ reactionEvent.shake = reaction.shake;
436
+ events.emit("combat.hitReaction", reactionEvent);
437
+ return reaction;
438
+ }
302
439
  function applyEffectAndFloat(input) {
303
440
  const positionsBefore = new Map();
304
441
  for (const entity of entities.list())
@@ -359,6 +496,8 @@ export function createGameContext(options) {
359
496
  list: entities.list,
360
497
  stats: entityStats,
361
498
  floatText: emitFloatText,
499
+ telegraph: fireTelegraph,
500
+ hitReaction: applyHitReaction,
362
501
  setTarget: targeting.setTarget,
363
502
  getTarget: targeting.getTarget,
364
503
  cycleTarget: targeting.cycleTarget,
@@ -373,6 +512,14 @@ export function createGameContext(options) {
373
512
  hasLineOfSight: spatial.hasLineOfSight,
374
513
  queryArc: spatial.queryArc,
375
514
  moveToward: spatial.moveToward,
515
+ form: forms,
516
+ },
517
+ worldItem: {
518
+ spawn: spawnWorldItem,
519
+ get: worldItems.get,
520
+ list: worldItems.list,
521
+ nearestInRadius: worldItems.nearestInRadius,
522
+ pickup: pickupWorldItem,
376
523
  },
377
524
  },
378
525
  game: {
@@ -408,6 +555,7 @@ export function createGameContext(options) {
408
555
  unlocks,
409
556
  economy,
410
557
  leaderboard,
558
+ roster,
411
559
  },
412
560
  player: {
413
561
  userId: player.userId,
@@ -417,6 +565,8 @@ export function createGameContext(options) {
417
565
  loadout: loadouts,
418
566
  applyLoadout: loadouts.applyLoadout,
419
567
  movement: pose,
568
+ possession,
569
+ cosmetics,
420
570
  },
421
571
  item: {
422
572
  use: {
@@ -1,13 +1,26 @@
1
1
  import type { LeaderboardScope } from "../game/leaderboard.js";
2
+ import type { NormalizedScenarioReset } from "./persistenceScope.js";
2
3
  import type { SaveConfig } from "./save.js";
3
4
  import type { GameRuntimeSnapshot, RuntimeChunkRow, RuntimePlayerRow, RuntimeServerRow } from "./snapshot.js";
4
5
  export type GameServerStatus = "open" | "running" | "closed";
6
+ export type SessionVisibility = "public" | "private";
7
+ export type SessionAttributes = {
8
+ label?: string;
9
+ mode?: string;
10
+ visibility?: SessionVisibility;
11
+ joinCode?: string;
12
+ tags?: string[];
13
+ };
5
14
  export type GameServerRecord = {
6
15
  serverId: string;
7
16
  gameId: string;
8
17
  status: GameServerStatus;
9
18
  mode?: string;
10
19
  modeConfig?: unknown;
20
+ label?: string;
21
+ visibility?: SessionVisibility;
22
+ joinCode?: string;
23
+ tags?: string[];
11
24
  memberUserIds: string[];
12
25
  slotsPerServer: number;
13
26
  save: SaveConfig;
@@ -58,11 +71,16 @@ export type ServerListing = {
58
71
  memberCount: number;
59
72
  slotsPerServer: number;
60
73
  mode?: string;
74
+ label?: string;
75
+ visibility?: SessionVisibility;
76
+ joinCode?: string;
77
+ tags?: string[];
61
78
  updatedAt: number;
62
79
  };
63
80
  export declare function toServerListing(record: GameServerRecord): ServerListing;
64
81
  export type HostPersistence = {
65
82
  savePlan?: (plan: ServerPersistPlan) => Promise<void>;
83
+ resetScenario?: (reset: NormalizedScenarioReset) => Promise<void>;
66
84
  loadServer: (serverId: string) => Promise<GameServerRecord | null>;
67
85
  saveServer: (record: GameServerRecord) => Promise<void>;
68
86
  listServers: (gameId: string) => Promise<GameServerRecord[]>;
@@ -42,6 +42,10 @@ export function toServerListing(record) {
42
42
  memberCount: record.memberUserIds.length,
43
43
  slotsPerServer: record.slotsPerServer,
44
44
  mode: record.mode,
45
+ label: record.label,
46
+ visibility: record.visibility,
47
+ joinCode: record.joinCode,
48
+ tags: record.tags,
45
49
  updatedAt: record.updatedAt,
46
50
  };
47
51
  }
@@ -0,0 +1,32 @@
1
+ import type { PlayerProfileRecord } from "./hostPersistence.js";
2
+ import type { RuntimePlayerRow } from "./snapshot.js";
3
+ export type PersistenceScope = "run" | "meta";
4
+ export interface ScopeSchema {
5
+ run: readonly string[];
6
+ }
7
+ export interface ScopedState<T = Record<string, unknown>> {
8
+ meta: Partial<T>;
9
+ run: Partial<T>;
10
+ }
11
+ export declare function partitionScopes<T extends Record<string, unknown>>(state: T, schema: ScopeSchema): ScopedState<T>;
12
+ export declare function resetRun<T extends Record<string, unknown>>(scoped: ScopedState<T>): ScopedState<T>;
13
+ export declare function mergeScopes<T extends Record<string, unknown>>(scoped: ScopedState<T>): Partial<T>;
14
+ export declare function clearRunFields(player: RuntimePlayerRow, runFields: readonly string[]): RuntimePlayerRow;
15
+ export declare function applyRunReset(profile: PlayerProfileRecord, runFields: readonly string[], now: number): PlayerProfileRecord;
16
+ export interface ScenarioReset {
17
+ gameId: string;
18
+ serverId?: string;
19
+ wipeChunks?: boolean;
20
+ wipeServerSession?: boolean;
21
+ resetPlayers?: PersistenceScope | "none";
22
+ runFields?: readonly string[];
23
+ }
24
+ export interface NormalizedScenarioReset {
25
+ gameId: string;
26
+ serverId: string | null;
27
+ wipeChunks: boolean;
28
+ wipeServerSession: boolean;
29
+ resetPlayers: "run" | "none";
30
+ runFields: readonly string[];
31
+ }
32
+ export declare function planScenarioReset(reset: ScenarioReset): NormalizedScenarioReset;
@@ -0,0 +1,64 @@
1
+ export function partitionScopes(state, schema) {
2
+ const runKeys = new Set(schema.run);
3
+ const meta = {};
4
+ const run = {};
5
+ for (const key of Object.keys(state)) {
6
+ if (runKeys.has(key))
7
+ run[key] = state[key];
8
+ else
9
+ meta[key] = state[key];
10
+ }
11
+ return { meta, run };
12
+ }
13
+ export function resetRun(scoped) {
14
+ return { meta: scoped.meta, run: {} };
15
+ }
16
+ export function mergeScopes(scoped) {
17
+ return { ...scoped.meta, ...scoped.run };
18
+ }
19
+ const RUN_FIELD_DEFAULTS = {
20
+ inventories: {},
21
+ economy: {},
22
+ unlocks: [],
23
+ quests: undefined,
24
+ social: undefined,
25
+ leaderboard: {},
26
+ session: {},
27
+ };
28
+ export function clearRunFields(player, runFields) {
29
+ const next = { ...player };
30
+ for (const field of runFields) {
31
+ if (field === "userId")
32
+ continue;
33
+ const key = field;
34
+ if (key in RUN_FIELD_DEFAULTS) {
35
+ const template = RUN_FIELD_DEFAULTS[key];
36
+ if (template === undefined)
37
+ delete next[field];
38
+ else
39
+ next[field] = Array.isArray(template) ? [...template] : { ...template };
40
+ }
41
+ else {
42
+ delete next[field];
43
+ }
44
+ }
45
+ return next;
46
+ }
47
+ export function applyRunReset(profile, runFields, now) {
48
+ return {
49
+ ...profile,
50
+ playerState: clearRunFields(profile.playerState, runFields),
51
+ revision: profile.revision + 1,
52
+ updatedAt: now,
53
+ };
54
+ }
55
+ export function planScenarioReset(reset) {
56
+ return {
57
+ gameId: reset.gameId,
58
+ serverId: reset.serverId ?? null,
59
+ wipeChunks: reset.wipeChunks ?? true,
60
+ wipeServerSession: reset.wipeServerSession ?? true,
61
+ resetPlayers: reset.resetPlayers === "run" ? "run" : "none",
62
+ runFields: reset.runFields ?? [],
63
+ };
64
+ }
@@ -0,0 +1,15 @@
1
+ export type AutoTargetPolicy = "nearest" | "farthest" | "random" | "strongest" | "weakest" | "first" | "last";
2
+ export interface AutoTargetDeps {
3
+ candidates: (fromId: string) => readonly string[];
4
+ distance: (fromId: string, toId: string) => number | null;
5
+ strength?: (toId: string) => number;
6
+ progress?: (toId: string) => number;
7
+ rng?: () => number;
8
+ }
9
+ export declare function selectAutoTarget(policy: AutoTargetPolicy, fromId: string, deps: AutoTargetDeps): string | null;
10
+ export interface AutoTargeter {
11
+ policy(): AutoTargetPolicy;
12
+ setPolicy(policy: AutoTargetPolicy): void;
13
+ pick(fromId: string): string | null;
14
+ }
15
+ export declare function createAutoTargeter(policy: AutoTargetPolicy, deps: AutoTargetDeps): AutoTargeter;
@@ -0,0 +1,54 @@
1
+ function metric(policy, fromId, toId, deps) {
2
+ switch (policy) {
3
+ case "nearest":
4
+ case "farthest":
5
+ return deps.distance(fromId, toId);
6
+ case "strongest":
7
+ case "weakest":
8
+ return deps.strength?.(toId) ?? 0;
9
+ case "first":
10
+ case "last":
11
+ return deps.progress?.(toId) ?? 0;
12
+ case "random":
13
+ return 0;
14
+ }
15
+ }
16
+ function prefersHigher(policy) {
17
+ return policy === "farthest" || policy === "strongest" || policy === "first";
18
+ }
19
+ export function selectAutoTarget(policy, fromId, deps) {
20
+ const candidates = deps.candidates(fromId).filter((id) => id !== fromId);
21
+ if (candidates.length === 0)
22
+ return null;
23
+ if (policy === "random") {
24
+ const rng = deps.rng ?? Math.random;
25
+ return candidates[Math.min(candidates.length - 1, Math.floor(rng() * candidates.length))];
26
+ }
27
+ const higher = prefersHigher(policy);
28
+ let best = null;
29
+ let bestScore = higher ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY;
30
+ for (const id of candidates) {
31
+ const score = metric(policy, fromId, id, deps);
32
+ if (score === null)
33
+ continue;
34
+ if (best === null || (higher ? score > bestScore : score < bestScore)) {
35
+ best = id;
36
+ bestScore = score;
37
+ }
38
+ }
39
+ return best;
40
+ }
41
+ export function createAutoTargeter(policy, deps) {
42
+ let current = policy;
43
+ return {
44
+ policy() {
45
+ return current;
46
+ },
47
+ setPolicy(next) {
48
+ current = next;
49
+ },
50
+ pick(fromId) {
51
+ return selectAutoTarget(current, fromId, deps);
52
+ },
53
+ };
54
+ }
@@ -1,8 +1,15 @@
1
1
  import { type ProximityPrompt } from "../interaction/proximityPrompt.js";
2
+ import type { Waypoint } from "../nav/pathFollow.js";
2
3
  export interface WanderBehavior {
3
4
  kind: "wander";
4
5
  radius: number;
5
6
  }
7
+ export interface PatrolBehavior {
8
+ kind: "patrol";
9
+ waypoints: readonly Waypoint[];
10
+ speed: number;
11
+ loop: boolean;
12
+ }
6
13
  export interface PromptableBehavior {
7
14
  kind: "promptable";
8
15
  prompt: ProximityPrompt;
@@ -10,10 +17,15 @@ export interface PromptableBehavior {
10
17
  export interface PlayerBehavior {
11
18
  kind: "player";
12
19
  }
13
- export type BehaviorDescriptor = WanderBehavior | PromptableBehavior | PlayerBehavior;
20
+ export type BehaviorDescriptor = WanderBehavior | PatrolBehavior | PromptableBehavior | PlayerBehavior;
14
21
  export declare function wander({ radius }: {
15
22
  radius: number;
16
23
  }): WanderBehavior;
24
+ export declare function patrol({ waypoints, speed, loop, }: {
25
+ waypoints: readonly Waypoint[];
26
+ speed: number;
27
+ loop?: boolean;
28
+ }): PatrolBehavior;
17
29
  export declare function promptable(prompt: ProximityPrompt): PromptableBehavior;
18
30
  export declare function talkable(dialogueId: string): PromptableBehavior;
19
31
  export declare function player(): PlayerBehavior;
@@ -2,6 +2,9 @@ import { command, keybind, proximityPrompt, } from "../interaction/proximityProm
2
2
  export function wander({ radius }) {
3
3
  return { kind: "wander", radius };
4
4
  }
5
+ export function patrol({ waypoints, speed, loop = true, }) {
6
+ return { kind: "patrol", waypoints, speed, loop };
7
+ }
5
8
  export function promptable(prompt) {
6
9
  return { kind: "promptable", prompt };
7
10
  }
@@ -0,0 +1,7 @@
1
+ export interface CaptureCheckInput {
2
+ hpFraction: number;
3
+ catchPower: number;
4
+ difficulty?: number;
5
+ }
6
+ export declare function captureChance(input: CaptureCheckInput): number;
7
+ export declare function rollCapture(input: CaptureCheckInput, rng?: () => number): boolean;