@shipload/sdk 1.0.0-next.2 → 1.0.0-next.20
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/lib/shipload.d.ts +1709 -1044
- package/lib/shipload.js +6762 -4658
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +6653 -4614
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +833 -0
- package/lib/testing.js +3647 -0
- package/lib/testing.js.map +1 -0
- package/lib/testing.m.js +3641 -0
- package/lib/testing.m.js.map +1 -0
- package/package.json +15 -2
- package/src/capabilities/gathering.ts +17 -7
- package/src/capabilities/modules.ts +9 -0
- package/src/capabilities/storage.ts +1 -1
- package/src/contracts/platform.ts +211 -3
- package/src/contracts/server.ts +723 -438
- package/src/data/capabilities.ts +9 -329
- package/src/data/capability-formulas.ts +76 -0
- package/src/data/catalog.ts +0 -5
- package/src/data/colors.ts +14 -28
- package/src/data/entities.json +46 -10
- package/src/data/item-ids.ts +17 -13
- package/src/data/items.json +308 -37
- package/src/data/kind-registry.json +85 -0
- package/src/data/kind-registry.ts +150 -0
- package/src/data/metadata.ts +99 -24
- package/src/data/recipes-runtime.ts +3 -23
- package/src/data/recipes.json +265 -96
- package/src/derivation/build-methods.ts +45 -0
- package/src/derivation/capabilities.ts +414 -0
- package/src/derivation/capability-mappings.ts +117 -0
- package/src/derivation/crafting.ts +23 -24
- package/src/derivation/index.ts +8 -2
- package/src/derivation/reserve-regen.ts +34 -0
- package/src/derivation/resources.ts +125 -38
- package/src/derivation/stats.ts +1 -2
- package/src/derivation/stratum.ts +15 -19
- package/src/derivation/tiers.ts +28 -7
- package/src/entities/entity.ts +98 -0
- package/src/entities/gamestate.ts +3 -28
- package/src/entities/makers.ts +75 -129
- package/src/entities/slot-multiplier.ts +37 -0
- package/src/errors.ts +10 -15
- package/src/format.ts +26 -4
- package/src/index-module.ts +149 -40
- package/src/managers/actions.ts +184 -82
- package/src/managers/base.ts +2 -2
- package/src/managers/construction-types.ts +47 -0
- package/src/managers/construction.ts +147 -0
- package/src/managers/context.ts +9 -0
- package/src/managers/entities.ts +18 -66
- package/src/managers/epochs.ts +40 -0
- package/src/managers/index.ts +14 -1
- package/src/managers/locations.ts +2 -20
- package/src/managers/nft.ts +28 -0
- package/src/managers/plot.ts +123 -0
- package/src/nft/atomicassets.ts +231 -0
- package/src/nft/atomicdata.ts +130 -0
- package/src/nft/buildImmutableData.ts +319 -0
- package/src/nft/description.ts +45 -13
- package/src/nft/index.ts +3 -0
- package/src/resolution/describe-module.ts +5 -8
- package/src/resolution/display-name.ts +38 -10
- package/src/resolution/resolve-item.ts +20 -12
- package/src/scheduling/accessor.ts +4 -0
- package/src/scheduling/projection.ts +79 -27
- package/src/scheduling/schedule.ts +15 -1
- package/src/scheduling/task-cargo.ts +46 -0
- package/src/shipload.ts +5 -0
- package/src/subscriptions/manager.ts +40 -6
- package/src/subscriptions/mappers.ts +3 -8
- package/src/subscriptions/types.ts +3 -2
- package/src/testing/catalog-hash.ts +19 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/projection-parity.ts +143 -0
- package/src/travel/travel.ts +61 -2
- package/src/types/index.ts +0 -1
- package/src/types.ts +17 -12
- package/src/utils/cargo.ts +27 -0
- package/src/utils/system.ts +25 -24
- package/src/entities/container.ts +0 -108
- package/src/entities/ship-deploy.ts +0 -258
- package/src/entities/ship.ts +0 -204
- package/src/entities/warehouse.ts +0 -119
- package/src/types/entity-traits.ts +0 -69
|
@@ -26,8 +26,9 @@ import {
|
|
|
26
26
|
computeLoaderCapabilities,
|
|
27
27
|
computeShipHullCapabilities,
|
|
28
28
|
computeWarehouseHullCapabilities,
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
computeContainerCapabilities,
|
|
30
|
+
computeContainerT2Capabilities,
|
|
31
|
+
} from '../derivation/capabilities'
|
|
31
32
|
import {
|
|
32
33
|
categoryColors,
|
|
33
34
|
categoryIcons,
|
|
@@ -39,6 +40,7 @@ import type {ServerContract} from '../contracts'
|
|
|
39
40
|
import {
|
|
40
41
|
ITEM_CONTAINER_T1_PACKED,
|
|
41
42
|
ITEM_CONTAINER_T2_PACKED,
|
|
43
|
+
ITEM_EXTRACTOR_T1_PACKED,
|
|
42
44
|
ITEM_SHIP_T1_PACKED,
|
|
43
45
|
ITEM_WAREHOUSE_T1_PACKED,
|
|
44
46
|
} from '../data/item-ids'
|
|
@@ -155,7 +157,8 @@ function resolveComponent(id: number, stats?: UInt64Type): ResolvedItem {
|
|
|
155
157
|
|
|
156
158
|
function computeCapabilityGroup(
|
|
157
159
|
moduleType: number,
|
|
158
|
-
stats: Record<string, number
|
|
160
|
+
stats: Record<string, number>,
|
|
161
|
+
tier: number
|
|
159
162
|
): ResolvedAttributeGroup | undefined {
|
|
160
163
|
switch (moduleType) {
|
|
161
164
|
case MODULE_ENGINE: {
|
|
@@ -179,14 +182,13 @@ function computeCapabilityGroup(
|
|
|
179
182
|
}
|
|
180
183
|
}
|
|
181
184
|
case MODULE_GATHERER: {
|
|
182
|
-
const caps = computeGathererCapabilities(stats)
|
|
185
|
+
const caps = computeGathererCapabilities(stats, tier)
|
|
183
186
|
return {
|
|
184
187
|
capability: 'Gatherer',
|
|
185
188
|
attributes: [
|
|
186
189
|
{label: 'Yield', value: caps.yield},
|
|
187
190
|
{label: 'Drain', value: caps.drain},
|
|
188
191
|
{label: 'Depth', value: caps.depth},
|
|
189
|
-
{label: 'Speed', value: caps.speed},
|
|
190
192
|
],
|
|
191
193
|
}
|
|
192
194
|
}
|
|
@@ -223,10 +225,11 @@ function computeCapabilityGroup(
|
|
|
223
225
|
}
|
|
224
226
|
}
|
|
225
227
|
case MODULE_STORAGE: {
|
|
226
|
-
const str = stats.strength
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
const
|
|
228
|
+
const str = stats.strength
|
|
229
|
+
const den = stats.density
|
|
230
|
+
const hrd = stats.hardness
|
|
231
|
+
const sat = stats.saturation
|
|
232
|
+
const statSum = str + den + hrd + sat
|
|
230
233
|
const pct = 10 + Math.floor((statSum * 10) / 2997)
|
|
231
234
|
return {capability: 'Storage', attributes: [{label: 'Capacity Bonus', value: pct}]}
|
|
232
235
|
}
|
|
@@ -241,7 +244,7 @@ function resolveModule(id: number, stats?: UInt64Type): ResolvedItem {
|
|
|
241
244
|
if (stats !== undefined) {
|
|
242
245
|
const decoded = decodeCraftedItemStats(id, toBigStats(stats))
|
|
243
246
|
const modType = getModuleCapabilityType(id)
|
|
244
|
-
const group = computeCapabilityGroup(modType, decoded)
|
|
247
|
+
const group = computeCapabilityGroup(modType, decoded, item.tier)
|
|
245
248
|
if (group) attributes = [group]
|
|
246
249
|
}
|
|
247
250
|
return {
|
|
@@ -268,6 +271,8 @@ function hullCapsForEntity(
|
|
|
268
271
|
return computeShipHullCapabilities(decoded)
|
|
269
272
|
case ITEM_WAREHOUSE_T1_PACKED:
|
|
270
273
|
return computeWarehouseHullCapabilities(decoded)
|
|
274
|
+
case ITEM_EXTRACTOR_T1_PACKED:
|
|
275
|
+
return computeShipHullCapabilities(decoded)
|
|
271
276
|
case ITEM_CONTAINER_T1_PACKED:
|
|
272
277
|
return computeContainerCapabilities(decoded)
|
|
273
278
|
case ITEM_CONTAINER_T2_PACKED:
|
|
@@ -310,13 +315,16 @@ function resolveEntity(
|
|
|
310
315
|
const modStats = BigInt(mod.installed.stats.toString())
|
|
311
316
|
const decodedStats = decodeCraftedItemStats(modItemId, modStats)
|
|
312
317
|
const modType = getModuleCapabilityType(modItemId)
|
|
313
|
-
const group = computeCapabilityGroup(modType, decodedStats)
|
|
314
318
|
let modName = 'Module'
|
|
319
|
+
let modTier = 1
|
|
315
320
|
try {
|
|
316
|
-
|
|
321
|
+
const modItem = getItem(modItemId)
|
|
322
|
+
modName = modItem.name
|
|
323
|
+
modTier = modItem.tier
|
|
317
324
|
} catch {
|
|
318
325
|
modName = itemMetadata[modItemId]?.name ?? 'Module'
|
|
319
326
|
}
|
|
327
|
+
const group = computeCapabilityGroup(modType, decodedStats, modTier)
|
|
320
328
|
return {
|
|
321
329
|
name: modName,
|
|
322
330
|
installed: true,
|
|
@@ -72,6 +72,10 @@ export class ScheduleAccessor {
|
|
|
72
72
|
return schedule.currentTaskProgress(this.entity, now)
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
currentTaskProgressFloat(now: Date): number {
|
|
76
|
+
return schedule.currentTaskProgressFloat(this.entity, now)
|
|
77
|
+
}
|
|
78
|
+
|
|
75
79
|
progress(now: Date): number {
|
|
76
80
|
return schedule.scheduleProgress(this.entity, now)
|
|
77
81
|
}
|
|
@@ -14,10 +14,12 @@ import {
|
|
|
14
14
|
RECIPE_INPUTS_INSUFFICIENT,
|
|
15
15
|
RECIPE_INPUTS_INVALID,
|
|
16
16
|
RECIPE_NOT_FOUND,
|
|
17
|
-
|
|
17
|
+
ENTITY_CARGO_NOT_LOADED,
|
|
18
18
|
} from '../errors'
|
|
19
|
-
import {getRecipe, type RecipeInput} from '../data/recipes-runtime'
|
|
20
|
-
import {
|
|
19
|
+
import {getEntityLayout, getRecipe, type RecipeInput} from '../data/recipes-runtime'
|
|
20
|
+
import {computeEntityCapabilities} from '../derivation/capabilities'
|
|
21
|
+
import {decodeCraftedItemStats} from '../derivation/crafting'
|
|
22
|
+
import {packedModulesToInstalled, type InstalledModule} from '../entities/slot-multiplier'
|
|
21
23
|
import {distanceBetweenCoordinates, lerp} from '../travel/travel'
|
|
22
24
|
import {
|
|
23
25
|
calcStacksMass,
|
|
@@ -63,19 +65,74 @@ export interface Projectable extends ScheduleData {
|
|
|
63
65
|
cargo: ServerContract.Types.cargo_item[]
|
|
64
66
|
cargomass: UInt32
|
|
65
67
|
owner?: Name
|
|
68
|
+
stats?: bigint
|
|
69
|
+
item_id?: number | UInt16
|
|
70
|
+
modules?: ServerContract.Types.module_entry[] | InstalledModule[]
|
|
66
71
|
}
|
|
67
72
|
|
|
68
|
-
function
|
|
69
|
-
|
|
73
|
+
function toInstalledModules(
|
|
74
|
+
modules: ServerContract.Types.module_entry[] | InstalledModule[]
|
|
75
|
+
): InstalledModule[] {
|
|
76
|
+
if (modules.length > 0 && 'itemId' in modules[0]) {
|
|
77
|
+
return modules as InstalledModule[]
|
|
78
|
+
}
|
|
79
|
+
return packedModulesToInstalled(modules as ServerContract.Types.module_entry[])
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface ProjectedCaps {
|
|
83
|
+
hullmass?: UInt32
|
|
84
|
+
capacity?: UInt32
|
|
85
|
+
engines?: ServerContract.Types.movement_stats
|
|
86
|
+
generator?: ServerContract.Types.energy_stats
|
|
87
|
+
loaders?: ServerContract.Types.loader_stats
|
|
88
|
+
hauler?: ServerContract.Types.hauler_stats
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function recomputeCaps(entity: Projectable): ProjectedCaps | undefined {
|
|
92
|
+
if (
|
|
93
|
+
entity.item_id === undefined ||
|
|
94
|
+
entity.modules === undefined ||
|
|
95
|
+
entity.stats === undefined
|
|
96
|
+
) {
|
|
97
|
+
return undefined
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const itemId = Number(
|
|
101
|
+
typeof entity.item_id === 'number' ? entity.item_id : entity.item_id.value
|
|
102
|
+
)
|
|
103
|
+
const hullStats = decodeCraftedItemStats(itemId, entity.stats)
|
|
104
|
+
const layout = getEntityLayout(itemId)?.slots ?? []
|
|
105
|
+
const installed = toInstalledModules(entity.modules)
|
|
106
|
+
const caps = computeEntityCapabilities(hullStats, itemId, installed, layout)
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
hullmass: UInt32.from(caps.hullmass),
|
|
110
|
+
capacity: UInt32.from(caps.capacity),
|
|
111
|
+
engines: caps.engines ? ServerContract.Types.movement_stats.from(caps.engines) : undefined,
|
|
112
|
+
generator: caps.generator
|
|
113
|
+
? ServerContract.Types.energy_stats.from(caps.generator)
|
|
114
|
+
: undefined,
|
|
115
|
+
loaders: caps.loaders ? ServerContract.Types.loader_stats.from(caps.loaders) : undefined,
|
|
116
|
+
hauler: caps.hauler ? ServerContract.Types.hauler_stats.from(caps.hauler) : undefined,
|
|
117
|
+
}
|
|
70
118
|
}
|
|
71
119
|
|
|
72
120
|
export function createProjectedEntity(entity: Projectable): ProjectedEntity {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
121
|
+
const needsRecompute =
|
|
122
|
+
entity.hullmass === undefined ||
|
|
123
|
+
entity.loaders === undefined ||
|
|
124
|
+
entity.engines === undefined ||
|
|
125
|
+
entity.generator === undefined ||
|
|
126
|
+
entity.hauler === undefined ||
|
|
127
|
+
entity.capacity === undefined
|
|
128
|
+
const caps = needsRecompute ? recomputeCaps(entity) : undefined
|
|
129
|
+
|
|
130
|
+
const shipMass = UInt32.from(entity.hullmass ?? caps?.hullmass ?? 0)
|
|
131
|
+
const loaders = entity.loaders ?? caps?.loaders
|
|
132
|
+
const engines = entity.engines ?? caps?.engines
|
|
133
|
+
const generator = entity.generator ?? caps?.generator
|
|
134
|
+
const hauler = entity.hauler ?? caps?.hauler
|
|
135
|
+
const capacity = entity.capacity ?? caps?.capacity
|
|
79
136
|
|
|
80
137
|
const cargo: CargoStack[] = entity.cargo.map(cargoItemToStack)
|
|
81
138
|
|
|
@@ -255,7 +312,6 @@ function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task):
|
|
|
255
312
|
applyAddCargoTask(projected, task)
|
|
256
313
|
break
|
|
257
314
|
case TaskType.UNLOAD:
|
|
258
|
-
case TaskType.WRAP:
|
|
259
315
|
applyRemoveCargoTask(projected, task)
|
|
260
316
|
break
|
|
261
317
|
case TaskType.GATHER:
|
|
@@ -267,6 +323,9 @@ function applyTask(projected: ProjectedEntity, task: ServerContract.Types.task):
|
|
|
267
323
|
case TaskType.DEPLOY:
|
|
268
324
|
applyDeployTask(projected, task)
|
|
269
325
|
break
|
|
326
|
+
case TaskType.UNDEPLOY:
|
|
327
|
+
case TaskType.DEMOLISH:
|
|
328
|
+
break
|
|
270
329
|
}
|
|
271
330
|
}
|
|
272
331
|
|
|
@@ -342,19 +401,10 @@ function validateCraftTask(task: ServerContract.Types.task, projected: Projected
|
|
|
342
401
|
let matched = false
|
|
343
402
|
for (let ri = 0; ri < recipe.length; ri++) {
|
|
344
403
|
const req = recipe[ri]
|
|
345
|
-
if (
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
break
|
|
350
|
-
}
|
|
351
|
-
} else {
|
|
352
|
-
const item = getItem(input.item_id)
|
|
353
|
-
if (item.category === req.category && item.tier === req.tier) {
|
|
354
|
-
groupedInputs[ri].push(input)
|
|
355
|
-
matched = true
|
|
356
|
-
break
|
|
357
|
-
}
|
|
404
|
+
if (input.item_id.toNumber() === req.itemId) {
|
|
405
|
+
groupedInputs[ri].push(input)
|
|
406
|
+
matched = true
|
|
407
|
+
break
|
|
358
408
|
}
|
|
359
409
|
}
|
|
360
410
|
if (!matched) throw new Error(RECIPE_INPUTS_INVALID)
|
|
@@ -385,7 +435,7 @@ function validateCraftTask(task: ServerContract.Types.task, projected: Projected
|
|
|
385
435
|
break
|
|
386
436
|
}
|
|
387
437
|
}
|
|
388
|
-
if (!found) throw new Error(
|
|
438
|
+
if (!found) throw new Error(ENTITY_CARGO_NOT_LOADED)
|
|
389
439
|
}
|
|
390
440
|
}
|
|
391
441
|
|
|
@@ -436,7 +486,6 @@ export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity
|
|
|
436
486
|
if (taskComplete) applyAddCargoTask(projected, task)
|
|
437
487
|
break
|
|
438
488
|
case TaskType.UNLOAD:
|
|
439
|
-
case TaskType.WRAP:
|
|
440
489
|
if (taskComplete) applyRemoveCargoTask(projected, task)
|
|
441
490
|
break
|
|
442
491
|
case TaskType.GATHER:
|
|
@@ -448,6 +497,9 @@ export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity
|
|
|
448
497
|
case TaskType.DEPLOY:
|
|
449
498
|
if (taskComplete) applyDeployTask(projected, task)
|
|
450
499
|
break
|
|
500
|
+
case TaskType.UNDEPLOY:
|
|
501
|
+
case TaskType.DEMOLISH:
|
|
502
|
+
break
|
|
451
503
|
}
|
|
452
504
|
}
|
|
453
505
|
|
|
@@ -77,7 +77,7 @@ export function currentTaskIndex(entity: ScheduleData, now: Date): number {
|
|
|
77
77
|
timeAccum += taskDuration
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
return
|
|
80
|
+
return -1
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
export function currentTask(entity: ScheduleData, now: Date): Task | undefined {
|
|
@@ -147,6 +147,20 @@ export function currentTaskProgress(entity: ScheduleData, now: Date): number {
|
|
|
147
147
|
return Math.min(1, elapsed / duration)
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
export function currentTaskProgressFloat(entity: ScheduleData, now: Date): number {
|
|
151
|
+
if (!entity.schedule || entity.schedule.tasks.length === 0) return 0
|
|
152
|
+
const index = currentTaskIndex(entity, now)
|
|
153
|
+
if (index < 0) return 0
|
|
154
|
+
const task = entity.schedule.tasks[index]
|
|
155
|
+
const durationMs = task.duration.toNumber() * 1000
|
|
156
|
+
if (durationMs === 0) return 1
|
|
157
|
+
const startedMs = entity.schedule.started.toDate().getTime()
|
|
158
|
+
const taskStartMs = startedMs + getTaskStartTime(entity, index) * 1000
|
|
159
|
+
const elapsedMs = now.getTime() - taskStartMs
|
|
160
|
+
if (elapsedMs <= 0) return 0
|
|
161
|
+
return Math.min(1, elapsedMs / durationMs)
|
|
162
|
+
}
|
|
163
|
+
|
|
150
164
|
export function scheduleProgress(entity: ScheduleData, now: Date): number {
|
|
151
165
|
const duration = scheduleDuration(entity)
|
|
152
166
|
if (duration === 0) return hasSchedule(entity) ? 1 : 0
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type {ServerContract} from '../contracts'
|
|
2
|
+
import {TaskType} from '../types'
|
|
3
|
+
|
|
4
|
+
export type TaskCargoDirection = 'in' | 'out'
|
|
5
|
+
|
|
6
|
+
export interface TaskCargoChange {
|
|
7
|
+
direction: TaskCargoDirection
|
|
8
|
+
item_id: number
|
|
9
|
+
stats: bigint
|
|
10
|
+
modules: ServerContract.Types.module_entry[]
|
|
11
|
+
quantity: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function toChange(
|
|
15
|
+
item: ServerContract.Types.cargo_item,
|
|
16
|
+
direction: TaskCargoDirection
|
|
17
|
+
): TaskCargoChange {
|
|
18
|
+
return {
|
|
19
|
+
direction,
|
|
20
|
+
item_id: Number(item.item_id),
|
|
21
|
+
stats: BigInt(item.stats.toString()),
|
|
22
|
+
modules: item.modules ?? [],
|
|
23
|
+
quantity: Number(item.quantity),
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function taskCargoChanges(task: ServerContract.Types.task): TaskCargoChange[] {
|
|
28
|
+
const items = task.cargo ?? []
|
|
29
|
+
if (items.length === 0) return []
|
|
30
|
+
switch (Number(task.type)) {
|
|
31
|
+
case TaskType.LOAD:
|
|
32
|
+
case TaskType.UNWRAP:
|
|
33
|
+
return items.map((i) => toChange(i, 'in'))
|
|
34
|
+
case TaskType.GATHER:
|
|
35
|
+
return task.entitytarget ? [] : items.map((i) => toChange(i, 'in'))
|
|
36
|
+
case TaskType.UNLOAD:
|
|
37
|
+
return items.map((i) => toChange(i, 'out'))
|
|
38
|
+
case TaskType.CRAFT:
|
|
39
|
+
return [
|
|
40
|
+
...items.slice(0, -1).map((i) => toChange(i, 'out')),
|
|
41
|
+
toChange(items[items.length - 1], 'in'),
|
|
42
|
+
]
|
|
43
|
+
default:
|
|
44
|
+
return []
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/shipload.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {PlayersManager} from './managers/players'
|
|
|
9
9
|
import type {LocationsManager} from './managers/locations'
|
|
10
10
|
import type {EpochsManager} from './managers/epochs'
|
|
11
11
|
import type {ActionsManager} from './managers/actions'
|
|
12
|
+
import type {NftManager} from './managers/nft'
|
|
12
13
|
import type {SubscriptionsManager} from './subscriptions/manager'
|
|
13
14
|
import type {GameState} from './entities/gamestate'
|
|
14
15
|
|
|
@@ -107,6 +108,10 @@ export class Shipload {
|
|
|
107
108
|
return this._context.actions
|
|
108
109
|
}
|
|
109
110
|
|
|
111
|
+
get nft(): NftManager {
|
|
112
|
+
return this._context.nft
|
|
113
|
+
}
|
|
114
|
+
|
|
110
115
|
get subscriptions(): SubscriptionsManager {
|
|
111
116
|
return this._context.subscriptions
|
|
112
117
|
}
|
|
@@ -13,12 +13,10 @@ import type {
|
|
|
13
13
|
WireEntity,
|
|
14
14
|
} from './types'
|
|
15
15
|
import {mapEntity, parseWireEntity} from './mappers'
|
|
16
|
-
import type {
|
|
17
|
-
import type {Warehouse} from '../entities/warehouse'
|
|
18
|
-
import type {Container} from '../entities/container'
|
|
16
|
+
import type {Entity} from '../entities/entity'
|
|
19
17
|
|
|
20
|
-
export type SubscriptionEntityType = 'ship' | 'warehouse' | 'container'
|
|
21
|
-
export type EntityInstance =
|
|
18
|
+
export type SubscriptionEntityType = 'ship' | 'warehouse' | 'container' | 'nexus'
|
|
19
|
+
export type EntityInstance = Entity
|
|
22
20
|
|
|
23
21
|
export interface SubscriptionsOptions {
|
|
24
22
|
url: string
|
|
@@ -34,6 +32,12 @@ export interface BoundsSubscriptionHandle {
|
|
|
34
32
|
current: Map<number, EntityInstance>
|
|
35
33
|
}
|
|
36
34
|
|
|
35
|
+
export interface OwnerSubscriptionHandle {
|
|
36
|
+
readonly subId: string
|
|
37
|
+
unsubscribe(): void
|
|
38
|
+
current: Map<number, EntityInstance>
|
|
39
|
+
}
|
|
40
|
+
|
|
37
41
|
export interface EntitySubscriptionHandle {
|
|
38
42
|
readonly subId: string
|
|
39
43
|
readonly entityType: SubscriptionEntityType
|
|
@@ -62,7 +66,7 @@ export class SubscriptionsManager {
|
|
|
62
66
|
onSnapshot?: (entities: EntityInstance[]) => void
|
|
63
67
|
onUpdate?: (entity: EntityInstance) => void
|
|
64
68
|
onBoundsDelta?: (entered: EntityInstance[], exited: number[]) => void
|
|
65
|
-
handle: BoundsSubscriptionHandle
|
|
69
|
+
handle: BoundsSubscriptionHandle | OwnerSubscriptionHandle
|
|
66
70
|
}
|
|
67
71
|
>()
|
|
68
72
|
private subCounter = 0
|
|
@@ -162,6 +166,36 @@ export class SubscriptionsManager {
|
|
|
162
166
|
return handle
|
|
163
167
|
}
|
|
164
168
|
|
|
169
|
+
subscribeOwner(
|
|
170
|
+
owner: string,
|
|
171
|
+
handlers: {
|
|
172
|
+
onSnapshot?: (entities: EntityInstance[]) => void
|
|
173
|
+
onUpdate?: (entity: EntityInstance) => void
|
|
174
|
+
} = {}
|
|
175
|
+
): OwnerSubscriptionHandle {
|
|
176
|
+
const subId = this.generateSubID('own')
|
|
177
|
+
const msg: SubscribeMessage = {
|
|
178
|
+
type: 'subscribe',
|
|
179
|
+
sub_id: subId,
|
|
180
|
+
owner,
|
|
181
|
+
}
|
|
182
|
+
const handle: OwnerSubscriptionHandle = {
|
|
183
|
+
subId,
|
|
184
|
+
unsubscribe: () => this.unsubscribeBounds(subId),
|
|
185
|
+
current: new Map(),
|
|
186
|
+
}
|
|
187
|
+
this.boundsSubs.set(subId, {
|
|
188
|
+
bounds: undefined,
|
|
189
|
+
owner,
|
|
190
|
+
prioritizeOwner: undefined,
|
|
191
|
+
onSnapshot: handlers.onSnapshot,
|
|
192
|
+
onUpdate: handlers.onUpdate,
|
|
193
|
+
handle,
|
|
194
|
+
})
|
|
195
|
+
this.sendMessage(msg)
|
|
196
|
+
return handle
|
|
197
|
+
}
|
|
198
|
+
|
|
165
199
|
private unsubscribeBounds(subId: string) {
|
|
166
200
|
this.boundsSubs.delete(subId)
|
|
167
201
|
this.sendMessage({type: 'unsubscribe', sub_id: subId})
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import {ServerContract} from '../contracts'
|
|
2
|
-
import {
|
|
3
|
-
import {Warehouse} from '../entities/warehouse'
|
|
4
|
-
import {Container} from '../entities/container'
|
|
2
|
+
import {Entity} from '../entities/entity'
|
|
5
3
|
import type {WireEntity} from './types'
|
|
6
4
|
|
|
7
|
-
export function mapEntity(ei: ServerContract.Types.entity_info):
|
|
8
|
-
|
|
9
|
-
if (ei.type.equals('warehouse')) return new Warehouse(ei)
|
|
10
|
-
if (ei.type.equals('container')) return new Container(ei)
|
|
11
|
-
throw new Error(`mapEntity: unknown entity type ${ei.type.toString()}`)
|
|
5
|
+
export function mapEntity(ei: ServerContract.Types.entity_info): Entity {
|
|
6
|
+
return new Entity(ei)
|
|
12
7
|
}
|
|
13
8
|
|
|
14
9
|
export function parseWireEntity(raw: WireEntity): ServerContract.Types.entity_info {
|
|
@@ -39,7 +39,7 @@ export type UnsubscribeMessage = {
|
|
|
39
39
|
export type SubscribeEntityMessage = {
|
|
40
40
|
type: 'subscribe_entity'
|
|
41
41
|
sub_id: string
|
|
42
|
-
entity_type: 'ship' | 'warehouse' | 'container'
|
|
42
|
+
entity_type: 'ship' | 'warehouse' | 'container' | 'nexus'
|
|
43
43
|
entity_id: string
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -80,10 +80,11 @@ export type AckMessage = {
|
|
|
80
80
|
|
|
81
81
|
export type WireEntity = Record<string, unknown> & {
|
|
82
82
|
type: number
|
|
83
|
-
type_name: 'ship' | 'warehouse' | 'container'
|
|
83
|
+
type_name: 'ship' | 'warehouse' | 'container' | 'nexus'
|
|
84
84
|
id: string | number
|
|
85
85
|
owner: string
|
|
86
86
|
coordinates: WireCoordinates
|
|
87
|
+
item_id: number
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
export type SnapshotMessage = {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto'
|
|
2
|
+
import {readFileSync} from 'node:fs'
|
|
3
|
+
|
|
4
|
+
export const CATALOG_FILES_REL = [
|
|
5
|
+
'items.json',
|
|
6
|
+
'recipes.json',
|
|
7
|
+
'entities.json',
|
|
8
|
+
'kind-registry.json',
|
|
9
|
+
'item-ids.ts',
|
|
10
|
+
] as const
|
|
11
|
+
|
|
12
|
+
export function computeCatalogHash(filePaths: ReadonlyArray<string>): string {
|
|
13
|
+
const hash = createHash('sha256')
|
|
14
|
+
for (const p of filePaths) {
|
|
15
|
+
hash.update(readFileSync(p))
|
|
16
|
+
hash.update('\0')
|
|
17
|
+
}
|
|
18
|
+
return hash.digest('hex')
|
|
19
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type {UInt16, UInt32} from '@wharfkit/antelope'
|
|
2
|
+
import type {ServerContract} from '../contracts'
|
|
3
|
+
import type {ProjectedEntity} from '../scheduling/projection'
|
|
4
|
+
import {type CargoStack, cargoItemToStack, mergeStacks} from '../capabilities/storage'
|
|
5
|
+
|
|
6
|
+
export interface ContractProjectedState {
|
|
7
|
+
owner: {toString(): string}
|
|
8
|
+
coordinates: ServerContract.Types.coordinates
|
|
9
|
+
energy?: UInt16
|
|
10
|
+
cargomass: UInt32
|
|
11
|
+
cargo: ServerContract.Types.cargo_view[]
|
|
12
|
+
hullmass?: UInt32
|
|
13
|
+
capacity?: UInt32
|
|
14
|
+
engines?: ServerContract.Types.movement_stats
|
|
15
|
+
loaders?: ServerContract.Types.loader_stats
|
|
16
|
+
generator?: ServerContract.Types.energy_stats
|
|
17
|
+
hauler?: ServerContract.Types.hauler_stats
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ProjectionComparisonOptions {
|
|
21
|
+
step?: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function assertProjectionEquals(
|
|
25
|
+
contract: ContractProjectedState,
|
|
26
|
+
sdk: ProjectedEntity,
|
|
27
|
+
options: ProjectionComparisonOptions = {}
|
|
28
|
+
): void {
|
|
29
|
+
const mismatches: string[] = []
|
|
30
|
+
|
|
31
|
+
const record = (name: string, c: unknown, s: unknown) => {
|
|
32
|
+
if (c !== s) mismatches.push(` ${name}: contract=${fmt(c)} sdk=${fmt(s)}`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const recordStatBlock = (name: string, c: unknown, s: unknown) => {
|
|
36
|
+
const cPresent = c !== undefined && c !== null
|
|
37
|
+
const sPresent = s !== undefined && s !== null
|
|
38
|
+
if (cPresent !== sPresent) {
|
|
39
|
+
mismatches.push(
|
|
40
|
+
` ${name}: contract=${cPresent ? 'present' : 'absent'} sdk=${sPresent ? 'present' : 'absent'}`
|
|
41
|
+
)
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
if (!cPresent) return
|
|
45
|
+
const cn = JSON.stringify(normaliseStatBlock(c))
|
|
46
|
+
const sn = JSON.stringify(normaliseStatBlock(s))
|
|
47
|
+
if (cn !== sn) mismatches.push(` ${name}: contract=${cn} sdk=${sn}`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
record('coordinates.x', toNum(contract.coordinates.x), Number(sdk.location.x))
|
|
51
|
+
record('coordinates.y', toNum(contract.coordinates.y), Number(sdk.location.y))
|
|
52
|
+
record('energy', toNum(contract.energy), Number(sdk.energy))
|
|
53
|
+
record('cargomass', toNum(contract.cargomass), Number(sdk.cargoMass))
|
|
54
|
+
record('hullmass', toNum(contract.hullmass), Number(sdk.shipMass))
|
|
55
|
+
record('capacity', toNum(contract.capacity), sdk.capacity ? Number(sdk.capacity) : undefined)
|
|
56
|
+
|
|
57
|
+
recordStatBlock('engines', contract.engines, sdk.engines)
|
|
58
|
+
recordStatBlock('loaders', contract.loaders, sdk.loaders)
|
|
59
|
+
recordStatBlock('generator', contract.generator, sdk.generator)
|
|
60
|
+
recordStatBlock('hauler', contract.hauler, sdk.hauler)
|
|
61
|
+
|
|
62
|
+
if (contract.cargo.length > 0 || sdk.cargo.length > 0) {
|
|
63
|
+
const contractCargo = normaliseCargo(mergeContractCargo(contract.cargo))
|
|
64
|
+
const sdkCargo = normaliseCargo(sdk.cargo)
|
|
65
|
+
if (contractCargo.length !== sdkCargo.length) {
|
|
66
|
+
mismatches.push(
|
|
67
|
+
` cargo.length: contract=${contractCargo.length} sdk=${sdkCargo.length}`
|
|
68
|
+
)
|
|
69
|
+
} else {
|
|
70
|
+
for (let i = 0; i < contractCargo.length; i++) {
|
|
71
|
+
const c = contractCargo[i]
|
|
72
|
+
const s = sdkCargo[i]
|
|
73
|
+
if (c.itemId !== s.itemId || c.stats !== s.stats || c.quantity !== s.quantity) {
|
|
74
|
+
mismatches.push(
|
|
75
|
+
` cargo[${i}]: contract={item:${c.itemId},stats:${c.stats},qty:${c.quantity}} sdk={item:${s.itemId},stats:${s.stats},qty:${s.quantity}}`
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (mismatches.length > 0) {
|
|
83
|
+
const header =
|
|
84
|
+
options.step !== undefined
|
|
85
|
+
? `projection divergence at step ${options.step}:`
|
|
86
|
+
: 'projection divergence:'
|
|
87
|
+
throw new Error([header, ...mismatches].join('\n'))
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface NormalisedStack {
|
|
92
|
+
itemId: number
|
|
93
|
+
stats: string
|
|
94
|
+
quantity: string
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function mergeContractCargo(cargo: ServerContract.Types.cargo_view[]): CargoStack[] {
|
|
98
|
+
return cargo.reduce<CargoStack[]>(
|
|
99
|
+
(acc, row) =>
|
|
100
|
+
mergeStacks(acc, cargoItemToStack(row as unknown as ServerContract.Types.cargo_item)),
|
|
101
|
+
[]
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function normaliseCargo(cargo: CargoStack[]): NormalisedStack[] {
|
|
106
|
+
return cargo
|
|
107
|
+
.map((s) => ({
|
|
108
|
+
itemId: Number(s.item_id),
|
|
109
|
+
stats: BigInt(s.stats.toString()).toString(),
|
|
110
|
+
quantity: BigInt(s.quantity.toString()).toString(),
|
|
111
|
+
}))
|
|
112
|
+
.sort(stackSort)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function stackSort(a: NormalisedStack, b: NormalisedStack): number {
|
|
116
|
+
if (a.itemId !== b.itemId) return a.itemId - b.itemId
|
|
117
|
+
return a.stats < b.stats ? -1 : a.stats > b.stats ? 1 : 0
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function toNum(v: unknown): number | undefined {
|
|
121
|
+
if (v === undefined || v === null) return undefined
|
|
122
|
+
if (typeof v === 'number') return v
|
|
123
|
+
if (typeof v === 'bigint') return Number(v)
|
|
124
|
+
if (typeof (v as {toNumber?: unknown}).toNumber === 'function') {
|
|
125
|
+
return (v as {toNumber(): number}).toNumber()
|
|
126
|
+
}
|
|
127
|
+
return Number(v as number)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function fmt(v: unknown): string {
|
|
131
|
+
if (v === undefined) return 'undefined'
|
|
132
|
+
if (v === null) return 'null'
|
|
133
|
+
return String(v)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function normaliseStatBlock(block: unknown): Record<string, number> {
|
|
137
|
+
const out: Record<string, number> = {}
|
|
138
|
+
const obj = block as Record<string, unknown>
|
|
139
|
+
for (const k of Object.keys(obj).sort()) {
|
|
140
|
+
out[k] = toNum(obj[k]) ?? 0
|
|
141
|
+
}
|
|
142
|
+
return out
|
|
143
|
+
}
|