@shipload/sdk 2.0.0-rc13 → 2.0.0-rc15
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.
- package/lib/shipload.d.ts +148 -60
- package/lib/shipload.js +970 -606
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +956 -605
- package/lib/shipload.m.js.map +1 -1
- package/package.json +1 -1
- package/src/capabilities/modules.ts +3 -3
- package/src/capabilities/storage.ts +6 -9
- package/src/contracts/server.ts +37 -34
- package/src/data/categories.ts +22 -25
- package/src/data/colors.ts +28 -10
- package/src/data/items.json +15 -15
- package/src/data/recipes.ts +93 -87
- package/src/derivation/crafting.ts +50 -36
- package/src/derivation/resources.ts +23 -23
- package/src/derivation/stats.ts +29 -29
- package/src/derivation/stratum.ts +12 -0
- package/src/entities/container.ts +7 -6
- package/src/entities/makers.ts +126 -11
- package/src/entities/ship-deploy.ts +25 -30
- package/src/entities/ship.ts +16 -4
- package/src/entities/warehouse.ts +36 -1
- package/src/format.ts +12 -0
- package/src/index-module.ts +26 -4
- package/src/managers/actions.ts +48 -8
- package/src/market/items.ts +1 -1
- package/src/nft/description.ts +47 -46
- package/src/nft/deserializers.ts +8 -8
- package/src/resolution/describe-module.ts +165 -0
- package/src/resolution/display-name.ts +57 -0
- package/src/resolution/resolve-item.ts +68 -45
- package/src/scheduling/projection.ts +3 -1
- package/src/types/capabilities.ts +5 -0
- package/src/types.ts +1 -1
package/src/index-module.ts
CHANGED
|
@@ -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'
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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,
|
|
@@ -312,3 +329,8 @@ export {
|
|
|
312
329
|
ITEM_TYPE_ENTITY,
|
|
313
330
|
itemTypeCode,
|
|
314
331
|
} from './data/tiers'
|
|
332
|
+
|
|
333
|
+
export {formatMass, formatMassDelta} from './format'
|
|
334
|
+
|
|
335
|
+
export {displayName, describeItem} from './resolution/display-name'
|
|
336
|
+
export type {DescribeOptions} from './resolution/display-name'
|
package/src/managers/actions.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Action,
|
|
3
|
+
Int64,
|
|
4
|
+
Name,
|
|
5
|
+
NameType,
|
|
6
|
+
UInt16,
|
|
7
|
+
UInt16Type,
|
|
8
|
+
UInt32,
|
|
9
|
+
UInt32Type,
|
|
10
|
+
UInt64,
|
|
11
|
+
UInt64Type,
|
|
12
|
+
} from '@wharfkit/antelope'
|
|
2
13
|
import {BaseManager} from './base'
|
|
3
14
|
import {CoordinatesType, EntityType, EntityTypeName} from '../types'
|
|
4
15
|
import {ServerContract} from '../contracts'
|
|
@@ -71,7 +82,8 @@ export class ActionsManager extends BaseManager {
|
|
|
71
82
|
sourceId: UInt64Type,
|
|
72
83
|
destType: EntityTypeName,
|
|
73
84
|
destId: UInt64Type,
|
|
74
|
-
|
|
85
|
+
itemId: UInt64Type,
|
|
86
|
+
stats: UInt64Type,
|
|
75
87
|
quantity: UInt64Type
|
|
76
88
|
): Action {
|
|
77
89
|
return this.server.action('transfer', {
|
|
@@ -79,7 +91,8 @@ export class ActionsManager extends BaseManager {
|
|
|
79
91
|
source_id: UInt64.from(sourceId),
|
|
80
92
|
dest_type: destType,
|
|
81
93
|
dest_id: UInt64.from(destId),
|
|
82
|
-
item_id: UInt16.from(
|
|
94
|
+
item_id: UInt16.from(itemId),
|
|
95
|
+
stats: UInt64.from(stats),
|
|
83
96
|
quantity: UInt32.from(quantity),
|
|
84
97
|
})
|
|
85
98
|
}
|
|
@@ -97,10 +110,21 @@ export class ActionsManager extends BaseManager {
|
|
|
97
110
|
})
|
|
98
111
|
}
|
|
99
112
|
|
|
100
|
-
gather(
|
|
113
|
+
gather(
|
|
114
|
+
source: EntityRefInput,
|
|
115
|
+
destination: EntityRefInput,
|
|
116
|
+
stratum: UInt16Type,
|
|
117
|
+
quantity: UInt32Type
|
|
118
|
+
): Action {
|
|
101
119
|
return this.server.action('gather', {
|
|
102
|
-
|
|
103
|
-
|
|
120
|
+
source: ServerContract.Types.entity_ref.from({
|
|
121
|
+
entity_type: source.entityType,
|
|
122
|
+
entity_id: UInt64.from(source.entityId),
|
|
123
|
+
}),
|
|
124
|
+
destination: ServerContract.Types.entity_ref.from({
|
|
125
|
+
entity_type: destination.entityType,
|
|
126
|
+
entity_id: UInt64.from(destination.entityId),
|
|
127
|
+
}),
|
|
104
128
|
stratum: UInt16.from(stratum),
|
|
105
129
|
quantity: UInt32.from(quantity),
|
|
106
130
|
})
|
|
@@ -152,14 +176,14 @@ export class ActionsManager extends BaseManager {
|
|
|
152
176
|
entityType: EntityTypeName,
|
|
153
177
|
entityId: UInt64Type,
|
|
154
178
|
packedItemId: number,
|
|
155
|
-
|
|
179
|
+
stats: bigint,
|
|
156
180
|
entityName: string
|
|
157
181
|
): Action {
|
|
158
182
|
return this.server.action('deploy', {
|
|
159
183
|
entity_type: entityType,
|
|
160
184
|
id: UInt64.from(entityId),
|
|
161
185
|
packed_item_id: UInt16.from(packedItemId),
|
|
162
|
-
|
|
186
|
+
stats: UInt64.from(stats),
|
|
163
187
|
entity_name: entityName,
|
|
164
188
|
})
|
|
165
189
|
}
|
|
@@ -194,6 +218,22 @@ export class ActionsManager extends BaseManager {
|
|
|
194
218
|
})
|
|
195
219
|
}
|
|
196
220
|
|
|
221
|
+
wrap(
|
|
222
|
+
owner: NameType,
|
|
223
|
+
entityType: EntityTypeName,
|
|
224
|
+
entityId: UInt64Type,
|
|
225
|
+
cargoId: UInt64Type,
|
|
226
|
+
quantity: UInt64Type
|
|
227
|
+
): Action {
|
|
228
|
+
return this.server.action('wrap', {
|
|
229
|
+
owner: Name.from(owner),
|
|
230
|
+
entity_type: entityType,
|
|
231
|
+
entity_id: UInt64.from(entityId),
|
|
232
|
+
cargo_id: UInt64.from(cargoId),
|
|
233
|
+
quantity: UInt64.from(quantity),
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
|
|
197
237
|
joinGame(account: NameType, companyName: string): Action[] {
|
|
198
238
|
return [this.foundCompany(account, companyName), this.join(account)]
|
|
199
239
|
}
|
package/src/market/items.ts
CHANGED
package/src/nft/description.ts
CHANGED
|
@@ -25,44 +25,45 @@ function idiv(a: number, b: number): number {
|
|
|
25
25
|
return Math.floor(a / b)
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export function computeBaseHullmass(
|
|
29
|
-
const density = decodeStat(
|
|
28
|
+
export function computeBaseHullmass(stats: bigint): number {
|
|
29
|
+
const density = decodeStat(stats, 1)
|
|
30
30
|
return 25000 + 75 * density
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export function computeBaseCapacityShip(
|
|
34
|
-
const s = decodeStat(
|
|
33
|
+
export function computeBaseCapacityShip(stats: bigint): number {
|
|
34
|
+
const s = decodeStat(stats, 0) + decodeStat(stats, 2) + decodeStat(stats, 3)
|
|
35
35
|
return Math.floor(1_000_000 * Math.pow(10, s / 2997))
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export function computeBaseCapacityWarehouse(
|
|
39
|
-
const s = decodeStat(
|
|
38
|
+
export function computeBaseCapacityWarehouse(stats: bigint): number {
|
|
39
|
+
const s = decodeStat(stats, 0) + decodeStat(stats, 2) + decodeStat(stats, 3)
|
|
40
40
|
return Math.floor(20_000_000 * Math.pow(10, s / 2997))
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
export const computeEngineThrust = (vol: number): number => 400 + idiv(vol * 3, 4)
|
|
44
44
|
export const computeEngineDrain = (thm: number): number => Math.max(30, 50 - idiv(thm, 70))
|
|
45
45
|
export const computeGeneratorCap = (res: number): number => 300 + idiv(res, 6)
|
|
46
|
-
export const computeGeneratorRech = (
|
|
46
|
+
export const computeGeneratorRech = (ref: number): number => 1 + idiv(ref * 3, 1000)
|
|
47
47
|
export const computeGathererYield = (str: number): number => 200 + str
|
|
48
|
-
export const computeGathererDrain = (con: number): number =>
|
|
48
|
+
export const computeGathererDrain = (con: number): number =>
|
|
49
|
+
Math.max(250, 1250 - idiv(con * 25, 20))
|
|
49
50
|
export const computeGathererDepth = (tol: number): number => 200 + idiv(tol * 3, 2)
|
|
50
51
|
export const computeGathererSpeed = (ref: number): number => 100 + idiv(ref * 4, 5)
|
|
51
|
-
export const computeLoaderMass = (
|
|
52
|
+
export const computeLoaderMass = (fin: number): number => Math.max(200, 2000 - fin * 2)
|
|
52
53
|
export const computeLoaderThrust = (pla: number): number => 1 + idiv(pla, 500)
|
|
53
54
|
export const computeCrafterSpeed = (rea: number): number => 100 + idiv(rea * 4, 5)
|
|
54
|
-
export const computeCrafterDrain = (
|
|
55
|
+
export const computeCrafterDrain = (com: number): number => Math.max(5, 30 - idiv(com, 33))
|
|
55
56
|
|
|
56
57
|
export function entityDisplayName(itemId: number): string {
|
|
57
58
|
switch (itemId) {
|
|
58
59
|
case ITEM_SHIP_T1_PACKED:
|
|
59
|
-
return 'Ship
|
|
60
|
+
return 'Ship'
|
|
60
61
|
case ITEM_WAREHOUSE_T1_PACKED:
|
|
61
|
-
return 'Warehouse
|
|
62
|
+
return 'Warehouse'
|
|
62
63
|
case ITEM_CONTAINER_T1_PACKED:
|
|
63
|
-
return 'Container
|
|
64
|
+
return 'Container'
|
|
64
65
|
case ITEM_CONTAINER_T2_PACKED:
|
|
65
|
-
return 'Container
|
|
66
|
+
return 'Container'
|
|
66
67
|
default:
|
|
67
68
|
return 'Entity'
|
|
68
69
|
}
|
|
@@ -71,23 +72,23 @@ export function entityDisplayName(itemId: number): string {
|
|
|
71
72
|
export function moduleDisplayName(itemId: number): string {
|
|
72
73
|
switch (itemId) {
|
|
73
74
|
case ITEM_ENGINE_T1:
|
|
74
|
-
return 'Engine
|
|
75
|
+
return 'Engine'
|
|
75
76
|
case ITEM_GENERATOR_T1:
|
|
76
|
-
return 'Generator
|
|
77
|
+
return 'Generator'
|
|
77
78
|
case ITEM_GATHERER_T1:
|
|
78
|
-
return 'Gatherer
|
|
79
|
+
return 'Gatherer'
|
|
79
80
|
case ITEM_LOADER_T1:
|
|
80
|
-
return 'Loader
|
|
81
|
+
return 'Loader'
|
|
81
82
|
case ITEM_MANUFACTURING_T1:
|
|
82
|
-
return 'Manufacturing
|
|
83
|
+
return 'Manufacturing'
|
|
83
84
|
case ITEM_STORAGE_T1:
|
|
84
|
-
return 'Storage
|
|
85
|
+
return 'Storage'
|
|
85
86
|
default:
|
|
86
87
|
return 'Module'
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
export function formatModuleLine(slot: number, itemId: number,
|
|
91
|
+
export function formatModuleLine(slot: number, itemId: number, stats: bigint): string {
|
|
91
92
|
let out = `Slot ${slot} - `
|
|
92
93
|
if (itemId === 0) {
|
|
93
94
|
out += '(empty)'
|
|
@@ -99,44 +100,44 @@ export function formatModuleLine(slot: number, itemId: number, seed: bigint): st
|
|
|
99
100
|
|
|
100
101
|
switch (subtype) {
|
|
101
102
|
case MODULE_ENGINE: {
|
|
102
|
-
const vol = decodeStat(
|
|
103
|
-
const thm = decodeStat(
|
|
103
|
+
const vol = decodeStat(stats, 0)
|
|
104
|
+
const thm = decodeStat(stats, 1)
|
|
104
105
|
out += ` Thrust ${computeEngineThrust(vol)} Drain ${computeEngineDrain(thm)}`
|
|
105
106
|
break
|
|
106
107
|
}
|
|
107
108
|
case MODULE_GENERATOR: {
|
|
108
|
-
const res = decodeStat(
|
|
109
|
-
const
|
|
110
|
-
out += ` Capacity ${computeGeneratorCap(res)} Recharge ${computeGeneratorRech(
|
|
109
|
+
const res = decodeStat(stats, 0)
|
|
110
|
+
const ref = decodeStat(stats, 1)
|
|
111
|
+
out += ` Capacity ${computeGeneratorCap(res)} Recharge ${computeGeneratorRech(ref)}`
|
|
111
112
|
break
|
|
112
113
|
}
|
|
113
114
|
case MODULE_GATHERER: {
|
|
114
|
-
const str = decodeStat(
|
|
115
|
-
const tol = decodeStat(
|
|
116
|
-
const con = decodeStat(
|
|
117
|
-
const ref = decodeStat(
|
|
115
|
+
const str = decodeStat(stats, 0)
|
|
116
|
+
const tol = decodeStat(stats, 1)
|
|
117
|
+
const con = decodeStat(stats, 3)
|
|
118
|
+
const ref = decodeStat(stats, 4)
|
|
118
119
|
out += ` Yield ${computeGathererYield(str)} Depth ${computeGathererDepth(
|
|
119
120
|
tol
|
|
120
121
|
)} Speed ${computeGathererSpeed(ref)} Drain ${computeGathererDrain(con)}`
|
|
121
122
|
break
|
|
122
123
|
}
|
|
123
124
|
case MODULE_LOADER: {
|
|
124
|
-
const
|
|
125
|
-
const pla = decodeStat(
|
|
126
|
-
out += ` Mass ${computeLoaderMass(
|
|
125
|
+
const fin = decodeStat(stats, 0)
|
|
126
|
+
const pla = decodeStat(stats, 1)
|
|
127
|
+
out += ` Mass ${computeLoaderMass(fin)} Thrust ${computeLoaderThrust(pla)}`
|
|
127
128
|
break
|
|
128
129
|
}
|
|
129
130
|
case MODULE_CRAFTER: {
|
|
130
|
-
const rea = decodeStat(
|
|
131
|
-
const
|
|
132
|
-
out += ` Speed ${computeCrafterSpeed(rea)} Drain ${computeCrafterDrain(
|
|
131
|
+
const rea = decodeStat(stats, 0)
|
|
132
|
+
const com = decodeStat(stats, 1)
|
|
133
|
+
out += ` Speed ${computeCrafterSpeed(rea)} Drain ${computeCrafterDrain(com)}`
|
|
133
134
|
break
|
|
134
135
|
}
|
|
135
136
|
case MODULE_STORAGE: {
|
|
136
|
-
const str = decodeStat(
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
const sum = str +
|
|
137
|
+
const str = decodeStat(stats, 0)
|
|
138
|
+
const fin = decodeStat(stats, 2)
|
|
139
|
+
const sat = decodeStat(stats, 3)
|
|
140
|
+
const sum = str + fin + sat
|
|
140
141
|
const pct = 10 + idiv(sum * 10, 2997)
|
|
141
142
|
out += ` +${pct}% capacity`
|
|
142
143
|
break
|
|
@@ -147,16 +148,16 @@ export function formatModuleLine(slot: number, itemId: number, seed: bigint): st
|
|
|
147
148
|
|
|
148
149
|
export function buildEntityDescription(
|
|
149
150
|
itemId: number,
|
|
150
|
-
|
|
151
|
+
hullStats: bigint,
|
|
151
152
|
moduleItems: number[],
|
|
152
|
-
|
|
153
|
+
moduleStats: bigint[]
|
|
153
154
|
): string {
|
|
154
|
-
const hullMass = computeBaseHullmass(
|
|
155
|
+
const hullMass = computeBaseHullmass(hullStats)
|
|
155
156
|
let baseCapacity = 0
|
|
156
157
|
if (itemId === ITEM_SHIP_T1_PACKED) {
|
|
157
|
-
baseCapacity = computeBaseCapacityShip(
|
|
158
|
+
baseCapacity = computeBaseCapacityShip(hullStats)
|
|
158
159
|
} else if (itemId === ITEM_WAREHOUSE_T1_PACKED) {
|
|
159
|
-
baseCapacity = computeBaseCapacityWarehouse(
|
|
160
|
+
baseCapacity = computeBaseCapacityWarehouse(hullStats)
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
let out = entityDisplayName(itemId)
|
|
@@ -167,7 +168,7 @@ export function buildEntityDescription(
|
|
|
167
168
|
out += '\n\n'
|
|
168
169
|
|
|
169
170
|
for (let i = 0; i < moduleItems.length; i++) {
|
|
170
|
-
out += formatModuleLine(i, moduleItems[i],
|
|
171
|
+
out += formatModuleLine(i, moduleItems[i], moduleStats[i] ?? 0n)
|
|
171
172
|
out += '\n'
|
|
172
173
|
}
|
|
173
174
|
|
package/src/nft/deserializers.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
|
|
10
10
|
export interface NFTInstalledModule {
|
|
11
11
|
item_id: number
|
|
12
|
-
|
|
12
|
+
stats: string
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export interface NFTModuleSlot {
|
|
@@ -20,13 +20,13 @@ export interface NFTModuleSlot {
|
|
|
20
20
|
export interface NFTCargoItem {
|
|
21
21
|
item_id: number
|
|
22
22
|
quantity: number
|
|
23
|
-
|
|
23
|
+
stats: string
|
|
24
24
|
modules?: NFTModuleSlot[]
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export interface NFTCommonBase {
|
|
28
28
|
quantity: number
|
|
29
|
-
|
|
29
|
+
stats: string
|
|
30
30
|
origin_x: string
|
|
31
31
|
origin_y: string
|
|
32
32
|
}
|
|
@@ -34,7 +34,7 @@ export interface NFTCommonBase {
|
|
|
34
34
|
export function readCommonBase(data: Record<string, any>): NFTCommonBase {
|
|
35
35
|
return {
|
|
36
36
|
quantity: Number(data.quantity),
|
|
37
|
-
|
|
37
|
+
stats: String(data.stats),
|
|
38
38
|
origin_x: String(data.origin_x),
|
|
39
39
|
origin_y: String(data.origin_y),
|
|
40
40
|
}
|
|
@@ -42,7 +42,7 @@ export function readCommonBase(data: Record<string, any>): NFTCommonBase {
|
|
|
42
42
|
|
|
43
43
|
export function deserializeScalar(data: Record<string, any>, itemId: number): NFTCargoItem {
|
|
44
44
|
const base = readCommonBase(data)
|
|
45
|
-
return {item_id: itemId, quantity: base.quantity,
|
|
45
|
+
return {item_id: itemId, quantity: base.quantity, stats: base.stats}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export const deserializeResource = deserializeScalar
|
|
@@ -52,18 +52,18 @@ export const deserializeModule = deserializeScalar
|
|
|
52
52
|
export function deserializeEntity(data: Record<string, any>, itemId: number): NFTCargoItem {
|
|
53
53
|
const base = readCommonBase(data)
|
|
54
54
|
const moduleItems: number[] = (data.module_items ?? []).map((v: any) => Number(v))
|
|
55
|
-
const
|
|
55
|
+
const moduleStats: string[] = (data.module_stats ?? []).map((v: any) => String(v))
|
|
56
56
|
const layout = getEntitySlotLayout(itemId)
|
|
57
57
|
|
|
58
58
|
const modules: NFTModuleSlot[] = layout.map((slot, i) => ({
|
|
59
59
|
type: slot.type,
|
|
60
60
|
installed:
|
|
61
61
|
moduleItems[i] && moduleItems[i] !== 0
|
|
62
|
-
? {item_id: moduleItems[i],
|
|
62
|
+
? {item_id: moduleItems[i], stats: moduleStats[i]}
|
|
63
63
|
: undefined,
|
|
64
64
|
}))
|
|
65
65
|
|
|
66
|
-
return {item_id: itemId, quantity: base.quantity,
|
|
66
|
+
return {item_id: itemId, quantity: base.quantity, stats: base.stats, modules}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
export function deserializeAsset(data: Record<string, any>, itemId: number): NFTCargoItem {
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type {ResolvedItem, ResolvedModuleSlot} from './resolve-item'
|
|
2
|
+
|
|
3
|
+
export interface TextSpan {
|
|
4
|
+
text: string
|
|
5
|
+
highlight?: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface CapabilityInput {
|
|
9
|
+
capability: string
|
|
10
|
+
attributes: {label: string; value: number}[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ModuleDescription {
|
|
14
|
+
id: string
|
|
15
|
+
template: string
|
|
16
|
+
params: Readonly<Record<string, number | string>>
|
|
17
|
+
highlightKeys: readonly string[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RenderDescriptionOptions {
|
|
21
|
+
translate?: (id: string, fallback: string) => string
|
|
22
|
+
formatNumber?: (n: number) => string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface TemplateSpec {
|
|
26
|
+
id: string
|
|
27
|
+
template: string
|
|
28
|
+
params: readonly [string, string][]
|
|
29
|
+
highlightKeys: readonly string[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const TEMPLATES: Record<string, TemplateSpec> = {
|
|
33
|
+
engine: {
|
|
34
|
+
id: 'module.engine.description',
|
|
35
|
+
template:
|
|
36
|
+
'generates {thrust} thrust for travel while draining {drain} energy per distance travelled',
|
|
37
|
+
params: [
|
|
38
|
+
['thrust', 'Thrust'],
|
|
39
|
+
['drain', 'Drain'],
|
|
40
|
+
],
|
|
41
|
+
highlightKeys: ['thrust', 'drain'],
|
|
42
|
+
},
|
|
43
|
+
generator: {
|
|
44
|
+
id: 'module.generator.description',
|
|
45
|
+
template:
|
|
46
|
+
'holds {capacity} maximum energy and restores {recharge} per second while recharging',
|
|
47
|
+
params: [
|
|
48
|
+
['capacity', 'Capacity'],
|
|
49
|
+
['recharge', 'Recharge'],
|
|
50
|
+
],
|
|
51
|
+
highlightKeys: ['capacity', 'recharge'],
|
|
52
|
+
},
|
|
53
|
+
gatherer: {
|
|
54
|
+
id: 'module.gatherer.description',
|
|
55
|
+
template:
|
|
56
|
+
'mines resources at {yield} speed to a max depth of {depth} with {speed} gather speed while draining {drain} energy per second',
|
|
57
|
+
params: [
|
|
58
|
+
['yield', 'Yield'],
|
|
59
|
+
['drain', 'Drain'],
|
|
60
|
+
['depth', 'Depth'],
|
|
61
|
+
['speed', 'Speed'],
|
|
62
|
+
],
|
|
63
|
+
highlightKeys: ['yield', 'depth', 'speed', 'drain'],
|
|
64
|
+
},
|
|
65
|
+
loader: {
|
|
66
|
+
id: 'module.loader.description',
|
|
67
|
+
template:
|
|
68
|
+
'{quantity} loader that generates {thrust} thrust with a weight of {mass} per unit',
|
|
69
|
+
params: [
|
|
70
|
+
['quantity', 'Quantity'],
|
|
71
|
+
['thrust', 'Thrust'],
|
|
72
|
+
['mass', 'Mass'],
|
|
73
|
+
],
|
|
74
|
+
highlightKeys: ['quantity', 'thrust', 'mass'],
|
|
75
|
+
},
|
|
76
|
+
manufacturing: {
|
|
77
|
+
id: 'module.manufacturing.description',
|
|
78
|
+
template: 'manufactures items at {speed} speed while draining {drain} energy per second',
|
|
79
|
+
params: [
|
|
80
|
+
['speed', 'Speed'],
|
|
81
|
+
['drain', 'Drain'],
|
|
82
|
+
],
|
|
83
|
+
highlightKeys: ['speed', 'drain'],
|
|
84
|
+
},
|
|
85
|
+
storage: {
|
|
86
|
+
id: 'module.storage.description',
|
|
87
|
+
template: 'boosts cargo capacity by {bonus}%',
|
|
88
|
+
params: [['bonus', 'Capacity Bonus']],
|
|
89
|
+
highlightKeys: ['bonus'],
|
|
90
|
+
},
|
|
91
|
+
hauler: {
|
|
92
|
+
id: 'module.hauler.description',
|
|
93
|
+
template:
|
|
94
|
+
'locks onto up to {capacity} targets at {efficiency} efficiency while draining {drain} energy per distance travelled per target',
|
|
95
|
+
params: [
|
|
96
|
+
['capacity', 'Capacity'],
|
|
97
|
+
['efficiency', 'Efficiency'],
|
|
98
|
+
['drain', 'Drain'],
|
|
99
|
+
],
|
|
100
|
+
highlightKeys: ['capacity', 'efficiency', 'drain'],
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function describeModule(input: CapabilityInput): ModuleDescription | null {
|
|
105
|
+
if (!input.attributes || input.attributes.length === 0) return null
|
|
106
|
+
const key = input.capability.toLowerCase()
|
|
107
|
+
const spec = TEMPLATES[key]
|
|
108
|
+
if (!spec) return null
|
|
109
|
+
const params: Record<string, number | string> = {}
|
|
110
|
+
for (const [paramName, attrLabel] of spec.params) {
|
|
111
|
+
const attr = input.attributes.find((a) => a.label === attrLabel)
|
|
112
|
+
if (attr) params[paramName] = attr.value
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
id: spec.id,
|
|
116
|
+
template: spec.template,
|
|
117
|
+
params,
|
|
118
|
+
highlightKeys: spec.highlightKeys,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function describeModuleForItem(resolved: ResolvedItem): ModuleDescription | null {
|
|
123
|
+
if (resolved.itemType !== 'module') return null
|
|
124
|
+
const group = resolved.attributes?.[0]
|
|
125
|
+
if (!group) return null
|
|
126
|
+
return describeModule({capability: group.capability, attributes: group.attributes})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function describeModuleForSlot(slot: ResolvedModuleSlot): ModuleDescription | null {
|
|
130
|
+
if (!slot.installed || !slot.name || !slot.attributes) return null
|
|
131
|
+
return describeModule({capability: slot.name, attributes: slot.attributes})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function renderDescription(
|
|
135
|
+
desc: ModuleDescription,
|
|
136
|
+
options?: RenderDescriptionOptions
|
|
137
|
+
): TextSpan[] {
|
|
138
|
+
const translate = options?.translate ?? ((_id: string, fallback: string) => fallback)
|
|
139
|
+
const formatNumber = options?.formatNumber ?? ((n: number) => n.toLocaleString('en-US'))
|
|
140
|
+
const tpl = translate(desc.id, desc.template)
|
|
141
|
+
|
|
142
|
+
const spans: TextSpan[] = []
|
|
143
|
+
const regex = /\{([A-Za-z_][A-Za-z0-9_]*)\}/g
|
|
144
|
+
let lastIndex = 0
|
|
145
|
+
let m: RegExpExecArray | null
|
|
146
|
+
while ((m = regex.exec(tpl)) !== null) {
|
|
147
|
+
if (m.index > lastIndex) {
|
|
148
|
+
spans.push({text: tpl.slice(lastIndex, m.index)})
|
|
149
|
+
}
|
|
150
|
+
const paramName = m[1] ?? ''
|
|
151
|
+
const raw = desc.params[paramName]
|
|
152
|
+
if (raw === undefined) {
|
|
153
|
+
spans.push({text: `{${paramName}}`})
|
|
154
|
+
} else {
|
|
155
|
+
const formatted = typeof raw === 'number' ? formatNumber(raw) : raw
|
|
156
|
+
const highlight = (desc.highlightKeys as readonly string[]).includes(paramName)
|
|
157
|
+
spans.push(highlight ? {text: formatted, highlight: true} : {text: formatted})
|
|
158
|
+
}
|
|
159
|
+
lastIndex = m.index + m[0].length
|
|
160
|
+
}
|
|
161
|
+
if (lastIndex < tpl.length) {
|
|
162
|
+
spans.push({text: tpl.slice(lastIndex)})
|
|
163
|
+
}
|
|
164
|
+
return spans
|
|
165
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type {ResolvedItem} from './resolve-item'
|
|
2
|
+
import type {ResourceCategory} from '../types'
|
|
3
|
+
import {formatMass as defaultFormatMass} from '../format'
|
|
4
|
+
|
|
5
|
+
const TIER_ADJECTIVES: Record<number, string> = {
|
|
6
|
+
1: 'Crude',
|
|
7
|
+
2: 'Dense',
|
|
8
|
+
3: 'Pure',
|
|
9
|
+
4: 'Prime',
|
|
10
|
+
5: 'Pristine',
|
|
11
|
+
6: 'Radiant',
|
|
12
|
+
7: 'Exotic',
|
|
13
|
+
8: 'Mythic',
|
|
14
|
+
9: 'Cosmic',
|
|
15
|
+
10: 'Ascendant',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const CATEGORY_LABELS: Record<ResourceCategory, string> = {
|
|
19
|
+
ore: 'Ore',
|
|
20
|
+
crystal: 'Crystal',
|
|
21
|
+
gas: 'Gas',
|
|
22
|
+
regolith: 'Regolith',
|
|
23
|
+
biomass: 'Biomass',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function tierNumber(tier: string): number {
|
|
27
|
+
return Number(String(tier).replace(/^t/i, ''))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function displayName(resolved: ResolvedItem): string {
|
|
31
|
+
if (resolved.itemType === 'resource') {
|
|
32
|
+
const tierNum = tierNumber(resolved.tier)
|
|
33
|
+
const adj = TIER_ADJECTIVES[tierNum] ?? 'Unknown'
|
|
34
|
+
const cat = resolved.category ? CATEGORY_LABELS[resolved.category] : 'Resource'
|
|
35
|
+
return `${adj} ${cat}`
|
|
36
|
+
}
|
|
37
|
+
return resolved.name
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface DescribeOptions {
|
|
41
|
+
translate?: (key: string) => string
|
|
42
|
+
formatNumber?: (n: number) => string
|
|
43
|
+
formatMass?: (kg: number) => string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function describeItem(resolved: ResolvedItem, opts?: DescribeOptions): string {
|
|
47
|
+
const massFmt = opts?.formatMass ?? defaultFormatMass
|
|
48
|
+
const mass = massFmt(resolved.mass)
|
|
49
|
+
const tier = `T${tierNumber(resolved.tier)}`
|
|
50
|
+
if (resolved.itemType === 'resource') {
|
|
51
|
+
const cat = resolved.category ? CATEGORY_LABELS[resolved.category] : 'Resource'
|
|
52
|
+
const header = `${tier} ${cat}`
|
|
53
|
+
const stats = resolved.stats?.map((s) => `${s.label} ${s.value}`).join(', ')
|
|
54
|
+
return [header, stats, mass].filter(Boolean).join(' · ')
|
|
55
|
+
}
|
|
56
|
+
return `${tier} ${resolved.name} · ${mass}`
|
|
57
|
+
}
|