@shipload/sdk 0.7.1 → 1.0.0-next.0

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.
Files changed (94) hide show
  1. package/lib/shipload.d.ts +2730 -287
  2. package/lib/shipload.js +10862 -2229
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +10434 -2171
  5. package/lib/shipload.m.js.map +1 -1
  6. package/package.json +11 -20
  7. package/src/capabilities/crafting.ts +22 -0
  8. package/src/capabilities/gathering.ts +36 -0
  9. package/src/capabilities/guards.ts +38 -0
  10. package/src/capabilities/hauling.ts +22 -0
  11. package/src/capabilities/index.ts +8 -0
  12. package/src/capabilities/loading.ts +8 -0
  13. package/src/capabilities/modules.ts +86 -0
  14. package/src/capabilities/movement.ts +29 -0
  15. package/src/capabilities/storage.ts +159 -0
  16. package/src/contracts/server.ts +1389 -285
  17. package/src/data/capabilities.ts +408 -0
  18. package/src/data/catalog.ts +135 -0
  19. package/src/data/categories.ts +55 -0
  20. package/src/data/colors.ts +84 -0
  21. package/src/data/entities.json +50 -0
  22. package/src/data/item-ids.ts +75 -0
  23. package/src/data/items.json +252 -0
  24. package/src/data/locations.ts +53 -0
  25. package/src/data/metadata.ts +208 -0
  26. package/src/data/nebula-adjectives.json +211 -0
  27. package/src/data/nebula-nouns.json +151 -0
  28. package/src/data/recipes-runtime.ts +65 -0
  29. package/src/data/recipes.json +878 -0
  30. package/src/data/syllables.json +1790 -0
  31. package/src/data/tiers.ts +45 -0
  32. package/src/derivation/crafting.ts +350 -0
  33. package/src/derivation/index.ts +32 -0
  34. package/src/derivation/location-size.ts +15 -0
  35. package/src/derivation/resources.ts +112 -0
  36. package/src/derivation/stats.ts +146 -0
  37. package/src/derivation/strata.ts +43 -0
  38. package/src/derivation/stratum.ts +134 -0
  39. package/src/derivation/tiers.ts +54 -0
  40. package/src/entities/cargo-utils.ts +84 -0
  41. package/src/entities/container.ts +108 -0
  42. package/src/entities/entity-inventory.ts +39 -0
  43. package/src/entities/gamestate.ts +152 -0
  44. package/src/entities/inventory-accessor.ts +42 -0
  45. package/src/entities/location.ts +60 -0
  46. package/src/entities/makers.ts +196 -0
  47. package/src/entities/player.ts +15 -0
  48. package/src/entities/ship-deploy.ts +258 -0
  49. package/src/entities/ship.ts +204 -0
  50. package/src/entities/warehouse.ts +119 -0
  51. package/src/errors.ts +100 -9
  52. package/src/format.ts +12 -0
  53. package/src/index-module.ts +317 -7
  54. package/src/managers/actions.ts +250 -0
  55. package/src/managers/base.ts +25 -0
  56. package/src/managers/context.ts +114 -0
  57. package/src/managers/entities.ts +103 -0
  58. package/src/managers/epochs.ts +47 -0
  59. package/src/managers/index.ts +9 -0
  60. package/src/managers/locations.ts +68 -0
  61. package/src/managers/players.ts +13 -0
  62. package/src/nft/description.ts +176 -0
  63. package/src/nft/deserializers.ts +83 -0
  64. package/src/nft/index.ts +2 -0
  65. package/src/resolution/describe-module.ts +166 -0
  66. package/src/resolution/display-name.ts +39 -0
  67. package/src/resolution/resolve-item.ts +358 -0
  68. package/src/scheduling/accessor.ts +82 -0
  69. package/src/{epoch.ts → scheduling/epoch.ts} +1 -1
  70. package/src/scheduling/projection.ts +463 -0
  71. package/src/scheduling/schedule.ts +179 -0
  72. package/src/shipload.ts +47 -160
  73. package/src/subscriptions/connection.ts +154 -0
  74. package/src/subscriptions/debug.ts +17 -0
  75. package/src/subscriptions/index.ts +5 -0
  76. package/src/subscriptions/manager.ts +240 -0
  77. package/src/subscriptions/mappers.ts +28 -0
  78. package/src/subscriptions/types.ts +143 -0
  79. package/src/travel/travel.ts +500 -0
  80. package/src/types/capabilities.ts +76 -0
  81. package/src/types/entity-traits.ts +69 -0
  82. package/src/types/entity.ts +39 -0
  83. package/src/types/index.ts +3 -0
  84. package/src/types.ts +140 -35
  85. package/src/{hash.ts → utils/hash.ts} +2 -2
  86. package/src/utils/system.ts +168 -0
  87. package/src/goods.ts +0 -124
  88. package/src/market.ts +0 -214
  89. package/src/rolls.ts +0 -8
  90. package/src/ship.ts +0 -36
  91. package/src/state.ts +0 -0
  92. package/src/syllables.ts +0 -1184
  93. package/src/system.ts +0 -37
  94. package/src/travel.ts +0 -259
@@ -0,0 +1,60 @@
1
+ import {Checksum256, type Checksum256Type, type UInt16Type, type UInt64} from '@wharfkit/antelope'
2
+ import {Coordinates, type CoordinatesType, type Distance, type LocationType} from '../types'
3
+ import {getLocationType, hasSystem, isGatherableLocation} from '../utils/system'
4
+ import {findNearbyPlanets} from '../travel/travel'
5
+
6
+ export class Location {
7
+ readonly coordinates: Coordinates
8
+ private _gameSeed?: Checksum256
9
+ private _hasSystem?: boolean
10
+ private _epoch?: UInt64
11
+
12
+ constructor(coordinates: CoordinatesType) {
13
+ this.coordinates = Coordinates.from(coordinates)
14
+ }
15
+
16
+ static from(coordinates: CoordinatesType): Location {
17
+ return new Location(Coordinates.from(coordinates))
18
+ }
19
+
20
+ hasSystemAt(gameSeed: Checksum256Type): boolean {
21
+ const seed = Checksum256.from(gameSeed)
22
+ if (this._hasSystem === undefined || !this._gameSeed?.equals(seed)) {
23
+ this._gameSeed = seed
24
+ this._hasSystem = hasSystem(seed, this.coordinates)
25
+ }
26
+ return this._hasSystem
27
+ }
28
+
29
+ getLocationTypeAt(gameSeed: Checksum256Type): LocationType {
30
+ return getLocationType(gameSeed, this.coordinates)
31
+ }
32
+
33
+ isGatherableAt(gameSeed: Checksum256Type): boolean {
34
+ return isGatherableLocation(this.getLocationTypeAt(gameSeed))
35
+ }
36
+
37
+ findNearby(gameSeed: Checksum256Type, maxDistance: UInt16Type = 20): Distance[] {
38
+ return findNearbyPlanets(Checksum256.from(gameSeed), this.coordinates, maxDistance)
39
+ }
40
+
41
+ equals(other: CoordinatesType | Location): boolean {
42
+ const otherCoords = other instanceof Location ? other.coordinates : Coordinates.from(other)
43
+ return this.coordinates.equals(otherCoords)
44
+ }
45
+
46
+ get epoch(): UInt64 | undefined {
47
+ return this._epoch
48
+ }
49
+
50
+ clearCache(): void {
51
+ this._epoch = undefined
52
+ }
53
+ }
54
+
55
+ export function toLocation(coords: CoordinatesType | Location): Location {
56
+ if (coords instanceof Location) {
57
+ return coords
58
+ }
59
+ return Location.from(coords)
60
+ }
@@ -0,0 +1,196 @@
1
+ import {Name, UInt16, UInt32, UInt64, UInt8} from '@wharfkit/antelope'
2
+ import {ServerContract} from '../contracts'
3
+ import {type PackedModuleInput, Ship, type ShipStateInput} from './ship'
4
+ import {computeWarehouseCapabilities, Warehouse, type WarehouseStateInput} from './warehouse'
5
+ import {Container, type ContainerStateInput} from './container'
6
+ import {ITEM_SHIP_T1_PACKED, ITEM_WAREHOUSE_T1_PACKED} from '../data/item-ids'
7
+ import {getEntityLayout} from '../data/recipes-runtime'
8
+ import {itemMetadata} from '../data/metadata'
9
+ import {getItem} from '../data/catalog'
10
+ import {
11
+ getModuleCapabilityType,
12
+ MODULE_STORAGE,
13
+ moduleAccepts,
14
+ moduleSlotTypeToCode,
15
+ } from '../capabilities/modules'
16
+ import {computeShipCapabilities, computeStorageCapabilities} from './ship-deploy'
17
+ import {decodeCraftedItemStats} from '../derivation/crafting'
18
+
19
+ function assignModulesToSlots(
20
+ packedEntityItemId: number,
21
+ modules: PackedModuleInput[],
22
+ entityLabel: string
23
+ ): ServerContract.Types.module_entry[] {
24
+ const layout = getEntityLayout(packedEntityItemId)
25
+ const slots = layout?.slots ?? []
26
+ const result: Array<{type: number; installed?: ServerContract.Types.packed_module}> = slots.map(
27
+ (s) => ({type: moduleSlotTypeToCode(s.type), installed: undefined})
28
+ )
29
+
30
+ for (const mod of modules) {
31
+ const itemId = Number(UInt16.from(mod.itemId).value.toString())
32
+ const modType = getModuleCapabilityType(itemId)
33
+ const slotIdx = result.findIndex((r) => !r.installed && moduleAccepts(r.type, modType))
34
+ if (slotIdx === -1) {
35
+ let modName: string
36
+ try {
37
+ modName = getItem(itemId).name
38
+ } catch {
39
+ modName = itemMetadata[itemId]?.name ?? `item ${itemId}`
40
+ }
41
+ throw new Error(
42
+ `No compatible slot for module ${modName} (type ${modType}) on ${entityLabel}`
43
+ )
44
+ }
45
+ result[slotIdx].installed = ServerContract.Types.packed_module.from({
46
+ item_id: UInt16.from(mod.itemId),
47
+ stats: UInt64.from(mod.stats),
48
+ })
49
+ }
50
+
51
+ return result.map((r) =>
52
+ ServerContract.Types.module_entry.from({
53
+ type: UInt8.from(r.type),
54
+ installed: r.installed,
55
+ })
56
+ )
57
+ }
58
+
59
+ function decodePackedInput(m: PackedModuleInput): {itemId: number; stats: bigint} {
60
+ return {
61
+ itemId: Number(UInt16.from(m.itemId).value.toString()),
62
+ stats: BigInt(UInt64.from(m.stats).toString()),
63
+ }
64
+ }
65
+
66
+ function computeStorageBonus(
67
+ decoded: {itemId: number; stats: bigint}[],
68
+ baseCapacity: number
69
+ ): number {
70
+ let totalBonus = 0
71
+ for (const m of decoded) {
72
+ if (getModuleCapabilityType(m.itemId) !== MODULE_STORAGE) continue
73
+ const stats = decodeCraftedItemStats(m.itemId, m.stats)
74
+ const {capacityBonus} = computeStorageCapabilities(stats, baseCapacity)
75
+ totalBonus += capacityBonus
76
+ }
77
+ return totalBonus
78
+ }
79
+
80
+ function deriveShipFromModules(
81
+ modules: PackedModuleInput[],
82
+ baseCapacity: number
83
+ ): {
84
+ capabilities: ReturnType<typeof computeShipCapabilities>
85
+ finalCapacity: number
86
+ } {
87
+ const decoded = modules.map(decodePackedInput)
88
+ const capabilities = computeShipCapabilities(decoded)
89
+ const totalBonus = computeStorageBonus(decoded, baseCapacity)
90
+ return {capabilities, finalCapacity: baseCapacity + totalBonus}
91
+ }
92
+
93
+ export function makeShip(state: ShipStateInput): Ship {
94
+ const info: Record<string, unknown> = {
95
+ type: Name.from('ship'),
96
+ id: UInt64.from(state.id),
97
+ owner: Name.from(state.owner),
98
+ entity_name: state.name,
99
+ coordinates: ServerContract.Types.coordinates.from(state.coordinates),
100
+ cargomass: UInt32.from(0),
101
+ cargo: state.cargo || [],
102
+ is_idle: !state.schedule,
103
+ current_task_elapsed: UInt32.from(0),
104
+ current_task_remaining: UInt32.from(0),
105
+ pending_tasks: [],
106
+ }
107
+ if (state.hullmass !== undefined) info.hullmass = UInt32.from(state.hullmass)
108
+ if (state.energy !== undefined) info.energy = UInt16.from(state.energy)
109
+ if (state.schedule) info.schedule = state.schedule
110
+
111
+ let moduleEntries: ServerContract.Types.module_entry[] = []
112
+ if (state.modules && state.modules.length > 0) {
113
+ moduleEntries = assignModulesToSlots(ITEM_SHIP_T1_PACKED, state.modules, 'Ship T1')
114
+ const {capabilities, finalCapacity} = deriveShipFromModules(
115
+ state.modules,
116
+ state.capacity ?? 0
117
+ )
118
+ if (capabilities.engines) info.engines = capabilities.engines
119
+ if (capabilities.generator) info.generator = capabilities.generator
120
+ if (capabilities.gatherer) info.gatherer = capabilities.gatherer
121
+ if (capabilities.hauler) info.hauler = capabilities.hauler
122
+ if (capabilities.loaders) info.loaders = capabilities.loaders
123
+ if (capabilities.crafter) info.crafter = capabilities.crafter
124
+ if (state.capacity !== undefined) info.capacity = UInt32.from(finalCapacity)
125
+ } else {
126
+ moduleEntries = assignModulesToSlots(ITEM_SHIP_T1_PACKED, [], 'Ship T1')
127
+ if (state.capacity !== undefined) info.capacity = UInt32.from(state.capacity)
128
+ }
129
+
130
+ info.modules = moduleEntries
131
+
132
+ const entityInfo = ServerContract.Types.entity_info.from(info)
133
+ return new Ship(entityInfo)
134
+ }
135
+
136
+ export function makeWarehouse(state: WarehouseStateInput): Warehouse {
137
+ const info: Record<string, unknown> = {
138
+ type: Name.from('warehouse'),
139
+ id: UInt64.from(state.id),
140
+ owner: Name.from(state.owner),
141
+ entity_name: state.name,
142
+ coordinates: ServerContract.Types.coordinates.from(state.coordinates),
143
+ capacity: UInt32.from(state.capacity),
144
+ cargomass: UInt32.from(0),
145
+ cargo: state.cargo || [],
146
+ is_idle: !state.schedule,
147
+ current_task_elapsed: UInt32.from(0),
148
+ current_task_remaining: UInt32.from(0),
149
+ pending_tasks: [],
150
+ }
151
+ if (state.hullmass !== undefined) info.hullmass = UInt32.from(state.hullmass)
152
+ if (state.schedule) info.schedule = state.schedule
153
+
154
+ let moduleEntries: ServerContract.Types.module_entry[] = []
155
+ if (state.modules && state.modules.length > 0) {
156
+ moduleEntries = assignModulesToSlots(
157
+ ITEM_WAREHOUSE_T1_PACKED,
158
+ state.modules,
159
+ 'Warehouse T1'
160
+ )
161
+ const decoded = state.modules.map(decodePackedInput)
162
+ const capabilities = computeWarehouseCapabilities(decoded)
163
+ if (capabilities.loaders) info.loaders = capabilities.loaders
164
+
165
+ const totalBonus = computeStorageBonus(decoded, state.capacity)
166
+ info.capacity = UInt32.from(state.capacity + totalBonus)
167
+ } else {
168
+ moduleEntries = assignModulesToSlots(ITEM_WAREHOUSE_T1_PACKED, [], 'Warehouse T1')
169
+ }
170
+
171
+ info.modules = moduleEntries
172
+
173
+ const entityInfo = ServerContract.Types.entity_info.from(info)
174
+ return new Warehouse(entityInfo)
175
+ }
176
+
177
+ export function makeContainer(state: ContainerStateInput): Container {
178
+ const entityInfo = ServerContract.Types.entity_info.from({
179
+ type: Name.from('container'),
180
+ id: UInt64.from(state.id),
181
+ owner: Name.from(state.owner),
182
+ entity_name: state.name,
183
+ coordinates: ServerContract.Types.coordinates.from(state.coordinates),
184
+ hullmass: UInt32.from(state.hullmass),
185
+ capacity: UInt32.from(state.capacity),
186
+ cargomass: UInt32.from(state.cargomass || 0),
187
+ cargo: state.cargo || [],
188
+ modules: [],
189
+ is_idle: !state.schedule,
190
+ current_task_elapsed: UInt32.from(0),
191
+ current_task_remaining: UInt32.from(0),
192
+ pending_tasks: [],
193
+ schedule: state.schedule,
194
+ })
195
+ return new Container(entityInfo)
196
+ }
@@ -0,0 +1,15 @@
1
+ import {Name, type NameType} from '@wharfkit/antelope'
2
+ import {ServerContract} from '../contracts'
3
+
4
+ export interface PlayerStateInput {
5
+ owner: NameType
6
+ }
7
+
8
+ export class Player extends ServerContract.Types.player_row {
9
+ static fromState(state: PlayerStateInput): Player {
10
+ const playerRow = ServerContract.Types.player_row.from({
11
+ owner: Name.from(state.owner),
12
+ })
13
+ return new Player(playerRow)
14
+ }
15
+ }
@@ -0,0 +1,258 @@
1
+ import {decodeCraftedItemStats} from '../derivation/crafting'
2
+ import {
3
+ getModuleCapabilityType,
4
+ MODULE_CRAFTER,
5
+ MODULE_ENGINE,
6
+ MODULE_GATHERER,
7
+ MODULE_GENERATOR,
8
+ MODULE_HAULER,
9
+ MODULE_LOADER,
10
+ } from '../capabilities/modules'
11
+
12
+ export function computeShipHullCapabilities(stats: Record<string, number>): {
13
+ hullmass: number
14
+ capacity: number
15
+ } {
16
+ const density = stats.density ?? 500
17
+ const strength = stats.strength ?? 500
18
+ const hardness = stats.hardness ?? 500
19
+ const saturation = stats.saturation ?? 500
20
+
21
+ const hullmass = 25000 + 75 * density
22
+ const statSum = strength + hardness + saturation
23
+ const exponent = statSum / 2997.0
24
+ const capacity = Math.floor(1000000 * 10 ** exponent)
25
+
26
+ return {hullmass, capacity}
27
+ }
28
+
29
+ export function computeEngineCapabilities(stats: Record<string, number>): {
30
+ thrust: number
31
+ drain: number
32
+ } {
33
+ const vol = stats.volatility ?? 500
34
+ const thm = stats.thermal ?? 500
35
+
36
+ return {
37
+ thrust: 400 + Math.floor((vol * 3) / 4),
38
+ drain: Math.max(30, 50 - Math.floor(thm / 70)),
39
+ }
40
+ }
41
+
42
+ export function computeGeneratorCapabilities(stats: Record<string, number>): {
43
+ capacity: number
44
+ recharge: number
45
+ } {
46
+ const res = stats.resonance ?? 500
47
+ const ref = stats.reflectivity ?? 500
48
+
49
+ return {
50
+ capacity: 300 + Math.floor(res / 6),
51
+ recharge: 1 + Math.floor((ref * 3) / 1000),
52
+ }
53
+ }
54
+
55
+ export function computeGathererCapabilities(stats: Record<string, number>): {
56
+ yield: number
57
+ drain: number
58
+ depth: number
59
+ speed: number
60
+ } {
61
+ const str = stats.strength ?? 500
62
+ const con = stats.conductivity ?? 500
63
+ const ref = stats.reflectivity ?? 500
64
+ const tol = stats.tolerance ?? 500
65
+
66
+ return {
67
+ yield: 200 + str,
68
+ drain: Math.max(250, 1250 - Math.floor((con * 25) / 20)),
69
+ depth: 200 + Math.floor((tol * 3) / 2),
70
+ speed: 100 + Math.floor((ref * 4) / 5),
71
+ }
72
+ }
73
+
74
+ export function computeLoaderCapabilities(stats: Record<string, number>): {
75
+ mass: number
76
+ thrust: number
77
+ quantity: number
78
+ } {
79
+ const hrd = stats.hardness ?? 500
80
+ const pla = stats.plasticity ?? 500
81
+
82
+ return {
83
+ mass: Math.max(200, 2000 - Math.floor(hrd * 2)),
84
+ thrust: 1 + Math.floor(pla / 500),
85
+ quantity: 1,
86
+ }
87
+ }
88
+
89
+ export function computeCrafterCapabilities(stats: Record<string, number>): {
90
+ speed: number
91
+ drain: number
92
+ } {
93
+ const rea = stats.reactivity ?? 500
94
+ const com = stats.composition ?? 500
95
+
96
+ return {
97
+ speed: 100 + Math.floor((rea * 4) / 5),
98
+ drain: Math.max(5, 30 - Math.floor(com / 33)),
99
+ }
100
+ }
101
+
102
+ export function computeHaulerCapabilities(stats: Record<string, number>): {
103
+ capacity: number
104
+ efficiency: number
105
+ drain: number
106
+ } {
107
+ const res = stats.resonance ?? 500
108
+ const con = stats.conductivity ?? 500
109
+ const ref = stats.reflectivity ?? 500
110
+
111
+ return {
112
+ capacity: Math.max(1, 1 + Math.floor(res / 400)),
113
+ efficiency: 2000 + con * 6,
114
+ drain: Math.max(3, 15 - Math.floor(ref / 80)),
115
+ }
116
+ }
117
+
118
+ export function computeStorageCapabilities(
119
+ stats: Record<string, number>,
120
+ baseCapacity: number
121
+ ): {
122
+ capacityBonus: number
123
+ } {
124
+ const strength = stats.strength ?? 500
125
+ const hardness = stats.hardness ?? 500
126
+ const saturation = stats.saturation ?? 500
127
+
128
+ const statSum = strength + hardness + saturation
129
+ const capacityBonus = Math.floor(
130
+ (baseCapacity * (10 + Math.floor((statSum * 10) / 2997))) / 100
131
+ )
132
+
133
+ return {capacityBonus}
134
+ }
135
+
136
+ export function computeWarehouseHullCapabilities(stats: Record<string, number>): {
137
+ hullmass: number
138
+ capacity: number
139
+ } {
140
+ const density = stats.density ?? 500
141
+ const strength = stats.strength ?? 500
142
+ const hardness = stats.hardness ?? 500
143
+ const saturation = stats.saturation ?? 500
144
+
145
+ const hullmass = 25000 + 75 * density
146
+ const statSum = strength + hardness + saturation
147
+ const exponent = statSum / 2997.0
148
+ const capacity = Math.floor(20000000 * 10 ** exponent)
149
+
150
+ return {hullmass, capacity}
151
+ }
152
+
153
+ export interface ShipCapabilities {
154
+ engines?: {thrust: number; drain: number}
155
+ generator?: {capacity: number; recharge: number}
156
+ gatherer?: {yield: number; drain: number; depth: number; speed: number}
157
+ hauler?: {capacity: number; efficiency: number; drain: number}
158
+ loaders?: {mass: number; thrust: number; quantity: number}
159
+ crafter?: {speed: number; drain: number}
160
+ }
161
+
162
+ export function computeShipCapabilities(
163
+ modules: {itemId: number; stats: bigint}[]
164
+ ): ShipCapabilities {
165
+ const ship: ShipCapabilities = {}
166
+
167
+ const engineModules = modules.filter((m) => getModuleCapabilityType(m.itemId) === MODULE_ENGINE)
168
+ if (engineModules.length > 0) {
169
+ let totalThrust = 0
170
+ let totalDrain = 0
171
+ for (const m of engineModules) {
172
+ const caps = computeEngineCapabilities(decodeCraftedItemStats(m.itemId, m.stats))
173
+ totalThrust += caps.thrust
174
+ totalDrain += caps.drain
175
+ }
176
+ ship.engines = {thrust: totalThrust, drain: totalDrain}
177
+ }
178
+
179
+ const generatorModules = modules.filter(
180
+ (m) => getModuleCapabilityType(m.itemId) === MODULE_GENERATOR
181
+ )
182
+ if (generatorModules.length > 0) {
183
+ let totalCapacity = 0
184
+ let totalRecharge = 0
185
+ for (const m of generatorModules) {
186
+ const caps = computeGeneratorCapabilities(decodeCraftedItemStats(m.itemId, m.stats))
187
+ totalCapacity += caps.capacity
188
+ totalRecharge += caps.recharge
189
+ }
190
+ ship.generator = {capacity: totalCapacity, recharge: totalRecharge}
191
+ }
192
+
193
+ const gathererModules = modules.filter(
194
+ (m) => getModuleCapabilityType(m.itemId) === MODULE_GATHERER
195
+ )
196
+ if (gathererModules.length > 0) {
197
+ let totalYield = 0
198
+ let totalDrain = 0
199
+ let totalDepth = 0
200
+ let totalSpeed = 0
201
+ for (const m of gathererModules) {
202
+ const caps = computeGathererCapabilities(decodeCraftedItemStats(m.itemId, m.stats))
203
+ totalYield += caps.yield
204
+ totalDrain += caps.drain
205
+ totalDepth += caps.depth
206
+ totalSpeed += caps.speed
207
+ }
208
+ ship.gatherer = {yield: totalYield, drain: totalDrain, depth: totalDepth, speed: totalSpeed}
209
+ }
210
+
211
+ const haulerModules = modules.filter((m) => getModuleCapabilityType(m.itemId) === MODULE_HAULER)
212
+ if (haulerModules.length > 0) {
213
+ let totalCapacity = 0
214
+ let weightedEffNum = 0
215
+ let totalDrain = 0
216
+ for (const m of haulerModules) {
217
+ const caps = computeHaulerCapabilities(decodeCraftedItemStats(m.itemId, m.stats))
218
+ totalCapacity += caps.capacity
219
+ weightedEffNum += caps.efficiency * caps.capacity
220
+ totalDrain += caps.drain
221
+ }
222
+ ship.hauler = {
223
+ capacity: totalCapacity,
224
+ efficiency: totalCapacity > 0 ? Math.floor(weightedEffNum / totalCapacity) : 0,
225
+ drain: totalDrain,
226
+ }
227
+ }
228
+
229
+ const loaderModules = modules.filter((m) => getModuleCapabilityType(m.itemId) === MODULE_LOADER)
230
+ if (loaderModules.length > 0) {
231
+ let totalMass = 0
232
+ let totalThrust = 0
233
+ let totalQuantity = 0
234
+ for (const m of loaderModules) {
235
+ const caps = computeLoaderCapabilities(decodeCraftedItemStats(m.itemId, m.stats))
236
+ totalMass += caps.mass
237
+ totalThrust += caps.thrust
238
+ totalQuantity += caps.quantity
239
+ }
240
+ ship.loaders = {mass: totalMass, thrust: totalThrust, quantity: totalQuantity}
241
+ }
242
+
243
+ const crafterModules = modules.filter(
244
+ (m) => getModuleCapabilityType(m.itemId) === MODULE_CRAFTER
245
+ )
246
+ if (crafterModules.length > 0) {
247
+ let totalSpeed = 0
248
+ let totalDrain = 0
249
+ for (const m of crafterModules) {
250
+ const caps = computeCrafterCapabilities(decodeCraftedItemStats(m.itemId, m.stats))
251
+ totalSpeed += caps.speed
252
+ totalDrain += caps.drain
253
+ }
254
+ ship.crafter = {speed: totalSpeed, drain: totalDrain}
255
+ }
256
+
257
+ return ship
258
+ }