@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.
- package/CHANGELOG.md +167 -2
- package/dist/ai/crowd.d.ts +36 -0
- package/dist/ai/crowd.js +213 -0
- package/dist/ai/jobBoard.d.ts +49 -0
- package/dist/ai/jobBoard.js +155 -0
- package/dist/ai/spawnDirector.d.ts +56 -0
- package/dist/ai/spawnDirector.js +150 -0
- package/dist/ai/threat.d.ts +25 -0
- package/dist/ai/threat.js +67 -0
- package/dist/audio/audioFalloff.d.ts +38 -0
- package/dist/audio/audioFalloff.js +32 -0
- package/dist/board/laneBoard.d.ts +63 -0
- package/dist/board/laneBoard.js +114 -0
- package/dist/board/timelineBoard.d.ts +36 -0
- package/dist/board/timelineBoard.js +70 -0
- package/dist/cards/cardPile.d.ts +63 -0
- package/dist/cards/cardPile.js +166 -0
- package/dist/cards/modifierPipeline.d.ts +33 -0
- package/dist/cards/modifierPipeline.js +48 -0
- package/dist/combat/abilityKit.d.ts +42 -0
- package/dist/combat/abilityKit.js +129 -0
- package/dist/combat/animationState.d.ts +47 -0
- package/dist/combat/animationState.js +101 -0
- package/dist/combat/attackTags.d.ts +14 -0
- package/dist/combat/attackTags.js +26 -0
- package/dist/combat/breakMeters.d.ts +32 -0
- package/dist/combat/breakMeters.js +51 -0
- package/dist/combat/comboString.d.ts +35 -0
- package/dist/combat/comboString.js +59 -0
- package/dist/combat/death.d.ts +8 -0
- package/dist/combat/death.js +5 -2
- package/dist/combat/defensiveWindow.d.ts +34 -0
- package/dist/combat/defensiveWindow.js +66 -0
- package/dist/combat/downed.d.ts +35 -0
- package/dist/combat/downed.js +104 -0
- package/dist/combat/effects.d.ts +11 -0
- package/dist/combat/effects.js +22 -13
- package/dist/combat/hitReaction.d.ts +23 -0
- package/dist/combat/hitReaction.js +20 -0
- package/dist/combat/resistance.d.ts +13 -0
- package/dist/combat/resistance.js +46 -0
- package/dist/combat/telegraph.d.ts +27 -0
- package/dist/combat/telegraph.js +37 -0
- package/dist/crafting/crop.d.ts +59 -0
- package/dist/crafting/crop.js +181 -0
- package/dist/crafting/production.d.ts +64 -0
- package/dist/crafting/production.js +130 -0
- package/dist/crafting/recipe.d.ts +62 -0
- package/dist/crafting/recipe.js +101 -0
- package/dist/economy/sharedWallet.d.ts +28 -0
- package/dist/economy/sharedWallet.js +62 -0
- package/dist/economy/techTree.d.ts +40 -0
- package/dist/economy/techTree.js +66 -0
- package/dist/game/cosmetics.d.ts +25 -0
- package/dist/game/cosmetics.js +51 -0
- package/dist/game/defineGame.d.ts +2 -0
- package/dist/game/events.d.ts +62 -0
- package/dist/game/lootFilter.d.ts +33 -0
- package/dist/game/lootFilter.js +48 -0
- package/dist/game/ping.d.ts +67 -0
- package/dist/game/ping.js +86 -0
- package/dist/game/playableGame.d.ts +281 -4
- package/dist/game/race.d.ts +95 -0
- package/dist/game/race.js +216 -0
- package/dist/game/runDraft.d.ts +34 -0
- package/dist/game/runDraft.js +97 -0
- package/dist/game/social.d.ts +27 -0
- package/dist/game/social.js +16 -0
- package/dist/game/worldItem.d.ts +92 -0
- package/dist/game/worldItem.js +132 -0
- package/dist/input/axisInput.d.ts +43 -0
- package/dist/input/axisInput.js +70 -0
- package/dist/input/pointer.d.ts +24 -0
- package/dist/input/pointer.js +17 -0
- package/dist/interaction/contextMenu.d.ts +30 -0
- package/dist/interaction/contextMenu.js +22 -0
- package/dist/interaction/qte.d.ts +20 -0
- package/dist/interaction/qte.js +24 -0
- package/dist/interaction/skillCheck.d.ts +20 -0
- package/dist/interaction/skillCheck.js +26 -0
- package/dist/inventory/shapedGrid.d.ts +56 -0
- package/dist/inventory/shapedGrid.js +176 -0
- package/dist/inventory/storageTier.d.ts +52 -0
- package/dist/inventory/storageTier.js +77 -0
- package/dist/item/affix.d.ts +54 -0
- package/dist/item/affix.js +133 -0
- package/dist/item/durability.d.ts +51 -0
- package/dist/item/durability.js +78 -0
- package/dist/item/modularItem.d.ts +46 -0
- package/dist/item/modularItem.js +75 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +35 -1
- package/dist/movement/dash.d.ts +37 -0
- package/dist/movement/dash.js +65 -0
- package/dist/multiplayer/combatSnapshot.d.ts +45 -0
- package/dist/multiplayer/combatSnapshot.js +89 -0
- package/dist/multiplayer/lagCompensation.d.ts +43 -0
- package/dist/multiplayer/lagCompensation.js +154 -0
- package/dist/multiplayer/matchmaking.d.ts +33 -0
- package/dist/multiplayer/matchmaking.js +60 -0
- package/dist/multiplayer/simultaneousCommit.d.ts +34 -0
- package/dist/multiplayer/simultaneousCommit.js +53 -0
- package/dist/nav/navGrid.d.ts +46 -0
- package/dist/nav/navGrid.js +248 -0
- package/dist/nav/pathFollow.d.ts +29 -0
- package/dist/nav/pathFollow.js +67 -0
- package/dist/physics/buoyancy.d.ts +49 -0
- package/dist/physics/buoyancy.js +112 -0
- package/dist/physics/carryable.d.ts +46 -0
- package/dist/physics/carryable.js +110 -0
- package/dist/physics/damageZones.d.ts +56 -0
- package/dist/physics/damageZones.js +107 -0
- package/dist/physics/forceVolume.d.ts +48 -0
- package/dist/physics/forceVolume.js +119 -0
- package/dist/physics/physicsWorld.d.ts +83 -0
- package/dist/physics/physicsWorld.js +261 -1
- package/dist/physics/ragdoll.d.ts +49 -0
- package/dist/physics/ragdoll.js +103 -0
- package/dist/physics/spatialGrid.d.ts +41 -0
- package/dist/physics/spatialGrid.js +138 -0
- package/dist/physics/structure.d.ts +83 -0
- package/dist/physics/structure.js +0 -0
- package/dist/physics/traversal.d.ts +75 -0
- package/dist/physics/traversal.js +138 -0
- package/dist/physics/vehicleBody.d.ts +93 -0
- package/dist/physics/vehicleBody.js +234 -0
- package/dist/runtime/gameContext.d.ts +64 -2
- package/dist/runtime/gameContext.js +154 -4
- package/dist/runtime/hostPersistence.d.ts +18 -0
- package/dist/runtime/hostPersistence.js +4 -0
- package/dist/runtime/persistenceScope.d.ts +32 -0
- package/dist/runtime/persistenceScope.js +64 -0
- package/dist/scene/autoTarget.d.ts +15 -0
- package/dist/scene/autoTarget.js +54 -0
- package/dist/scene/behaviors.d.ts +13 -1
- package/dist/scene/behaviors.js +3 -0
- package/dist/scene/captureCheck.d.ts +7 -0
- package/dist/scene/captureCheck.js +11 -0
- package/dist/scene/entityStore.d.ts +1 -1
- package/dist/scene/form.d.ts +40 -0
- package/dist/scene/form.js +56 -0
- package/dist/scene/mount.d.ts +62 -0
- package/dist/scene/mount.js +107 -0
- package/dist/scene/possession.d.ts +30 -0
- package/dist/scene/possession.js +51 -0
- package/dist/scene/roster.d.ts +25 -0
- package/dist/scene/roster.js +57 -0
- package/dist/scene/selection.d.ts +30 -0
- package/dist/scene/selection.js +38 -0
- package/dist/scene/stationClaim.d.ts +43 -0
- package/dist/scene/stationClaim.js +109 -0
- package/dist/sensor/frustumSensor.d.ts +60 -0
- package/dist/sensor/frustumSensor.js +104 -0
- package/dist/sensor/hiddenStateProbe.d.ts +31 -0
- package/dist/sensor/hiddenStateProbe.js +40 -0
- package/dist/sensor/recordingBuffer.d.ts +29 -0
- package/dist/sensor/recordingBuffer.js +61 -0
- package/dist/sensor/revealQuery.d.ts +22 -0
- package/dist/sensor/revealQuery.js +36 -0
- package/dist/session/contestedChannel.d.ts +36 -0
- package/dist/session/contestedChannel.js +100 -0
- package/dist/session/extraction.d.ts +54 -0
- package/dist/session/extraction.js +108 -0
- package/dist/session/ring.d.ts +36 -0
- package/dist/session/ring.js +88 -0
- package/dist/session/roundState.d.ts +55 -0
- package/dist/session/roundState.js +129 -0
- package/dist/stats/accumulatorMeter.d.ts +31 -0
- package/dist/stats/accumulatorMeter.js +81 -0
- package/dist/stats/eventMeter.d.ts +34 -0
- package/dist/stats/eventMeter.js +76 -0
- package/dist/stats/rollCheck.d.ts +15 -0
- package/dist/stats/rollCheck.js +15 -0
- package/dist/survival/decayMeter.d.ts +59 -0
- package/dist/survival/decayMeter.js +95 -0
- package/dist/survival/moodle.d.ts +48 -0
- package/dist/survival/moodle.js +91 -0
- package/dist/survival/regionHealth.d.ts +72 -0
- package/dist/survival/regionHealth.js +129 -0
- package/dist/tactics/predictiveQuery.d.ts +34 -0
- package/dist/tactics/predictiveQuery.js +44 -0
- package/dist/tactics/snapshot.d.ts +18 -0
- package/dist/tactics/snapshot.js +65 -0
- package/dist/tactics/surface.d.ts +52 -0
- package/dist/tactics/surface.js +135 -0
- package/dist/tactics/tacticalGrid.d.ts +57 -0
- package/dist/tactics/tacticalGrid.js +244 -0
- package/dist/time/beatClock.d.ts +40 -0
- package/dist/time/beatClock.js +73 -0
- package/dist/turn/commit.d.ts +27 -0
- package/dist/turn/commit.js +72 -0
- package/dist/turn/turnLoop.d.ts +58 -0
- package/dist/turn/turnLoop.js +216 -0
- package/dist/world/buildPermissions.d.ts +47 -0
- package/dist/world/buildPermissions.js +134 -0
- package/dist/world/carve.d.ts +105 -0
- package/dist/world/carve.js +175 -0
- package/dist/world/connectors.d.ts +40 -0
- package/dist/world/connectors.js +67 -0
- package/dist/world/envField.d.ts +71 -0
- package/dist/world/envField.js +96 -0
- package/dist/world/fog.d.ts +40 -0
- package/dist/world/fog.js +137 -0
- package/dist/world/markers.d.ts +52 -0
- package/dist/world/markers.js +96 -0
- package/dist/world/minimap.d.ts +42 -0
- package/dist/world/minimap.js +72 -0
- package/dist/world/placedStructureStore.d.ts +39 -0
- package/dist/world/placedStructureStore.js +95 -0
- package/dist/world/placementController.d.ts +48 -0
- package/dist/world/placementController.js +97 -0
- package/dist/world/realm.d.ts +61 -0
- package/dist/world/realm.js +66 -0
- package/dist/world/support.d.ts +27 -0
- package/dist/world/support.js +75 -0
- package/dist/world/terraform.d.ts +54 -0
- package/dist/world/terraform.js +186 -0
- package/dist/world/walls.d.ts +61 -0
- package/dist/world/walls.js +221 -0
- package/dist/world/weather.d.ts +92 -0
- package/dist/world/weather.js +169 -0
- 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({
|
|
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
|
|
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
|
-
|
|
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;
|
package/dist/scene/behaviors.js
CHANGED
|
@@ -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;
|