@shipload/sdk 2.0.0-rc20 → 2.0.0-rc22

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.
@@ -1,6 +1,8 @@
1
1
  export * from './contracts'
2
2
  export * from './errors'
3
3
  export * from './types'
4
+ export * from './data/item-ids'
5
+ export * from './data/recipes-runtime'
4
6
 
5
7
  import {ServerContract} from './contracts'
6
8
 
@@ -141,9 +143,16 @@ export {
141
143
  createProjectedEntity,
142
144
  projectEntity,
143
145
  projectEntityAt,
146
+ projectFromCurrentState,
147
+ projectFromCurrentStateAt,
144
148
  validateSchedule,
145
149
  } from './scheduling/projection'
146
- export type {Projectable, ProjectedEntity, ProjectionOptions} from './scheduling/projection'
150
+ export type {
151
+ Projectable,
152
+ ProjectableSnapshot,
153
+ ProjectedEntity,
154
+ ProjectionOptions,
155
+ } from './scheduling/projection'
147
156
 
148
157
  export * from './types/capabilities'
149
158
  export * from './types/entity'
@@ -182,51 +191,6 @@ export {
182
191
  } from './data/capabilities'
183
192
  export type {CapabilityAttribute, StatMapping} from './data/capabilities'
184
193
 
185
- export {
186
- components,
187
- entityRecipes,
188
- moduleRecipes,
189
- getComponentById,
190
- getEntityRecipe,
191
- getEntityRecipeByItemId,
192
- getModuleRecipe,
193
- getModuleRecipeByItemId,
194
- getAllCraftableItems,
195
- getComponentsForCategory,
196
- getComponentsForStat,
197
- ITEM_HULL_PLATES,
198
- ITEM_CARGO_LINING,
199
- ITEM_CONTAINER_T1_PACKED,
200
- ITEM_THRUSTER_CORE,
201
- ITEM_POWER_CELL,
202
- ITEM_ENGINE_T1,
203
- ITEM_GENERATOR_T1,
204
- ITEM_SHIP_T1_PACKED,
205
- ITEM_WAREHOUSE_T1_PACKED,
206
- ITEM_MATTER_CONDUIT,
207
- ITEM_SURVEY_PROBE,
208
- ITEM_CARGO_ARM,
209
- ITEM_TOOL_BIT,
210
- ITEM_REACTION_CHAMBER,
211
- ITEM_GATHERER_T1,
212
- ITEM_LOADER_T1,
213
- ITEM_CRAFTER_T1,
214
- ITEM_STORAGE_T1,
215
- ITEM_HULL_PLATES_T2,
216
- ITEM_CARGO_LINING_T2,
217
- ITEM_CONTAINER_T2_PACKED,
218
- ITEM_FOCUSING_ARRAY,
219
- } from './data/recipes'
220
- export type {
221
- ComponentDefinition,
222
- ComponentStat,
223
- RecipeInput,
224
- EntityRecipe,
225
- ModuleRecipe,
226
- ModuleSlot,
227
- CraftableItem,
228
- } from './data/recipes'
229
-
230
194
  export {
231
195
  encodeStats,
232
196
  encodeGatheredCargoStats,
@@ -239,7 +203,6 @@ export {
239
203
  computeEntityStats,
240
204
  blendCargoStacks,
241
205
  blendCrossGroup,
242
- categoryItemMass,
243
206
  computeInputMass,
244
207
  computeCraftedOutputStats,
245
208
  } from './derivation/crafting'
@@ -321,7 +284,6 @@ export {
321
284
  computeCrafterDrain,
322
285
  } from './nft/description'
323
286
 
324
- export {getEntitySlotLayout} from './data/recipes'
325
287
  export {
326
288
  ITEM_TYPE_RESOURCE,
327
289
  ITEM_TYPE_COMPONENT,
@@ -1,93 +1,41 @@
1
1
  import {UInt16, UInt16Type} from '@wharfkit/antelope'
2
+ import items from '../data/items.json'
3
+ import {itemMetadata} from '../data/metadata'
2
4
  import {Item} from '../types'
3
- import itemsData from '../data/items.json'
4
- import {computeInputMass} from '../derivation/crafting'
5
- import {getComponentById, getEntityRecipeByItemId, getModuleRecipeByItemId} from '../data/recipes'
6
5
 
7
- const itemsById: Map<number, Item> = new Map()
8
- const synthesizedCache: Map<number, Item> = new Map()
6
+ const itemsById = new Map<number, Item>()
9
7
 
10
- for (const g of itemsData) {
11
- const item = Item.from({
12
- id: g.id,
13
- name: g.name,
14
- description: g.description,
15
- mass: g.mass,
16
- category: g.category,
17
- tier: g.tier,
18
- color: g.color,
19
- })
20
- itemsById.set(item.id.toNumber(), item)
21
- }
22
-
23
- export const itemIds = Array.from(itemsById.values(), (i) => i.id)
24
-
25
- interface RecipeSource {
26
- name: string
27
- description: string
28
- mass: number
29
- color: string
30
- }
31
-
32
- function synthesizeItem(id: number, source: RecipeSource): Item {
33
- return Item.from({
34
- id,
35
- name: source.name,
36
- description: source.description,
37
- mass: source.mass,
38
- category: 'ore',
39
- tier: 't1',
40
- color: source.color,
8
+ for (const raw of items as any[]) {
9
+ const meta = itemMetadata[raw.id]
10
+ if (!meta) {
11
+ throw new Error(`Missing metadata for item ${raw.id}. Add an entry to metadata.ts.`)
12
+ }
13
+ itemsById.set(raw.id, {
14
+ id: raw.id,
15
+ name: meta.name,
16
+ description: meta.description,
17
+ color: meta.color,
18
+ mass: raw.mass,
19
+ type: raw.type,
20
+ tier: raw.tier,
21
+ category: raw.category,
22
+ moduleType: raw.type === 'module' ? raw.subtype : undefined,
41
23
  })
42
24
  }
43
25
 
44
- function synthesizeFromRecipes(id: number): Item | undefined {
45
- const component = getComponentById(id)
46
- if (component) return synthesizeItem(id, component)
47
-
48
- const entityRecipe = getEntityRecipeByItemId(id)
49
- if (entityRecipe) {
50
- return synthesizeItem(id, {
51
- ...entityRecipe,
52
- mass: computeInputMass(entityRecipe.id, 'entity'),
53
- })
54
- }
55
-
56
- const moduleRecipe = getModuleRecipeByItemId(id)
57
- if (moduleRecipe) {
58
- return synthesizeItem(id, {
59
- ...moduleRecipe,
60
- mass: computeInputMass(moduleRecipe.id, 'module'),
61
- })
62
- }
63
-
64
- return undefined
65
- }
26
+ export const itemIds = Array.from(itemsById.keys())
66
27
 
67
28
  export function getItem(itemId: UInt16Type): Item {
68
29
  const id = UInt16.from(itemId).toNumber()
69
- const existing = itemsById.get(id) ?? synthesizedCache.get(id)
70
- if (existing) return existing
71
-
72
- const synthesized = synthesizeFromRecipes(id)
73
- if (synthesized) {
74
- synthesizedCache.set(id, synthesized)
75
- return synthesized
76
- }
77
-
78
- throw new Error(`Item with id ${id} not found`)
30
+ const item = itemsById.get(id)
31
+ if (!item) throw new Error(`Unknown item id: ${id}`)
32
+ return item
79
33
  }
80
34
 
81
35
  export function getItems(): Item[] {
82
36
  return Array.from(itemsById.values())
83
37
  }
84
38
 
85
- /**
86
- * @internal Test-only: registers an item into the in-memory map. Tests should
87
- * use `test/item-mock.ts`'s `registerMockItem()` instead of calling this directly.
88
- */
89
39
  export function __registerItemInternal(item: Item): void {
90
- const id = item.id.toNumber()
91
- itemsById.set(id, item)
92
- synthesizedCache.delete(id)
40
+ itemsById.set(item.id, item)
93
41
  }
@@ -1,11 +1,5 @@
1
1
  import {
2
2
  getModuleCapabilityType,
3
- ITEM_CRAFTER_T1,
4
- ITEM_ENGINE_T1,
5
- ITEM_GATHERER_T1,
6
- ITEM_GENERATOR_T1,
7
- ITEM_LOADER_T1,
8
- ITEM_STORAGE_T1,
9
3
  MODULE_CRAFTER,
10
4
  MODULE_ENGINE,
11
5
  MODULE_GATHERER,
@@ -16,9 +10,15 @@ import {
16
10
  import {
17
11
  ITEM_CONTAINER_T1_PACKED,
18
12
  ITEM_CONTAINER_T2_PACKED,
13
+ ITEM_CRAFTER_T1,
14
+ ITEM_ENGINE_T1,
15
+ ITEM_GATHERER_T1,
16
+ ITEM_GENERATOR_T1,
17
+ ITEM_LOADER_T1,
19
18
  ITEM_SHIP_T1_PACKED,
19
+ ITEM_STORAGE_T1,
20
20
  ITEM_WAREHOUSE_T1_PACKED,
21
- } from '../data/recipes'
21
+ } from '../data/item-ids'
22
22
  import {decodeStat} from '../derivation/crafting'
23
23
 
24
24
  function idiv(a: number, b: number): number {
@@ -1,4 +1,5 @@
1
- import {getEntitySlotLayout} from '../data/recipes'
1
+ import {getEntityLayout} from '../data/recipes-runtime'
2
+ import {moduleSlotTypeToCode} from '../capabilities/modules'
2
3
  import {
3
4
  ITEM_TYPE_COMPONENT,
4
5
  ITEM_TYPE_ENTITY,
@@ -53,10 +54,11 @@ export function deserializeEntity(data: Record<string, any>, itemId: number): NF
53
54
  const base = readCommonBase(data)
54
55
  const moduleItems: number[] = (data.module_items ?? []).map((v: any) => Number(v))
55
56
  const moduleStats: string[] = (data.module_stats ?? []).map((v: any) => String(v))
56
- const layout = getEntitySlotLayout(itemId)
57
+ const layout = getEntityLayout(itemId)
58
+ const slots = layout?.slots ?? []
57
59
 
58
- const modules: NFTModuleSlot[] = layout.map((slot, i) => ({
59
- type: slot.type,
60
+ const modules: NFTModuleSlot[] = slots.map((slot, i) => ({
61
+ type: moduleSlotTypeToCode(slot.type),
60
62
  installed:
61
63
  moduleItems[i] && moduleItems[i] !== 0
62
64
  ? {item_id: moduleItems[i], stats: moduleStats[i]}
@@ -1,18 +1,22 @@
1
1
  import type {ResolvedItem} from './resolve-item'
2
- import type {ResourceCategory, ResourceTier} from '../types'
2
+ import type {ResourceCategory} from '../types'
3
3
  import {CATEGORY_LABELS, TIER_ADJECTIVES, tierNumber} from '../types'
4
4
  import {formatMass as defaultFormatMass} from '../format'
5
5
 
6
6
  export interface DisplayNameInput {
7
7
  itemType: 'resource' | 'component' | 'module' | 'entity' | string
8
- tier: ResourceTier | string
8
+ tier: number | string
9
9
  category?: ResourceCategory
10
10
  name: string
11
11
  }
12
12
 
13
+ function asTierNumber(tier: number | string): number {
14
+ return typeof tier === 'number' ? tier : tierNumber(tier)
15
+ }
16
+
13
17
  export function displayName(resolved: DisplayNameInput): string {
14
18
  if (resolved.itemType === 'resource') {
15
- const adj = TIER_ADJECTIVES[tierNumber(resolved.tier)] ?? 'Unknown'
19
+ const adj = TIER_ADJECTIVES[asTierNumber(resolved.tier)] ?? 'Unknown'
16
20
  const cat = resolved.category ? CATEGORY_LABELS[resolved.category] : 'Resource'
17
21
  return `${adj} ${cat}`
18
22
  }
@@ -28,7 +32,7 @@ export interface DescribeOptions {
28
32
  export function describeItem(resolved: ResolvedItem, opts?: DescribeOptions): string {
29
33
  const massFmt = opts?.formatMass ?? defaultFormatMass
30
34
  const mass = massFmt(resolved.mass)
31
- const tier = `T${tierNumber(resolved.tier)}`
35
+ const tier = `T${asTierNumber(resolved.tier)}`
32
36
  if (resolved.itemType === 'resource') {
33
37
  const cat = resolved.category ? CATEGORY_LABELS[resolved.category] : 'Resource'
34
38
  const header = `${tier} ${cat}`
@@ -1,8 +1,9 @@
1
1
  import {UInt16, UInt64} from '@wharfkit/antelope'
2
2
  import type {UInt16Type, UInt64Type} from '@wharfkit/antelope'
3
- import type {ResourceCategory, ResourceTier} from '../types'
3
+ import type {ResourceCategory} from '../types'
4
4
  import {getItem} from '../market/items'
5
- import {getComponentById, getEntityRecipeByItemId, getModuleRecipeByItemId} from '../data/recipes'
5
+ import {getEntityLayout} from '../data/recipes-runtime'
6
+ import {entityMetadata, itemMetadata} from '../data/metadata'
6
7
  import {
7
8
  getModuleCapabilityType,
8
9
  isModuleItem,
@@ -35,6 +36,12 @@ import {
35
36
  moduleIcon,
36
37
  } from '../data/colors'
37
38
  import {ServerContract} from '../contracts'
39
+ import {
40
+ ITEM_CONTAINER_T1_PACKED,
41
+ ITEM_CONTAINER_T2_PACKED,
42
+ ITEM_SHIP_T1_PACKED,
43
+ ITEM_WAREHOUSE_T1_PACKED,
44
+ } from '../data/item-ids'
38
45
 
39
46
  export interface ResolvedItemStat {
40
47
  key: string
@@ -42,7 +49,7 @@ export interface ResolvedItemStat {
42
49
  abbreviation: string
43
50
  value: number
44
51
  color: string
45
- category: ResourceCategory
52
+ category?: ResourceCategory
46
53
  inverted?: boolean
47
54
  }
48
55
 
@@ -65,7 +72,7 @@ export interface ResolvedItem {
65
72
  icon: string
66
73
  abbreviation: string | null
67
74
  category?: ResourceCategory
68
- tier: ResourceTier
75
+ tier: number
69
76
  mass: number
70
77
  itemType: ResolvedItemType
71
78
  stats?: ResolvedItemStat[]
@@ -85,7 +92,7 @@ function resolveResource(id: number, stats?: UInt64Type): ResolvedItem {
85
92
  const item = getItem(id)
86
93
  const cat = item.category
87
94
  let resolvedStats: ResolvedItemStat[] | undefined
88
- if (stats !== undefined) {
95
+ if (stats !== undefined && cat) {
89
96
  const bigStats = toBigStats(stats)
90
97
  const defs = getStatDefinitions(cat)
91
98
  const values = [decodeStat(bigStats, 0), decodeStat(bigStats, 1), decodeStat(bigStats, 2)]
@@ -101,19 +108,19 @@ function resolveResource(id: number, stats?: UInt64Type): ResolvedItem {
101
108
  }
102
109
  return {
103
110
  itemId: id,
104
- name: item.displayName,
105
- icon: categoryIcons[cat] ?? '⬡',
111
+ name: item.name,
112
+ icon: cat ? categoryIcons[cat] : '⬡',
106
113
  abbreviation: null,
107
114
  category: cat,
108
115
  tier: item.tier,
109
- mass: Number(item.mass.value.toString()),
116
+ mass: item.mass,
110
117
  itemType: 'resource',
111
118
  stats: resolvedStats,
112
119
  }
113
120
  }
114
121
 
115
122
  function resolveComponent(id: number, stats?: UInt64Type): ResolvedItem {
116
- const comp = getComponentById(id)!
123
+ const item = getItem(id)
117
124
  let resolvedStats: ResolvedItemStat[] | undefined
118
125
  if (stats !== undefined) {
119
126
  const decoded = decodeCraftedItemStats(id, toBigStats(stats))
@@ -124,26 +131,23 @@ function resolveComponent(id: number, stats?: UInt64Type): ResolvedItem {
124
131
  .concat(getStatDefinitions('regolith'))
125
132
  .concat(getStatDefinitions('biomass'))
126
133
  const def = allDefs.find((d) => d.key === key)
127
- const statDef = comp.stats.find((s) => s.key === key)
128
- const cat = (statDef?.source ?? 'ore') as ResourceCategory
129
134
  return {
130
135
  key,
131
136
  label: def?.label ?? key,
132
137
  abbreviation: def?.abbreviation ?? key.slice(0, 3).toUpperCase(),
133
138
  value,
134
- color: categoryColors[cat],
135
- category: cat,
139
+ color: '#9BADB8',
136
140
  inverted: def?.inverted,
137
141
  }
138
142
  })
139
143
  }
140
144
  return {
141
145
  itemId: id,
142
- name: comp.name,
146
+ name: item.name,
143
147
  icon: itemAbbreviations[id] ?? componentIcon,
144
148
  abbreviation: itemAbbreviations[id] ?? null,
145
- tier: 't1' as ResourceTier,
146
- mass: comp.mass,
149
+ tier: item.tier,
150
+ mass: item.mass,
147
151
  itemType: 'component',
148
152
  stats: resolvedStats,
149
153
  }
@@ -220,9 +224,9 @@ function computeCapabilityGroup(
220
224
  }
221
225
  case MODULE_STORAGE: {
222
226
  const str = stats.strength ?? 500
223
- const fin = stats.fineness ?? 500
227
+ const hrd = stats.hardness ?? 500
224
228
  const sat = stats.saturation ?? 500
225
- const statSum = str + fin + sat
229
+ const statSum = str + hrd + sat
226
230
  const pct = 10 + Math.floor((statSum * 10) / 2997)
227
231
  return {capability: 'Storage', attributes: [{label: 'Capacity Bonus', value: pct}]}
228
232
  }
@@ -232,7 +236,7 @@ function computeCapabilityGroup(
232
236
  }
233
237
 
234
238
  function resolveModule(id: number, stats?: UInt64Type): ResolvedItem {
235
- const recipe = getModuleRecipeByItemId(id)!
239
+ const item = getItem(id)
236
240
  let attributes: ResolvedAttributeGroup[] | undefined
237
241
  if (stats !== undefined) {
238
242
  const decoded = decodeCraftedItemStats(id, toBigStats(stats))
@@ -242,59 +246,64 @@ function resolveModule(id: number, stats?: UInt64Type): ResolvedItem {
242
246
  }
243
247
  return {
244
248
  itemId: id,
245
- name: recipe.name,
249
+ name: item.name,
246
250
  icon: itemAbbreviations[id] ?? moduleIcon,
247
251
  abbreviation: itemAbbreviations[id] ?? null,
248
- tier: 't1' as ResourceTier,
249
- mass: 0,
252
+ tier: item.tier,
253
+ mass: item.mass,
250
254
  itemType: 'module',
251
255
  attributes,
252
256
  }
253
257
  }
254
258
 
259
+ function hullCapsForEntity(
260
+ itemId: number,
261
+ decoded: Record<string, number>
262
+ ): {
263
+ hullmass: number
264
+ capacity: number
265
+ } {
266
+ switch (itemId) {
267
+ case ITEM_SHIP_T1_PACKED:
268
+ return computeShipHullCapabilities(decoded)
269
+ case ITEM_WAREHOUSE_T1_PACKED:
270
+ return computeWarehouseHullCapabilities(decoded)
271
+ case ITEM_CONTAINER_T1_PACKED:
272
+ return computeContainerCapabilities(decoded)
273
+ case ITEM_CONTAINER_T2_PACKED:
274
+ return computeContainerT2Capabilities(decoded)
275
+ default:
276
+ throw new Error(`resolveItem: no capacity formula wired for entity item ${itemId}`)
277
+ }
278
+ }
279
+
255
280
  function resolveEntity(
256
281
  id: number,
257
282
  stats?: UInt64Type,
258
283
  modules?: ServerContract.Types.module_entry[]
259
284
  ): ResolvedItem {
260
- const recipe = getEntityRecipeByItemId(id)!
285
+ const item = getItem(id)
286
+ const layout = getEntityLayout(id)
261
287
  let attributes: ResolvedAttributeGroup[] | undefined
262
288
  let moduleSlots: ResolvedModuleSlot[] | undefined
263
289
 
264
290
  if (stats !== undefined) {
265
291
  const decoded = decodeCraftedItemStats(id, toBigStats(stats))
266
- attributes = []
267
-
268
- let hullCaps: {hullmass: number; capacity: number}
269
- switch (recipe.id) {
270
- case 'ship-t1':
271
- hullCaps = computeShipHullCapabilities(decoded)
272
- break
273
- case 'warehouse-t1':
274
- hullCaps = computeWarehouseHullCapabilities(decoded)
275
- break
276
- case 'container':
277
- hullCaps = computeContainerCapabilities(decoded)
278
- break
279
- case 'container-t2':
280
- hullCaps = computeContainerT2Capabilities(decoded)
281
- break
282
- default:
283
- throw new Error(
284
- `resolveItem: no capacity formula wired for entity recipe "${recipe.id}"`
285
- )
286
- }
287
- attributes.push({
288
- capability: 'Hull',
289
- attributes: [
290
- {label: 'Mass', value: hullCaps.hullmass},
291
- {label: 'Capacity', value: hullCaps.capacity},
292
- ],
293
- })
292
+ const hullCaps = hullCapsForEntity(id, decoded)
293
+ attributes = [
294
+ {
295
+ capability: 'Hull',
296
+ attributes: [
297
+ {label: 'Mass', value: hullCaps.hullmass},
298
+ {label: 'Capacity', value: hullCaps.capacity},
299
+ ],
300
+ },
301
+ ]
294
302
  }
295
303
 
296
- if (recipe.moduleSlots) {
297
- moduleSlots = recipe.moduleSlots.map((slot, i) => {
304
+ if (layout && layout.slots.length > 0) {
305
+ const slotLabels = entityMetadata[id]?.moduleSlotLabels ?? []
306
+ moduleSlots = layout.slots.map((slot, i) => {
298
307
  const mod = modules?.[i]
299
308
  if (mod?.installed) {
300
309
  const modItemId = Number(mod.installed.item_id.value.toString())
@@ -302,24 +311,32 @@ function resolveEntity(
302
311
  const decodedStats = decodeCraftedItemStats(modItemId, modStats)
303
312
  const modType = getModuleCapabilityType(modItemId)
304
313
  const group = computeCapabilityGroup(modType, decodedStats)
305
- const modRecipe = getModuleRecipeByItemId(modItemId)
314
+ let modName = 'Module'
315
+ try {
316
+ modName = getItem(modItemId).name
317
+ } catch {
318
+ modName = itemMetadata[modItemId]?.name ?? 'Module'
319
+ }
306
320
  return {
307
- name: modRecipe?.name ?? 'Module',
321
+ name: modName,
308
322
  installed: true,
309
323
  attributes: group?.attributes,
310
324
  }
311
325
  }
312
- return {installed: false}
326
+ return {
327
+ name: slotLabels[i] ?? slot.type,
328
+ installed: false,
329
+ }
313
330
  })
314
331
  }
315
332
 
316
333
  return {
317
334
  itemId: id,
318
- name: recipe.name,
335
+ name: item.name,
319
336
  icon: itemAbbreviations[id] ?? componentIcon,
320
337
  abbreviation: itemAbbreviations[id] ?? null,
321
- tier: 't1' as ResourceTier,
322
- mass: 0,
338
+ tier: item.tier,
339
+ mass: item.mass,
323
340
  itemType: 'entity',
324
341
  attributes,
325
342
  moduleSlots,
@@ -332,12 +349,10 @@ export function resolveItem(
332
349
  modules?: ServerContract.Types.module_entry[]
333
350
  ): ResolvedItem {
334
351
  const id = toNum(itemId)
352
+ const item = getItem(id)
335
353
 
336
- if (isModuleItem(id)) return resolveModule(id, stats)
337
-
338
- if (getComponentById(id)) return resolveComponent(id, stats)
339
-
340
- if (getEntityRecipeByItemId(id)) return resolveEntity(id, stats, modules)
341
-
354
+ if (item.type === 'module' || isModuleItem(id)) return resolveModule(id, stats)
355
+ if (item.type === 'component') return resolveComponent(id, stats)
356
+ if (item.type === 'entity') return resolveEntity(id, stats, modules)
342
357
  return resolveResource(id, stats)
343
358
  }