@shipload/sdk 1.0.0-next.2 → 1.0.0-next.21
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 +1731 -1044
- package/lib/shipload.js +6758 -4523
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +6649 -4479
- 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 +151 -40
- package/src/managers/actions.ts +184 -82
- package/src/managers/base.ts +2 -2
- package/src/managers/construction-types.ts +68 -0
- package/src/managers/construction.ts +292 -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 +16 -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
|
@@ -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
|
+
}
|
package/src/travel/travel.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
type Distance,
|
|
29
29
|
MAX_ORBITAL_ALTITUDE,
|
|
30
30
|
MIN_ORBITAL_ALTITUDE,
|
|
31
|
+
MIN_TRANSFER_DISTANCE,
|
|
31
32
|
PRECISION,
|
|
32
33
|
type ShipLike,
|
|
33
34
|
TaskType,
|
|
@@ -77,6 +78,59 @@ export function lerp(
|
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
export interface FloatPosition {
|
|
82
|
+
x: number
|
|
83
|
+
y: number
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function easeFlightProgress(t: number): number {
|
|
87
|
+
if (t <= 0) return 0
|
|
88
|
+
if (t >= 1) return 1
|
|
89
|
+
return t < 0.5 ? 2 * t * t : 1 - 2 * (1 - t) * (1 - t)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function flightSpeedFactor(t: number): number {
|
|
93
|
+
if (t <= 0 || t >= 1) return 0
|
|
94
|
+
return t < 0.5 ? 4 * t : 4 * (1 - t)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function interpolateFlightPosition(
|
|
98
|
+
origin: {x: Int64Type | number; y: Int64Type | number},
|
|
99
|
+
destination: {x: Int64Type | number; y: Int64Type | number},
|
|
100
|
+
taskProgress: number,
|
|
101
|
+
options?: {easing?: 'physics' | 'linear'}
|
|
102
|
+
): FloatPosition {
|
|
103
|
+
const t = options?.easing === 'linear' ? taskProgress : easeFlightProgress(taskProgress)
|
|
104
|
+
return {
|
|
105
|
+
x: (1 - t) * Number(origin.x) + t * Number(destination.x),
|
|
106
|
+
y: (1 - t) * Number(origin.y) + t * Number(destination.y),
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function getInterpolatedPosition(
|
|
111
|
+
entity: HasScheduleAndLocation,
|
|
112
|
+
taskIndex: number,
|
|
113
|
+
taskProgress: number
|
|
114
|
+
): FloatPosition {
|
|
115
|
+
if (!entity.schedule || entity.schedule.tasks.length === 0) {
|
|
116
|
+
return {x: Number(entity.coordinates.x), y: Number(entity.coordinates.y)}
|
|
117
|
+
}
|
|
118
|
+
if (taskIndex < 0) {
|
|
119
|
+
const settled = getFlightOrigin(entity, entity.schedule.tasks.length)
|
|
120
|
+
return {x: Number(settled.x), y: Number(settled.y)}
|
|
121
|
+
}
|
|
122
|
+
const task = entity.schedule.tasks[taskIndex]
|
|
123
|
+
if (!task.type.equals(TaskType.TRAVEL) || !task.coordinates) {
|
|
124
|
+
const origin = getFlightOrigin(entity, taskIndex)
|
|
125
|
+
return {x: Number(origin.x), y: Number(origin.y)}
|
|
126
|
+
}
|
|
127
|
+
return interpolateFlightPosition(
|
|
128
|
+
getFlightOrigin(entity, taskIndex),
|
|
129
|
+
task.coordinates,
|
|
130
|
+
taskProgress
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
80
134
|
export function rotation(
|
|
81
135
|
origin: ServerContract.ActionParams.Type.coordinates,
|
|
82
136
|
destination: ServerContract.ActionParams.Type.coordinates
|
|
@@ -408,14 +462,18 @@ export function getDestinationLocation(
|
|
|
408
462
|
return undefined
|
|
409
463
|
}
|
|
410
464
|
|
|
465
|
+
/** Returns chain-tile coordinates (rounded). For visual position use getInterpolatedPosition. */
|
|
411
466
|
export function getPositionAt(
|
|
412
467
|
entity: HasScheduleAndLocation,
|
|
413
468
|
taskIndex: number,
|
|
414
469
|
taskProgress: number
|
|
415
470
|
): ServerContract.ActionParams.Type.coordinates {
|
|
416
|
-
if (!entity.schedule || entity.schedule.tasks.length === 0
|
|
471
|
+
if (!entity.schedule || entity.schedule.tasks.length === 0) {
|
|
417
472
|
return entity.coordinates
|
|
418
473
|
}
|
|
474
|
+
if (taskIndex < 0) {
|
|
475
|
+
return getFlightOrigin(entity, entity.schedule.tasks.length)
|
|
476
|
+
}
|
|
419
477
|
|
|
420
478
|
const task = entity.schedule.tasks[taskIndex]
|
|
421
479
|
|
|
@@ -490,7 +548,8 @@ export function calc_transfer_duration(
|
|
|
490
548
|
: (source.location.z?.toNumber() ?? 0)
|
|
491
549
|
const destZ =
|
|
492
550
|
typeof dest.location.z === 'number' ? dest.location.z : (dest.location.z?.toNumber() ?? 0)
|
|
493
|
-
const
|
|
551
|
+
const rawDistance = Math.abs(sourceZ - destZ)
|
|
552
|
+
const distance = rawDistance < MIN_TRANSFER_DISTANCE ? MIN_TRANSFER_DISTANCE : rawDistance
|
|
494
553
|
|
|
495
554
|
const totalMass = cargoMass + totalLoaderMass
|
|
496
555
|
const acceleration = calc_acceleration(totalThrust, totalMass)
|
package/src/types/index.ts
CHANGED