@shipload/sdk 1.0.0-next.16 → 1.0.0-next.17

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.
@@ -7,3 +7,5 @@ export {LocationsManager} from './locations'
7
7
  export type {LocationStratum} from './locations'
8
8
  export {EpochsManager} from './epochs'
9
9
  export {ActionsManager} from './actions'
10
+ export {NftManager} from './nft'
11
+ export type {NftConfigForItem} from './nft'
@@ -0,0 +1,28 @@
1
+ import {UInt64, type UInt64Type} from '@wharfkit/antelope'
2
+ import {BaseManager} from './base'
3
+ import type {ServerContract} from '../contracts'
4
+
5
+ export interface NftConfigForItem {
6
+ templateId: number
7
+ schemaName: string
8
+ }
9
+
10
+ export class NftManager extends BaseManager {
11
+ private cache = new Map<string, NftConfigForItem | null>()
12
+
13
+ async getNftConfigForItem(itemId: UInt64Type): Promise<NftConfigForItem | undefined> {
14
+ const id = UInt64.from(itemId)
15
+ const key = id.toString()
16
+ if (this.cache.has(key)) {
17
+ return this.cache.get(key) ?? undefined
18
+ }
19
+ const row = (await this.server.table('nftconfig').get(id)) as
20
+ | ServerContract.Types.nftconfig_row
21
+ | undefined
22
+ const result: NftConfigForItem | null = row
23
+ ? {templateId: Number(row.template_id), schemaName: String(row.schema_name)}
24
+ : null
25
+ this.cache.set(key, result)
26
+ return result ?? undefined
27
+ }
28
+ }
@@ -1,10 +1,133 @@
1
- import {type APIClient, Name, type NameType, UInt64} from '@wharfkit/antelope'
1
+ import {
2
+ ABI,
3
+ type ABIDef,
4
+ Action,
5
+ type APIClient,
6
+ type NameType,
7
+ Name,
8
+ PermissionLevel,
9
+ UInt64,
10
+ } from '@wharfkit/antelope'
2
11
  import {deserializeAtomicData, type SchemaField} from './atomicdata'
3
12
  import {deserializeAsset, type NFTCargoItem, type NFTModuleSlot} from './deserializers'
13
+ import type {ImmutableEntry} from './buildImmutableData'
14
+
15
+ const PLACEHOLDER_AUTH = PermissionLevel.from({
16
+ actor: '............1',
17
+ permission: '............2',
18
+ })
4
19
 
5
20
  export const ATOMICASSETS_ACCOUNT = 'atomicassets'
6
21
  export const SHIPLOAD_COLLECTION = 'shipload'
7
22
 
23
+ const ATOMIC_ATTRIBUTE_VARIANT_NAME =
24
+ 'variant_int8_int16_int32_int64_uint8_uint16_uint32_uint64_float32_float64_string_INT8_VEC_INT16_VEC_INT32_VEC_INT64_VEC_UINT8_VEC_UINT16_VEC_UINT32_VEC_UINT64_VEC_FLOAT_VEC_DOUBLE_VEC_STRING_VEC'
25
+
26
+ const MINTASSET_ABI_DEF: ABIDef = {
27
+ version: 'eosio::abi/1.2',
28
+ types: [
29
+ {new_type_name: 'ATOMIC_ATTRIBUTE', type: ATOMIC_ATTRIBUTE_VARIANT_NAME},
30
+ {new_type_name: 'ATTRIBUTE_MAP', type: 'pair_string_ATOMIC_ATTRIBUTE[]'},
31
+ {new_type_name: 'INT8_VEC', type: 'bytes'},
32
+ {new_type_name: 'INT16_VEC', type: 'int16[]'},
33
+ {new_type_name: 'INT32_VEC', type: 'int32[]'},
34
+ {new_type_name: 'INT64_VEC', type: 'int64[]'},
35
+ {new_type_name: 'UINT8_VEC', type: 'bytes'},
36
+ {new_type_name: 'UINT16_VEC', type: 'uint16[]'},
37
+ {new_type_name: 'UINT32_VEC', type: 'uint32[]'},
38
+ {new_type_name: 'UINT64_VEC', type: 'uint64[]'},
39
+ {new_type_name: 'FLOAT_VEC', type: 'float32[]'},
40
+ {new_type_name: 'DOUBLE_VEC', type: 'float64[]'},
41
+ {new_type_name: 'STRING_VEC', type: 'string[]'},
42
+ ],
43
+ structs: [
44
+ {
45
+ name: 'pair_string_ATOMIC_ATTRIBUTE',
46
+ base: '',
47
+ fields: [
48
+ {name: 'first', type: 'string'},
49
+ {name: 'second', type: 'ATOMIC_ATTRIBUTE'},
50
+ ],
51
+ },
52
+ {
53
+ name: 'mintasset',
54
+ base: '',
55
+ fields: [
56
+ {name: 'authorized_minter', type: 'name'},
57
+ {name: 'collection_name', type: 'name'},
58
+ {name: 'schema_name', type: 'name'},
59
+ {name: 'template_id', type: 'int32'},
60
+ {name: 'new_asset_owner', type: 'name'},
61
+ {name: 'immutable_data', type: 'ATTRIBUTE_MAP'},
62
+ {name: 'mutable_data', type: 'ATTRIBUTE_MAP'},
63
+ {name: 'tokens_to_back', type: 'asset[]'},
64
+ ],
65
+ },
66
+ ],
67
+ actions: [{name: 'mintasset', type: 'mintasset', ricardian_contract: ''}],
68
+ variants: [
69
+ {
70
+ name: ATOMIC_ATTRIBUTE_VARIANT_NAME,
71
+ types: [
72
+ 'int8',
73
+ 'int16',
74
+ 'int32',
75
+ 'int64',
76
+ 'uint8',
77
+ 'uint16',
78
+ 'uint32',
79
+ 'uint64',
80
+ 'float32',
81
+ 'float64',
82
+ 'string',
83
+ 'INT8_VEC',
84
+ 'INT16_VEC',
85
+ 'INT32_VEC',
86
+ 'INT64_VEC',
87
+ 'UINT8_VEC',
88
+ 'UINT16_VEC',
89
+ 'UINT32_VEC',
90
+ 'UINT64_VEC',
91
+ 'FLOAT_VEC',
92
+ 'DOUBLE_VEC',
93
+ 'STRING_VEC',
94
+ ],
95
+ },
96
+ ],
97
+ }
98
+
99
+ export interface MintAssetParams {
100
+ authorizedMinter: NameType
101
+ collectionName: NameType
102
+ schemaName: NameType
103
+ templateId: number
104
+ newAssetOwner: NameType
105
+ immutableData: ImmutableEntry[]
106
+ }
107
+
108
+ const MINTASSET_ABI = ABI.from(MINTASSET_ABI_DEF)
109
+
110
+ export function buildMintAssetAction(params: MintAssetParams): Action {
111
+ return Action.from(
112
+ {
113
+ account: Name.from(ATOMICASSETS_ACCOUNT),
114
+ name: Name.from('mintasset'),
115
+ authorization: [PLACEHOLDER_AUTH],
116
+ data: {
117
+ authorized_minter: Name.from(params.authorizedMinter),
118
+ collection_name: Name.from(params.collectionName),
119
+ schema_name: Name.from(params.schemaName),
120
+ template_id: params.templateId,
121
+ new_asset_owner: Name.from(params.newAssetOwner),
122
+ immutable_data: params.immutableData,
123
+ mutable_data: [],
124
+ tokens_to_back: [],
125
+ },
126
+ },
127
+ MINTASSET_ABI
128
+ )
129
+ }
130
+
8
131
  export interface AtomicAssetRow {
9
132
  asset_id: string
10
133
  collection_name: string
@@ -0,0 +1,316 @@
1
+ import {Serializer} from '@wharfkit/antelope'
2
+ import {getItem} from '../data/catalog'
3
+ import {
4
+ getModuleCapabilityType,
5
+ MODULE_CRAFTER,
6
+ MODULE_ENGINE,
7
+ MODULE_GATHERER,
8
+ MODULE_GENERATOR,
9
+ MODULE_HAULER,
10
+ MODULE_LOADER,
11
+ MODULE_STORAGE,
12
+ MODULE_WARP,
13
+ } from '../capabilities/modules'
14
+ import {decodeStat, decodeCraftedItemStats} from '../derivation/crafting'
15
+ import {getStatDefinitions} from '../derivation/stats'
16
+ import type {ResourceCategory} from '../types'
17
+ import {Types as ServerTypes} from '../contracts/server'
18
+ import {
19
+ buildEntityDescription,
20
+ computeCrafterDrain,
21
+ computeCrafterSpeed,
22
+ computeEngineDrain,
23
+ computeEngineThrust,
24
+ computeGathererDepth,
25
+ computeGathererDrain,
26
+ computeGathererYield,
27
+ computeGeneratorCap,
28
+ computeGeneratorRech,
29
+ computeHaulerCapacity,
30
+ computeHaulerDrain,
31
+ computeHaulerEfficiency,
32
+ computeLoaderMass,
33
+ computeLoaderThrust,
34
+ computeWarpRange,
35
+ } from './description'
36
+
37
+ export type AtomicAttributeType =
38
+ | 'string'
39
+ | 'uint8'
40
+ | 'uint16'
41
+ | 'uint32'
42
+ | 'uint64'
43
+ | 'int32'
44
+ | 'image'
45
+ | 'ipfs'
46
+ | 'UINT16_VEC'
47
+ | 'UINT64_VEC'
48
+
49
+ export interface ImmutableEntry {
50
+ first: string
51
+ second: [AtomicAttributeType, unknown]
52
+ }
53
+
54
+ export interface ImmutableModuleSlot {
55
+ type?: number | string | bigint
56
+ installed?: {item_id: number | string | bigint; stats: number | string | bigint}
57
+ }
58
+
59
+ export function moduleSlotsForImmutable(
60
+ modules: ServerTypes.module_entry[]
61
+ ): ImmutableModuleSlot[] {
62
+ return modules.map((m) => ({
63
+ type: Number(m.type.toString()),
64
+ installed: m.installed
65
+ ? {
66
+ item_id: Number(m.installed.item_id.toString()),
67
+ stats: BigInt(m.installed.stats.toString()),
68
+ }
69
+ : undefined,
70
+ }))
71
+ }
72
+
73
+ const IMAGE_HOST_URL = 'https://item.shiploadgame.com/item'
74
+
75
+ function bytesToBase64Url(bytes: Uint8Array): string {
76
+ let binary = ''
77
+ for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]!)
78
+ const b64 = btoa(binary)
79
+ return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
80
+ }
81
+
82
+ export function computeNftImageUrl(
83
+ item: {item_id: number; stats: bigint; modules: ImmutableModuleSlot[]; quantity: number},
84
+ originX: number,
85
+ originY: number
86
+ ): string {
87
+ const payload = ServerTypes.nft_item_payload.from({
88
+ item: {
89
+ item_id: item.item_id,
90
+ stats: String(item.stats),
91
+ modules: item.modules,
92
+ quantity: item.quantity,
93
+ },
94
+ location: {x: String(originX), y: String(originY)},
95
+ })
96
+ const bytes = Serializer.encode({object: payload}).array
97
+ return `${IMAGE_HOST_URL}/${bytesToBase64Url(bytes)}.png`
98
+ }
99
+
100
+ function commonBaseImmutable(
101
+ quantity: number,
102
+ stats: bigint,
103
+ originX: number,
104
+ originY: number,
105
+ img: string
106
+ ): ImmutableEntry[] {
107
+ return [
108
+ {first: 'quantity', second: ['uint32', quantity]},
109
+ {first: 'stats', second: ['uint64', String(stats)]},
110
+ {first: 'origin_x', second: ['int32', originX]},
111
+ {first: 'origin_y', second: ['int32', originY]},
112
+ {first: 'img', second: ['string', img]},
113
+ ]
114
+ }
115
+
116
+ export function buildResourceImmutable(
117
+ itemId: number,
118
+ quantity: number,
119
+ stats: bigint,
120
+ originX: number,
121
+ originY: number
122
+ ): ImmutableEntry[] {
123
+ const item = getItem(itemId)
124
+ const cat = item.category
125
+ if (!cat) throw new Error(`Resource item ${itemId} has no category`)
126
+ const definitions = getStatDefinitions(cat as ResourceCategory)
127
+ const img = computeNftImageUrl(
128
+ {item_id: itemId, stats, modules: [], quantity},
129
+ originX,
130
+ originY
131
+ )
132
+ const base = commonBaseImmutable(quantity, stats, originX, originY, img)
133
+ base.push({first: definitions[0].key, second: ['uint16', decodeStat(stats, 0)]})
134
+ base.push({first: definitions[1].key, second: ['uint16', decodeStat(stats, 1)]})
135
+ base.push({first: definitions[2].key, second: ['uint16', decodeStat(stats, 2)]})
136
+ return base
137
+ }
138
+
139
+ export function buildComponentImmutable(
140
+ itemId: number,
141
+ quantity: number,
142
+ stats: bigint,
143
+ originX: number,
144
+ originY: number
145
+ ): ImmutableEntry[] {
146
+ const img = computeNftImageUrl(
147
+ {item_id: itemId, stats, modules: [], quantity},
148
+ originX,
149
+ originY
150
+ )
151
+ const base = commonBaseImmutable(quantity, stats, originX, originY, img)
152
+ const decoded = decodeCraftedItemStats(itemId, stats)
153
+ for (const [key, value] of Object.entries(decoded)) {
154
+ base.push({first: key, second: ['uint16', value]})
155
+ }
156
+ return base
157
+ }
158
+
159
+ export function buildModuleImmutable(
160
+ itemId: number,
161
+ quantity: number,
162
+ stats: bigint,
163
+ originX: number,
164
+ originY: number
165
+ ): ImmutableEntry[] {
166
+ const img = computeNftImageUrl(
167
+ {item_id: itemId, stats, modules: [], quantity},
168
+ originX,
169
+ originY
170
+ )
171
+ const base = commonBaseImmutable(quantity, stats, originX, originY, img)
172
+ const subtype = getModuleCapabilityType(itemId)
173
+ const item = getItem(itemId)
174
+ switch (subtype) {
175
+ case MODULE_ENGINE: {
176
+ const vol = decodeStat(stats, 0)
177
+ const thm = decodeStat(stats, 1)
178
+ base.push({first: 'volatility', second: ['uint16', vol]})
179
+ base.push({first: 'thermal', second: ['uint16', thm]})
180
+ base.push({first: 'thrust', second: ['uint32', computeEngineThrust(vol)]})
181
+ base.push({first: 'drain', second: ['uint16', computeEngineDrain(thm)]})
182
+ break
183
+ }
184
+ case MODULE_GENERATOR: {
185
+ const res = decodeStat(stats, 0)
186
+ const ref = decodeStat(stats, 1)
187
+ base.push({first: 'resonance', second: ['uint16', res]})
188
+ base.push({first: 'reflectivity', second: ['uint16', ref]})
189
+ base.push({first: 'capacity', second: ['uint16', computeGeneratorCap(res)]})
190
+ base.push({first: 'recharge', second: ['uint16', computeGeneratorRech(ref)]})
191
+ break
192
+ }
193
+ case MODULE_GATHERER: {
194
+ const str = decodeStat(stats, 0)
195
+ const tol = decodeStat(stats, 1)
196
+ const con = decodeStat(stats, 3)
197
+ base.push({first: 'strength', second: ['uint16', str]})
198
+ base.push({first: 'tolerance', second: ['uint16', tol]})
199
+ base.push({first: 'conductivity', second: ['uint16', con]})
200
+ base.push({first: 'yield', second: ['uint16', computeGathererYield(str)]})
201
+ base.push({first: 'drain', second: ['uint16', computeGathererDrain(con)]})
202
+ base.push({first: 'depth', second: ['uint16', computeGathererDepth(tol, item.tier)]})
203
+ break
204
+ }
205
+ case MODULE_LOADER: {
206
+ const fin = decodeStat(stats, 0)
207
+ const pla = decodeStat(stats, 1)
208
+ base.push({first: 'fineness', second: ['uint16', fin]})
209
+ base.push({first: 'plasticity', second: ['uint16', pla]})
210
+ base.push({first: 'mass', second: ['uint32', computeLoaderMass(fin)]})
211
+ base.push({first: 'thrust', second: ['uint16', computeLoaderThrust(pla)]})
212
+ break
213
+ }
214
+ case MODULE_WARP: {
215
+ const res = decodeStat(stats, 0)
216
+ base.push({first: 'resonance', second: ['uint16', res]})
217
+ base.push({first: 'range', second: ['uint32', computeWarpRange(res)]})
218
+ break
219
+ }
220
+ case MODULE_CRAFTER: {
221
+ const rea = decodeStat(stats, 0)
222
+ const com = decodeStat(stats, 1)
223
+ base.push({first: 'reactivity', second: ['uint16', rea]})
224
+ base.push({first: 'composition', second: ['uint16', com]})
225
+ base.push({first: 'speed', second: ['uint16', computeCrafterSpeed(rea)]})
226
+ base.push({first: 'drain', second: ['uint16', computeCrafterDrain(com)]})
227
+ break
228
+ }
229
+ case MODULE_STORAGE: {
230
+ const str = decodeStat(stats, 0)
231
+ const fin = decodeStat(stats, 1)
232
+ const sat = decodeStat(stats, 2)
233
+ const sum = str + fin + sat
234
+ base.push({first: 'strength', second: ['uint16', str]})
235
+ base.push({first: 'fineness', second: ['uint16', fin]})
236
+ base.push({first: 'saturation', second: ['uint16', sat]})
237
+ base.push({
238
+ first: 'capacity_bonus_pct',
239
+ second: ['uint16', 10 + Math.floor((sum * 10) / 2997)],
240
+ })
241
+ break
242
+ }
243
+ case MODULE_HAULER: {
244
+ const com = decodeStat(stats, 0)
245
+ const con = decodeStat(stats, 1)
246
+ const fin = decodeStat(stats, 2)
247
+ const res = decodeStat(stats, 3)
248
+ base.push({first: 'composition', second: ['uint16', com]})
249
+ base.push({first: 'conductivity', second: ['uint16', con]})
250
+ base.push({first: 'fineness', second: ['uint16', fin]})
251
+ base.push({first: 'resonance', second: ['uint16', res]})
252
+ base.push({first: 'capacity', second: ['uint8', computeHaulerCapacity(com)]})
253
+ base.push({first: 'efficiency', second: ['uint16', computeHaulerEfficiency(con)]})
254
+ base.push({first: 'drain', second: ['uint16', computeHaulerDrain(fin)]})
255
+ break
256
+ }
257
+ }
258
+ return base
259
+ }
260
+
261
+ export function buildEntityImmutable(
262
+ itemId: number,
263
+ quantity: number,
264
+ stats: bigint,
265
+ originX: number,
266
+ originY: number,
267
+ modules: ImmutableModuleSlot[]
268
+ ): ImmutableEntry[] {
269
+ const moduleItems: number[] = []
270
+ const moduleStats: string[] = []
271
+ for (const m of modules) {
272
+ if (m.installed) {
273
+ moduleItems.push(Number(m.installed.item_id))
274
+ moduleStats.push(String(m.installed.stats))
275
+ } else {
276
+ moduleItems.push(0)
277
+ moduleStats.push('0')
278
+ }
279
+ }
280
+ const img = computeNftImageUrl({item_id: itemId, stats, modules, quantity}, originX, originY)
281
+ const base = commonBaseImmutable(quantity, stats, originX, originY, img)
282
+ base.push({first: 'module_items', second: ['UINT16_VEC', moduleItems]})
283
+ base.push({first: 'module_stats', second: ['UINT64_VEC', moduleStats]})
284
+ const description = buildEntityDescription(
285
+ itemId,
286
+ stats,
287
+ moduleItems,
288
+ moduleStats.map((s) => BigInt(s))
289
+ )
290
+ base.push({first: 'description', second: ['string', description]})
291
+ return base
292
+ }
293
+
294
+ export function buildImmutableData(
295
+ itemId: number,
296
+ quantity: number,
297
+ stats: bigint,
298
+ originX: number,
299
+ originY: number,
300
+ modules: ImmutableModuleSlot[] = []
301
+ ): ImmutableEntry[] {
302
+ const item = getItem(itemId)
303
+ if (item.type === 'resource') {
304
+ return buildResourceImmutable(itemId, quantity, stats, originX, originY)
305
+ }
306
+ if (item.type === 'component') {
307
+ return buildComponentImmutable(itemId, quantity, stats, originX, originY)
308
+ }
309
+ if (item.type === 'module') {
310
+ return buildModuleImmutable(itemId, quantity, stats, originX, originY)
311
+ }
312
+ if (item.type === 'entity') {
313
+ return buildEntityImmutable(itemId, quantity, stats, originX, originY, modules)
314
+ }
315
+ throw new Error(`Unsupported item type for wrap: ${item.type}`)
316
+ }
@@ -56,7 +56,6 @@ export const computeGathererDrain = (con: number): number =>
56
56
  Math.max(250, 1250 - idiv(con * 25, 20))
57
57
  export const computeGathererDepth = (tol: number, tier: number): number =>
58
58
  gathererDepthForTier(tol, tier)
59
- export const computeGathererSpeed = (ref: number): number => 100 + idiv(ref * 4, 5)
60
59
  export const computeLoaderMass = (ins: number): number => Math.max(200, 2000 - ins * 2)
61
60
  export const computeLoaderThrust = (pla: number): number => 1 + idiv(pla, 500)
62
61
  export const computeCrafterSpeed = (rea: number): number => 100 + idiv(rea * 4, 5)
@@ -133,12 +132,11 @@ export function formatModuleLine(slot: number, itemId: number, stats: bigint): s
133
132
  const str = decodeStat(stats, 0)
134
133
  const tol = decodeStat(stats, 1)
135
134
  const con = decodeStat(stats, 3)
136
- const ref = decodeStat(stats, 4)
137
135
  const tier = getItem(itemId).tier
138
136
  out += ` Yield ${computeGathererYield(str)} Depth ${computeGathererDepth(
139
137
  tol,
140
138
  tier
141
- )} Speed ${computeGathererSpeed(ref)} Drain ${computeGathererDrain(con)}`
139
+ )} Drain ${computeGathererDrain(con)}`
142
140
  break
143
141
  }
144
142
  case MODULE_LOADER: {
package/src/nft/index.ts CHANGED
@@ -2,3 +2,4 @@ export * from './deserializers'
2
2
  export * from './description'
3
3
  export * from './atomicdata'
4
4
  export * from './atomicassets'
5
+ export * from './buildImmutableData'
@@ -53,14 +53,13 @@ const TEMPLATES: Record<string, TemplateSpec> = {
53
53
  gatherer: {
54
54
  id: 'module.gatherer.description',
55
55
  template:
56
- 'mines resources at {yield} speed to a max depth of {depth} with {speed} gather speed while draining {drain} energy per second',
56
+ 'mines resources at {yield} yield to a max depth of {depth} while draining {drain} energy per second',
57
57
  params: [
58
58
  ['yield', 'Yield'],
59
- ['drain', 'Drain'],
60
59
  ['depth', 'Depth'],
61
- ['speed', 'Speed'],
60
+ ['drain', 'Drain'],
62
61
  ],
63
- highlightKeys: ['yield', 'depth', 'speed', 'drain'],
62
+ highlightKeys: ['yield', 'depth', 'drain'],
64
63
  },
65
64
  loader: {
66
65
  id: 'module.loader.description',
@@ -189,7 +189,6 @@ function computeCapabilityGroup(
189
189
  {label: 'Yield', value: caps.yield},
190
190
  {label: 'Drain', value: caps.drain},
191
191
  {label: 'Depth', value: caps.depth},
192
- {label: 'Speed', value: caps.speed},
193
192
  ],
194
193
  }
195
194
  }
package/src/shipload.ts CHANGED
@@ -9,6 +9,7 @@ import type {PlayersManager} from './managers/players'
9
9
  import type {LocationsManager} from './managers/locations'
10
10
  import type {EpochsManager} from './managers/epochs'
11
11
  import type {ActionsManager} from './managers/actions'
12
+ import type {NftManager} from './managers/nft'
12
13
  import type {SubscriptionsManager} from './subscriptions/manager'
13
14
  import type {GameState} from './entities/gamestate'
14
15
 
@@ -107,6 +108,10 @@ export class Shipload {
107
108
  return this._context.actions
108
109
  }
109
110
 
111
+ get nft(): NftManager {
112
+ return this._context.nft
113
+ }
114
+
110
115
  get subscriptions(): SubscriptionsManager {
111
116
  return this._context.subscriptions
112
117
  }
package/src/types.ts CHANGED
@@ -117,6 +117,7 @@ export type ModuleType =
117
117
  | 'launcher'
118
118
  | 'storage'
119
119
  | 'hauler'
120
+ | 'battery'
120
121
 
121
122
  export const RESOURCE_TIER_ADJECTIVES: Record<number, string> = {
122
123
  1: 'Crude',