@shipload/sdk 1.0.0-next.35 → 1.0.0-next.36
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 +237 -80
- package/lib/shipload.js +2979 -2598
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +2960 -2599
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +66 -20
- package/lib/testing.js +95 -57
- package/lib/testing.js.map +1 -1
- package/lib/testing.m.js +95 -57
- package/lib/testing.m.js.map +1 -1
- package/package.json +1 -1
- package/src/capabilities/crafting.ts +2 -3
- package/src/capabilities/gathering.test.ts +16 -0
- package/src/capabilities/gathering.ts +8 -11
- package/src/contracts/server.ts +45 -29
- package/src/coordinates/address.ts +9 -5
- package/src/coordinates/constants.test.ts +15 -0
- package/src/coordinates/constants.ts +5 -3
- package/src/coordinates/index.ts +11 -0
- package/src/coordinates/memo.test.ts +47 -0
- package/src/coordinates/memo.ts +20 -0
- package/src/data/capability-formulas.ts +0 -1
- package/src/data/entities.json +4 -4
- package/src/data/items.json +5 -5
- package/src/data/recipes.json +39 -65
- package/src/derivation/capabilities.test.ts +133 -0
- package/src/derivation/capabilities.ts +66 -14
- package/src/derivation/rollups.test.ts +55 -0
- package/src/derivation/rollups.ts +56 -0
- package/src/entities/makers.ts +30 -3
- package/src/index-module.ts +15 -2
- package/src/managers/actions.ts +34 -3
- package/src/managers/construction.ts +6 -4
- package/src/managers/context.ts +9 -0
- package/src/managers/coordinates.ts +14 -0
- package/src/managers/plot.ts +2 -4
- package/src/nft/description.ts +25 -6
- package/src/planner/index.ts +127 -0
- package/src/planner/planner.test.ts +319 -0
- package/src/resolution/resolve-item.ts +4 -1
- package/src/scheduling/cancel.test.ts +21 -0
- package/src/scheduling/lanes.test.ts +249 -0
- package/src/scheduling/lanes.ts +140 -2
- package/src/scheduling/projection.ts +73 -16
- package/src/shipload.ts +5 -0
- package/src/testing/projection-parity.ts +26 -2
- package/src/travel/travel.ts +102 -101
- package/src/types/capabilities.ts +23 -6
- package/src/types/entity.ts +3 -3
- package/src/types.ts +1 -1
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {UInt8, UInt16, UInt32} from '@wharfkit/antelope'
|
|
2
|
+
import type {ServerContract} from '../contracts'
|
|
3
|
+
|
|
4
|
+
export function rollupGatherer(
|
|
5
|
+
lanes: ServerContract.Types.gatherer_lane[]
|
|
6
|
+
): {yield: UInt16; drain: UInt32; depth: UInt16} | undefined {
|
|
7
|
+
if (lanes.length === 0) return undefined
|
|
8
|
+
let totalYield = 0
|
|
9
|
+
let totalDrain = 0
|
|
10
|
+
let maxDepth = 0
|
|
11
|
+
for (const l of lanes) {
|
|
12
|
+
totalYield += Number(l.yield)
|
|
13
|
+
totalDrain += Number(l.drain)
|
|
14
|
+
const d = Number(l.depth)
|
|
15
|
+
if (d > maxDepth) maxDepth = d
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
yield: UInt16.from(Math.min(totalYield, 65535)),
|
|
19
|
+
drain: UInt32.from(totalDrain),
|
|
20
|
+
depth: UInt16.from(maxDepth),
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function rollupCrafter(
|
|
25
|
+
lanes: ServerContract.Types.crafter_lane[]
|
|
26
|
+
): {speed: UInt16; drain: UInt32} | undefined {
|
|
27
|
+
if (lanes.length === 0) return undefined
|
|
28
|
+
let totalSpeed = 0
|
|
29
|
+
let totalDrain = 0
|
|
30
|
+
for (const l of lanes) {
|
|
31
|
+
totalSpeed += Number(l.speed)
|
|
32
|
+
totalDrain += Number(l.drain)
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
speed: UInt16.from(Math.min(totalSpeed, 65535)),
|
|
36
|
+
drain: UInt32.from(totalDrain),
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function rollupLoaders(
|
|
41
|
+
lanes: ServerContract.Types.loader_lane[]
|
|
42
|
+
): {mass: UInt32; thrust: UInt16; quantity: UInt8} | undefined {
|
|
43
|
+
if (lanes.length === 0) return undefined
|
|
44
|
+
const count = lanes.length
|
|
45
|
+
let totalMass = 0
|
|
46
|
+
let totalThrust = 0
|
|
47
|
+
for (const l of lanes) {
|
|
48
|
+
totalMass += Number(l.mass)
|
|
49
|
+
totalThrust += Number(l.thrust)
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
mass: UInt32.from(Math.floor(totalMass / count)),
|
|
53
|
+
thrust: UInt16.from(Math.min(totalThrust, 65535)),
|
|
54
|
+
quantity: UInt8.from(count),
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/entities/makers.ts
CHANGED
|
@@ -140,13 +140,40 @@ export function makeEntity(packedItemId: number, state: EntityStateInput): Entit
|
|
|
140
140
|
|
|
141
141
|
if (caps.engines) info.engines = caps.engines
|
|
142
142
|
if (caps.generator) info.generator = caps.generator
|
|
143
|
-
if (caps.gatherer) info.gatherer = caps.gatherer
|
|
144
|
-
if (caps.loaders) info.loaders = caps.loaders
|
|
145
|
-
if (caps.crafter) info.crafter = caps.crafter
|
|
146
143
|
if (caps.hauler) info.hauler = caps.hauler
|
|
147
144
|
if (caps.warp) info.warp = caps.warp
|
|
145
|
+
|
|
146
|
+
info.gatherer_lanes = (caps.gathererLanes ?? []).map((l) =>
|
|
147
|
+
ServerContract.Types.gatherer_lane.from({
|
|
148
|
+
slot_index: l.slotIndex,
|
|
149
|
+
yield: l.yield,
|
|
150
|
+
drain: l.drain,
|
|
151
|
+
depth: l.depth,
|
|
152
|
+
output_pct: l.outputPct,
|
|
153
|
+
})
|
|
154
|
+
)
|
|
155
|
+
info.crafter_lanes = (caps.crafterLanes ?? []).map((l) =>
|
|
156
|
+
ServerContract.Types.crafter_lane.from({
|
|
157
|
+
slot_index: l.slotIndex,
|
|
158
|
+
speed: l.speed,
|
|
159
|
+
drain: l.drain,
|
|
160
|
+
output_pct: l.outputPct,
|
|
161
|
+
})
|
|
162
|
+
)
|
|
163
|
+
info.loader_lanes = (caps.loaderLanes ?? []).map((l) =>
|
|
164
|
+
ServerContract.Types.loader_lane.from({
|
|
165
|
+
slot_index: l.slotIndex,
|
|
166
|
+
mass: l.mass,
|
|
167
|
+
thrust: l.thrust,
|
|
168
|
+
output_pct: l.outputPct,
|
|
169
|
+
})
|
|
170
|
+
)
|
|
148
171
|
}
|
|
149
172
|
|
|
173
|
+
if (!info.gatherer_lanes) info.gatherer_lanes = []
|
|
174
|
+
if (!info.crafter_lanes) info.crafter_lanes = []
|
|
175
|
+
if (!info.loader_lanes) info.loader_lanes = []
|
|
176
|
+
|
|
150
177
|
const entityInfo = ServerContract.Types.entity_info.from(info)
|
|
151
178
|
return new Entity(entityInfo)
|
|
152
179
|
}
|
package/src/index-module.ts
CHANGED
|
@@ -26,13 +26,11 @@ export type {InstalledModule} from './entities/slot-multiplier'
|
|
|
26
26
|
|
|
27
27
|
export type movement_stats = ServerContract.Types.movement_stats
|
|
28
28
|
export type energy_stats = ServerContract.Types.energy_stats
|
|
29
|
-
export type loader_stats = ServerContract.Types.loader_stats
|
|
30
29
|
export type schedule = ServerContract.Types.schedule
|
|
31
30
|
export type lane = ServerContract.Types.lane
|
|
32
31
|
export type task = ServerContract.Types.task
|
|
33
32
|
export type cargo_item = ServerContract.Types.cargo_item
|
|
34
33
|
export type entity_row = ServerContract.Types.entity_row
|
|
35
|
-
export type gatherer_stats = ServerContract.Types.gatherer_stats
|
|
36
34
|
|
|
37
35
|
export type location_static = ServerContract.Types.location_static
|
|
38
36
|
export type location_derived = ServerContract.Types.location_derived
|
|
@@ -158,6 +156,7 @@ export {
|
|
|
158
156
|
calc_flighttime,
|
|
159
157
|
calc_loader_acceleration,
|
|
160
158
|
calc_loader_flighttime,
|
|
159
|
+
calc_onesided_duration,
|
|
161
160
|
calc_orbital_altitude,
|
|
162
161
|
calc_rechargetime,
|
|
163
162
|
calc_ship_acceleration,
|
|
@@ -204,8 +203,17 @@ export {
|
|
|
204
203
|
candidateLaneCompletesAt,
|
|
205
204
|
laneKeyForModule,
|
|
206
205
|
rawScheduleEnd,
|
|
206
|
+
resolveLaneGatherer,
|
|
207
|
+
resolveLaneCrafter,
|
|
208
|
+
resolveLaneLoader,
|
|
209
|
+
selectGatherLane,
|
|
207
210
|
workerLaneKey,
|
|
208
211
|
} from './scheduling/lanes'
|
|
212
|
+
export type {
|
|
213
|
+
ResolvedGathererLane,
|
|
214
|
+
ResolvedCrafterLane,
|
|
215
|
+
ResolvedLoaderLane,
|
|
216
|
+
} from './scheduling/lanes'
|
|
209
217
|
export {ScheduleAccessor, createScheduleAccessor} from './scheduling/accessor'
|
|
210
218
|
export {InventoryAccessor, createInventoryAccessor} from './entities/inventory-accessor'
|
|
211
219
|
export type {HasCargo} from './entities/inventory-accessor'
|
|
@@ -378,6 +386,8 @@ export {
|
|
|
378
386
|
isValidWormholePair,
|
|
379
387
|
} from './derivation/wormhole'
|
|
380
388
|
|
|
389
|
+
export {rollupGatherer, rollupCrafter, rollupLoaders} from './derivation/rollups'
|
|
390
|
+
|
|
381
391
|
export {resolveItem} from './resolution/resolve-item'
|
|
382
392
|
export type {
|
|
383
393
|
ResolvedItem,
|
|
@@ -488,3 +498,6 @@ export {displayName, baseName, describeItem} from './resolution/display-name'
|
|
|
488
498
|
export type {DescribeOptions} from './resolution/display-name'
|
|
489
499
|
|
|
490
500
|
export * from './subscriptions'
|
|
501
|
+
|
|
502
|
+
export {planParallelGather, planParallelTransfer} from './planner'
|
|
503
|
+
export type {LanePlanEntry, PlanTarget, GatherPlanEntity} from './planner'
|
package/src/managers/actions.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type Int64Type,
|
|
7
7
|
Name,
|
|
8
8
|
type NameType,
|
|
9
|
+
Transaction,
|
|
9
10
|
UInt8,
|
|
10
11
|
type UInt8Type,
|
|
11
12
|
UInt16,
|
|
@@ -182,13 +183,39 @@ export class ActionsManager extends BaseManager {
|
|
|
182
183
|
sourceId: UInt64Type,
|
|
183
184
|
destinationId: UInt64Type,
|
|
184
185
|
stratum: UInt16Type,
|
|
185
|
-
quantity: UInt32Type
|
|
186
|
+
quantity: UInt32Type,
|
|
187
|
+
slot?: UInt8Type
|
|
186
188
|
): Action {
|
|
187
|
-
|
|
189
|
+
const params: ServerContract.ActionParams.gather = {
|
|
188
190
|
source_id: UInt64.from(sourceId),
|
|
189
191
|
destination_id: UInt64.from(destinationId),
|
|
190
192
|
stratum: UInt16.from(stratum),
|
|
191
193
|
quantity: UInt32.from(quantity),
|
|
194
|
+
}
|
|
195
|
+
if (slot !== undefined) {
|
|
196
|
+
params.slot = UInt8.from(slot)
|
|
197
|
+
}
|
|
198
|
+
return this.server.action('gather', params)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Packs N gather actions into one Transaction; the wallet/session fills in TAPoS at sign time.
|
|
202
|
+
bundleGather(
|
|
203
|
+
gathers: {
|
|
204
|
+
sourceId: UInt64Type
|
|
205
|
+
destinationId: UInt64Type
|
|
206
|
+
stratum: UInt16Type
|
|
207
|
+
quantity: UInt32Type
|
|
208
|
+
slot?: UInt8Type
|
|
209
|
+
}[]
|
|
210
|
+
): Transaction {
|
|
211
|
+
const actions = gathers.map(({sourceId, destinationId, stratum, quantity, slot}) =>
|
|
212
|
+
this.gather(sourceId, destinationId, stratum, quantity, slot)
|
|
213
|
+
)
|
|
214
|
+
return Transaction.from({
|
|
215
|
+
expiration: 0,
|
|
216
|
+
ref_block_num: 0,
|
|
217
|
+
ref_block_prefix: 0,
|
|
218
|
+
actions,
|
|
192
219
|
})
|
|
193
220
|
}
|
|
194
221
|
|
|
@@ -208,7 +235,8 @@ export class ActionsManager extends BaseManager {
|
|
|
208
235
|
recipeId: number,
|
|
209
236
|
quantity: number,
|
|
210
237
|
inputs: ServerContract.ActionParams.Type.cargo_item[],
|
|
211
|
-
target?: UInt64Type
|
|
238
|
+
target?: UInt64Type,
|
|
239
|
+
slot?: UInt8Type
|
|
212
240
|
): Action {
|
|
213
241
|
const params: ServerContract.ActionParams.craft = {
|
|
214
242
|
id: UInt64.from(entityId),
|
|
@@ -219,6 +247,9 @@ export class ActionsManager extends BaseManager {
|
|
|
219
247
|
if (target !== undefined) {
|
|
220
248
|
params.target = UInt64.from(target)
|
|
221
249
|
}
|
|
250
|
+
if (slot !== undefined) {
|
|
251
|
+
params.slot = UInt8.from(slot)
|
|
252
|
+
}
|
|
222
253
|
return this.server.action('craft', params)
|
|
223
254
|
}
|
|
224
255
|
|
|
@@ -67,8 +67,9 @@ export class ConstructionManager extends BaseManager {
|
|
|
67
67
|
if (!entity.owner.equals(target.ownerName)) continue
|
|
68
68
|
if (entity.id.equals(target.entityId)) continue
|
|
69
69
|
if (!coordsEqual(entity.coordinates, target.coordinates)) continue
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
70
|
+
const crafterLanes = entity.crafter_lanes ?? []
|
|
71
|
+
if (crafterLanes.length === 0) continue
|
|
72
|
+
const speed = crafterLanes.reduce((s, l) => s + Number(l.speed), 0)
|
|
72
73
|
out.push({
|
|
73
74
|
entityId: entity.id,
|
|
74
75
|
entityType: entity.type,
|
|
@@ -345,8 +346,9 @@ function partitionSources(
|
|
|
345
346
|
const reserved = reservedByItemFor(entity)
|
|
346
347
|
const relevant = matchRelevantCargo(entity, target, cargo, reserved)
|
|
347
348
|
if (relevant.length === 0) continue
|
|
348
|
-
const
|
|
349
|
-
const
|
|
349
|
+
const loaderLanes = entity.loader_lanes ?? []
|
|
350
|
+
const loaderCount = loaderLanes.length
|
|
351
|
+
const loaderTotalMass = loaderLanes.reduce((s, l) => s + Number(l.mass), 0)
|
|
350
352
|
const ref: SourceEntityRef = {
|
|
351
353
|
entityId: entity.id,
|
|
352
354
|
name: entity.id.toString(),
|
package/src/managers/context.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {GameState} from '../entities/gamestate'
|
|
|
6
6
|
import {EntitiesManager} from './entities'
|
|
7
7
|
import {PlayersManager} from './players'
|
|
8
8
|
import {LocationsManager} from './locations'
|
|
9
|
+
import {CoordinatesManager} from './coordinates'
|
|
9
10
|
import {EpochsManager} from './epochs'
|
|
10
11
|
import {ActionsManager} from './actions'
|
|
11
12
|
import {NftManager} from './nft'
|
|
@@ -15,6 +16,7 @@ export class GameContext {
|
|
|
15
16
|
private _entities?: EntitiesManager
|
|
16
17
|
private _players?: PlayersManager
|
|
17
18
|
private _locations?: LocationsManager
|
|
19
|
+
private _coordinates?: CoordinatesManager
|
|
18
20
|
private _epochs?: EpochsManager
|
|
19
21
|
private _actions?: ActionsManager
|
|
20
22
|
private _nft?: NftManager
|
|
@@ -52,6 +54,13 @@ export class GameContext {
|
|
|
52
54
|
return this._locations
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
get coordinates(): CoordinatesManager {
|
|
58
|
+
if (!this._coordinates) {
|
|
59
|
+
this._coordinates = new CoordinatesManager(this)
|
|
60
|
+
}
|
|
61
|
+
return this._coordinates
|
|
62
|
+
}
|
|
63
|
+
|
|
55
64
|
get epochs(): EpochsManager {
|
|
56
65
|
if (!this._epochs) {
|
|
57
66
|
this._epochs = new EpochsManager(this)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {BaseManager} from './base'
|
|
2
|
+
import {type CoordinateAddress, decodeAddress, encodeAddressMemo} from '../coordinates'
|
|
3
|
+
|
|
4
|
+
export class CoordinatesManager extends BaseManager {
|
|
5
|
+
async encode(x: number, y: number): Promise<CoordinateAddress> {
|
|
6
|
+
const game = await this.getGame()
|
|
7
|
+
return encodeAddressMemo(game.config.seed, x, y)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async decode(addr: CoordinateAddress): Promise<{x: number; y: number}> {
|
|
11
|
+
const game = await this.getGame()
|
|
12
|
+
return decodeAddress(game.config.seed, addr)
|
|
13
|
+
}
|
|
14
|
+
}
|
package/src/managers/plot.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {computeInputMass} from '../derivation/crafting'
|
|
|
5
5
|
import {calc_craft_duration} from '../capabilities/crafting'
|
|
6
6
|
import {TaskType} from '../types'
|
|
7
7
|
import {BaseManager} from './base'
|
|
8
|
+
import type {CrafterStats} from '../types/capabilities'
|
|
8
9
|
import type {ServerContract} from '../contracts'
|
|
9
10
|
import type {BuildableTarget, ScheduledBuild} from './construction-types'
|
|
10
11
|
|
|
@@ -112,10 +113,7 @@ export class PlotManager extends BaseManager {
|
|
|
112
113
|
return this.progress(plot, cargo).isComplete
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
timeToComplete(
|
|
116
|
-
plot: ServerContract.Types.entity_info,
|
|
117
|
-
crafter: ServerContract.Types.crafter_stats
|
|
118
|
-
): number {
|
|
116
|
+
timeToComplete(plot: ServerContract.Types.entity_info, crafter: CrafterStats): number {
|
|
119
117
|
const capacity = Number(plot.capacity?.toString() ?? '0')
|
|
120
118
|
const speed = Number(crafter.speed.toString())
|
|
121
119
|
if (speed === 0) return 0
|
package/src/nft/description.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
ITEM_CRAFTER_T1,
|
|
16
16
|
ITEM_ENGINE_T1,
|
|
17
17
|
ITEM_EXTRACTOR_T1_PACKED,
|
|
18
|
+
ITEM_FACTORY_T1_PACKED,
|
|
18
19
|
ITEM_GATHERER_T1,
|
|
19
20
|
ITEM_GENERATOR_T1,
|
|
20
21
|
ITEM_HAULER_T1,
|
|
@@ -38,13 +39,23 @@ export function computeBaseHullmass(stats: bigint): number {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export function computeBaseCapacityShip(stats: bigint): number {
|
|
41
|
-
const s = decodeStat(stats, 0) + decodeStat(stats, 2)
|
|
42
|
-
return Math.floor(5_000_000 * 6 ** (s /
|
|
42
|
+
const s = decodeStat(stats, 0) + decodeStat(stats, 2)
|
|
43
|
+
return Math.floor(5_000_000 * 6 ** (s / 1998))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function computeBaseCapacityContainer(stats: bigint): number {
|
|
47
|
+
const s = decodeStat(stats, 0) + decodeStat(stats, 2)
|
|
48
|
+
return Math.floor(22_000_000 * 6 ** (s / 1998))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function computeBaseCapacityContainerT2(stats: bigint): number {
|
|
52
|
+
const s = decodeStat(stats, 0) + decodeStat(stats, 2)
|
|
53
|
+
return Math.floor(24_000_000 * 6 ** (s / 2947))
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
export function computeBaseCapacityWarehouse(stats: bigint): number {
|
|
46
|
-
const s = decodeStat(stats, 0) + decodeStat(stats, 2)
|
|
47
|
-
return Math.floor(100_000_000 * 6 ** (s /
|
|
57
|
+
const s = decodeStat(stats, 0) + decodeStat(stats, 2)
|
|
58
|
+
return Math.floor(100_000_000 * 6 ** (s / 1998))
|
|
48
59
|
}
|
|
49
60
|
|
|
50
61
|
export const computeEngineThrust = (vol: number): number => 400 + idiv(vol * 3, 4)
|
|
@@ -73,6 +84,8 @@ export function entityDisplayName(itemId: number): string {
|
|
|
73
84
|
return 'Warehouse'
|
|
74
85
|
case ITEM_EXTRACTOR_T1_PACKED:
|
|
75
86
|
return 'Extractor'
|
|
87
|
+
case ITEM_FACTORY_T1_PACKED:
|
|
88
|
+
return 'Factory'
|
|
76
89
|
case ITEM_CONTAINER_T1_PACKED:
|
|
77
90
|
return 'Container'
|
|
78
91
|
case ITEM_CONTAINER_T2_PACKED:
|
|
@@ -188,8 +201,14 @@ export function buildEntityDescription(
|
|
|
188
201
|
baseCapacity = computeBaseCapacityShip(hullStats)
|
|
189
202
|
} else if (itemId === ITEM_WAREHOUSE_T1_PACKED) {
|
|
190
203
|
baseCapacity = computeBaseCapacityWarehouse(hullStats)
|
|
191
|
-
} else if (
|
|
192
|
-
|
|
204
|
+
} else if (
|
|
205
|
+
itemId === ITEM_EXTRACTOR_T1_PACKED ||
|
|
206
|
+
itemId === ITEM_FACTORY_T1_PACKED ||
|
|
207
|
+
itemId === ITEM_CONTAINER_T1_PACKED
|
|
208
|
+
) {
|
|
209
|
+
baseCapacity = computeBaseCapacityContainer(hullStats)
|
|
210
|
+
} else if (itemId === ITEM_CONTAINER_T2_PACKED) {
|
|
211
|
+
baseCapacity = computeBaseCapacityContainerT2(hullStats)
|
|
193
212
|
}
|
|
194
213
|
|
|
195
214
|
let out = entityDisplayName(itemId)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type {GathererStats} from '../types/capabilities'
|
|
2
|
+
import type {ServerContract} from '../contracts'
|
|
3
|
+
import {
|
|
4
|
+
calc_gather_duration,
|
|
5
|
+
calc_gather_energy,
|
|
6
|
+
GATHER_MASS_DIVISOR,
|
|
7
|
+
} from '../capabilities/gathering'
|
|
8
|
+
import {projectRemainingAt, type Projectable} from '../scheduling/projection'
|
|
9
|
+
|
|
10
|
+
export interface LanePlanEntry {
|
|
11
|
+
slot: number
|
|
12
|
+
quantity: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type PlanTarget = {quantity: number} | 'max'
|
|
16
|
+
|
|
17
|
+
export interface GatherPlanEntity extends Projectable {
|
|
18
|
+
gatherer_lanes: ServerContract.Types.gatherer_lane[]
|
|
19
|
+
loader_lanes: ServerContract.Types.loader_lane[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// massFactor=1 so plan-time cost is conservative; on-chain recomputes with real mass/richness.
|
|
23
|
+
const PLAN_ITEM_MASS = GATHER_MASS_DIVISOR
|
|
24
|
+
const PLAN_RICHNESS = 1000
|
|
25
|
+
|
|
26
|
+
// 'max' ceiling; Phase 3 passes a real reserve/capacity cap instead.
|
|
27
|
+
const MAX_PLAN_QTY = 10000
|
|
28
|
+
|
|
29
|
+
function gatherEnergyCost(
|
|
30
|
+
lane: ServerContract.Types.gatherer_lane,
|
|
31
|
+
quantity: number,
|
|
32
|
+
stratum: number
|
|
33
|
+
): number {
|
|
34
|
+
const stats = lane as unknown as GathererStats
|
|
35
|
+
const dur = Number(
|
|
36
|
+
calc_gather_duration(stats, PLAN_ITEM_MASS, quantity, stratum, PLAN_RICHNESS)
|
|
37
|
+
)
|
|
38
|
+
return Number(calc_gather_energy(stats, dur))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function allocateProportional(
|
|
42
|
+
lanes: {slot: number; weight: number}[],
|
|
43
|
+
total: number
|
|
44
|
+
): LanePlanEntry[] {
|
|
45
|
+
if (lanes.length === 0) return []
|
|
46
|
+
const weightSum = lanes.reduce((s, l) => s + l.weight, 0)
|
|
47
|
+
if (weightSum === 0) return []
|
|
48
|
+
|
|
49
|
+
const entries: LanePlanEntry[] = lanes.map((l) => ({
|
|
50
|
+
slot: l.slot,
|
|
51
|
+
quantity: Math.floor((total * l.weight) / weightSum),
|
|
52
|
+
}))
|
|
53
|
+
|
|
54
|
+
let remainder = total - entries.reduce((s, e) => s + e.quantity, 0)
|
|
55
|
+
for (let i = 0; remainder > 0; i = (i + 1) % entries.length) {
|
|
56
|
+
entries[i].quantity++
|
|
57
|
+
remainder--
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return entries
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function planParallelGather(
|
|
64
|
+
entity: GatherPlanEntity,
|
|
65
|
+
target: PlanTarget,
|
|
66
|
+
stratum: number,
|
|
67
|
+
now: Date
|
|
68
|
+
): LanePlanEntry[] {
|
|
69
|
+
const reaching = entity.gatherer_lanes.filter((l) => l.depth.toNumber() >= stratum)
|
|
70
|
+
if (reaching.length === 0) throw new Error('no gatherer reaches this stratum')
|
|
71
|
+
|
|
72
|
+
// Projected energy nets out already-queued/in-flight task costs (contract projected_energy()).
|
|
73
|
+
const energy = entity.generator ? Number(projectRemainingAt(entity, now).energy) : Infinity
|
|
74
|
+
|
|
75
|
+
const requestedQty = target === 'max' ? MAX_PLAN_QTY : (target as {quantity: number}).quantity
|
|
76
|
+
|
|
77
|
+
// Ascending by yield so slice(1) sheds the lowest-yield lane first when energy-starved.
|
|
78
|
+
let activeLanes = reaching.slice().sort((a, b) => a.yield.toNumber() - b.yield.toNumber())
|
|
79
|
+
|
|
80
|
+
while (activeLanes.length > 0) {
|
|
81
|
+
const laneWeights = activeLanes.map((l) => ({
|
|
82
|
+
slot: l.slot_index.toNumber(),
|
|
83
|
+
weight: l.yield.toNumber(),
|
|
84
|
+
}))
|
|
85
|
+
|
|
86
|
+
const proposed = allocateProportional(laneWeights, requestedQty)
|
|
87
|
+
|
|
88
|
+
const totalEnergyCost = proposed.reduce((sum, entry) => {
|
|
89
|
+
const lane = activeLanes.find((l) => l.slot_index.toNumber() === entry.slot)!
|
|
90
|
+
return sum + gatherEnergyCost(lane, entry.quantity, stratum)
|
|
91
|
+
}, 0)
|
|
92
|
+
|
|
93
|
+
if (totalEnergyCost <= energy) {
|
|
94
|
+
return proposed.filter((e) => e.quantity > 0)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (activeLanes.length === 1) {
|
|
98
|
+
const lane = activeLanes[0]
|
|
99
|
+
const energyPerUnit = gatherEnergyCost(lane, 1, stratum)
|
|
100
|
+
if (energyPerUnit === 0) return proposed.filter((e) => e.quantity > 0)
|
|
101
|
+
const maxQty = Math.min(requestedQty, Math.floor(energy / energyPerUnit))
|
|
102
|
+
if (maxQty <= 0) return []
|
|
103
|
+
return [{slot: lane.slot_index.toNumber(), quantity: maxQty}]
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
activeLanes = activeLanes.slice(1)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return []
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function planParallelTransfer(
|
|
113
|
+
entity: GatherPlanEntity,
|
|
114
|
+
target: PlanTarget
|
|
115
|
+
): LanePlanEntry[] {
|
|
116
|
+
const lanes = entity.loader_lanes.filter((l) => l.thrust.toNumber() > 0)
|
|
117
|
+
if (lanes.length === 0) return []
|
|
118
|
+
|
|
119
|
+
const requestedQty = target === 'max' ? MAX_PLAN_QTY : (target as {quantity: number}).quantity
|
|
120
|
+
|
|
121
|
+
const laneWeights = lanes.map((l) => ({
|
|
122
|
+
slot: l.slot_index.toNumber(),
|
|
123
|
+
weight: l.thrust.toNumber(),
|
|
124
|
+
}))
|
|
125
|
+
|
|
126
|
+
return allocateProportional(laneWeights, requestedQty).filter((e) => e.quantity > 0)
|
|
127
|
+
}
|