@shipload/sdk 1.0.0-next.37 → 1.0.0-next.39

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.
@@ -2,6 +2,7 @@ import {Serializer} from '@wharfkit/antelope'
2
2
  import {getItem} from '../data/catalog'
3
3
  import {
4
4
  getModuleCapabilityType,
5
+ MODULE_BATTERY,
5
6
  MODULE_CRAFTER,
6
7
  MODULE_ENGINE,
7
8
  MODULE_GATHERER,
@@ -26,6 +27,8 @@ import {
26
27
  computeGathererYield,
27
28
  computeGeneratorCap,
28
29
  computeGeneratorRech,
30
+ computeCargoBayCapacity,
31
+ computeBatteryBankCapacity,
29
32
  computeHaulerCapacity,
30
33
  computeHaulerDrain,
31
34
  computeHaulerEfficiency,
@@ -224,11 +227,11 @@ export function buildModuleImmutable(
224
227
  }
225
228
  case MODULE_CRAFTER: {
226
229
  const rea = decodeStat(stats, 0)
227
- const fin = decodeStat(stats, 1)
230
+ const con = decodeStat(stats, 1)
228
231
  base.push({first: 'reactivity', second: ['uint16', rea]})
229
- base.push({first: 'fineness', second: ['uint16', fin]})
232
+ base.push({first: 'conductivity', second: ['uint16', con]})
230
233
  base.push({first: 'speed', second: ['uint16', computeCrafterSpeed(rea)]})
231
- base.push({first: 'drain', second: ['uint16', computeCrafterDrain(fin)]})
234
+ base.push({first: 'drain', second: ['uint16', computeCrafterDrain(con)]})
232
235
  break
233
236
  }
234
237
  case MODULE_STORAGE: {
@@ -236,14 +239,28 @@ export function buildModuleImmutable(
236
239
  const den = decodeStat(stats, 1)
237
240
  const hrd = decodeStat(stats, 2)
238
241
  const com = decodeStat(stats, 3)
239
- const sum = str + den + hrd + com
240
242
  base.push({first: 'strength', second: ['uint16', str]})
241
243
  base.push({first: 'density', second: ['uint16', den]})
242
244
  base.push({first: 'hardness', second: ['uint16', hrd]})
243
245
  base.push({first: 'cohesion', second: ['uint16', com]})
244
246
  base.push({
245
- first: 'capacity_bonus_pct',
246
- second: ['uint16', 10 + Math.floor((sum * 10) / 2997)],
247
+ first: 'capacity',
248
+ second: ['uint32', computeCargoBayCapacity(str, den, hrd, com)],
249
+ })
250
+ break
251
+ }
252
+ case MODULE_BATTERY: {
253
+ const vol = decodeStat(stats, 0)
254
+ const thm = decodeStat(stats, 1)
255
+ const pla = decodeStat(stats, 2)
256
+ const ins = decodeStat(stats, 3)
257
+ base.push({first: 'volatility', second: ['uint16', vol]})
258
+ base.push({first: 'thermal', second: ['uint16', thm]})
259
+ base.push({first: 'plasticity', second: ['uint16', pla]})
260
+ base.push({first: 'insulation', second: ['uint16', ins]})
261
+ base.push({
262
+ first: 'capacity',
263
+ second: ['uint32', computeBatteryBankCapacity(vol, thm, pla, ins)],
247
264
  })
248
265
  break
249
266
  }
@@ -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,
@@ -19,6 +20,7 @@ import {
19
20
  ITEM_GATHERER_T1,
20
21
  ITEM_GENERATOR_T1,
21
22
  ITEM_HAULER_T1,
23
+ ITEM_BATTERY_T1,
22
24
  ITEM_LOADER_T1,
23
25
  ITEM_SHIP_T1_PACKED,
24
26
  ITEM_STORAGE_T1,
@@ -60,6 +62,16 @@ export function computeBaseCapacityWarehouse(stats: bigint): number {
60
62
 
61
63
  export const computeEngineThrust = (vol: number): number => 400 + idiv(vol * 3, 4)
62
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
+ }
63
75
  export const computeGeneratorCap = (com: number): number => 950 + idiv(com, 2)
64
76
  export const computeGeneratorRech = (fin: number): number => 2 * (1 + idiv(fin * 3, 1000))
65
77
  export const computeGathererYield = (str: number): number => 200 + str
@@ -75,6 +87,18 @@ export const computeHaulerCapacity = (fin: number): number => Math.max(1, 1 + id
75
87
  export const computeHaulerEfficiency = (con: number): number => 2000 + con * 6
76
88
  export const computeHaulerDrain = (com: number): number => Math.max(3, 15 - idiv(com, 80))
77
89
  export const computeWarpRange = (stat: number): number => 100 + stat * 3
90
+ export const computeCargoBayCapacity = (
91
+ strength: number,
92
+ density: number,
93
+ hardness: number,
94
+ cohesion: number
95
+ ): number => 10_000_000 + idiv((strength + density + hardness + cohesion) * 50_000_000, 3996)
96
+ export const computeBatteryBankCapacity = (
97
+ volatility: number,
98
+ thermal: number,
99
+ plasticity: number,
100
+ insulation: number
101
+ ): number => 2_500 + idiv((volatility + thermal + plasticity + insulation) * 7_500, 3996)
78
102
 
79
103
  export function entityDisplayName(itemId: number): string {
80
104
  switch (itemId) {
@@ -108,11 +132,13 @@ export function moduleDisplayName(itemId: number): string {
108
132
  case ITEM_CRAFTER_T1:
109
133
  return 'Crafter'
110
134
  case ITEM_STORAGE_T1:
111
- return 'Storage'
135
+ return 'Cargo Bay'
112
136
  case ITEM_HAULER_T1:
113
137
  return 'Hauler'
114
138
  case ITEM_WARP_T1:
115
139
  return 'Warp'
140
+ case ITEM_BATTERY_T1:
141
+ return 'Battery Bank'
116
142
  default:
117
143
  return 'Module'
118
144
  }
@@ -160,17 +186,16 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
160
186
  }
161
187
  case MODULE_CRAFTER: {
162
188
  const rea = decodeStat(stats, 0)
163
- const com = decodeStat(stats, 1)
164
- out += ` Speed ${computeCrafterSpeed(rea)} Drain ${computeCrafterDrain(com)}`
189
+ const con = decodeStat(stats, 1)
190
+ out += ` Speed ${computeCrafterSpeed(rea)} Drain ${computeCrafterDrain(con)}`
165
191
  break
166
192
  }
167
193
  case MODULE_STORAGE: {
168
194
  const str = decodeStat(stats, 0)
169
- const fin = decodeStat(stats, 2)
170
- const sat = decodeStat(stats, 3)
171
- const sum = str + fin + sat
172
- const pct = 10 + idiv(sum * 10, 2997)
173
- out += ` +${pct}% capacity`
195
+ const den = decodeStat(stats, 1)
196
+ const hrd = decodeStat(stats, 2)
197
+ const com = decodeStat(stats, 3)
198
+ out += ` Cargo Capacity ${computeCargoBayCapacity(str, den, hrd, com)}`
174
199
  break
175
200
  }
176
201
  case MODULE_HAULER: {
@@ -185,6 +210,14 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
185
210
  out += ` Range ${computeWarpRange(stat)}`
186
211
  break
187
212
  }
213
+ case MODULE_BATTERY: {
214
+ const vol = decodeStat(stats, 0)
215
+ const thm = decodeStat(stats, 1)
216
+ const pla = decodeStat(stats, 2)
217
+ const ins = decodeStat(stats, 3)
218
+ out += ` Energy Capacity ${computeBatteryBankCapacity(vol, thm, pla, ins)}`
219
+ break
220
+ }
188
221
  }
189
222
  return out
190
223
  }
@@ -81,9 +81,15 @@ const TEMPLATES: Record<string, TemplateSpec> = {
81
81
  },
82
82
  storage: {
83
83
  id: 'module.storage.description',
84
- template: 'boosts cargo capacity by {bonus}%',
85
- params: [['bonus', 'Capacity Bonus']],
86
- highlightKeys: ['bonus'],
84
+ template: 'adds {capacity} cargo capacity',
85
+ params: [['capacity', 'Cargo Capacity']],
86
+ highlightKeys: ['capacity'],
87
+ },
88
+ energy: {
89
+ id: 'module.energy-capacity.description',
90
+ template: 'adds {capacity} energy capacity',
91
+ params: [['capacity', 'Energy Capacity']],
92
+ highlightKeys: ['capacity'],
87
93
  },
88
94
  hauler: {
89
95
  id: 'module.hauler.description',
@@ -124,8 +130,10 @@ export function describeModuleForItem(resolved: ResolvedItem): ModuleDescription
124
130
  }
125
131
 
126
132
  export function describeModuleForSlot(slot: ResolvedModuleSlot): ModuleDescription | null {
127
- if (!slot.installed || !slot.name || !slot.attributes) return null
128
- return describeModule({capability: slot.name, attributes: slot.attributes})
133
+ if (!slot.installed || !slot.attributes) return null
134
+ const capability = slot.capability ?? slot.name
135
+ if (!capability) return null
136
+ return describeModule({capability, attributes: slot.attributes})
129
137
  }
130
138
 
131
139
  export function renderDescription(
@@ -0,0 +1,37 @@
1
+ import {expect, test} from 'bun:test'
2
+ import {UInt16, UInt64} from '@wharfkit/antelope'
3
+ import {resolveItem} from './resolve-item'
4
+ import {encodeStats} from '../derivation/crafting'
5
+ import {computeContainerCapabilities} from '../derivation/capabilities'
6
+ import {
7
+ ITEM_EXTRACTOR_T1_PACKED,
8
+ ITEM_FACTORY_T1_PACKED,
9
+ ITEM_MASS_DRIVER_T1_PACKED,
10
+ ITEM_MASS_CATCHER_T1_PACKED,
11
+ } from '../data/item-ids'
12
+
13
+ function hullStats(strength: number, density: number, hardness: number): UInt64 {
14
+ return UInt64.from(encodeStats([strength, density, hardness]).toString())
15
+ }
16
+
17
+ const CONTAINER_ENTITIES = [
18
+ ['factory', ITEM_FACTORY_T1_PACKED],
19
+ ['extractor', ITEM_EXTRACTOR_T1_PACKED],
20
+ ['mass driver', ITEM_MASS_DRIVER_T1_PACKED],
21
+ ['mass catcher', ITEM_MASS_CATCHER_T1_PACKED],
22
+ ] as const
23
+
24
+ for (const [label, itemId] of CONTAINER_ENTITIES) {
25
+ test(`resolveItem resolves ${label} hull capacity via container formula`, () => {
26
+ const stats = hullStats(300, 100, 400)
27
+ const resolved = resolveItem(UInt16.from(itemId), stats)
28
+ const hull = resolved.attributes?.find((g) => g.capability === 'Hull')
29
+ const capacity = hull?.attributes.find((a) => a.label === 'Capacity')?.value
30
+ const expected = computeContainerCapabilities({
31
+ strength: 300,
32
+ hardness: 400,
33
+ density: 100,
34
+ }).capacity
35
+ expect(capacity).toBe(expected)
36
+ })
37
+ }
@@ -9,6 +9,7 @@ import {
9
9
  isModuleItem,
10
10
  MODULE_CRAFTER,
11
11
  MODULE_ENGINE,
12
+ MODULE_BATTERY,
12
13
  MODULE_GATHERER,
13
14
  MODULE_GENERATOR,
14
15
  MODULE_HAULER,
@@ -19,6 +20,7 @@ import {decodeCraftedItemStats, decodeStat} from '../derivation/crafting'
19
20
  import {getStatDefinitions} from '../derivation/stats'
20
21
  import {
21
22
  computeCrafterCapabilities,
23
+ computeBatteryCapabilities,
22
24
  computeEngineCapabilities,
23
25
  computeGathererCapabilities,
24
26
  computeGeneratorCapabilities,
@@ -28,13 +30,18 @@ import {
28
30
  computeWarehouseHullCapabilities,
29
31
  computeContainerCapabilities,
30
32
  computeContainerT2Capabilities,
33
+ computeStorageCapabilities,
31
34
  } from '../derivation/capabilities'
35
+ import {applySlotMultiplierUint32} from '../entities/slot-multiplier'
32
36
  import {categoryColors, componentIcon, itemAbbreviations, moduleIcon} from '../data/colors'
33
37
  import type {ServerContract} from '../contracts'
34
38
  import {
35
39
  ITEM_CONTAINER_T1_PACKED,
36
40
  ITEM_CONTAINER_T2_PACKED,
37
41
  ITEM_EXTRACTOR_T1_PACKED,
42
+ ITEM_FACTORY_T1_PACKED,
43
+ ITEM_MASS_CATCHER_T1_PACKED,
44
+ ITEM_MASS_DRIVER_T1_PACKED,
38
45
  ITEM_SHIP_T1_PACKED,
39
46
  ITEM_WAREHOUSE_T1_PACKED,
40
47
  } from '../data/item-ids'
@@ -58,6 +65,7 @@ export type ResolvedItemType = 'resource' | 'component' | 'module' | 'entity'
58
65
 
59
66
  export interface ResolvedModuleSlot {
60
67
  name?: string
68
+ capability?: string
61
69
  installed: boolean
62
70
  attributes?: {label: string; value: number}[]
63
71
  }
@@ -152,7 +160,8 @@ function resolveComponent(id: number, stats?: UInt64Type): ResolvedItem {
152
160
  function computeCapabilityGroup(
153
161
  moduleType: number,
154
162
  stats: Record<string, number>,
155
- tier: number
163
+ tier: number,
164
+ outputPct = 100
156
165
  ): ResolvedAttributeGroup | undefined {
157
166
  switch (moduleType) {
158
167
  case MODULE_ENGINE: {
@@ -219,13 +228,28 @@ function computeCapabilityGroup(
219
228
  }
220
229
  }
221
230
  case MODULE_STORAGE: {
222
- const str = stats.strength
223
- const den = stats.density
224
- const hrd = stats.hardness
225
- const com = stats.cohesion
226
- const statSum = str + den + hrd + com
227
- const pct = 10 + Math.floor((statSum * 10) / 2997)
228
- return {capability: 'Storage', attributes: [{label: 'Capacity Bonus', value: pct}]}
231
+ const caps = computeStorageCapabilities(stats)
232
+ return {
233
+ capability: 'Storage',
234
+ attributes: [
235
+ {
236
+ label: 'Cargo Capacity',
237
+ value: applySlotMultiplierUint32(caps.capacity, outputPct),
238
+ },
239
+ ],
240
+ }
241
+ }
242
+ case MODULE_BATTERY: {
243
+ const caps = computeBatteryCapabilities(stats)
244
+ return {
245
+ capability: 'Energy',
246
+ attributes: [
247
+ {
248
+ label: 'Energy Capacity',
249
+ value: applySlotMultiplierUint32(caps.capacity, outputPct),
250
+ },
251
+ ],
252
+ }
229
253
  }
230
254
  default:
231
255
  return undefined
@@ -266,7 +290,9 @@ function hullCapsForEntity(
266
290
  case ITEM_WAREHOUSE_T1_PACKED:
267
291
  return computeWarehouseHullCapabilities(decoded)
268
292
  case ITEM_EXTRACTOR_T1_PACKED:
269
- return computeShipHullCapabilities(decoded)
293
+ case ITEM_FACTORY_T1_PACKED:
294
+ case ITEM_MASS_DRIVER_T1_PACKED:
295
+ case ITEM_MASS_CATCHER_T1_PACKED:
270
296
  case ITEM_CONTAINER_T1_PACKED:
271
297
  return computeContainerCapabilities(decoded)
272
298
  case ITEM_CONTAINER_T2_PACKED:
@@ -321,9 +347,10 @@ function resolveEntity(
321
347
  } catch {
322
348
  modName = itemMetadata[modItemId]?.name ?? 'Module'
323
349
  }
324
- const group = computeCapabilityGroup(modType, decodedStats, modTier)
350
+ const group = computeCapabilityGroup(modType, decodedStats, modTier, slot.outputPct)
325
351
  return {
326
352
  name: modName,
353
+ capability: group?.capability,
327
354
  installed: true,
328
355
  attributes: group?.attributes,
329
356
  }
@@ -41,6 +41,7 @@ export interface ProjectedEntity {
41
41
  loaderLanes: ServerContract.Types.loader_lane[]
42
42
  generator?: ServerContract.Types.energy_stats
43
43
  hauler?: ServerContract.Types.hauler_stats
44
+ launcher?: ServerContract.Types.launcher_stats
44
45
  readonly cargoMass: UInt64
45
46
  readonly totalMass: UInt64
46
47
  readonly gathererLanes: ServerContract.Types.gatherer_lane[]
@@ -49,6 +50,7 @@ export interface ProjectedEntity {
49
50
  hasMovement(): boolean
50
51
  hasStorage(): boolean
51
52
  hasLoaders(): boolean
53
+ hasLauncher(): boolean
52
54
 
53
55
  capabilities(): EntityCapabilities
54
56
  state(): EntityState
@@ -64,6 +66,7 @@ export interface Projectable extends ScheduleData {
64
66
  gatherer_lanes?: ServerContract.Types.gatherer_lane[]
65
67
  crafter_lanes?: ServerContract.Types.crafter_lane[]
66
68
  hauler?: ServerContract.Types.hauler_stats
69
+ launcher?: ServerContract.Types.launcher_stats
67
70
  capacity?: UInt32
68
71
  cargo: ServerContract.Types.cargo_item[]
69
72
  cargomass: UInt32
@@ -91,6 +94,7 @@ interface ProjectedCaps {
91
94
  gathererLanes: ServerContract.Types.gatherer_lane[]
92
95
  crafterLanes: ServerContract.Types.crafter_lane[]
93
96
  hauler?: ServerContract.Types.hauler_stats
97
+ launcher?: ServerContract.Types.launcher_stats
94
98
  }
95
99
 
96
100
  function recomputeCaps(entity: Projectable): ProjectedCaps | undefined {
@@ -164,6 +168,13 @@ function recomputeCaps(entity: Projectable): ProjectedCaps | undefined {
164
168
  gathererLanes: (caps.gathererLanes ?? []).map(toGathererLane),
165
169
  crafterLanes: (caps.crafterLanes ?? []).map(toCrafterLane),
166
170
  hauler: caps.hauler ? ServerContract.Types.hauler_stats.from(caps.hauler) : undefined,
171
+ launcher: caps.launcher
172
+ ? ServerContract.Types.launcher_stats.from({
173
+ charge_rate: caps.launcher.chargeRate,
174
+ velocity: caps.launcher.velocity,
175
+ drain: caps.launcher.drain,
176
+ })
177
+ : undefined,
167
178
  }
168
179
  }
169
180
 
@@ -180,6 +191,7 @@ export function createProjectedEntity(entity: Projectable): ProjectedEntity {
180
191
  entity.engines === undefined ||
181
192
  entity.generator === undefined ||
182
193
  entity.hauler === undefined ||
194
+ entity.launcher === undefined ||
183
195
  entity.capacity === undefined
184
196
  const caps = needsRecompute ? recomputeCaps(entity) : undefined
185
197
 
@@ -190,6 +202,7 @@ export function createProjectedEntity(entity: Projectable): ProjectedEntity {
190
202
  const engines = entity.engines ?? caps?.engines
191
203
  const generator = entity.generator ?? caps?.generator
192
204
  const hauler = entity.hauler ?? caps?.hauler
205
+ const launcher = entity.launcher ?? caps?.launcher
193
206
  const capacity = entity.capacity ?? caps?.capacity
194
207
 
195
208
  const cargo: CargoStack[] = entity.cargo.map(cargoItemToStack)
@@ -203,6 +216,7 @@ export function createProjectedEntity(entity: Projectable): ProjectedEntity {
203
216
  engines,
204
217
  generator,
205
218
  hauler,
219
+ launcher,
206
220
  loaderLanes,
207
221
  gathererLanes,
208
222
  crafterLanes,
@@ -229,12 +243,17 @@ export function createProjectedEntity(entity: Projectable): ProjectedEntity {
229
243
  return this.loaderLanes.length > 0
230
244
  },
231
245
 
246
+ hasLauncher() {
247
+ return this.launcher !== undefined
248
+ },
249
+
232
250
  capabilities(): EntityCapabilities {
233
251
  return {
234
252
  hullmass: this.shipMass,
235
253
  capacity: this.capacity ? UInt32.from(this.capacity) : undefined,
236
254
  engines: this.engines,
237
255
  generator: this.generator,
256
+ launcher: this.launcher,
238
257
  }
239
258
  },
240
259
 
@@ -32,6 +32,7 @@ export interface RouteFailure {
32
32
  reason: RouteFailureReason
33
33
  furthest?: Coord
34
34
  legsNeeded?: number
35
+ partialWaypoints?: Coord[]
35
36
  }
36
37
 
37
38
  export type RouteResult = RoutePlan | RouteFailure
@@ -50,11 +51,13 @@ const key = (c: Coord): string => `${c.x},${c.y}`
50
51
  const sameCoord = (a: Coord, b: Coord): boolean => a.x === b.x && a.y === b.y
51
52
  const dist = (a: Coord, b: Coord): number => Math.hypot(a.x - b.x, a.y - b.y)
52
53
 
54
+ export const MAX_LEGS = 12
55
+
53
56
  export function planRoute(params: PlanRouteParams): RouteResult {
54
57
  const {origin, dest, perLegReach, graph} = params
55
58
  const corridorSlack = params.corridorSlack ?? perLegReach
56
59
  const nodeBudget = params.nodeBudget ?? 5000
57
- const maxLegs = params.maxLegs ?? 12
60
+ const maxLegs = params.maxLegs ?? MAX_LEGS
58
61
 
59
62
  if (!graph.hasSystem(dest)) {
60
63
  return {ok: false, reason: 'empty-destination'}
@@ -122,9 +125,32 @@ export function planRoute(params: PlanRouteParams): RouteResult {
122
125
  }
123
126
 
124
127
  if (cappedByMaxLegs) {
125
- return {ok: false, reason: 'max-legs', furthest}
128
+ return {
129
+ ok: false,
130
+ reason: 'max-legs',
131
+ furthest,
132
+ partialWaypoints: reconstructWaypoints(cameFrom, origin, furthest),
133
+ }
134
+ }
135
+ return {
136
+ ok: false,
137
+ reason: 'no-path',
138
+ furthest,
139
+ partialWaypoints: reconstructWaypoints(cameFrom, origin, furthest),
140
+ }
141
+ }
142
+
143
+ function reconstructWaypoints(cameFrom: Map<string, Coord>, origin: Coord, target: Coord): Coord[] {
144
+ if (sameCoord(target, origin)) return []
145
+ const path: Coord[] = [target]
146
+ let cur = target
147
+ while (!sameCoord(cur, origin)) {
148
+ const prev = cameFrom.get(key(cur))
149
+ if (!prev) break
150
+ path.unshift(prev)
151
+ cur = prev
126
152
  }
127
- return {ok: false, reason: 'no-path', furthest}
153
+ return path.slice(1)
128
154
  }
129
155
 
130
156
  function reconstruct(cameFrom: Map<string, Coord>, origin: Coord, dest: Coord): RoutePlan {
@@ -59,6 +59,7 @@ export interface EntityCapabilities {
59
59
  gatherer?: GathererStats
60
60
  crafter?: CrafterStats
61
61
  hauler?: ServerContract.Types.hauler_stats
62
+ launcher?: ServerContract.Types.launcher_stats
62
63
  }
63
64
 
64
65
  export interface EntityState {
@@ -92,3 +93,7 @@ export function capsHasMass(caps: EntityCapabilities): boolean {
92
93
  export function capsHasHauler(caps: EntityCapabilities): boolean {
93
94
  return caps.hauler !== undefined
94
95
  }
96
+
97
+ export function capsHasLauncher(caps: EntityCapabilities): boolean {
98
+ return caps.launcher !== undefined
99
+ }
package/src/types.ts CHANGED
@@ -129,6 +129,7 @@ export type ModuleType =
129
129
  | 'storage'
130
130
  | 'hauler'
131
131
  | 'battery'
132
+ | 'catcher'
132
133
 
133
134
  export const RESOURCE_TIER_ADJECTIVES: Record<number, string> = {
134
135
  1: 'Crude',