@shipload/sdk 2.0.0-rc12 → 2.0.0-rc14

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.
@@ -22,27 +22,27 @@ export interface CategoryStacks {
22
22
  }
23
23
 
24
24
  export function encodeStats(values: number[]): bigint {
25
- let seed = 0n
25
+ let stats = 0n
26
26
  for (let i = 0; i < values.length && i < 6; i++) {
27
- seed |= BigInt(values[i] & 0x3ff) << BigInt(i * 10)
27
+ stats |= BigInt(values[i] & 0x3ff) << BigInt(i * 10)
28
28
  }
29
- return seed
29
+ return stats
30
30
  }
31
31
 
32
- export function decodeStat(seed: bigint, index: number): number {
33
- return Number((seed >> BigInt(index * 10)) & 0x3ffn)
32
+ export function decodeStat(stats: bigint, index: number): number {
33
+ return Number((stats >> BigInt(index * 10)) & 0x3ffn)
34
34
  }
35
35
 
36
- export function decodeStats(seed: bigint, count: number): number[] {
37
- const stats: number[] = []
36
+ export function decodeStats(stats: bigint, count: number): number[] {
37
+ const result: number[] = []
38
38
  for (let i = 0; i < count; i++) {
39
- stats.push(decodeStat(seed, i))
39
+ result.push(decodeStat(stats, i))
40
40
  }
41
- return stats
41
+ return result
42
42
  }
43
43
 
44
- function mapStatsToKeys(seed: bigint, statDefs: {key: string}[]): Record<string, number> {
45
- const values = decodeStats(seed, statDefs.length)
44
+ function mapStatsToKeys(stats: bigint, statDefs: {key: string}[]): Record<string, number> {
45
+ const values = decodeStats(stats, statDefs.length)
46
46
  const result: Record<string, number> = {}
47
47
  for (let i = 0; i < statDefs.length; i++) {
48
48
  result[statDefs[i].key] = values[i]
@@ -50,15 +50,15 @@ function mapStatsToKeys(seed: bigint, statDefs: {key: string}[]): Record<string,
50
50
  return result
51
51
  }
52
52
 
53
- export function decodeCraftedItemStats(itemId: number, seed: bigint): Record<string, number> {
53
+ export function decodeCraftedItemStats(itemId: number, stats: bigint): Record<string, number> {
54
54
  const comp = getComponentById(itemId)
55
- if (comp) return mapStatsToKeys(seed, comp.stats)
55
+ if (comp) return mapStatsToKeys(stats, comp.stats)
56
56
 
57
57
  const entityRecipe = entityRecipes.find((r) => r.packedItemId === itemId)
58
- if (entityRecipe) return mapStatsToKeys(seed, entityRecipe.stats)
58
+ if (entityRecipe) return mapStatsToKeys(stats, entityRecipe.stats)
59
59
 
60
60
  const moduleRecipe = moduleRecipes.find((r) => r.itemId === itemId)
61
- if (moduleRecipe) return mapStatsToKeys(seed, moduleRecipe.stats)
61
+ if (moduleRecipe) return mapStatsToKeys(stats, moduleRecipe.stats)
62
62
 
63
63
  return {}
64
64
  }
@@ -126,12 +126,12 @@ export function computeEntityStats(
126
126
  })
127
127
  }
128
128
 
129
- function decodeStackStats(itemId: number, seed: UInt64): Record<string, number> {
129
+ function decodeStackStats(itemId: number, stats: UInt64): Record<string, number> {
130
130
  if (itemId >= 10000) {
131
- return decodeCraftedItemStats(itemId, BigInt(seed.toString()))
131
+ return decodeCraftedItemStats(itemId, BigInt(stats.toString()))
132
132
  }
133
- const raw = deriveResourceStats(BigInt(seed.toString()))
134
- return {stat1: raw.stat1, stat2: raw.stat2, stat3: raw.stat3}
133
+ const s = BigInt(stats.toString())
134
+ return {stat1: decodeStat(s, 0), stat2: decodeStat(s, 1), stat3: decodeStat(s, 2)}
135
135
  }
136
136
 
137
137
  export const categoryItemMass: Record<string, number> = {
@@ -187,11 +187,11 @@ export function blendCrossGroup(sources: {value: number; weight: number}[]): num
187
187
 
188
188
  export function blendCargoStacks(
189
189
  itemId: number,
190
- stacks: {quantity: number; seed: UInt64}[]
190
+ stacks: {quantity: number; stats: UInt64}[]
191
191
  ): UInt64 {
192
192
  const decoded = stacks.map((s) => ({
193
193
  quantity: s.quantity,
194
- stats: decodeStackStats(itemId, s.seed),
194
+ stats: decodeStackStats(itemId, s.stats),
195
195
  }))
196
196
  const allKeys = Object.keys(decoded[0]?.stats ?? {})
197
197
  const blended = allKeys.map((key) => Math.max(1, Math.min(999, blendStacks(decoded, key))))
@@ -201,23 +201,22 @@ export function blendCargoStacks(
201
201
  export interface RecipeSlotInput {
202
202
  itemId: number
203
203
  category: ResourceCategory | undefined
204
- stacks: {quantity: number; seed: bigint}[]
204
+ stacks: {quantity: number; stats: bigint}[]
205
205
  }
206
206
 
207
207
  function decodeRawStackToCategoryStats(
208
- seed: bigint,
208
+ stats: bigint,
209
209
  category: ResourceCategory
210
210
  ): Record<string, number> {
211
- const raw = deriveResourceStats(seed)
212
211
  const defs = getStatDefinitions(category)
213
212
  const result: Record<string, number> = {}
214
- if (defs[0]) result[defs[0].key] = raw.stat1
215
- if (defs[1]) result[defs[1].key] = raw.stat2
216
- if (defs[2]) result[defs[2].key] = raw.stat3
213
+ if (defs[0]) result[defs[0].key] = decodeStat(stats, 0)
214
+ if (defs[1]) result[defs[1].key] = decodeStat(stats, 1)
215
+ if (defs[2]) result[defs[2].key] = decodeStat(stats, 2)
217
216
  return result
218
217
  }
219
218
 
220
- export function computeCraftedOutputSeed(
219
+ export function computeCraftedOutputStats(
221
220
  outputItemId: number,
222
221
  slotInputs: RecipeSlotInput[]
223
222
  ): UInt64 {
@@ -230,8 +229,8 @@ export function computeCraftedOutputSeed(
230
229
  const stacks: StackInput[] = slot.stacks.map((s) => ({
231
230
  quantity: s.quantity,
232
231
  stats: slotIsComponent
233
- ? decodeCraftedItemStats(slot.itemId, s.seed)
234
- : decodeRawStackToCategoryStats(s.seed, slot.category!),
232
+ ? decodeCraftedItemStats(slot.itemId, s.stats)
233
+ : decodeRawStackToCategoryStats(s.stats, slot.category!),
235
234
  }))
236
235
  categoryStacks.push({category: slot.category, stacks})
237
236
  }
@@ -257,7 +256,7 @@ export function computeCraftedOutputSeed(
257
256
  for (const s of slot.stacks) {
258
257
  list.push({
259
258
  quantity: s.quantity,
260
- stats: decodeCraftedItemStats(slot.itemId, s.seed),
259
+ stats: decodeCraftedItemStats(slot.itemId, s.stats),
261
260
  })
262
261
  }
263
262
  }
@@ -269,5 +268,20 @@ export function computeCraftedOutputSeed(
269
268
  return UInt64.from(encodeStats(ordered))
270
269
  }
271
270
 
272
- throw new Error(`computeCraftedOutputSeed: no recipe found for outputItemId=${outputItemId}`)
271
+ throw new Error(`computeCraftedOutputStats: no recipe found for outputItemId=${outputItemId}`)
272
+ }
273
+
274
+ /**
275
+ * Mirrors the contract's gather-time transform. Takes a deposit's entropy
276
+ * seed (bigint from deriveStratum), derives stats via weibull hashing, and
277
+ * returns a UInt64 whose bit-packed form matches what the contract writes
278
+ * to cargo_item.stats on gather.
279
+ *
280
+ * Use this whenever off-chain code simulates a gather (testmap, player
281
+ * scanners that project cargo outcomes) and needs a value that matches
282
+ * what on-chain cargo would carry.
283
+ */
284
+ export function encodeGatheredCargoStats(depositSeed: bigint): UInt64 {
285
+ const raw = deriveResourceStats(depositSeed)
286
+ return UInt64.from(encodeStats([raw.stat1, raw.stat2, raw.stat3]))
273
287
  }
@@ -89,6 +89,18 @@ export function deriveStratum(
89
89
  return {itemId: selectedItemId, seed: seedBigInt, richness, reserve}
90
90
  }
91
91
 
92
+ /**
93
+ * Derives the three stat values for a raw resource from a deposit's
94
+ * entropy seed (hash-based, weibull-transformed).
95
+ *
96
+ * **Use only on deposit seeds** — the bigint returned by `deriveStratum`
97
+ * or carried on a `MassDeposit`. Do NOT call this on a cargo item's
98
+ * `stats` field; cargo stats are bit-packed and must be read via
99
+ * `decodeStat` (or `decodeStackStats` for category-mapped output).
100
+ *
101
+ * Passing a cargo `stats` value here produces meaningless output
102
+ * (hash of the packed bits, unrelated to the actual stats).
103
+ */
92
104
  export function deriveResourceStats(seed: bigint): ResourceStats {
93
105
  const seedBytes = new Uint8Array(8)
94
106
  for (let i = 0; i < 8; i++) {
@@ -13,6 +13,7 @@ export interface ContainerStateInput {
13
13
  hullmass: number
14
14
  capacity: number
15
15
  cargomass?: number
16
+ cargo?: ServerContract.Types.cargo_item[]
16
17
  schedule?: ServerContract.Types.schedule
17
18
  }
18
19
 
@@ -1,8 +1,86 @@
1
- import {Name, UInt16, UInt32, UInt64} from '@wharfkit/antelope'
1
+ import {Name, UInt16, UInt32, UInt64, UInt8} from '@wharfkit/antelope'
2
2
  import {ServerContract} from '../contracts'
3
- import {Ship, ShipStateInput} from './ship'
4
- import {Warehouse, WarehouseStateInput} from './warehouse'
3
+ import {PackedModuleInput, Ship, ShipStateInput} from './ship'
4
+ import {computeWarehouseCapabilities, Warehouse, WarehouseStateInput} from './warehouse'
5
5
  import {Container, ContainerStateInput} from './container'
6
+ import {
7
+ getEntitySlotLayout,
8
+ getModuleRecipeByItemId,
9
+ ITEM_SHIP_T1_PACKED,
10
+ ITEM_WAREHOUSE_T1_PACKED,
11
+ } from '../data/recipes'
12
+ import {getModuleCapabilityType, MODULE_STORAGE, moduleAccepts} from '../capabilities/modules'
13
+ import {computeShipCapabilities, computeStorageCapabilities} from './ship-deploy'
14
+ import {decodeCraftedItemStats} from '../derivation/crafting'
15
+
16
+ function assignModulesToSlots(
17
+ packedEntityItemId: number,
18
+ modules: PackedModuleInput[],
19
+ entityLabel: string
20
+ ): ServerContract.Types.module_entry[] {
21
+ const slots = getEntitySlotLayout(packedEntityItemId)
22
+ const result: Array<{type: number; installed?: ServerContract.Types.packed_module}> = slots.map(
23
+ (s) => ({type: s.type, installed: undefined})
24
+ )
25
+
26
+ for (const mod of modules) {
27
+ const itemId = Number(UInt16.from(mod.itemId).value.toString())
28
+ const modType = getModuleCapabilityType(itemId)
29
+ const slotIdx = result.findIndex((r) => !r.installed && moduleAccepts(r.type, modType))
30
+ if (slotIdx === -1) {
31
+ const recipe = getModuleRecipeByItemId(itemId)
32
+ const modName = recipe?.name ?? `item ${itemId}`
33
+ throw new Error(
34
+ `No compatible slot for module ${modName} (type ${modType}) on ${entityLabel}`
35
+ )
36
+ }
37
+ result[slotIdx].installed = ServerContract.Types.packed_module.from({
38
+ item_id: UInt16.from(mod.itemId),
39
+ stats: UInt64.from(mod.stats),
40
+ })
41
+ }
42
+
43
+ return result.map((r) =>
44
+ ServerContract.Types.module_entry.from({
45
+ type: UInt8.from(r.type),
46
+ installed: r.installed,
47
+ })
48
+ )
49
+ }
50
+
51
+ function decodePackedInput(m: PackedModuleInput): {itemId: number; stats: bigint} {
52
+ return {
53
+ itemId: Number(UInt16.from(m.itemId).value.toString()),
54
+ stats: BigInt(UInt64.from(m.stats).toString()),
55
+ }
56
+ }
57
+
58
+ function computeStorageBonus(
59
+ decoded: {itemId: number; stats: bigint}[],
60
+ baseCapacity: number
61
+ ): number {
62
+ let totalBonus = 0
63
+ for (const m of decoded) {
64
+ if (getModuleCapabilityType(m.itemId) !== MODULE_STORAGE) continue
65
+ const stats = decodeCraftedItemStats(m.itemId, m.stats)
66
+ const {capacityBonus} = computeStorageCapabilities(stats, baseCapacity)
67
+ totalBonus += capacityBonus
68
+ }
69
+ return totalBonus
70
+ }
71
+
72
+ function deriveShipFromModules(
73
+ modules: PackedModuleInput[],
74
+ baseCapacity: number
75
+ ): {
76
+ capabilities: ReturnType<typeof computeShipCapabilities>
77
+ finalCapacity: number
78
+ } {
79
+ const decoded = modules.map(decodePackedInput)
80
+ const capabilities = computeShipCapabilities(decoded)
81
+ const totalBonus = computeStorageBonus(decoded, baseCapacity)
82
+ return {capabilities, finalCapacity: baseCapacity + totalBonus}
83
+ }
6
84
 
7
85
  export function makeShip(state: ShipStateInput): Ship {
8
86
  const info: Record<string, unknown> = {
@@ -19,14 +97,32 @@ export function makeShip(state: ShipStateInput): Ship {
19
97
  pending_tasks: [],
20
98
  }
21
99
  if (state.hullmass !== undefined) info.hullmass = UInt32.from(state.hullmass)
22
- if (state.capacity !== undefined) info.capacity = UInt32.from(state.capacity)
23
100
  if (state.energy !== undefined) info.energy = UInt16.from(state.energy)
24
- if (state.engines) info.engines = state.engines
25
- if (state.generator) info.generator = state.generator
26
- if (state.loaders) info.loaders = state.loaders
27
101
  if (state.schedule) info.schedule = state.schedule
102
+
103
+ let moduleEntries: ServerContract.Types.module_entry[] = []
104
+ if (state.modules && state.modules.length > 0) {
105
+ moduleEntries = assignModulesToSlots(ITEM_SHIP_T1_PACKED, state.modules, 'Ship T1')
106
+ const {capabilities, finalCapacity} = deriveShipFromModules(
107
+ state.modules,
108
+ state.capacity ?? 0
109
+ )
110
+ if (capabilities.engines) info.engines = capabilities.engines
111
+ if (capabilities.generator) info.generator = capabilities.generator
112
+ if (capabilities.gatherer) info.gatherer = capabilities.gatherer
113
+ if (capabilities.hauler) info.hauler = capabilities.hauler
114
+ if (capabilities.loaders) info.loaders = capabilities.loaders
115
+ if (capabilities.crafter) info.crafter = capabilities.crafter
116
+ if (state.capacity !== undefined) info.capacity = UInt32.from(finalCapacity)
117
+ } else {
118
+ moduleEntries = assignModulesToSlots(ITEM_SHIP_T1_PACKED, [], 'Ship T1')
119
+ if (state.capacity !== undefined) info.capacity = UInt32.from(state.capacity)
120
+ }
121
+
28
122
  const entityInfo = ServerContract.Types.entity_info.from(info)
29
- return new Ship(entityInfo)
123
+ const ship = new Ship(entityInfo)
124
+ ship.setModules(moduleEntries)
125
+ return ship
30
126
  }
31
127
 
32
128
  export function makeWarehouse(state: WarehouseStateInput): Warehouse {
@@ -45,10 +141,29 @@ export function makeWarehouse(state: WarehouseStateInput): Warehouse {
45
141
  pending_tasks: [],
46
142
  }
47
143
  if (state.hullmass !== undefined) info.hullmass = UInt32.from(state.hullmass)
48
- if (state.loaders) info.loaders = state.loaders
49
144
  if (state.schedule) info.schedule = state.schedule
145
+
146
+ let moduleEntries: ServerContract.Types.module_entry[] = []
147
+ if (state.modules && state.modules.length > 0) {
148
+ moduleEntries = assignModulesToSlots(
149
+ ITEM_WAREHOUSE_T1_PACKED,
150
+ state.modules,
151
+ 'Warehouse T1'
152
+ )
153
+ const decoded = state.modules.map(decodePackedInput)
154
+ const capabilities = computeWarehouseCapabilities(decoded)
155
+ if (capabilities.loaders) info.loaders = capabilities.loaders
156
+
157
+ const totalBonus = computeStorageBonus(decoded, state.capacity)
158
+ info.capacity = UInt32.from(state.capacity + totalBonus)
159
+ } else {
160
+ moduleEntries = assignModulesToSlots(ITEM_WAREHOUSE_T1_PACKED, [], 'Warehouse T1')
161
+ }
162
+
50
163
  const entityInfo = ServerContract.Types.entity_info.from(info)
51
- return new Warehouse(entityInfo)
164
+ const warehouse = new Warehouse(entityInfo)
165
+ warehouse.setModules(moduleEntries)
166
+ return warehouse
52
167
  }
53
168
 
54
169
  export function makeContainer(state: ContainerStateInput): Container {
@@ -61,7 +176,7 @@ export function makeContainer(state: ContainerStateInput): Container {
61
176
  hullmass: UInt32.from(state.hullmass),
62
177
  capacity: UInt32.from(state.capacity),
63
178
  cargomass: UInt32.from(state.cargomass || 0),
64
- cargo: [],
179
+ cargo: state.cargo || [],
65
180
  is_idle: !state.schedule,
66
181
  current_task_elapsed: UInt32.from(0),
67
182
  current_task_remaining: UInt32.from(0),
@@ -48,7 +48,7 @@ export function computeGeneratorCapabilities(stats: Record<string, number>): {
48
48
 
49
49
  return {
50
50
  capacity: 300 + Math.floor(res / 6),
51
- recharge: 5 + Math.floor((clr * 15) / 1000),
51
+ recharge: 1 + Math.floor((clr * 3) / 1000),
52
52
  }
53
53
  }
54
54
 
@@ -65,7 +65,7 @@ export function computeGathererCapabilities(stats: Record<string, number>): {
65
65
 
66
66
  return {
67
67
  yield: 200 + str,
68
- drain: Math.max(10, 50 - Math.floor(con / 20)),
68
+ drain: Math.max(250, 1250 - Math.floor((con * 25) / 20)),
69
69
  depth: 200 + Math.floor((tol * 3) / 2),
70
70
  speed: 100 + Math.floor((ref * 4) / 5),
71
71
  }
@@ -160,7 +160,7 @@ export interface ShipCapabilities {
160
160
  }
161
161
 
162
162
  export function computeShipCapabilities(
163
- modules: {itemId: number; seed: bigint}[]
163
+ modules: {itemId: number; stats: bigint}[]
164
164
  ): ShipCapabilities {
165
165
  const ship: ShipCapabilities = {}
166
166
 
@@ -169,7 +169,7 @@ export function computeShipCapabilities(
169
169
  let totalThrust = 0
170
170
  let totalDrain = 0
171
171
  for (const m of engineModules) {
172
- const caps = computeEngineCapabilities(decodeCraftedItemStats(m.itemId, m.seed))
172
+ const caps = computeEngineCapabilities(decodeCraftedItemStats(m.itemId, m.stats))
173
173
  totalThrust += caps.thrust
174
174
  totalDrain += caps.drain
175
175
  }
@@ -183,7 +183,7 @@ export function computeShipCapabilities(
183
183
  let totalCapacity = 0
184
184
  let totalRecharge = 0
185
185
  for (const m of generatorModules) {
186
- const caps = computeGeneratorCapabilities(decodeCraftedItemStats(m.itemId, m.seed))
186
+ const caps = computeGeneratorCapabilities(decodeCraftedItemStats(m.itemId, m.stats))
187
187
  totalCapacity += caps.capacity
188
188
  totalRecharge += caps.recharge
189
189
  }
@@ -199,7 +199,7 @@ export function computeShipCapabilities(
199
199
  let totalDepth = 0
200
200
  let totalSpeed = 0
201
201
  for (const m of gathererModules) {
202
- const caps = computeGathererCapabilities(decodeCraftedItemStats(m.itemId, m.seed))
202
+ const caps = computeGathererCapabilities(decodeCraftedItemStats(m.itemId, m.stats))
203
203
  totalYield += caps.yield
204
204
  totalDrain += caps.drain
205
205
  totalDepth += caps.depth
@@ -214,12 +214,7 @@ export function computeShipCapabilities(
214
214
  let weightedEffNum = 0
215
215
  let totalDrain = 0
216
216
  for (const m of haulerModules) {
217
- const decoded = decodeCraftedItemStats(m.itemId, m.seed)
218
- const caps = computeHaulerCapabilities({
219
- resonance: decoded.capacity,
220
- conductivity: decoded.efficiency,
221
- clarity: decoded.drain,
222
- })
217
+ const caps = computeHaulerCapabilities(decodeCraftedItemStats(m.itemId, m.stats))
223
218
  totalCapacity += caps.capacity
224
219
  weightedEffNum += caps.efficiency * caps.capacity
225
220
  totalDrain += caps.drain
@@ -237,7 +232,7 @@ export function computeShipCapabilities(
237
232
  let totalThrust = 0
238
233
  let totalQuantity = 0
239
234
  for (const m of loaderModules) {
240
- const caps = computeLoaderCapabilities(decodeCraftedItemStats(m.itemId, m.seed))
235
+ const caps = computeLoaderCapabilities(decodeCraftedItemStats(m.itemId, m.stats))
241
236
  totalMass += caps.mass
242
237
  totalThrust += caps.thrust
243
238
  totalQuantity += caps.quantity
@@ -252,7 +247,7 @@ export function computeShipCapabilities(
252
247
  let totalSpeed = 0
253
248
  let totalDrain = 0
254
249
  for (const m of crafterModules) {
255
- const caps = computeManufacturingCapabilities(decodeCraftedItemStats(m.itemId, m.seed))
250
+ const caps = computeManufacturingCapabilities(decodeCraftedItemStats(m.itemId, m.stats))
256
251
  totalSpeed += caps.speed
257
252
  totalDrain += caps.drain
258
253
  }
@@ -1,4 +1,4 @@
1
- import {UInt16, UInt32, UInt64, UInt64Type} from '@wharfkit/antelope'
1
+ import {UInt16, UInt16Type, UInt32, UInt64, UInt64Type} from '@wharfkit/antelope'
2
2
  import {ServerContract} from '../contracts'
3
3
  import {Coordinates, CoordinatesType} from '../types'
4
4
  import {
@@ -23,6 +23,11 @@ import {
23
23
  } from '../capabilities/movement'
24
24
  import * as schedule from '../scheduling/schedule'
25
25
 
26
+ export interface PackedModuleInput {
27
+ itemId: UInt16Type
28
+ stats: UInt64Type
29
+ }
30
+
26
31
  export interface ShipStateInput {
27
32
  id: UInt64Type
28
33
  owner: string
@@ -31,9 +36,7 @@ export interface ShipStateInput {
31
36
  hullmass?: number
32
37
  capacity?: number
33
38
  energy?: number
34
- engines?: ServerContract.Types.movement_stats
35
- generator?: ServerContract.Types.energy_stats
36
- loaders?: ServerContract.Types.loader_stats
39
+ modules?: PackedModuleInput[]
37
40
  schedule?: ServerContract.Types.schedule
38
41
  cargo?: ServerContract.Types.cargo_item[]
39
42
  }
@@ -47,11 +50,20 @@ type MovementEntity = {
47
50
  export class Ship extends ServerContract.Types.entity_info {
48
51
  private _sched?: ScheduleAccessor
49
52
  private _inv?: InventoryAccessor
53
+ private _modules: ServerContract.Types.module_entry[] = []
50
54
 
51
55
  get name(): string {
52
56
  return this.entity_name
53
57
  }
54
58
 
59
+ get modules(): ServerContract.Types.module_entry[] {
60
+ return this._modules
61
+ }
62
+
63
+ setModules(modules: ServerContract.Types.module_entry[]): void {
64
+ this._modules = modules
65
+ }
66
+
55
67
  get inv(): InventoryAccessor {
56
68
  return (this._inv ??= new InventoryAccessor(this))
57
69
  }
@@ -6,6 +6,10 @@ import {ScheduleAccessor} from '../scheduling/accessor'
6
6
  import {InventoryAccessor} from './inventory-accessor'
7
7
  import {EntityInventory} from './entity-inventory'
8
8
  import * as schedule from '../scheduling/schedule'
9
+ import type {PackedModuleInput} from './ship'
10
+ import {decodeCraftedItemStats} from '../derivation/crafting'
11
+ import {getModuleCapabilityType, MODULE_LOADER} from '../capabilities/modules'
12
+ import {computeLoaderCapabilities} from './ship-deploy'
9
13
 
10
14
  export interface WarehouseStateInput {
11
15
  id: UInt64Type
@@ -14,7 +18,7 @@ export interface WarehouseStateInput {
14
18
  coordinates: CoordinatesType | {x: number; y: number; z?: number}
15
19
  hullmass?: number
16
20
  capacity: number
17
- loaders?: ServerContract.Types.loader_stats
21
+ modules?: PackedModuleInput[]
18
22
  schedule?: ServerContract.Types.schedule
19
23
  cargo?: ServerContract.Types.cargo_item[]
20
24
  }
@@ -22,11 +26,20 @@ export interface WarehouseStateInput {
22
26
  export class Warehouse extends ServerContract.Types.entity_info {
23
27
  private _sched?: ScheduleAccessor
24
28
  private _inv?: InventoryAccessor
29
+ private _modules: ServerContract.Types.module_entry[] = []
25
30
 
26
31
  get name(): string {
27
32
  return this.entity_name
28
33
  }
29
34
 
35
+ get modules(): ServerContract.Types.module_entry[] {
36
+ return this._modules
37
+ }
38
+
39
+ setModules(modules: ServerContract.Types.module_entry[]): void {
40
+ this._modules = modules
41
+ }
42
+
30
43
  get inv(): InventoryAccessor {
31
44
  return (this._inv ??= new InventoryAccessor(this))
32
45
  }
@@ -89,3 +102,25 @@ export class Warehouse extends ServerContract.Types.entity_info {
89
102
  return hull.adding(this.totalCargoMass)
90
103
  }
91
104
  }
105
+
106
+ export function computeWarehouseCapabilities(modules: {itemId: number; stats: bigint}[]): {
107
+ loaders?: {mass: number; thrust: number; quantity: number}
108
+ } {
109
+ const warehouse: {loaders?: {mass: number; thrust: number; quantity: number}} = {}
110
+
111
+ const loaderModules = modules.filter((m) => getModuleCapabilityType(m.itemId) === MODULE_LOADER)
112
+ if (loaderModules.length > 0) {
113
+ let totalMass = 0
114
+ let totalThrust = 0
115
+ let totalQuantity = 0
116
+ for (const m of loaderModules) {
117
+ const caps = computeLoaderCapabilities(decodeCraftedItemStats(m.itemId, m.stats))
118
+ totalMass += caps.mass
119
+ totalThrust += caps.thrust
120
+ totalQuantity += caps.quantity
121
+ }
122
+ warehouse.loaders = {mass: totalMass, thrust: totalThrust, quantity: totalQuantity}
123
+ }
124
+
125
+ return warehouse
126
+ }
@@ -6,8 +6,8 @@ import {ServerContract} from './contracts'
6
6
 
7
7
  export {Shipload} from './shipload'
8
8
  export {Ship} from './entities/ship'
9
- export type {ShipStateInput} from './entities/ship'
10
- export {Warehouse} from './entities/warehouse'
9
+ export type {ShipStateInput, PackedModuleInput} from './entities/ship'
10
+ export {Warehouse, computeWarehouseCapabilities} from './entities/warehouse'
11
11
  export type {WarehouseStateInput} from './entities/warehouse'
12
12
  export {Container} from './entities/container'
13
13
  export type {ContainerStateInput} from './entities/container'
@@ -143,7 +143,7 @@ export {
143
143
  projectEntityAt,
144
144
  validateSchedule,
145
145
  } from './scheduling/projection'
146
- export type {Projectable, ProjectedEntity} from './scheduling/projection'
146
+ export type {Projectable, ProjectedEntity, ProjectionOptions} from './scheduling/projection'
147
147
 
148
148
  export * from './types/capabilities'
149
149
  export * from './types/entity'
@@ -152,11 +152,14 @@ export * from './capabilities'
152
152
  export {
153
153
  categoryColors,
154
154
  tierColors,
155
+ tierLabels,
155
156
  categoryIcons,
157
+ categoryIconShapes,
156
158
  componentIcon,
157
159
  moduleIcon,
158
- itemIcons,
160
+ itemAbbreviations,
159
161
  } from './data/colors'
162
+ export type {CategoryIconShape} from './data/colors'
160
163
 
161
164
  export {itemTier, itemOffset, itemCategory, isRelatedItem, isCraftedItem} from './data/tiers'
162
165
  export type {CraftedItemCategory} from './data/tiers'
@@ -226,6 +229,7 @@ export type {
226
229
 
227
230
  export {
228
231
  encodeStats,
232
+ encodeGatheredCargoStats,
229
233
  decodeStat,
230
234
  decodeStats,
231
235
  decodeCraftedItemStats,
@@ -237,7 +241,7 @@ export {
237
241
  blendCrossGroup,
238
242
  categoryItemMass,
239
243
  computeInputMass,
240
- computeCraftedOutputSeed,
244
+ computeCraftedOutputStats,
241
245
  } from './derivation/crafting'
242
246
  export type {StackInput, CategoryStacks, RecipeSlotInput} from './derivation/crafting'
243
247
 
@@ -266,6 +270,19 @@ export type {
266
270
  ResolvedItemType,
267
271
  } from './resolution/resolve-item'
268
272
 
273
+ export {
274
+ describeModule,
275
+ describeModuleForItem,
276
+ describeModuleForSlot,
277
+ renderDescription,
278
+ } from './resolution/describe-module'
279
+ export type {
280
+ TextSpan,
281
+ CapabilityInput,
282
+ ModuleDescription,
283
+ RenderDescriptionOptions,
284
+ } from './resolution/describe-module'
285
+
269
286
  export * as NFT from './nft'
270
287
  export {
271
288
  deserializeAsset,