@shipload/sdk 1.0.0-next.3 → 1.0.0-next.30
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 +1847 -962
- package/lib/shipload.js +9088 -4854
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +8957 -4805
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +856 -0
- package/lib/testing.js +3739 -0
- package/lib/testing.js.map +1 -0
- package/lib/testing.m.js +3733 -0
- package/lib/testing.m.js.map +1 -0
- package/package.json +15 -2
- package/src/capabilities/craftable.ts +51 -0
- package/src/capabilities/crafting.test.ts +7 -0
- package/src/capabilities/crafting.ts +3 -3
- package/src/capabilities/gathering.ts +17 -7
- package/src/capabilities/index.ts +0 -1
- package/src/capabilities/modules.ts +6 -0
- package/src/capabilities/storage.ts +16 -1
- package/src/contracts/platform.ts +231 -3
- package/src/contracts/server.ts +816 -471
- package/src/data/capabilities.ts +14 -329
- package/src/data/capability-formulas.ts +76 -0
- package/src/data/catalog.ts +0 -5
- package/src/data/colors.ts +14 -47
- package/src/data/entities.json +46 -10
- package/src/data/item-ids.ts +15 -12
- package/src/data/items.json +302 -38
- package/src/data/kind-registry.json +85 -0
- package/src/data/kind-registry.ts +150 -0
- package/src/data/metadata.ts +100 -31
- package/src/data/recipes-runtime.ts +3 -23
- package/src/data/recipes.json +250 -113
- package/src/derivation/build-methods.ts +45 -0
- package/src/derivation/capabilities.ts +415 -0
- package/src/derivation/capability-mappings.ts +117 -0
- package/src/derivation/crafting.ts +23 -24
- package/src/derivation/index.ts +17 -2
- package/src/derivation/reserve-regen.ts +34 -0
- package/src/derivation/resources.ts +125 -38
- package/src/derivation/stars.test.ts +51 -0
- package/src/derivation/stars.ts +15 -0
- package/src/derivation/stats.ts +6 -6
- 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 +91 -136
- package/src/entities/slot-multiplier.ts +39 -0
- package/src/errors.ts +10 -15
- package/src/format.ts +26 -4
- package/src/index-module.ts +189 -47
- package/src/managers/actions.ts +252 -83
- package/src/managers/base.ts +6 -2
- package/src/managers/construction-types.ts +79 -0
- package/src/managers/construction.ts +396 -0
- package/src/managers/context.ts +11 -1
- package/src/managers/entities.ts +18 -66
- package/src/managers/epochs.ts +40 -0
- package/src/managers/index.ts +17 -1
- package/src/managers/locations.ts +25 -29
- package/src/managers/nft.ts +28 -0
- package/src/managers/plot.ts +127 -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 +321 -0
- package/src/nft/description.ts +37 -15
- 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 +22 -20
- package/src/scheduling/accessor.ts +68 -22
- package/src/scheduling/availability.ts +108 -0
- package/src/scheduling/energy.ts +48 -0
- package/src/scheduling/lane-core.ts +130 -0
- package/src/scheduling/lanes.ts +60 -0
- package/src/scheduling/projection.ts +121 -94
- package/src/scheduling/schedule.ts +237 -103
- package/src/scheduling/task-cargo.ts +46 -0
- package/src/shipload.ts +16 -1
- 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 +90 -13
- package/src/types/capabilities.ts +1 -0
- package/src/types/index.ts +0 -1
- package/src/types.ts +19 -12
- package/src/utils/cargo.ts +27 -0
- package/src/utils/display-name.ts +61 -0
- package/src/utils/system.ts +25 -24
- package/src/capabilities/loading.ts +0 -8
- 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
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {EntityClass, getKindMeta, getTemplateMeta} from '../data/kind-registry'
|
|
2
|
+
import {getRecipe} from '../data/recipes-runtime'
|
|
3
|
+
import {getItems} from '../data/catalog'
|
|
4
|
+
import type {Item} from '../types'
|
|
5
|
+
|
|
6
|
+
export type BuildMethod = 'craft+deploy' | 'plot'
|
|
7
|
+
|
|
8
|
+
export function availableBuildMethods(itemId: number): BuildMethod[] {
|
|
9
|
+
const recipe = getRecipe(itemId)
|
|
10
|
+
if (!recipe) return []
|
|
11
|
+
|
|
12
|
+
const template = getTemplateMeta(itemId)
|
|
13
|
+
if (!template) return ['craft+deploy']
|
|
14
|
+
|
|
15
|
+
const kindMeta = getKindMeta(template.kind)
|
|
16
|
+
if (!kindMeta) return ['craft+deploy']
|
|
17
|
+
|
|
18
|
+
if (kindMeta.classification === EntityClass.PlanetaryStructure) {
|
|
19
|
+
return ['craft+deploy', 'plot']
|
|
20
|
+
}
|
|
21
|
+
return ['craft+deploy']
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isBuildable(itemId: number): boolean {
|
|
25
|
+
return availableBuildMethods(itemId).length > 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isPlotBuildable(itemId: number): boolean {
|
|
29
|
+
return availableBuildMethods(itemId).includes('plot')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function filterByBuildMethod<T extends {itemId: number}>(
|
|
33
|
+
items: T[],
|
|
34
|
+
method: BuildMethod
|
|
35
|
+
): T[] {
|
|
36
|
+
return items.filter((i) => availableBuildMethods(i.itemId).includes(method))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function allBuildableItems(): Item[] {
|
|
40
|
+
return getItems().filter((item) => isBuildable(item.id))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function allPlotBuildableItems(): Item[] {
|
|
44
|
+
return getItems().filter((item) => isPlotBuildable(item.id))
|
|
45
|
+
}
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
export function computeBaseHullmass(stats: Record<string, number>): number {
|
|
2
|
+
return 100000 - 75 * stats.density
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function computeShipHullCapabilities(stats: Record<string, number>): {
|
|
6
|
+
hullmass: number
|
|
7
|
+
capacity: number
|
|
8
|
+
} {
|
|
9
|
+
const statSum = stats.strength + stats.hardness + stats.cohesion
|
|
10
|
+
const exponent = statSum / 2997.0
|
|
11
|
+
return {
|
|
12
|
+
hullmass: computeBaseHullmass(stats),
|
|
13
|
+
capacity: Math.floor(5000000 * 6 ** exponent),
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function computeEngineCapabilities(stats: Record<string, number>): {
|
|
18
|
+
thrust: number
|
|
19
|
+
drain: number
|
|
20
|
+
} {
|
|
21
|
+
const vol = stats.volatility
|
|
22
|
+
const thm = stats.thermal
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
thrust: 400 + Math.floor((vol * 3) / 4),
|
|
26
|
+
drain: 2 * Math.max(30, 50 - Math.floor(thm / 70)),
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function computeGeneratorCapabilities(stats: Record<string, number>): {
|
|
31
|
+
capacity: number
|
|
32
|
+
recharge: number
|
|
33
|
+
} {
|
|
34
|
+
const res = stats.resonance
|
|
35
|
+
const ref = stats.reflectivity
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
capacity: 950 + Math.floor(res / 2),
|
|
39
|
+
recharge: 2 * (1 + Math.floor((ref * 3) / 1000)),
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface GathererDepthParams {
|
|
44
|
+
readonly floor: number
|
|
45
|
+
readonly slope: number
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const GATHERER_DEPTH_TABLE: readonly GathererDepthParams[] = [
|
|
49
|
+
{floor: 500, slope: 5},
|
|
50
|
+
{floor: 2000, slope: 11},
|
|
51
|
+
{floor: 7000, slope: 16},
|
|
52
|
+
{floor: 15000, slope: 18},
|
|
53
|
+
{floor: 25000, slope: 19},
|
|
54
|
+
{floor: 35000, slope: 16},
|
|
55
|
+
{floor: 46000, slope: 12},
|
|
56
|
+
{floor: 53500, slope: 10},
|
|
57
|
+
{floor: 60000, slope: 5},
|
|
58
|
+
{floor: 63537, slope: 2},
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
export const GATHERER_DEPTH_MAX_TIER = 10
|
|
62
|
+
|
|
63
|
+
export function gathererDepthForTier(tol: number, tier: number): number {
|
|
64
|
+
if (tier < 1 || tier > GATHERER_DEPTH_MAX_TIER) {
|
|
65
|
+
throw new Error(`gatherer tier out of range: ${tier}`)
|
|
66
|
+
}
|
|
67
|
+
const p = GATHERER_DEPTH_TABLE[tier - 1]
|
|
68
|
+
return p.floor + tol * p.slope
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function computeGathererCapabilities(
|
|
72
|
+
stats: Record<string, number>,
|
|
73
|
+
tier: number
|
|
74
|
+
): {
|
|
75
|
+
yield: number
|
|
76
|
+
drain: number
|
|
77
|
+
depth: number
|
|
78
|
+
} {
|
|
79
|
+
const str = stats.strength
|
|
80
|
+
const con = stats.conductivity
|
|
81
|
+
const tol = stats.tolerance
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
yield: 200 + str,
|
|
85
|
+
drain: 2 * Math.max(250, 1250 - Math.floor((con * 25) / 20)),
|
|
86
|
+
depth: gathererDepthForTier(tol, tier),
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function computeLoaderCapabilities(stats: Record<string, number>): {
|
|
91
|
+
mass: number
|
|
92
|
+
thrust: number
|
|
93
|
+
quantity: number
|
|
94
|
+
} {
|
|
95
|
+
const insulation = stats.insulation
|
|
96
|
+
const plasticity = stats.plasticity
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
mass: Math.max(200, 2000 - Math.floor(insulation * 2)),
|
|
100
|
+
thrust: 1 + Math.floor(plasticity / 500),
|
|
101
|
+
quantity: 1,
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function computeCrafterCapabilities(stats: Record<string, number>): {
|
|
106
|
+
speed: number
|
|
107
|
+
drain: number
|
|
108
|
+
} {
|
|
109
|
+
const rea = stats.reactivity
|
|
110
|
+
const fin = stats.fineness
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
speed: 100 + Math.floor((rea * 4) / 5),
|
|
114
|
+
drain: Math.max(5, 30 - Math.floor(fin / 33)),
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function computeHaulerCapabilities(stats: Record<string, number>): {
|
|
119
|
+
capacity: number
|
|
120
|
+
efficiency: number
|
|
121
|
+
drain: number
|
|
122
|
+
} {
|
|
123
|
+
const resonance = stats.resonance
|
|
124
|
+
const plasticity = stats.plasticity
|
|
125
|
+
const reflectivity = stats.reflectivity
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
capacity: Math.max(1, 1 + Math.floor(resonance / 400)),
|
|
129
|
+
efficiency: 2000 + plasticity * 6,
|
|
130
|
+
drain: Math.max(3, 15 - Math.floor(reflectivity / 80)),
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function computeStorageCapabilities(
|
|
135
|
+
stats: Record<string, number>,
|
|
136
|
+
baseCapacity: number
|
|
137
|
+
): {
|
|
138
|
+
capacityBonus: number
|
|
139
|
+
} {
|
|
140
|
+
const strength = stats.strength
|
|
141
|
+
const density = stats.density
|
|
142
|
+
const hardness = stats.hardness
|
|
143
|
+
const cohesion = stats.cohesion
|
|
144
|
+
|
|
145
|
+
const statSum = strength + density + hardness + cohesion
|
|
146
|
+
const capacityBonus = Math.floor(
|
|
147
|
+
(baseCapacity * (10 + Math.floor((statSum * 10) / 2997))) / 100
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return {capacityBonus}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
import {
|
|
154
|
+
ITEM_CONTAINER_T1_PACKED,
|
|
155
|
+
ITEM_CONTAINER_T2_PACKED,
|
|
156
|
+
ITEM_EXTRACTOR_T1_PACKED,
|
|
157
|
+
ITEM_FACTORY_T1_PACKED,
|
|
158
|
+
ITEM_SHIP_T1_PACKED,
|
|
159
|
+
ITEM_WAREHOUSE_T1_PACKED,
|
|
160
|
+
} from '../data/item-ids'
|
|
161
|
+
import {
|
|
162
|
+
getModuleCapabilityType,
|
|
163
|
+
MODULE_BATTERY,
|
|
164
|
+
MODULE_ENGINE,
|
|
165
|
+
MODULE_GENERATOR,
|
|
166
|
+
MODULE_GATHERER,
|
|
167
|
+
MODULE_LOADER,
|
|
168
|
+
MODULE_STORAGE,
|
|
169
|
+
MODULE_CRAFTER,
|
|
170
|
+
MODULE_HAULER,
|
|
171
|
+
MODULE_WARP,
|
|
172
|
+
} from '../capabilities/modules'
|
|
173
|
+
import {getItem} from '../data/catalog'
|
|
174
|
+
import {decodeCraftedItemStats} from './crafting'
|
|
175
|
+
import {
|
|
176
|
+
applySlotMultiplier,
|
|
177
|
+
clampUint16,
|
|
178
|
+
clampUint32,
|
|
179
|
+
getSlotAmp,
|
|
180
|
+
type InstalledModule,
|
|
181
|
+
} from '../entities/slot-multiplier'
|
|
182
|
+
import type {EntitySlot} from '../data/recipes-runtime'
|
|
183
|
+
|
|
184
|
+
export function computeBaseCapacity(itemId: number, stats: Record<string, number>): number {
|
|
185
|
+
switch (itemId) {
|
|
186
|
+
case ITEM_SHIP_T1_PACKED:
|
|
187
|
+
case ITEM_EXTRACTOR_T1_PACKED:
|
|
188
|
+
case ITEM_FACTORY_T1_PACKED:
|
|
189
|
+
return computeShipHullCapabilities(stats).capacity
|
|
190
|
+
case ITEM_CONTAINER_T1_PACKED:
|
|
191
|
+
return computeContainerCapabilities(stats).capacity
|
|
192
|
+
case ITEM_WAREHOUSE_T1_PACKED:
|
|
193
|
+
return computeWarehouseHullCapabilities(stats).capacity
|
|
194
|
+
case ITEM_CONTAINER_T2_PACKED:
|
|
195
|
+
return computeContainerT2Capabilities(stats).capacity
|
|
196
|
+
default:
|
|
197
|
+
return 0
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function computeWarpCapabilities(stats: Record<string, number>): {
|
|
202
|
+
range: number
|
|
203
|
+
} {
|
|
204
|
+
const resonance = stats.resonance
|
|
205
|
+
return {range: 100 + resonance * 3}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function computeWarehouseHullCapabilities(stats: Record<string, number>): {
|
|
209
|
+
hullmass: number
|
|
210
|
+
capacity: number
|
|
211
|
+
} {
|
|
212
|
+
const statSum = stats.strength + stats.hardness + stats.cohesion
|
|
213
|
+
const exponent = statSum / 2997.0
|
|
214
|
+
return {
|
|
215
|
+
hullmass: computeBaseHullmass(stats),
|
|
216
|
+
capacity: Math.floor(100000000 * 6 ** exponent),
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface ComputedCapabilities {
|
|
221
|
+
hullmass: number
|
|
222
|
+
capacity: number
|
|
223
|
+
engines?: {thrust: number; drain: number}
|
|
224
|
+
generator?: {capacity: number; recharge: number}
|
|
225
|
+
gatherer?: {yield: number; drain: number; depth: number}
|
|
226
|
+
loaders?: {mass: number; thrust: number; quantity: number}
|
|
227
|
+
crafter?: {speed: number; drain: number}
|
|
228
|
+
hauler?: {capacity: number; efficiency: number; drain: number}
|
|
229
|
+
warp?: {range: number}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function computeEntityCapabilities(
|
|
233
|
+
stats: Record<string, number>,
|
|
234
|
+
itemId: number,
|
|
235
|
+
modules: InstalledModule[],
|
|
236
|
+
layout: EntitySlot[]
|
|
237
|
+
): ComputedCapabilities {
|
|
238
|
+
let totalThrust = 0
|
|
239
|
+
let totalEngineDrain = 0
|
|
240
|
+
let hasEngine = false
|
|
241
|
+
|
|
242
|
+
let totalGenCapacity = 0
|
|
243
|
+
let totalGenRecharge = 0
|
|
244
|
+
let hasGenerator = false
|
|
245
|
+
|
|
246
|
+
let totalLoaderMass = 0
|
|
247
|
+
let totalLoaderThrust = 0
|
|
248
|
+
let totalLoaderQuantity = 0
|
|
249
|
+
let hasLoader = false
|
|
250
|
+
|
|
251
|
+
let totalGathYield = 0
|
|
252
|
+
let totalGathDrain = 0
|
|
253
|
+
let maxGathDepth = 0
|
|
254
|
+
let hasGatherer = false
|
|
255
|
+
|
|
256
|
+
let totalStorageBonus = 0
|
|
257
|
+
const baseCapacity = computeBaseCapacity(itemId, stats)
|
|
258
|
+
let installedModuleMass = 0
|
|
259
|
+
|
|
260
|
+
let totalCrafterSpeed = 0
|
|
261
|
+
let totalCrafterDrain = 0
|
|
262
|
+
let hasCrafter = false
|
|
263
|
+
|
|
264
|
+
let totalHaulerCapacity = 0
|
|
265
|
+
let weightedHaulerEffNum = 0n
|
|
266
|
+
let totalHaulerDrain = 0
|
|
267
|
+
let hasHauler = false
|
|
268
|
+
|
|
269
|
+
let totalWarpRange = 0
|
|
270
|
+
let hasWarp = false
|
|
271
|
+
|
|
272
|
+
let totalBatteryStatSum = 0
|
|
273
|
+
let batteryCount = 0
|
|
274
|
+
|
|
275
|
+
for (const mod of modules) {
|
|
276
|
+
const item = getItem(mod.itemId)
|
|
277
|
+
const modType = getModuleCapabilityType(mod.itemId)
|
|
278
|
+
const amp = getSlotAmp(layout, mod.slotIndex)
|
|
279
|
+
const decodedStats = decodeCraftedItemStats(mod.itemId, mod.stats)
|
|
280
|
+
installedModuleMass += item.mass
|
|
281
|
+
|
|
282
|
+
if (modType === MODULE_ENGINE) {
|
|
283
|
+
hasEngine = true
|
|
284
|
+
const caps = computeEngineCapabilities(decodedStats)
|
|
285
|
+
totalThrust += applySlotMultiplier(caps.thrust, amp)
|
|
286
|
+
totalEngineDrain += caps.drain
|
|
287
|
+
} else if (modType === MODULE_GENERATOR) {
|
|
288
|
+
hasGenerator = true
|
|
289
|
+
const caps = computeGeneratorCapabilities(decodedStats)
|
|
290
|
+
totalGenCapacity += applySlotMultiplier(caps.capacity, amp)
|
|
291
|
+
totalGenRecharge += applySlotMultiplier(caps.recharge, amp)
|
|
292
|
+
} else if (modType === MODULE_GATHERER) {
|
|
293
|
+
hasGatherer = true
|
|
294
|
+
const tier = item.tier
|
|
295
|
+
const caps = computeGathererCapabilities(decodedStats, tier)
|
|
296
|
+
totalGathYield += applySlotMultiplier(caps.yield, amp)
|
|
297
|
+
totalGathDrain += caps.drain
|
|
298
|
+
if (caps.depth > maxGathDepth) maxGathDepth = caps.depth
|
|
299
|
+
} else if (modType === MODULE_LOADER) {
|
|
300
|
+
hasLoader = true
|
|
301
|
+
const caps = computeLoaderCapabilities(decodedStats)
|
|
302
|
+
totalLoaderMass += caps.mass
|
|
303
|
+
totalLoaderThrust += applySlotMultiplier(caps.thrust, amp)
|
|
304
|
+
totalLoaderQuantity += caps.quantity
|
|
305
|
+
} else if (modType === MODULE_STORAGE) {
|
|
306
|
+
const caps = computeStorageCapabilities(decodedStats, baseCapacity)
|
|
307
|
+
totalStorageBonus += caps.capacityBonus
|
|
308
|
+
} else if (modType === MODULE_CRAFTER) {
|
|
309
|
+
hasCrafter = true
|
|
310
|
+
const caps = computeCrafterCapabilities(decodedStats)
|
|
311
|
+
totalCrafterSpeed += applySlotMultiplier(caps.speed, amp)
|
|
312
|
+
totalCrafterDrain += caps.drain
|
|
313
|
+
} else if (modType === MODULE_HAULER) {
|
|
314
|
+
hasHauler = true
|
|
315
|
+
const caps = computeHaulerCapabilities(decodedStats)
|
|
316
|
+
const eff = applySlotMultiplier(caps.efficiency, amp)
|
|
317
|
+
totalHaulerCapacity += caps.capacity
|
|
318
|
+
weightedHaulerEffNum += BigInt(eff) * BigInt(caps.capacity)
|
|
319
|
+
totalHaulerDrain += caps.drain
|
|
320
|
+
} else if (modType === MODULE_WARP) {
|
|
321
|
+
hasWarp = true
|
|
322
|
+
const caps = computeWarpCapabilities(decodedStats)
|
|
323
|
+
totalWarpRange += applySlotMultiplier(caps.range, amp)
|
|
324
|
+
} else if (modType === MODULE_BATTERY) {
|
|
325
|
+
batteryCount++
|
|
326
|
+
const vol = decodedStats.volatility ?? 0
|
|
327
|
+
const thm = decodedStats.thermal ?? 0
|
|
328
|
+
const pla = decodedStats.plasticity ?? 0
|
|
329
|
+
const ins = decodedStats.insulation ?? 0
|
|
330
|
+
totalBatteryStatSum += vol + thm + pla + ins
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (hasGenerator && batteryCount > 0) {
|
|
335
|
+
const genCapBase = totalGenCapacity
|
|
336
|
+
const bonusPctNum = 10 * batteryCount + Math.floor((totalBatteryStatSum * 10) / 2997)
|
|
337
|
+
totalGenCapacity += Math.floor((genCapBase * bonusPctNum) / 100)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const result: ComputedCapabilities = {
|
|
341
|
+
hullmass: computeBaseHullmass(stats) + installedModuleMass,
|
|
342
|
+
capacity: baseCapacity + totalStorageBonus,
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (hasEngine) {
|
|
346
|
+
result.engines = {thrust: totalThrust, drain: totalEngineDrain}
|
|
347
|
+
}
|
|
348
|
+
if (hasGenerator) {
|
|
349
|
+
result.generator = {
|
|
350
|
+
capacity: clampUint32(totalGenCapacity),
|
|
351
|
+
recharge: clampUint32(totalGenRecharge),
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (hasGatherer) {
|
|
355
|
+
result.gatherer = {
|
|
356
|
+
yield: clampUint16(totalGathYield),
|
|
357
|
+
drain: totalGathDrain,
|
|
358
|
+
depth: maxGathDepth,
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (hasLoader) {
|
|
362
|
+
result.loaders = {
|
|
363
|
+
mass: totalLoaderMass,
|
|
364
|
+
thrust: clampUint16(totalLoaderThrust),
|
|
365
|
+
quantity: totalLoaderQuantity,
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (hasCrafter) {
|
|
369
|
+
result.crafter = {speed: clampUint16(totalCrafterSpeed), drain: totalCrafterDrain}
|
|
370
|
+
}
|
|
371
|
+
if (hasHauler) {
|
|
372
|
+
const efficiency =
|
|
373
|
+
totalHaulerCapacity > 0 ? Number(weightedHaulerEffNum / BigInt(totalHaulerCapacity)) : 0
|
|
374
|
+
result.hauler = {
|
|
375
|
+
capacity: totalHaulerCapacity,
|
|
376
|
+
efficiency: clampUint16(efficiency),
|
|
377
|
+
drain: totalHaulerDrain,
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (hasWarp) {
|
|
381
|
+
result.warp = {range: totalWarpRange}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return result
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function computeContainerCapabilities(stats: Record<string, number>): {
|
|
388
|
+
hullmass: number
|
|
389
|
+
capacity: number
|
|
390
|
+
} {
|
|
391
|
+
const statSum = stats.strength + stats.hardness + stats.cohesion
|
|
392
|
+
const exponent = statSum / 2997.0
|
|
393
|
+
return {
|
|
394
|
+
hullmass: computeBaseHullmass(stats),
|
|
395
|
+
capacity: Math.floor(22000000 * 6 ** exponent),
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export function computeContainerT2Capabilities(stats: Record<string, number>): {
|
|
400
|
+
hullmass: number
|
|
401
|
+
capacity: number
|
|
402
|
+
} {
|
|
403
|
+
const strength = stats.strength
|
|
404
|
+
const density = stats.density
|
|
405
|
+
const hardness = stats.hardness
|
|
406
|
+
const cohesion = stats.cohesion
|
|
407
|
+
|
|
408
|
+
const hullmass = 70000 - 50 * density
|
|
409
|
+
|
|
410
|
+
const statSum = strength + hardness + cohesion
|
|
411
|
+
const exponent = statSum / 2947
|
|
412
|
+
const capacity = Math.floor(24000000 * 6 ** exponent)
|
|
413
|
+
|
|
414
|
+
return {hullmass, capacity}
|
|
415
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {SLOT_FORMULAS, type SlotConsumerKind} from '../data/capability-formulas'
|
|
2
|
+
import {getStatDefinitions, type StatDefinition} from './stats'
|
|
3
|
+
import {getRecipe, type Recipe} from '../data/recipes-runtime'
|
|
4
|
+
import {getItem} from '../data/catalog'
|
|
5
|
+
import {
|
|
6
|
+
ITEM_ENGINE_T1,
|
|
7
|
+
ITEM_EXTRACTOR_T1_PACKED,
|
|
8
|
+
ITEM_GENERATOR_T1,
|
|
9
|
+
ITEM_GATHERER_T1,
|
|
10
|
+
ITEM_LOADER_T1,
|
|
11
|
+
ITEM_CRAFTER_T1,
|
|
12
|
+
ITEM_STORAGE_T1,
|
|
13
|
+
ITEM_HAULER_T1,
|
|
14
|
+
ITEM_WARP_T1,
|
|
15
|
+
ITEM_BATTERY_T1,
|
|
16
|
+
ITEM_SHIP_T1_PACKED,
|
|
17
|
+
ITEM_CONTAINER_T1_PACKED,
|
|
18
|
+
ITEM_WAREHOUSE_T1_PACKED,
|
|
19
|
+
ITEM_CONTAINER_T2_PACKED,
|
|
20
|
+
} from '../data/item-ids'
|
|
21
|
+
import type {StatMapping} from '../data/capabilities'
|
|
22
|
+
|
|
23
|
+
export const KIND_TO_ITEM_ID: Record<SlotConsumerKind, number> = {
|
|
24
|
+
engine: ITEM_ENGINE_T1,
|
|
25
|
+
generator: ITEM_GENERATOR_T1,
|
|
26
|
+
gatherer: ITEM_GATHERER_T1,
|
|
27
|
+
loader: ITEM_LOADER_T1,
|
|
28
|
+
crafter: ITEM_CRAFTER_T1,
|
|
29
|
+
storage: ITEM_STORAGE_T1,
|
|
30
|
+
hauler: ITEM_HAULER_T1,
|
|
31
|
+
warp: ITEM_WARP_T1,
|
|
32
|
+
battery: ITEM_BATTERY_T1,
|
|
33
|
+
'ship-t1': ITEM_SHIP_T1_PACKED,
|
|
34
|
+
'container-t1': ITEM_CONTAINER_T1_PACKED,
|
|
35
|
+
'warehouse-t1': ITEM_WAREHOUSE_T1_PACKED,
|
|
36
|
+
'extractor-t1': ITEM_EXTRACTOR_T1_PACKED,
|
|
37
|
+
'container-t2': ITEM_CONTAINER_T2_PACKED,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Walk a recipe's slot source down to the raw category stat that ultimately
|
|
42
|
+
* lands in that slot. Returns the StatDefinition or undefined if the trace
|
|
43
|
+
* dead-ends (unknown sub-component, missing slot, etc.).
|
|
44
|
+
*
|
|
45
|
+
* Multi-source sub-slots collapse to `sources[0]`; top-level multi-source slots
|
|
46
|
+
* are expanded by the caller (`deriveStatMappings`).
|
|
47
|
+
*/
|
|
48
|
+
function traceToRawCategoryStat(
|
|
49
|
+
recipe: Recipe,
|
|
50
|
+
source: {inputIndex: number; statIndex: number},
|
|
51
|
+
visited: Set<number> = new Set()
|
|
52
|
+
): StatDefinition | undefined {
|
|
53
|
+
const input = recipe.inputs[source.inputIndex]
|
|
54
|
+
if (!input) return undefined
|
|
55
|
+
const inputItem = getItem(input.itemId)
|
|
56
|
+
if (inputItem.type === 'resource' && inputItem.category) {
|
|
57
|
+
const defs = getStatDefinitions(inputItem.category)
|
|
58
|
+
return defs[source.statIndex]
|
|
59
|
+
}
|
|
60
|
+
if (visited.has(input.itemId)) return undefined
|
|
61
|
+
const subRecipe = getRecipe(input.itemId)
|
|
62
|
+
if (!subRecipe) return undefined
|
|
63
|
+
const subSlot = subRecipe.statSlots[source.statIndex]
|
|
64
|
+
if (!subSlot) return undefined
|
|
65
|
+
const subSource = subSlot.sources[0]
|
|
66
|
+
if (!subSource) return undefined
|
|
67
|
+
const nextVisited = new Set(visited)
|
|
68
|
+
nextVisited.add(input.itemId)
|
|
69
|
+
return traceToRawCategoryStat(subRecipe, subSource, nextVisited)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let cached: StatMapping[] | undefined
|
|
73
|
+
|
|
74
|
+
export function deriveStatMappings(): StatMapping[] {
|
|
75
|
+
if (cached) return cached
|
|
76
|
+
const out: StatMapping[] = []
|
|
77
|
+
const seen = new Set<string>()
|
|
78
|
+
for (const [kind, slots] of Object.entries(SLOT_FORMULAS) as [
|
|
79
|
+
SlotConsumerKind,
|
|
80
|
+
Record<number, {capability: string; attribute: string}>,
|
|
81
|
+
][]) {
|
|
82
|
+
const itemId = KIND_TO_ITEM_ID[kind]
|
|
83
|
+
const recipe = getRecipe(itemId)
|
|
84
|
+
if (!recipe) continue
|
|
85
|
+
for (const [slotIdxStr, consumer] of Object.entries(slots)) {
|
|
86
|
+
const slotIdx = Number(slotIdxStr)
|
|
87
|
+
const slot = recipe.statSlots[slotIdx]
|
|
88
|
+
if (!slot) continue
|
|
89
|
+
for (const source of slot.sources) {
|
|
90
|
+
const stat = traceToRawCategoryStat(recipe, source)
|
|
91
|
+
if (!stat) continue
|
|
92
|
+
const key = `${stat.label}|${consumer.capability}|${consumer.attribute}`
|
|
93
|
+
if (seen.has(key)) continue
|
|
94
|
+
seen.add(key)
|
|
95
|
+
out.push({
|
|
96
|
+
stat: stat.label,
|
|
97
|
+
capability: consumer.capability,
|
|
98
|
+
attribute: consumer.attribute,
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
cached = out
|
|
104
|
+
return out
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function getStatMappings(): StatMapping[] {
|
|
108
|
+
return deriveStatMappings()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function getStatMappingsForStat(stat: string): StatMapping[] {
|
|
112
|
+
return deriveStatMappings().filter((m) => m.stat === stat)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function getStatMappingsForCapability(capability: string): StatMapping[] {
|
|
116
|
+
return deriveStatMappings().filter((m) => m.capability === capability)
|
|
117
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {UInt64} from '@wharfkit/antelope'
|
|
2
2
|
import type {ResourceCategory} from '../types'
|
|
3
|
-
import {
|
|
3
|
+
import {getRecipe, type Recipe} from '../data/recipes-runtime'
|
|
4
4
|
import {getItem} from '../data/catalog'
|
|
5
5
|
import {getStatDefinitions} from './stats'
|
|
6
6
|
import {deriveResourceStats} from './stratum'
|
|
@@ -58,11 +58,8 @@ function keyForStatSlot(
|
|
|
58
58
|
function keyForRecipeInputStat(recipe: Recipe, inputIndex: number, statIndex: number): string {
|
|
59
59
|
const input = recipe.inputs[inputIndex]
|
|
60
60
|
if (!input) return ''
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return defs[statIndex]?.key ?? ''
|
|
64
|
-
}
|
|
65
|
-
// itemId-typed input — its stats follow that item's own statSlots layout.
|
|
61
|
+
// Every input names an item by id; its stats follow that item's own layout
|
|
62
|
+
// (resource stat definitions for resources, statSlots for crafted items).
|
|
66
63
|
const innerKeys = getItemStatKeys(input.itemId)
|
|
67
64
|
return innerKeys[statIndex] ?? ''
|
|
68
65
|
}
|
|
@@ -117,10 +114,11 @@ export function computeComponentStats(
|
|
|
117
114
|
const src = slot.sources[0]
|
|
118
115
|
const key = keyForStatSlot(recipe, slot)
|
|
119
116
|
const input = src ? recipe.inputs[src.inputIndex] : undefined
|
|
120
|
-
|
|
117
|
+
const inputItem = input ? getItem(input.itemId) : undefined
|
|
118
|
+
if (!inputItem || inputItem.type !== 'resource' || !inputItem.category) {
|
|
121
119
|
return {key, value: Math.max(1, Math.min(999, 0))}
|
|
122
120
|
}
|
|
123
|
-
const matching = categoryStacks.find((cs) => cs.category ===
|
|
121
|
+
const matching = categoryStacks.find((cs) => cs.category === inputItem.category)
|
|
124
122
|
const value = matching ? blendStacks(matching.stacks, key) : 0
|
|
125
123
|
return {key, value: Math.max(1, Math.min(999, value))}
|
|
126
124
|
})
|
|
@@ -147,7 +145,7 @@ export function computeEntityStats(
|
|
|
147
145
|
const key = keyForStatSlot(recipe, slot)
|
|
148
146
|
if (!src) return {key, value: 1}
|
|
149
147
|
const input = recipe.inputs[src.inputIndex]
|
|
150
|
-
if (!input
|
|
148
|
+
if (!input) {
|
|
151
149
|
return {key, value: 1}
|
|
152
150
|
}
|
|
153
151
|
const blended = blendedByComponent[input.itemId] ?? {}
|
|
@@ -185,12 +183,7 @@ export function computeInputMass(itemId: number): number {
|
|
|
185
183
|
|
|
186
184
|
let total = 0
|
|
187
185
|
for (const input of recipe.inputs) {
|
|
188
|
-
|
|
189
|
-
total += getItem(input.itemId).mass * input.quantity
|
|
190
|
-
} else {
|
|
191
|
-
const item = findItemByCategoryAndTier(input.category, input.tier)
|
|
192
|
-
total += item.mass * input.quantity
|
|
193
|
-
}
|
|
186
|
+
total += getItem(input.itemId).mass * input.quantity
|
|
194
187
|
}
|
|
195
188
|
return total
|
|
196
189
|
}
|
|
@@ -305,10 +298,13 @@ export function computeCraftedOutputStats(
|
|
|
305
298
|
const key = keyForRecipeInputStat(recipe, src.inputIndex, src.statIndex)
|
|
306
299
|
const input = recipe.inputs[src.inputIndex]
|
|
307
300
|
let value = 0
|
|
308
|
-
if (input
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
301
|
+
if (input) {
|
|
302
|
+
const inputItem = getItem(input.itemId)
|
|
303
|
+
if (inputItem.type === 'resource' && inputItem.category) {
|
|
304
|
+
value = blendStacks(decodedByCategory[inputItem.category] ?? [], key)
|
|
305
|
+
} else {
|
|
306
|
+
value = blendedByItem[input.itemId]?.[key] ?? 0
|
|
307
|
+
}
|
|
312
308
|
}
|
|
313
309
|
out.push(Math.max(1, Math.min(999, value)))
|
|
314
310
|
} else {
|
|
@@ -319,10 +315,13 @@ export function computeCraftedOutputStats(
|
|
|
319
315
|
const input = recipe.inputs[src.inputIndex]
|
|
320
316
|
const weight = recipe.blendWeights[src.inputIndex] ?? 1
|
|
321
317
|
let value = 0
|
|
322
|
-
if (input
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
318
|
+
if (input) {
|
|
319
|
+
const inputItem = getItem(input.itemId)
|
|
320
|
+
if (inputItem.type === 'resource' && inputItem.category) {
|
|
321
|
+
value = blendStacks(decodedByCategory[inputItem.category] ?? [], key)
|
|
322
|
+
} else {
|
|
323
|
+
value = blendedByItem[input.itemId]?.[key] ?? 0
|
|
324
|
+
}
|
|
326
325
|
}
|
|
327
326
|
weightedSum += value * weight
|
|
328
327
|
totalWeight += weight
|
|
@@ -340,7 +339,7 @@ export function computeCraftedOutputStats(
|
|
|
340
339
|
* returns a UInt64 whose bit-packed form matches what the contract writes
|
|
341
340
|
* to cargo_item.stats on gather.
|
|
342
341
|
*
|
|
343
|
-
* Use this whenever off-chain code simulates a gather (
|
|
342
|
+
* Use this whenever off-chain code simulates a gather (webapp, player
|
|
344
343
|
* scanners that project cargo outcomes) and needs a value that matches
|
|
345
344
|
* what on-chain cargo would carry.
|
|
346
345
|
*/
|