@shipload/sdk 1.0.0-next.72 → 1.0.0-next.74
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 +576 -133
- package/lib/shipload.js +6019 -5114
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +6002 -5108
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +194 -52
- package/lib/testing.js +767 -468
- package/lib/testing.js.map +1 -1
- package/lib/testing.m.js +767 -468
- package/lib/testing.m.js.map +1 -1
- package/package.json +1 -1
- package/src/contracts/server.ts +523 -143
- package/src/data/catalog.test.ts +81 -1
- package/src/data/catalog.ts +6 -4
- package/src/data/entities.json +2 -2
- package/src/data/metadata.ts +197 -363
- package/src/data/task-types.json +184 -0
- package/src/entities/makers.ts +2 -2
- package/src/index-module.ts +31 -2
- package/src/influence/civic-shuttle.test.ts +58 -0
- package/src/influence/civic-shuttle.ts +52 -6
- package/src/influence/duration.ts +0 -65
- package/src/influence/index.ts +4 -5
- package/src/influence/influence.test.ts +51 -136
- package/src/influence/valuation.ts +75 -0
- package/src/managers/actions.ts +49 -10
- package/src/managers/balances.test.ts +33 -0
- package/src/managers/balances.ts +15 -0
- package/src/managers/context.ts +50 -11
- package/src/managers/index.ts +14 -0
- package/src/managers/jobs.test.ts +82 -1
- package/src/managers/jobs.ts +41 -2
- package/src/managers/nft.test.ts +173 -1
- package/src/managers/nft.ts +35 -2
- package/src/managers/shuttle.test.ts +562 -0
- package/src/managers/shuttle.ts +397 -0
- package/src/resolution/resolve-item.ts +2 -2
- package/src/scheduling/availability.ts +10 -0
- package/src/scheduling/energy.ts +55 -1
- package/src/scheduling/jobs.test.ts +171 -1
- package/src/scheduling/jobs.ts +110 -13
- package/src/scheduling/projection.ts +2 -1
- package/src/scheduling/task-effects.ts +38 -0
- package/src/scheduling/unwrap.test.ts +60 -0
- package/src/scheduling/unwrap.ts +24 -8
- package/src/shipload.ts +23 -4
- package/src/types.ts +1 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {describe, expect, test} from 'bun:test'
|
|
2
|
-
import {Checksum256
|
|
3
|
-
import type {ServerContract} from '../contracts'
|
|
2
|
+
import {Checksum256} from '@wharfkit/antelope'
|
|
4
3
|
import {ITEM_ORE_T1, ITEM_PLATE, ITEM_CRYSTAL_T1} from '../data/item-ids'
|
|
5
4
|
import {categoryIndex} from './categories'
|
|
6
5
|
import {
|
|
@@ -20,9 +19,10 @@ import {
|
|
|
20
19
|
popcount5,
|
|
21
20
|
} from './demand'
|
|
22
21
|
import {findDecomp, DECOMP_REGISTRY} from './decomp'
|
|
23
|
-
import {
|
|
22
|
+
import {contributeDuration} from './duration'
|
|
24
23
|
import {getStatCount, statsSumSq} from './quality'
|
|
25
|
-
import {pricingFromWeights, valueCargoItem, valueContribution} from './valuation'
|
|
24
|
+
import {explainCargoItem, pricingFromWeights, valueCargoItem, valueContribution} from './valuation'
|
|
25
|
+
import {getItems} from '../data/catalog'
|
|
26
26
|
import {citizenryName, citizenryPatternCount} from './citizenry'
|
|
27
27
|
import citizenryAdjectives from '../data/citizenry-adjectives.json'
|
|
28
28
|
import citizenryNouns from '../data/citizenry-nouns.json'
|
|
@@ -155,6 +155,53 @@ describe('decomposition', () => {
|
|
|
155
155
|
})
|
|
156
156
|
})
|
|
157
157
|
|
|
158
|
+
describe('explainCargoItem', () => {
|
|
159
|
+
test('factors reproduce valueCargoItem for every valuable catalog item', () => {
|
|
160
|
+
const acute = buildDemand(0, categoryIndex('regolith'), DEMAND_TRIPLE_SEED)
|
|
161
|
+
for (const def of getItems()) {
|
|
162
|
+
if (def.type !== 'resource' && def.type !== 'component') continue
|
|
163
|
+
const item = {itemId: Number(def.id), quantity: 7, stats: packStats(363, 535, 246)}
|
|
164
|
+
const explained = explainCargoItem(item, acute, pricing)
|
|
165
|
+
expect(explained.totalAtomic).toBe(valueCargoItem(item, acute, pricing))
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test('a resource explains mass, weight, quality and need', () => {
|
|
170
|
+
const acute = buildDemand(0, categoryIndex('crystal'), DEMAND_TRIPLE_SEED)
|
|
171
|
+
const explained = explainCargoItem(
|
|
172
|
+
{itemId: ITEM_CRYSTAL_T1, quantity: 5000, stats: packStats(PAR)},
|
|
173
|
+
acute,
|
|
174
|
+
pricing
|
|
175
|
+
)
|
|
176
|
+
if (explained.kind !== 'resource') throw new Error('expected resource')
|
|
177
|
+
expect(explained.mass).toBe(
|
|
178
|
+
BigInt(getItems().find((i) => Number(i.id) === ITEM_CRYSTAL_T1)!.mass) * 5000n
|
|
179
|
+
)
|
|
180
|
+
expect(explained.weight).toBe(1)
|
|
181
|
+
expect(explained.quality).toBeCloseTo(1, 6)
|
|
182
|
+
expect(explained.need).toBe(2)
|
|
183
|
+
expect(explained.points).toBeCloseTo(
|
|
184
|
+
(Number(explained.mass) / 10) * explained.weight * explained.quality * explained.need,
|
|
185
|
+
3
|
|
186
|
+
)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('a component explains raw mass, material weight, quality and effort', () => {
|
|
190
|
+
const explained = explainCargoItem(
|
|
191
|
+
{itemId: ITEM_PLATE, quantity: 3, stats: packStats(PAR)},
|
|
192
|
+
baseDemand,
|
|
193
|
+
pricing
|
|
194
|
+
)
|
|
195
|
+
if (explained.kind !== 'component') throw new Error('expected component')
|
|
196
|
+
const entry = findDecomp(ITEM_PLATE)!
|
|
197
|
+
const raw = entry.buckets.reduce((sum, b) => sum + b.rawMass, 0)
|
|
198
|
+
expect(explained.rawMass).toBe(raw * 3)
|
|
199
|
+
expect(explained.processedMass).toBe(entry.processedMass * 3)
|
|
200
|
+
expect(explained.weight).toBeGreaterThanOrEqual(1)
|
|
201
|
+
expect(explained.baseAtomic + explained.effortAtomic).toBe(explained.totalAtomic)
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
|
|
158
205
|
describe('valuation', () => {
|
|
159
206
|
test('a tonne of par T1 ore at base demand is worth exactly one point', () => {
|
|
160
207
|
const value = valueCargoItem(
|
|
@@ -316,135 +363,3 @@ describe('scales', () => {
|
|
|
316
363
|
expect(NEED_FP_SCALE % INFLUENCE_ATOMIC_PER_POINT).toBe(0)
|
|
317
364
|
})
|
|
318
365
|
})
|
|
319
|
-
|
|
320
|
-
describe('depot transfer duration', () => {
|
|
321
|
-
type ModuleEntry = ServerContract.Types.module_entry
|
|
322
|
-
const DEPOT_ITEM = 10219
|
|
323
|
-
const LOADER_T1 = 10103
|
|
324
|
-
|
|
325
|
-
function makeModuleEntry(itemId: number, stats: bigint): ModuleEntry {
|
|
326
|
-
return {
|
|
327
|
-
type: UInt8.from(0),
|
|
328
|
-
installed: {item_id: UInt16.from(itemId), stats: UInt64.from(stats)},
|
|
329
|
-
} as unknown as ModuleEntry
|
|
330
|
-
}
|
|
331
|
-
function emptySlot(): ModuleEntry {
|
|
332
|
-
return {type: UInt8.from(0)} as unknown as ModuleEntry
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// A spawned depot: loader in slot 0, four empty bays after it.
|
|
336
|
-
const depotWithLoader = (): ModuleEntry[] => [
|
|
337
|
-
makeModuleEntry(LOADER_T1, packStats(500)),
|
|
338
|
-
emptySlot(),
|
|
339
|
-
emptySlot(),
|
|
340
|
-
emptySlot(),
|
|
341
|
-
emptySlot(),
|
|
342
|
-
]
|
|
343
|
-
const params = (overrides: Record<string, unknown> = {}) => ({
|
|
344
|
-
depotModules: depotWithLoader(),
|
|
345
|
-
depotItemId: DEPOT_ITEM,
|
|
346
|
-
depotKind: 'depot',
|
|
347
|
-
depotZ: 0,
|
|
348
|
-
shipKind: 'ship',
|
|
349
|
-
shipZ: 0,
|
|
350
|
-
cargoMass: 100,
|
|
351
|
-
...overrides,
|
|
352
|
-
})
|
|
353
|
-
|
|
354
|
-
test('drives off the depot loader, not the ship', () => {
|
|
355
|
-
expect(depotTransferDuration(params())).toBeGreaterThan(0)
|
|
356
|
-
})
|
|
357
|
-
|
|
358
|
-
test('grows with cargo mass', () => {
|
|
359
|
-
expect(depotTransferDuration(params({cargoMass: 10_000}))).toBeGreaterThan(
|
|
360
|
-
depotTransferDuration(params({cargoMass: 100}))
|
|
361
|
-
)
|
|
362
|
-
})
|
|
363
|
-
|
|
364
|
-
test('empty cargo costs nothing', () => {
|
|
365
|
-
expect(depotTransferDuration(params({cargoMass: 0}))).toBe(0)
|
|
366
|
-
})
|
|
367
|
-
|
|
368
|
-
test('a depot with no loader installed cannot transfer', () => {
|
|
369
|
-
expect(depotTransferDuration(params({depotModules: [emptySlot()]}))).toBe(0)
|
|
370
|
-
})
|
|
371
|
-
|
|
372
|
-
test('co-located transfers are floored at the orbital minimum distance', () => {
|
|
373
|
-
// max(planetary 100, orbital 200) = 200, so anything under 200 reads as 200
|
|
374
|
-
const touching = depotTransferDuration(params({shipZ: 0, depotZ: 0}))
|
|
375
|
-
const under = depotTransferDuration(params({shipZ: 150, depotZ: 0}))
|
|
376
|
-
const over = depotTransferDuration(params({shipZ: 400, depotZ: 0}))
|
|
377
|
-
expect(under).toBe(touching)
|
|
378
|
-
expect(over).toBeGreaterThan(touching)
|
|
379
|
-
})
|
|
380
|
-
|
|
381
|
-
test('never reports zero for real cargo', () => {
|
|
382
|
-
expect(depotTransferDuration(params({cargoMass: 1}))).toBeGreaterThanOrEqual(1)
|
|
383
|
-
})
|
|
384
|
-
})
|
|
385
|
-
|
|
386
|
-
describe('civic drop-off duration', () => {
|
|
387
|
-
type ModuleEntry = ServerContract.Types.module_entry
|
|
388
|
-
const DEPOT_ITEM = 10219
|
|
389
|
-
const WORKSHOP_ITEM = 10208
|
|
390
|
-
const LOADER_T1 = 10103
|
|
391
|
-
|
|
392
|
-
function makeModuleEntry(itemId: number, stats: bigint): ModuleEntry {
|
|
393
|
-
return {
|
|
394
|
-
type: UInt8.from(0),
|
|
395
|
-
installed: {item_id: UInt16.from(itemId), stats: UInt64.from(stats)},
|
|
396
|
-
} as unknown as ModuleEntry
|
|
397
|
-
}
|
|
398
|
-
function emptySlot(): ModuleEntry {
|
|
399
|
-
return {type: UInt8.from(0)} as unknown as ModuleEntry
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
const withLoader = () => ({
|
|
403
|
-
buildingModules: [makeModuleEntry(LOADER_T1, packStats(500)), emptySlot()],
|
|
404
|
-
buildingItemId: DEPOT_ITEM,
|
|
405
|
-
buildingKind: 'depot',
|
|
406
|
-
buildingZ: 0,
|
|
407
|
-
shipKind: 'ship',
|
|
408
|
-
shipZ: 400,
|
|
409
|
-
cargoMass: 100,
|
|
410
|
-
})
|
|
411
|
-
const withoutLoader = () => ({
|
|
412
|
-
buildingModules: [] as ModuleEntry[],
|
|
413
|
-
buildingItemId: WORKSHOP_ITEM,
|
|
414
|
-
buildingKind: 'workshop',
|
|
415
|
-
buildingZ: 0,
|
|
416
|
-
shipKind: 'ship',
|
|
417
|
-
shipZ: 400,
|
|
418
|
-
cargoMass: 100,
|
|
419
|
-
})
|
|
420
|
-
|
|
421
|
-
test('a building with a slot-0 loader prices off that loader', () => {
|
|
422
|
-
const p = withLoader()
|
|
423
|
-
expect(civicDropoffDuration(p)).toBe(
|
|
424
|
-
depotTransferDuration({
|
|
425
|
-
depotModules: p.buildingModules,
|
|
426
|
-
depotItemId: p.buildingItemId,
|
|
427
|
-
depotKind: p.buildingKind,
|
|
428
|
-
depotZ: p.buildingZ,
|
|
429
|
-
shipKind: p.shipKind,
|
|
430
|
-
shipZ: p.shipZ,
|
|
431
|
-
cargoMass: p.cargoMass,
|
|
432
|
-
})
|
|
433
|
-
)
|
|
434
|
-
})
|
|
435
|
-
|
|
436
|
-
test('a building with no loader falls back to the civic loader', () => {
|
|
437
|
-
const p = withoutLoader()
|
|
438
|
-
expect(civicDropoffDuration(p)).toBe(contributeDuration(p.cargoMass, p.shipZ))
|
|
439
|
-
expect(civicDropoffDuration(p)).toBeGreaterThan(0)
|
|
440
|
-
})
|
|
441
|
-
|
|
442
|
-
test('grows with cargo mass on both paths', () => {
|
|
443
|
-
expect(civicDropoffDuration({...withLoader(), cargoMass: 10_000})).toBeGreaterThan(
|
|
444
|
-
civicDropoffDuration(withLoader())
|
|
445
|
-
)
|
|
446
|
-
expect(civicDropoffDuration({...withoutLoader(), cargoMass: 10_000})).toBeGreaterThan(
|
|
447
|
-
civicDropoffDuration(withoutLoader())
|
|
448
|
-
)
|
|
449
|
-
})
|
|
450
|
-
})
|
|
@@ -126,6 +126,81 @@ export function valueCargoItem(
|
|
|
126
126
|
return withEffort
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
export interface ResourceValuation {
|
|
130
|
+
kind: 'resource'
|
|
131
|
+
category: number
|
|
132
|
+
tier: number
|
|
133
|
+
mass: bigint
|
|
134
|
+
weight: number
|
|
135
|
+
quality: number
|
|
136
|
+
need: number
|
|
137
|
+
totalAtomic: bigint
|
|
138
|
+
points: number
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface ComponentValuation {
|
|
142
|
+
kind: 'component'
|
|
143
|
+
rawMass: number
|
|
144
|
+
processedMass: number
|
|
145
|
+
weight: number
|
|
146
|
+
quality: number
|
|
147
|
+
baseAtomic: bigint
|
|
148
|
+
effortAtomic: bigint
|
|
149
|
+
totalAtomic: bigint
|
|
150
|
+
points: number
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export type CargoValuation = ResourceValuation | ComponentValuation
|
|
154
|
+
|
|
155
|
+
export function explainCargoItem(
|
|
156
|
+
item: ValuedItem,
|
|
157
|
+
demand: DemandView,
|
|
158
|
+
pricing: InfluencePricing
|
|
159
|
+
): CargoValuation {
|
|
160
|
+
const def = getItem(item.itemId)
|
|
161
|
+
const totalAtomic = valueCargoItem(item, demand, pricing)
|
|
162
|
+
const points = Number(totalAtomic) / INFLUENCE_ATOMIC_PER_POINT
|
|
163
|
+
const nStats = getStatCount(item.itemId)
|
|
164
|
+
const quality = statsSumSq(item.stats, nStats) / (nStats * pricing.d1)
|
|
165
|
+
|
|
166
|
+
if (def.type === 'resource') {
|
|
167
|
+
if (def.category === undefined) throw new Error('resource has no category')
|
|
168
|
+
const category = categoryIndex(def.category)
|
|
169
|
+
return {
|
|
170
|
+
kind: 'resource',
|
|
171
|
+
category,
|
|
172
|
+
tier: def.tier,
|
|
173
|
+
mass: BigInt(def.mass) * BigInt(item.quantity),
|
|
174
|
+
weight: Number(pricing.weight(category, def.tier)) / WEIGHT_FP_SCALE,
|
|
175
|
+
quality,
|
|
176
|
+
need: Number(needForCategory(demand, category)) / NEED_FP_SCALE,
|
|
177
|
+
totalAtomic,
|
|
178
|
+
points,
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const entry = findDecomp(item.itemId)
|
|
183
|
+
if (!entry) throw new Error('item has no decomposition')
|
|
184
|
+
let rawMass = 0
|
|
185
|
+
let weighted = 0n
|
|
186
|
+
for (const bucket of entry.buckets) {
|
|
187
|
+
rawMass += bucket.rawMass
|
|
188
|
+
weighted += BigInt(bucket.rawMass) * pricing.weight(bucket.category, bucket.tier)
|
|
189
|
+
}
|
|
190
|
+
const effortAtomic = componentEffortAtomic(entry.processedMass, item.quantity)
|
|
191
|
+
return {
|
|
192
|
+
kind: 'component',
|
|
193
|
+
rawMass: rawMass * item.quantity,
|
|
194
|
+
processedMass: entry.processedMass * item.quantity,
|
|
195
|
+
weight: rawMass === 0 ? 0 : Number(weighted) / (rawMass * WEIGHT_FP_SCALE),
|
|
196
|
+
quality,
|
|
197
|
+
baseAtomic: totalAtomic - effortAtomic,
|
|
198
|
+
effortAtomic,
|
|
199
|
+
totalAtomic,
|
|
200
|
+
points,
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
129
204
|
export function valueContribution(
|
|
130
205
|
bundle: ValuedItem[],
|
|
131
206
|
demand: DemandView,
|
package/src/managers/actions.ts
CHANGED
|
@@ -332,25 +332,35 @@ export class ActionsManager extends BaseManager {
|
|
|
332
332
|
depotstore(
|
|
333
333
|
shipId: UInt64Type,
|
|
334
334
|
depotId: UInt64Type,
|
|
335
|
-
items: ServerContract.ActionParams.Type.cargo_item[]
|
|
335
|
+
items: ServerContract.ActionParams.Type.cargo_item[],
|
|
336
|
+
shuttledBy?: UInt64Type
|
|
336
337
|
): Action {
|
|
337
|
-
|
|
338
|
+
const params: ServerContract.ActionParams.depotstore = {
|
|
338
339
|
ship_id: UInt64.from(shipId),
|
|
339
340
|
depot_id: UInt64.from(depotId),
|
|
340
341
|
items,
|
|
341
|
-
}
|
|
342
|
+
}
|
|
343
|
+
if (shuttledBy !== undefined) {
|
|
344
|
+
params.shuttled_by = UInt64.from(shuttledBy)
|
|
345
|
+
}
|
|
346
|
+
return this.server.action('depotstore', params)
|
|
342
347
|
}
|
|
343
348
|
|
|
344
349
|
depottake(
|
|
345
350
|
shipId: UInt64Type,
|
|
346
351
|
depotId: UInt64Type,
|
|
347
|
-
items: ServerContract.ActionParams.Type.cargo_item[]
|
|
352
|
+
items: ServerContract.ActionParams.Type.cargo_item[],
|
|
353
|
+
shuttledBy?: UInt64Type
|
|
348
354
|
): Action {
|
|
349
|
-
|
|
355
|
+
const params: ServerContract.ActionParams.depottake = {
|
|
350
356
|
ship_id: UInt64.from(shipId),
|
|
351
357
|
depot_id: UInt64.from(depotId),
|
|
352
358
|
items,
|
|
353
|
-
}
|
|
359
|
+
}
|
|
360
|
+
if (shuttledBy !== undefined) {
|
|
361
|
+
params.shuttled_by = UInt64.from(shuttledBy)
|
|
362
|
+
}
|
|
363
|
+
return this.server.action('depottake', params)
|
|
354
364
|
}
|
|
355
365
|
|
|
356
366
|
shuttle(
|
|
@@ -518,7 +528,8 @@ export class ActionsManager extends BaseManager {
|
|
|
518
528
|
workshopId: UInt64Type,
|
|
519
529
|
recipeId: UInt16Type,
|
|
520
530
|
quantity: UInt32Type,
|
|
521
|
-
inputs: ServerContract.ActionParams.Type.cargo_item[]
|
|
531
|
+
inputs: ServerContract.ActionParams.Type.cargo_item[],
|
|
532
|
+
shuttledBy?: UInt64Type
|
|
522
533
|
): Action {
|
|
523
534
|
const params: ServerContract.ActionParams.craftjob = {
|
|
524
535
|
ship_id: UInt64.from(shipId),
|
|
@@ -527,6 +538,9 @@ export class ActionsManager extends BaseManager {
|
|
|
527
538
|
quantity: UInt32.from(quantity),
|
|
528
539
|
inputs,
|
|
529
540
|
}
|
|
541
|
+
if (shuttledBy !== undefined) {
|
|
542
|
+
params.shuttled_by = UInt64.from(shuttledBy)
|
|
543
|
+
}
|
|
530
544
|
return this.server.action('craftjob', params)
|
|
531
545
|
}
|
|
532
546
|
|
|
@@ -540,6 +554,9 @@ export class ActionsManager extends BaseManager {
|
|
|
540
554
|
canceljob(route: JobCancelRoute): Action {
|
|
541
555
|
if (route.kind === 'dropoff') return this.cancel(route.shipId, route.laneKey, route.count)
|
|
542
556
|
if (route.kind === 'build') return this.cancelbuild(route.jobId)
|
|
557
|
+
if (route.kind === 'civic') {
|
|
558
|
+
return this.cancelcivic(route.buildingId, route.laneKey, route.fromId)
|
|
559
|
+
}
|
|
543
560
|
return this.cancelcraft(route.jobId)
|
|
544
561
|
}
|
|
545
562
|
|
|
@@ -566,11 +583,14 @@ export class ActionsManager extends BaseManager {
|
|
|
566
583
|
return this.server.action('clustercraft', params)
|
|
567
584
|
}
|
|
568
585
|
|
|
569
|
-
claimcraft(jobId: UInt64Type, shipId: UInt64Type): Action {
|
|
586
|
+
claimcraft(jobId: UInt64Type, shipId: UInt64Type, shuttledBy?: UInt64Type): Action {
|
|
570
587
|
const params: ServerContract.ActionParams.claimcraft = {
|
|
571
588
|
job_id: UInt64.from(jobId),
|
|
572
589
|
ship_id: UInt64.from(shipId),
|
|
573
590
|
}
|
|
591
|
+
if (shuttledBy !== undefined) {
|
|
592
|
+
params.shuttled_by = UInt64.from(shuttledBy)
|
|
593
|
+
}
|
|
574
594
|
return this.server.action('claimcraft', params)
|
|
575
595
|
}
|
|
576
596
|
|
|
@@ -578,7 +598,8 @@ export class ActionsManager extends BaseManager {
|
|
|
578
598
|
targetId: UInt64Type,
|
|
579
599
|
dockId: UInt64Type,
|
|
580
600
|
targetItemId: UInt16Type,
|
|
581
|
-
inputs: ServerContract.ActionParams.Type.cargo_item[]
|
|
601
|
+
inputs: ServerContract.ActionParams.Type.cargo_item[],
|
|
602
|
+
shuttledBy?: UInt64Type
|
|
582
603
|
): Action {
|
|
583
604
|
const params: ServerContract.ActionParams.buildjob = {
|
|
584
605
|
target_id: UInt64.from(targetId),
|
|
@@ -586,6 +607,9 @@ export class ActionsManager extends BaseManager {
|
|
|
586
607
|
target_item_id: UInt16.from(targetItemId),
|
|
587
608
|
inputs,
|
|
588
609
|
}
|
|
610
|
+
if (shuttledBy !== undefined) {
|
|
611
|
+
params.shuttled_by = UInt64.from(shuttledBy)
|
|
612
|
+
}
|
|
589
613
|
return this.server.action('buildjob', params)
|
|
590
614
|
}
|
|
591
615
|
|
|
@@ -596,10 +620,13 @@ export class ActionsManager extends BaseManager {
|
|
|
596
620
|
return this.server.action('claimbuild', params)
|
|
597
621
|
}
|
|
598
622
|
|
|
599
|
-
cancelbuild(jobId: UInt64Type): Action {
|
|
623
|
+
cancelbuild(jobId: UInt64Type, shuttledBy?: UInt64Type): Action {
|
|
600
624
|
const params: ServerContract.ActionParams.cancelbuild = {
|
|
601
625
|
job_id: UInt64.from(jobId),
|
|
602
626
|
}
|
|
627
|
+
if (shuttledBy !== undefined) {
|
|
628
|
+
params.shuttled_by = UInt64.from(shuttledBy)
|
|
629
|
+
}
|
|
603
630
|
return this.server.action('cancelbuild', params)
|
|
604
631
|
}
|
|
605
632
|
|
|
@@ -610,6 +637,18 @@ export class ActionsManager extends BaseManager {
|
|
|
610
637
|
})
|
|
611
638
|
}
|
|
612
639
|
|
|
640
|
+
blenddepot(
|
|
641
|
+
depotId: UInt64Type,
|
|
642
|
+
owner: NameType,
|
|
643
|
+
inputs: ServerContract.ActionParams.Type.cargo_item[]
|
|
644
|
+
): Action {
|
|
645
|
+
return this.server.action('blenddepot', {
|
|
646
|
+
depot_id: UInt64.from(depotId),
|
|
647
|
+
owner: Name.from(owner),
|
|
648
|
+
inputs,
|
|
649
|
+
})
|
|
650
|
+
}
|
|
651
|
+
|
|
613
652
|
deploy(
|
|
614
653
|
entityId: UInt64Type,
|
|
615
654
|
ref: ServerContract.ActionParams.Type.cargo_ref,
|
|
@@ -78,6 +78,39 @@ describe('getDepositConfig', () => {
|
|
|
78
78
|
})
|
|
79
79
|
})
|
|
80
80
|
|
|
81
|
+
describe('setDepositConfig', () => {
|
|
82
|
+
test('a seeded config keeps every balance read off the chain', async () => {
|
|
83
|
+
const {manager, counts} = stubContext({
|
|
84
|
+
depositConfig: SCRAP,
|
|
85
|
+
balances: [{token_contract: 'scrap.gm', balance: '5 SCRAP'}],
|
|
86
|
+
wallet: ['7 SCRAP'],
|
|
87
|
+
})
|
|
88
|
+
manager.setDepositConfig({tokenContract: 'scrap.gm', symbol: '0,SCRAP'})
|
|
89
|
+
|
|
90
|
+
const config = await manager.getDepositConfig()
|
|
91
|
+
expect(String(config?.tokenContract)).toBe('scrap.gm')
|
|
92
|
+
expect(String(config?.symbol)).toBe('0,SCRAP')
|
|
93
|
+
await manager.getPlatformBalance('alice')
|
|
94
|
+
await manager.getWalletBalance('alice')
|
|
95
|
+
|
|
96
|
+
expect(counts.depositcfg).toBe(0)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('a reload still reaches the chain', async () => {
|
|
100
|
+
const {manager, counts} = stubContext({depositConfig: SCRAP})
|
|
101
|
+
manager.setDepositConfig({tokenContract: 'scrap.gm', symbol: '0,SCRAP'})
|
|
102
|
+
await manager.getDepositConfig(true)
|
|
103
|
+
expect(counts.depositcfg).toBe(1)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('seeding null records a chain with no deposit token', async () => {
|
|
107
|
+
const {manager, counts} = stubContext({depositConfig: SCRAP})
|
|
108
|
+
manager.setDepositConfig(null)
|
|
109
|
+
expect(await manager.getDepositConfig()).toBeNull()
|
|
110
|
+
expect(counts.depositcfg).toBe(0)
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
81
114
|
describe('getPlatformBalance', () => {
|
|
82
115
|
test('returns null when the owner has never opened a row', async () => {
|
|
83
116
|
const {manager} = stubContext({depositConfig: SCRAP, balances: []})
|
package/src/managers/balances.ts
CHANGED
|
@@ -7,6 +7,11 @@ export interface DepositConfig {
|
|
|
7
7
|
symbol: Asset.Symbol
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
export interface DepositConfigInput {
|
|
11
|
+
tokenContract: NameType
|
|
12
|
+
symbol: Asset.SymbolType
|
|
13
|
+
}
|
|
14
|
+
|
|
10
15
|
export interface PlatformBalance {
|
|
11
16
|
owner: Name
|
|
12
17
|
tokenContract: Name
|
|
@@ -22,6 +27,16 @@ export interface TokenBalance {
|
|
|
22
27
|
export class BalancesManager extends BaseManager {
|
|
23
28
|
private depositConfig?: DepositConfig | null
|
|
24
29
|
|
|
30
|
+
// A caller holding the config already, from the indexer or its own cache, seeds it here so no balance read reaches the chain for it.
|
|
31
|
+
setDepositConfig(config: DepositConfigInput | null): void {
|
|
32
|
+
this.depositConfig = config
|
|
33
|
+
? {
|
|
34
|
+
tokenContract: Name.from(config.tokenContract),
|
|
35
|
+
symbol: Asset.Symbol.from(config.symbol),
|
|
36
|
+
}
|
|
37
|
+
: null
|
|
38
|
+
}
|
|
39
|
+
|
|
25
40
|
async getDepositConfig(reload = false): Promise<DepositConfig | null> {
|
|
26
41
|
if (!reload && this.depositConfig !== undefined) {
|
|
27
42
|
return this.depositConfig
|
package/src/managers/context.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {NftManager} from './nft'
|
|
|
15
15
|
import {BalancesManager} from './balances'
|
|
16
16
|
import {FundManager} from './fund'
|
|
17
17
|
import {JobsManager} from './jobs'
|
|
18
|
+
import {ShuttleManager} from './shuttle'
|
|
18
19
|
import {InfluenceManager} from './influence'
|
|
19
20
|
import {SubscriptionsManager} from '../subscriptions/manager'
|
|
20
21
|
|
|
@@ -30,12 +31,15 @@ export class GameContext {
|
|
|
30
31
|
private _balances?: BalancesManager
|
|
31
32
|
private _funds?: FundManager
|
|
32
33
|
private _jobs?: JobsManager
|
|
34
|
+
private _shuttle?: ShuttleManager
|
|
33
35
|
private _influence?: InfluenceManager
|
|
34
36
|
private _subscriptions?: SubscriptionsManager
|
|
35
37
|
private _subscriptionsUrl?: string
|
|
36
38
|
|
|
37
39
|
private _gameCache?: PlatformContract.Types.game_row
|
|
40
|
+
private _gameInflight?: Promise<PlatformContract.Types.game_row>
|
|
38
41
|
private _stateCache?: GameState
|
|
42
|
+
private _stateInflight?: Promise<GameState>
|
|
39
43
|
|
|
40
44
|
constructor(
|
|
41
45
|
public readonly client: APIClient,
|
|
@@ -122,6 +126,13 @@ export class GameContext {
|
|
|
122
126
|
return this._jobs
|
|
123
127
|
}
|
|
124
128
|
|
|
129
|
+
get shuttle(): ShuttleManager {
|
|
130
|
+
if (!this._shuttle) {
|
|
131
|
+
this._shuttle = new ShuttleManager(this)
|
|
132
|
+
}
|
|
133
|
+
return this._shuttle
|
|
134
|
+
}
|
|
135
|
+
|
|
125
136
|
get influence(): InfluenceManager {
|
|
126
137
|
if (!this._influence) {
|
|
127
138
|
this._influence = new InfluenceManager(this)
|
|
@@ -149,25 +160,53 @@ export class GameContext {
|
|
|
149
160
|
if (!reload && this._gameCache) {
|
|
150
161
|
return this._gameCache
|
|
151
162
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
163
|
+
if (!reload && this._gameInflight) {
|
|
164
|
+
return this._gameInflight
|
|
165
|
+
}
|
|
166
|
+
const inflight = (async () => {
|
|
167
|
+
const game = await this.platform.table('games').get()
|
|
168
|
+
if (!game) {
|
|
169
|
+
throw new Error('Game not initialized')
|
|
170
|
+
}
|
|
171
|
+
this._gameCache = game
|
|
172
|
+
return game
|
|
173
|
+
})()
|
|
174
|
+
this._gameInflight = inflight
|
|
175
|
+
try {
|
|
176
|
+
return await inflight
|
|
177
|
+
} finally {
|
|
178
|
+
if (this._gameInflight === inflight) {
|
|
179
|
+
this._gameInflight = undefined
|
|
180
|
+
}
|
|
155
181
|
}
|
|
156
|
-
this._gameCache = game
|
|
157
|
-
return game
|
|
158
182
|
}
|
|
159
183
|
|
|
160
184
|
async getState(reload = false): Promise<GameState> {
|
|
161
185
|
if (!reload && this._stateCache) {
|
|
162
186
|
return this._stateCache
|
|
163
187
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
188
|
+
if (!reload && this._stateInflight) {
|
|
189
|
+
return this._stateInflight
|
|
190
|
+
}
|
|
191
|
+
const inflight = (async () => {
|
|
192
|
+
const [state, game] = await Promise.all([
|
|
193
|
+
this.server.table('state').get(),
|
|
194
|
+
this.getGame(),
|
|
195
|
+
])
|
|
196
|
+
if (!state) {
|
|
197
|
+
throw new Error('Game state not initialized')
|
|
198
|
+
}
|
|
199
|
+
this._stateCache = GameState.from(state, game)
|
|
200
|
+
return this._stateCache
|
|
201
|
+
})()
|
|
202
|
+
this._stateInflight = inflight
|
|
203
|
+
try {
|
|
204
|
+
return await inflight
|
|
205
|
+
} finally {
|
|
206
|
+
if (this._stateInflight === inflight) {
|
|
207
|
+
this._stateInflight = undefined
|
|
208
|
+
}
|
|
167
209
|
}
|
|
168
|
-
const game = this._gameCache
|
|
169
|
-
this._stateCache = GameState.from(state, game)
|
|
170
|
-
return this._stateCache
|
|
171
210
|
}
|
|
172
211
|
|
|
173
212
|
get cachedGame(): PlatformContract.Types.game_row | undefined {
|
package/src/managers/index.ts
CHANGED
|
@@ -15,6 +15,20 @@ export type {NftConfigForItem, WrapConfig, WrapGate} from './nft'
|
|
|
15
15
|
export {BalancesManager} from './balances'
|
|
16
16
|
export type {DepositConfig, PlatformBalance, TokenBalance} from './balances'
|
|
17
17
|
export {JobsManager} from './jobs'
|
|
18
|
+
export {
|
|
19
|
+
ShuttleManager,
|
|
20
|
+
rankShuttleOptions,
|
|
21
|
+
shuttleReasonCode,
|
|
22
|
+
BOOKING_LEVEL_CODES,
|
|
23
|
+
OMITTED_REASONS,
|
|
24
|
+
} from './shuttle'
|
|
25
|
+
export type {
|
|
26
|
+
ShuttleMode,
|
|
27
|
+
ShuttleOption,
|
|
28
|
+
ShuttleOptions,
|
|
29
|
+
ShuttleReason,
|
|
30
|
+
ShuttleReasonCode,
|
|
31
|
+
} from './shuttle'
|
|
18
32
|
export {InfluenceManager} from './influence'
|
|
19
33
|
export type {
|
|
20
34
|
CharterProgress,
|