@shipload/sdk 1.0.0-next.5 → 1.0.0-next.51
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/scan.d.ts +52 -0
- package/lib/scan.js +162 -0
- package/lib/scan.js.map +1 -0
- package/lib/scan.m.js +152 -0
- package/lib/scan.m.js.map +1 -0
- package/lib/shipload.d.ts +3167 -1353
- package/lib/shipload.js +13319 -5640
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +13091 -5589
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +1037 -0
- package/lib/testing.js +4363 -0
- package/lib/testing.js.map +1 -0
- package/lib/testing.m.js +4357 -0
- package/lib/testing.m.js.map +1 -0
- package/package.json +20 -2
- package/src/capabilities/craftable.ts +51 -0
- package/src/capabilities/crafting.test.ts +7 -0
- package/src/capabilities/crafting.ts +6 -7
- package/src/capabilities/gathering.test.ts +29 -0
- package/src/capabilities/gathering.ts +37 -19
- package/src/capabilities/index.ts +0 -1
- package/src/capabilities/modules.ts +22 -0
- package/src/capabilities/storage.ts +16 -1
- package/src/contracts/platform.ts +231 -3
- package/src/contracts/server.ts +1178 -511
- package/src/coordinates/address.ts +88 -0
- package/src/coordinates/constants.test.ts +15 -0
- package/src/coordinates/constants.ts +23 -0
- package/src/coordinates/index.ts +15 -0
- package/src/coordinates/memo.test.ts +47 -0
- package/src/coordinates/memo.ts +20 -0
- package/src/coordinates/permutation.ts +77 -0
- package/src/coordinates/regions.ts +48 -0
- package/src/coordinates/sectors.ts +115 -0
- package/src/data/capabilities.ts +31 -16
- package/src/data/capability-formulas.ts +21 -14
- package/src/data/catalog.ts +0 -5
- package/src/data/colors.ts +13 -47
- package/src/data/entities.json +154 -10
- package/src/data/item-ids.ts +31 -12
- package/src/data/items.json +192 -74
- package/src/data/kind-registry.json +131 -0
- package/src/data/kind-registry.ts +172 -0
- package/src/data/metadata.ts +166 -48
- package/src/data/recipes-runtime.ts +5 -23
- package/src/data/recipes.json +1003 -163
- package/src/derivation/build-methods.test.ts +13 -0
- package/src/derivation/build-methods.ts +48 -0
- package/src/derivation/capabilities.test.ts +151 -0
- package/src/derivation/capabilities.ts +527 -0
- package/src/derivation/capability-mappings.ts +58 -16
- package/src/derivation/crafting.ts +36 -33
- package/src/derivation/index.ts +28 -2
- package/src/derivation/recipe-usage.test.ts +81 -0
- package/src/derivation/recipe-usage.ts +141 -0
- package/src/derivation/reserve-regen.ts +34 -0
- package/src/derivation/resources.ts +98 -19
- package/src/derivation/rollups.test.ts +55 -0
- package/src/derivation/rollups.ts +56 -0
- package/src/derivation/stars.test.ts +67 -0
- package/src/derivation/stars.ts +25 -0
- package/src/derivation/stats.ts +6 -6
- package/src/derivation/stratum.ts +26 -22
- package/src/derivation/tiers.ts +40 -7
- package/src/derivation/upgrades.ts +14 -0
- package/src/derivation/wormhole.ts +141 -0
- package/src/entities/entity.ts +98 -0
- package/src/entities/gamestate.ts +3 -28
- package/src/entities/makers.ts +131 -134
- package/src/entities/slot-multiplier.ts +43 -0
- package/src/errors.ts +12 -16
- package/src/format.ts +26 -4
- package/src/index-module.ts +294 -34
- package/src/managers/actions.ts +616 -105
- package/src/managers/base.ts +6 -2
- package/src/managers/cluster.test.ts +39 -0
- package/src/managers/cluster.ts +53 -0
- package/src/managers/construction-types.ts +80 -0
- package/src/managers/construction.ts +412 -0
- package/src/managers/context.ts +29 -1
- package/src/managers/coordinates.ts +14 -0
- package/src/managers/entities.ts +27 -66
- package/src/managers/epochs.ts +40 -0
- package/src/managers/flatten-gather-plan.test.ts +80 -0
- package/src/managers/index.ts +20 -1
- package/src/managers/locations.ts +25 -29
- package/src/managers/nft.test.ts +14 -0
- package/src/managers/nft.ts +70 -0
- package/src/managers/players.ts +25 -0
- package/src/managers/plot.ts +122 -0
- package/src/nft/atomicassets.abi.json +1342 -0
- package/src/nft/atomicassets.ts +237 -0
- package/src/nft/atomicdata.ts +130 -0
- package/src/nft/buildImmutableData.ts +336 -0
- package/src/nft/description.ts +104 -36
- package/src/nft/index.ts +3 -0
- package/src/planner/index.ts +242 -0
- package/src/planner/planner.test.ts +325 -0
- package/src/resolution/describe-module.ts +43 -25
- package/src/resolution/display-name.ts +38 -10
- package/src/resolution/resolve-item.test.ts +48 -0
- package/src/resolution/resolve-item.ts +74 -47
- package/src/scan/index.ts +244 -0
- package/src/scan/scan-wasm.base64.ts +2 -0
- package/src/scheduling/accessor.ts +65 -23
- package/src/scheduling/availability.ts +108 -0
- package/src/scheduling/cancel.test.ts +348 -0
- package/src/scheduling/cancel.ts +209 -0
- package/src/scheduling/energy.ts +47 -0
- package/src/scheduling/idle-resolve.ts +45 -0
- package/src/scheduling/lane-core.ts +128 -0
- package/src/scheduling/lanes.test.ts +249 -0
- package/src/scheduling/lanes.ts +198 -0
- package/src/scheduling/projection.ts +210 -107
- package/src/scheduling/schedule.ts +240 -117
- package/src/scheduling/task-cargo.ts +46 -0
- package/src/scheduling/unwrap.test.ts +86 -0
- package/src/scheduling/unwrap.ts +190 -0
- package/src/shipload.ts +26 -1
- package/src/subscriptions/manager.cluster.test.ts +51 -0
- package/src/subscriptions/manager.ts +245 -172
- package/src/subscriptions/mappers.ts +5 -8
- package/src/subscriptions/types.ts +28 -3
- package/src/testing/catalog-hash.ts +19 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/projection-parity.ts +167 -0
- package/src/travel/reach.ts +23 -0
- package/src/travel/route-planner.ts +277 -0
- package/src/travel/route-simulator.ts +174 -0
- package/src/travel/travel.ts +175 -118
- package/src/types/capabilities.ts +29 -6
- package/src/types/entity.ts +3 -3
- package/src/types/index.ts +0 -1
- package/src/types.ts +39 -14
- package/src/utils/cargo.test.ts +14 -0
- package/src/utils/cargo.ts +29 -0
- package/src/utils/display-name.ts +70 -0
- package/src/utils/system.ts +36 -24
- package/src/capabilities/loading.ts +0 -8
- package/src/entities/container.ts +0 -123
- package/src/entities/ship-deploy.ts +0 -295
- package/src/entities/ship.ts +0 -221
- package/src/entities/warehouse.ts +0 -127
- package/src/types/entity-traits.ts +0 -69
package/src/nft/description.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
MODULE_ENGINE,
|
|
5
5
|
MODULE_GATHERER,
|
|
6
6
|
MODULE_GENERATOR,
|
|
7
|
+
MODULE_BATTERY,
|
|
7
8
|
MODULE_HAULER,
|
|
8
9
|
MODULE_LOADER,
|
|
9
10
|
MODULE_STORAGE,
|
|
@@ -14,17 +15,25 @@ import {
|
|
|
14
15
|
ITEM_CONTAINER_T2_PACKED,
|
|
15
16
|
ITEM_CRAFTER_T1,
|
|
16
17
|
ITEM_ENGINE_T1,
|
|
18
|
+
ITEM_EXTRACTOR_T1_PACKED,
|
|
19
|
+
ITEM_FACTORY_T1_PACKED,
|
|
17
20
|
ITEM_GATHERER_T1,
|
|
21
|
+
ITEM_GATHERER_T2,
|
|
18
22
|
ITEM_GENERATOR_T1,
|
|
23
|
+
ITEM_HAULER_SHIP_T2_PACKED,
|
|
19
24
|
ITEM_HAULER_T1,
|
|
25
|
+
ITEM_HAULER_T2,
|
|
26
|
+
ITEM_BATTERY_T1,
|
|
27
|
+
ITEM_LAUNCHER_T1,
|
|
20
28
|
ITEM_LOADER_T1,
|
|
29
|
+
ITEM_PROSPECTOR_T2_PACKED,
|
|
21
30
|
ITEM_SHIP_T1_PACKED,
|
|
22
31
|
ITEM_STORAGE_T1,
|
|
23
32
|
ITEM_WAREHOUSE_T1_PACKED,
|
|
24
33
|
ITEM_WARP_T1,
|
|
25
34
|
} from '../data/item-ids'
|
|
26
35
|
import {decodeStat} from '../derivation/crafting'
|
|
27
|
-
import {gathererDepthForTier} from '../
|
|
36
|
+
import {gathererDepthForTier} from '../derivation/capabilities'
|
|
28
37
|
import {getItem} from '../data/catalog'
|
|
29
38
|
|
|
30
39
|
function idiv(a: number, b: number): number {
|
|
@@ -33,48 +42,83 @@ function idiv(a: number, b: number): number {
|
|
|
33
42
|
|
|
34
43
|
export function computeBaseHullmass(stats: bigint): number {
|
|
35
44
|
const density = decodeStat(stats, 1)
|
|
36
|
-
return
|
|
45
|
+
return 100000 - 75 * density
|
|
37
46
|
}
|
|
38
47
|
|
|
39
48
|
export function computeBaseCapacityShip(stats: bigint): number {
|
|
40
|
-
const s = decodeStat(stats, 0) + decodeStat(stats, 2)
|
|
41
|
-
return Math.floor(
|
|
49
|
+
const s = decodeStat(stats, 0) + decodeStat(stats, 2)
|
|
50
|
+
return Math.floor(5_000_000 * 6 ** (s / 1998))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function computeBaseCapacityContainer(stats: bigint): number {
|
|
54
|
+
const s = decodeStat(stats, 0) + decodeStat(stats, 2)
|
|
55
|
+
return Math.floor(22_000_000 * 6 ** (s / 1998))
|
|
42
56
|
}
|
|
43
57
|
|
|
44
58
|
export function computeBaseCapacityWarehouse(stats: bigint): number {
|
|
45
|
-
const s = decodeStat(stats, 0) + decodeStat(stats, 2)
|
|
46
|
-
return Math.floor(
|
|
59
|
+
const s = decodeStat(stats, 0) + decodeStat(stats, 2)
|
|
60
|
+
return Math.floor(100_000_000 * 6 ** (s / 1998))
|
|
47
61
|
}
|
|
48
62
|
|
|
49
63
|
export const computeEngineThrust = (vol: number): number => 400 + idiv(vol * 3, 4)
|
|
50
|
-
export const computeEngineDrain = (thm: number): number => Math.max(30, 50 - idiv(thm, 70))
|
|
51
|
-
export const
|
|
52
|
-
export const
|
|
64
|
+
export const computeEngineDrain = (thm: number): number => 2 * Math.max(30, 50 - idiv(thm, 70))
|
|
65
|
+
export const ENGINE_DRAIN_BASE = 118
|
|
66
|
+
export const ENGINE_DRAIN_REF_THRUST = 775
|
|
67
|
+
export const ENGINE_DRAIN_REF_THM = 500
|
|
68
|
+
|
|
69
|
+
export const computeTravelDrain = (totalThrust: number, avgThm: number): number => {
|
|
70
|
+
if (totalThrust <= 0) return 0
|
|
71
|
+
const num = ENGINE_DRAIN_BASE * ENGINE_DRAIN_REF_THRUST * computeEngineDrain(avgThm)
|
|
72
|
+
const den = totalThrust * computeEngineDrain(ENGINE_DRAIN_REF_THM)
|
|
73
|
+
return idiv(num, den)
|
|
74
|
+
}
|
|
75
|
+
export const computeGeneratorCap = (com: number): number => 1300 + idiv(com, 2)
|
|
76
|
+
export const computeGeneratorRech = (fin: number): number => 2 * (1 + idiv(fin * 3, 1000))
|
|
53
77
|
export const computeGathererYield = (str: number): number => 200 + str
|
|
54
78
|
export const computeGathererDrain = (con: number): number =>
|
|
55
|
-
Math.max(250, 1250 - idiv(con * 25, 20))
|
|
79
|
+
2 * Math.max(250, 1250 - idiv(con * 25, 20))
|
|
56
80
|
export const computeGathererDepth = (tol: number, tier: number): number =>
|
|
57
81
|
gathererDepthForTier(tol, tier)
|
|
58
|
-
export const computeGathererSpeed = (ref: number): number => 100 + idiv(ref * 4, 5)
|
|
59
82
|
export const computeLoaderMass = (ins: number): number => Math.max(200, 2000 - ins * 2)
|
|
60
|
-
export const computeLoaderThrust = (pla: number): number => 1 + idiv(pla,
|
|
83
|
+
export const computeLoaderThrust = (pla: number): number => 1 + idiv(pla * pla, 10000)
|
|
61
84
|
export const computeCrafterSpeed = (rea: number): number => 100 + idiv(rea * 4, 5)
|
|
62
85
|
export const computeCrafterDrain = (fin: number): number => Math.max(5, 30 - idiv(fin, 33))
|
|
63
|
-
export const computeHaulerCapacity = (fin: number): number =>
|
|
86
|
+
export const computeHaulerCapacity = (fin: number, tier: number): number =>
|
|
87
|
+
Math.max(tier, tier + idiv(fin, 400))
|
|
64
88
|
export const computeHaulerEfficiency = (con: number): number => 2000 + con * 6
|
|
65
89
|
export const computeHaulerDrain = (com: number): number => Math.max(3, 15 - idiv(com, 80))
|
|
66
90
|
export const computeWarpRange = (stat: number): number => 100 + stat * 3
|
|
91
|
+
export const computeCargoBayCapacity = (
|
|
92
|
+
strength: number,
|
|
93
|
+
density: number,
|
|
94
|
+
hardness: number,
|
|
95
|
+
cohesion: number
|
|
96
|
+
): number => 10_000_000 + idiv((strength + density + hardness + cohesion) * 50_000_000, 3996)
|
|
97
|
+
export const computeBatteryBankCapacity = (
|
|
98
|
+
volatility: number,
|
|
99
|
+
thermal: number,
|
|
100
|
+
plasticity: number,
|
|
101
|
+
insulation: number
|
|
102
|
+
): number => 2_500 + idiv((volatility + thermal + plasticity + insulation) * 7_500, 3996)
|
|
67
103
|
|
|
68
104
|
export function entityDisplayName(itemId: number): string {
|
|
69
105
|
switch (itemId) {
|
|
70
106
|
case ITEM_SHIP_T1_PACKED:
|
|
71
|
-
return '
|
|
107
|
+
return 'Roustabout'
|
|
72
108
|
case ITEM_WAREHOUSE_T1_PACKED:
|
|
73
109
|
return 'Warehouse'
|
|
110
|
+
case ITEM_EXTRACTOR_T1_PACKED:
|
|
111
|
+
return 'Mining Rig'
|
|
112
|
+
case ITEM_FACTORY_T1_PACKED:
|
|
113
|
+
return 'Factory'
|
|
74
114
|
case ITEM_CONTAINER_T1_PACKED:
|
|
75
115
|
return 'Container'
|
|
76
116
|
case ITEM_CONTAINER_T2_PACKED:
|
|
77
117
|
return 'Container'
|
|
118
|
+
case ITEM_PROSPECTOR_T2_PACKED:
|
|
119
|
+
return 'Prospector'
|
|
120
|
+
case ITEM_HAULER_SHIP_T2_PACKED:
|
|
121
|
+
return 'Hauler'
|
|
78
122
|
default:
|
|
79
123
|
return 'Entity'
|
|
80
124
|
}
|
|
@@ -85,19 +129,25 @@ export function moduleDisplayName(itemId: number): string {
|
|
|
85
129
|
case ITEM_ENGINE_T1:
|
|
86
130
|
return 'Engine'
|
|
87
131
|
case ITEM_GENERATOR_T1:
|
|
88
|
-
return '
|
|
132
|
+
return 'Power Core'
|
|
89
133
|
case ITEM_GATHERER_T1:
|
|
90
|
-
|
|
134
|
+
case ITEM_GATHERER_T2:
|
|
135
|
+
return 'Limpet Bay'
|
|
91
136
|
case ITEM_LOADER_T1:
|
|
92
|
-
return '
|
|
137
|
+
return 'Shuttle Bay'
|
|
93
138
|
case ITEM_CRAFTER_T1:
|
|
94
|
-
return '
|
|
139
|
+
return 'Fabricator'
|
|
95
140
|
case ITEM_STORAGE_T1:
|
|
96
|
-
return '
|
|
141
|
+
return 'Cargo Hold'
|
|
97
142
|
case ITEM_HAULER_T1:
|
|
98
|
-
|
|
143
|
+
case ITEM_HAULER_T2:
|
|
144
|
+
return 'Tractor Beam'
|
|
99
145
|
case ITEM_WARP_T1:
|
|
100
|
-
return 'Warp'
|
|
146
|
+
return 'Warp Drive'
|
|
147
|
+
case ITEM_BATTERY_T1:
|
|
148
|
+
return 'Battery Bank'
|
|
149
|
+
case ITEM_LAUNCHER_T1:
|
|
150
|
+
return 'Drive Coil'
|
|
101
151
|
default:
|
|
102
152
|
return 'Module'
|
|
103
153
|
}
|
|
@@ -129,13 +179,12 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
|
|
|
129
179
|
case MODULE_GATHERER: {
|
|
130
180
|
const str = decodeStat(stats, 0)
|
|
131
181
|
const tol = decodeStat(stats, 1)
|
|
132
|
-
const con = decodeStat(stats,
|
|
133
|
-
const ref = decodeStat(stats, 4)
|
|
182
|
+
const con = decodeStat(stats, 2)
|
|
134
183
|
const tier = getItem(itemId).tier
|
|
135
184
|
out += ` Yield ${computeGathererYield(str)} Depth ${computeGathererDepth(
|
|
136
185
|
tol,
|
|
137
186
|
tier
|
|
138
|
-
)}
|
|
187
|
+
)} Drain ${computeGathererDrain(con)}`
|
|
139
188
|
break
|
|
140
189
|
}
|
|
141
190
|
case MODULE_LOADER: {
|
|
@@ -146,24 +195,24 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
|
|
|
146
195
|
}
|
|
147
196
|
case MODULE_CRAFTER: {
|
|
148
197
|
const rea = decodeStat(stats, 0)
|
|
149
|
-
const
|
|
150
|
-
out += ` Speed ${computeCrafterSpeed(rea)} Drain ${computeCrafterDrain(
|
|
198
|
+
const con = decodeStat(stats, 1)
|
|
199
|
+
out += ` Speed ${computeCrafterSpeed(rea)} Drain ${computeCrafterDrain(con)}`
|
|
151
200
|
break
|
|
152
201
|
}
|
|
153
202
|
case MODULE_STORAGE: {
|
|
154
203
|
const str = decodeStat(stats, 0)
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
out += ` +${pct}% capacity`
|
|
204
|
+
const den = decodeStat(stats, 1)
|
|
205
|
+
const hrd = decodeStat(stats, 2)
|
|
206
|
+
const com = decodeStat(stats, 3)
|
|
207
|
+
out += ` Cargo Capacity ${computeCargoBayCapacity(str, den, hrd, com)}`
|
|
160
208
|
break
|
|
161
209
|
}
|
|
162
210
|
case MODULE_HAULER: {
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
|
|
211
|
+
const res = decodeStat(stats, 0)
|
|
212
|
+
const pla = decodeStat(stats, 1)
|
|
213
|
+
const ref = decodeStat(stats, 2)
|
|
214
|
+
const tier = getItem(itemId).tier
|
|
215
|
+
out += ` Capacity ${computeHaulerCapacity(res, tier)} Efficiency ${computeHaulerEfficiency(pla)} Drain ${computeHaulerDrain(ref)}`
|
|
167
216
|
break
|
|
168
217
|
}
|
|
169
218
|
case MODULE_WARP: {
|
|
@@ -171,6 +220,14 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
|
|
|
171
220
|
out += ` Range ${computeWarpRange(stat)}`
|
|
172
221
|
break
|
|
173
222
|
}
|
|
223
|
+
case MODULE_BATTERY: {
|
|
224
|
+
const vol = decodeStat(stats, 0)
|
|
225
|
+
const thm = decodeStat(stats, 1)
|
|
226
|
+
const pla = decodeStat(stats, 2)
|
|
227
|
+
const ins = decodeStat(stats, 3)
|
|
228
|
+
out += ` Energy Capacity ${computeBatteryBankCapacity(vol, thm, pla, ins)}`
|
|
229
|
+
break
|
|
230
|
+
}
|
|
174
231
|
}
|
|
175
232
|
return out
|
|
176
233
|
}
|
|
@@ -183,10 +240,21 @@ export function buildEntityDescription(
|
|
|
183
240
|
): string {
|
|
184
241
|
const hullMass = computeBaseHullmass(hullStats)
|
|
185
242
|
let baseCapacity = 0
|
|
186
|
-
if (
|
|
243
|
+
if (
|
|
244
|
+
itemId === ITEM_SHIP_T1_PACKED ||
|
|
245
|
+
itemId === ITEM_PROSPECTOR_T2_PACKED ||
|
|
246
|
+
itemId === ITEM_HAULER_SHIP_T2_PACKED
|
|
247
|
+
) {
|
|
187
248
|
baseCapacity = computeBaseCapacityShip(hullStats)
|
|
188
249
|
} else if (itemId === ITEM_WAREHOUSE_T1_PACKED) {
|
|
189
250
|
baseCapacity = computeBaseCapacityWarehouse(hullStats)
|
|
251
|
+
} else if (
|
|
252
|
+
itemId === ITEM_EXTRACTOR_T1_PACKED ||
|
|
253
|
+
itemId === ITEM_FACTORY_T1_PACKED ||
|
|
254
|
+
itemId === ITEM_CONTAINER_T1_PACKED ||
|
|
255
|
+
itemId === ITEM_CONTAINER_T2_PACKED
|
|
256
|
+
) {
|
|
257
|
+
baseCapacity = computeBaseCapacityContainer(hullStats)
|
|
190
258
|
}
|
|
191
259
|
|
|
192
260
|
let out = entityDisplayName(itemId)
|
package/src/nft/index.ts
CHANGED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import type {GathererStats} from '../types/capabilities'
|
|
2
|
+
import type {ServerContract} from '../contracts'
|
|
3
|
+
import {calc_gather_duration, calc_gather_energy} from '../capabilities/gathering'
|
|
4
|
+
import {calc_rechargetime} from '../travel/travel'
|
|
5
|
+
import {projectRemainingAt, type Projectable} from '../scheduling/projection'
|
|
6
|
+
|
|
7
|
+
export interface LanePlanEntry {
|
|
8
|
+
slot: number
|
|
9
|
+
quantity: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type PlanTarget = {quantity: number} | 'max'
|
|
13
|
+
|
|
14
|
+
export interface GatherPlanEntity extends Projectable {
|
|
15
|
+
gatherer_lanes: ServerContract.Types.gatherer_lane[]
|
|
16
|
+
loader_lanes: ServerContract.Types.loader_lane[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface GatherLimpet {
|
|
20
|
+
slot: number
|
|
21
|
+
quantity: number
|
|
22
|
+
durationSeconds: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface GatherCycle {
|
|
26
|
+
rechargeBefore: boolean
|
|
27
|
+
rechargeSeconds: number
|
|
28
|
+
limpets: GatherLimpet[]
|
|
29
|
+
gatherSeconds: number
|
|
30
|
+
batchOre: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type FillCap = 'reserve' | 'hold' | 'requested'
|
|
34
|
+
|
|
35
|
+
export interface GatherPlan {
|
|
36
|
+
cycles: GatherCycle[]
|
|
37
|
+
cycleCount: number
|
|
38
|
+
totalOre: number
|
|
39
|
+
totalSeconds: number
|
|
40
|
+
cap: FillCap
|
|
41
|
+
reachingCount: number
|
|
42
|
+
totalLimpets: number
|
|
43
|
+
warnings: string[]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface BuildGatherPlanOpts {
|
|
47
|
+
richness: number
|
|
48
|
+
itemMass: number
|
|
49
|
+
holdRoom: number
|
|
50
|
+
reserveRemaining: number
|
|
51
|
+
now: Date
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const MAX_CYCLES = 10_000
|
|
55
|
+
|
|
56
|
+
const MAX_TRANSFER_QTY = 10000
|
|
57
|
+
|
|
58
|
+
export function allocateProportional(
|
|
59
|
+
lanes: {slot: number; weight: number}[],
|
|
60
|
+
total: number
|
|
61
|
+
): LanePlanEntry[] {
|
|
62
|
+
if (lanes.length === 0) return []
|
|
63
|
+
const weightSum = lanes.reduce((s, l) => s + l.weight, 0)
|
|
64
|
+
if (weightSum === 0) return []
|
|
65
|
+
|
|
66
|
+
const entries: LanePlanEntry[] = lanes.map((l) => ({
|
|
67
|
+
slot: l.slot,
|
|
68
|
+
quantity: Math.floor((total * l.weight) / weightSum),
|
|
69
|
+
}))
|
|
70
|
+
|
|
71
|
+
let remainder = total - entries.reduce((s, e) => s + e.quantity, 0)
|
|
72
|
+
for (let i = 0; remainder > 0; i = (i + 1) % entries.length) {
|
|
73
|
+
entries[i].quantity++
|
|
74
|
+
remainder--
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return entries
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function laneStats(lane: ServerContract.Types.gatherer_lane): GathererStats {
|
|
81
|
+
return lane as unknown as GathererStats
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function gatherEnergyCost(
|
|
85
|
+
lane: ServerContract.Types.gatherer_lane,
|
|
86
|
+
quantity: number,
|
|
87
|
+
stratum: number,
|
|
88
|
+
itemMass: number,
|
|
89
|
+
richness: number
|
|
90
|
+
): number {
|
|
91
|
+
if (quantity <= 0) return 0
|
|
92
|
+
const dur = Number(calc_gather_duration(laneStats(lane), itemMass, quantity, stratum, richness))
|
|
93
|
+
return Number(calc_gather_energy(laneStats(lane), dur))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function splitCost(
|
|
97
|
+
reaching: ServerContract.Types.gatherer_lane[],
|
|
98
|
+
quantity: number,
|
|
99
|
+
stratum: number,
|
|
100
|
+
itemMass: number,
|
|
101
|
+
richness: number
|
|
102
|
+
): number {
|
|
103
|
+
if (quantity <= 0) return 0
|
|
104
|
+
const weights = reaching.map((l) => ({
|
|
105
|
+
slot: l.slot_index.toNumber(),
|
|
106
|
+
weight: l.yield.toNumber(),
|
|
107
|
+
}))
|
|
108
|
+
const split = allocateProportional(weights, quantity)
|
|
109
|
+
return split.reduce((sum, e) => {
|
|
110
|
+
const lane = reaching.find((l) => l.slot_index.toNumber() === e.slot)!
|
|
111
|
+
return sum + gatherEnergyCost(lane, e.quantity, stratum, itemMass, richness)
|
|
112
|
+
}, 0)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Binary search is exact because splitCost is monotonic non-decreasing in Q.
|
|
116
|
+
export function maxQtyForCharge(
|
|
117
|
+
reaching: ServerContract.Types.gatherer_lane[],
|
|
118
|
+
hi: number,
|
|
119
|
+
capacity: number,
|
|
120
|
+
stratum: number,
|
|
121
|
+
itemMass: number,
|
|
122
|
+
richness: number
|
|
123
|
+
): number {
|
|
124
|
+
if (hi <= 0) return 0
|
|
125
|
+
if (splitCost(reaching, hi, stratum, itemMass, richness) <= capacity) return hi
|
|
126
|
+
let lo = 0
|
|
127
|
+
let high = hi
|
|
128
|
+
while (lo < high) {
|
|
129
|
+
const mid = Math.ceil((lo + high) / 2)
|
|
130
|
+
if (splitCost(reaching, mid, stratum, itemMass, richness) <= capacity) lo = mid
|
|
131
|
+
else high = mid - 1
|
|
132
|
+
}
|
|
133
|
+
return lo
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function buildGatherPlan(
|
|
137
|
+
entity: GatherPlanEntity,
|
|
138
|
+
stratum: number,
|
|
139
|
+
target: PlanTarget,
|
|
140
|
+
opts: BuildGatherPlanOpts
|
|
141
|
+
): GatherPlan {
|
|
142
|
+
const {richness, itemMass, holdRoom, reserveRemaining, now} = opts
|
|
143
|
+
const warnings: string[] = []
|
|
144
|
+
|
|
145
|
+
const reaching = entity.gatherer_lanes.filter((l) => l.depth.toNumber() >= stratum)
|
|
146
|
+
if (reaching.length === 0) throw new Error('no gatherer reaches this stratum')
|
|
147
|
+
if (!entity.generator) throw new Error('entity has no generator')
|
|
148
|
+
|
|
149
|
+
const blocked = entity.gatherer_lanes.length - reaching.length
|
|
150
|
+
if (blocked > 0) {
|
|
151
|
+
warnings.push(
|
|
152
|
+
`${blocked} of ${entity.gatherer_lanes.length} limpets can't reach this depth`
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const capacity = entity.generator.capacity.toNumber()
|
|
157
|
+
const rechargeRate = entity.generator.recharge.toNumber()
|
|
158
|
+
let energy = Number(projectRemainingAt(entity, now).energy)
|
|
159
|
+
|
|
160
|
+
const requested = target === 'max' ? Infinity : target.quantity
|
|
161
|
+
let fillTarget = requested
|
|
162
|
+
let cap: FillCap = 'requested'
|
|
163
|
+
if (holdRoom < fillTarget) {
|
|
164
|
+
fillTarget = holdRoom
|
|
165
|
+
cap = 'hold'
|
|
166
|
+
}
|
|
167
|
+
if (reserveRemaining < fillTarget) {
|
|
168
|
+
fillTarget = reserveRemaining
|
|
169
|
+
cap = 'reserve'
|
|
170
|
+
}
|
|
171
|
+
fillTarget = Math.max(0, Math.floor(fillTarget))
|
|
172
|
+
|
|
173
|
+
const cycles: GatherCycle[] = []
|
|
174
|
+
let remaining = fillTarget
|
|
175
|
+
let guard = 0
|
|
176
|
+
while (remaining > 0 && guard++ < MAX_CYCLES) {
|
|
177
|
+
const batch = maxQtyForCharge(reaching, remaining, capacity, stratum, itemMass, richness)
|
|
178
|
+
if (batch <= 0) {
|
|
179
|
+
warnings.push('a single gather cannot fit within one full charge')
|
|
180
|
+
break
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const costFull = splitCost(reaching, batch, stratum, itemMass, richness)
|
|
184
|
+
const rechargeBefore = energy < costFull
|
|
185
|
+
const rechargeSeconds = rechargeBefore
|
|
186
|
+
? Number(calc_rechargetime(capacity, energy, rechargeRate))
|
|
187
|
+
: 0
|
|
188
|
+
if (rechargeBefore) energy = capacity
|
|
189
|
+
|
|
190
|
+
const weights = reaching.map((l) => ({
|
|
191
|
+
slot: l.slot_index.toNumber(),
|
|
192
|
+
weight: l.yield.toNumber(),
|
|
193
|
+
}))
|
|
194
|
+
const split = allocateProportional(weights, batch).filter((e) => e.quantity > 0)
|
|
195
|
+
const limpets: GatherLimpet[] = split.map((e) => {
|
|
196
|
+
const lane = reaching.find((l) => l.slot_index.toNumber() === e.slot)!
|
|
197
|
+
const dur = Number(
|
|
198
|
+
calc_gather_duration(laneStats(lane), itemMass, e.quantity, stratum, richness)
|
|
199
|
+
)
|
|
200
|
+
return {slot: e.slot, quantity: e.quantity, durationSeconds: dur}
|
|
201
|
+
})
|
|
202
|
+
const actualCost = limpets.reduce((sum, l) => {
|
|
203
|
+
const lane = reaching.find((r) => r.slot_index.toNumber() === l.slot)!
|
|
204
|
+
return sum + Number(calc_gather_energy(laneStats(lane), l.durationSeconds))
|
|
205
|
+
}, 0)
|
|
206
|
+
energy = Math.max(0, energy - actualCost)
|
|
207
|
+
|
|
208
|
+
const gatherSeconds = limpets.reduce((m, l) => Math.max(m, l.durationSeconds), 0)
|
|
209
|
+
cycles.push({rechargeBefore, rechargeSeconds, limpets, gatherSeconds, batchOre: batch})
|
|
210
|
+
remaining -= batch
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const totalOre = cycles.reduce((s, c) => s + c.batchOre, 0)
|
|
214
|
+
const totalSeconds = cycles.reduce((s, c) => s + c.rechargeSeconds + c.gatherSeconds, 0)
|
|
215
|
+
return {
|
|
216
|
+
cycles,
|
|
217
|
+
cycleCount: cycles.length,
|
|
218
|
+
totalOre,
|
|
219
|
+
totalSeconds,
|
|
220
|
+
cap,
|
|
221
|
+
reachingCount: reaching.length,
|
|
222
|
+
totalLimpets: entity.gatherer_lanes.length,
|
|
223
|
+
warnings,
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function planParallelTransfer(
|
|
228
|
+
entity: GatherPlanEntity,
|
|
229
|
+
target: PlanTarget
|
|
230
|
+
): LanePlanEntry[] {
|
|
231
|
+
const lanes = entity.loader_lanes.filter((l) => l.thrust.toNumber() > 0)
|
|
232
|
+
if (lanes.length === 0) return []
|
|
233
|
+
|
|
234
|
+
const requestedQty = target === 'max' ? MAX_TRANSFER_QTY : target.quantity
|
|
235
|
+
|
|
236
|
+
const laneWeights = lanes.map((l) => ({
|
|
237
|
+
slot: l.slot_index.toNumber(),
|
|
238
|
+
weight: l.thrust.toNumber(),
|
|
239
|
+
}))
|
|
240
|
+
|
|
241
|
+
return allocateProportional(laneWeights, requestedQty).filter((e) => e.quantity > 0)
|
|
242
|
+
}
|