@shipload/sdk 1.0.0-next.11 → 1.0.0-next.13

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.
@@ -0,0 +1,397 @@
1
+ export function computeBaseHullmass(stats: Record<string, number>): number {
2
+ return 100000 - 75 * stats.density
3
+ }
4
+
5
+ export function computeShipHullCapabilities(stats: Record<string, number>): {
6
+ hullmass: number
7
+ capacity: number
8
+ } {
9
+ const statSum = stats.strength + stats.hardness + stats.saturation
10
+ const exponent = statSum / 2997.0
11
+ return {
12
+ hullmass: computeBaseHullmass(stats),
13
+ capacity: Math.floor(1000000 * 10 ** exponent),
14
+ }
15
+ }
16
+
17
+ export function computeEngineCapabilities(stats: Record<string, number>): {
18
+ thrust: number
19
+ drain: number
20
+ } {
21
+ const vol = stats.volatility
22
+ const thm = stats.thermal
23
+
24
+ return {
25
+ thrust: 400 + Math.floor((vol * 3) / 4),
26
+ drain: Math.max(30, 50 - Math.floor(thm / 70)),
27
+ }
28
+ }
29
+
30
+ export function computeGeneratorCapabilities(stats: Record<string, number>): {
31
+ capacity: number
32
+ recharge: number
33
+ } {
34
+ const com = stats.composition
35
+ const fin = stats.fineness
36
+
37
+ return {
38
+ capacity: 300 + Math.floor(com / 6),
39
+ recharge: 1 + Math.floor((fin * 3) / 1000),
40
+ }
41
+ }
42
+
43
+ export interface GathererDepthParams {
44
+ readonly floor: number
45
+ readonly slope: number
46
+ }
47
+
48
+ export const GATHERER_DEPTH_TABLE: readonly GathererDepthParams[] = [
49
+ {floor: 500, slope: 5},
50
+ {floor: 2000, slope: 11},
51
+ {floor: 7000, slope: 16},
52
+ {floor: 15000, slope: 18},
53
+ {floor: 25000, slope: 19},
54
+ {floor: 35000, slope: 16},
55
+ {floor: 46000, slope: 12},
56
+ {floor: 53500, slope: 10},
57
+ {floor: 60000, slope: 5},
58
+ {floor: 63500, slope: 2},
59
+ ]
60
+
61
+ export const GATHERER_DEPTH_MAX_TIER = 10
62
+
63
+ export function gathererDepthForTier(tol: number, tier: number): number {
64
+ if (tier < 1 || tier > GATHERER_DEPTH_MAX_TIER) {
65
+ throw new Error(`gatherer tier out of range: ${tier}`)
66
+ }
67
+ const p = GATHERER_DEPTH_TABLE[tier - 1]
68
+ return p.floor + tol * p.slope
69
+ }
70
+
71
+ export function computeGathererCapabilities(
72
+ stats: Record<string, number>,
73
+ tier: number
74
+ ): {
75
+ yield: number
76
+ drain: number
77
+ depth: number
78
+ speed: number
79
+ } {
80
+ const str = stats.strength
81
+ const con = stats.conductivity
82
+ const ref = stats.reflectivity
83
+ const tol = stats.tolerance
84
+
85
+ return {
86
+ yield: 200 + str,
87
+ drain: Math.max(250, 1250 - Math.floor((con * 25) / 20)),
88
+ depth: gathererDepthForTier(tol, tier),
89
+ speed: 100 + Math.floor((ref * 4) / 5),
90
+ }
91
+ }
92
+
93
+ export function computeLoaderCapabilities(stats: Record<string, number>): {
94
+ mass: number
95
+ thrust: number
96
+ quantity: number
97
+ } {
98
+ const insulation = stats.insulation
99
+ const plasticity = stats.plasticity
100
+
101
+ return {
102
+ mass: Math.max(200, 2000 - Math.floor(insulation * 2)),
103
+ thrust: 1 + Math.floor(plasticity / 500),
104
+ quantity: 1,
105
+ }
106
+ }
107
+
108
+ export function computeCrafterCapabilities(stats: Record<string, number>): {
109
+ speed: number
110
+ drain: number
111
+ } {
112
+ const rea = stats.reactivity
113
+ const fin = stats.fineness
114
+
115
+ return {
116
+ speed: 100 + Math.floor((rea * 4) / 5),
117
+ drain: Math.max(5, 30 - Math.floor(fin / 33)),
118
+ }
119
+ }
120
+
121
+ export function computeHaulerCapabilities(stats: Record<string, number>): {
122
+ capacity: number
123
+ efficiency: number
124
+ drain: number
125
+ } {
126
+ const fineness = stats.fineness
127
+ const conductivity = stats.conductivity
128
+ const composition = stats.composition
129
+
130
+ return {
131
+ capacity: Math.max(1, 1 + Math.floor(fineness / 400)),
132
+ efficiency: 2000 + conductivity * 6,
133
+ drain: Math.max(3, 15 - Math.floor(composition / 80)),
134
+ }
135
+ }
136
+
137
+ export function computeStorageCapabilities(
138
+ stats: Record<string, number>,
139
+ baseCapacity: number
140
+ ): {
141
+ capacityBonus: number
142
+ } {
143
+ const strength = stats.strength
144
+ const density = stats.density
145
+ const hardness = stats.hardness
146
+ const saturation = stats.saturation
147
+
148
+ const statSum = strength + density + hardness + saturation
149
+ const capacityBonus = Math.floor(
150
+ (baseCapacity * (10 + Math.floor((statSum * 10) / 2997))) / 100
151
+ )
152
+
153
+ return {capacityBonus}
154
+ }
155
+
156
+ import {
157
+ ITEM_CONTAINER_T1_PACKED,
158
+ ITEM_CONTAINER_T2_PACKED,
159
+ ITEM_EXTRACTOR_T1_PACKED,
160
+ ITEM_FACTORY_T1_PACKED,
161
+ ITEM_SHIP_T1_PACKED,
162
+ ITEM_WAREHOUSE_T1_PACKED,
163
+ } from '../data/item-ids'
164
+ import {
165
+ getModuleCapabilityType,
166
+ MODULE_ENGINE,
167
+ MODULE_GENERATOR,
168
+ MODULE_GATHERER,
169
+ MODULE_LOADER,
170
+ MODULE_STORAGE,
171
+ MODULE_CRAFTER,
172
+ MODULE_HAULER,
173
+ MODULE_WARP,
174
+ } from '../capabilities/modules'
175
+ import {getItem} from '../data/catalog'
176
+ import {decodeCraftedItemStats} from './crafting'
177
+ import {
178
+ applySlotMultiplier,
179
+ clampUint16,
180
+ getSlotAmp,
181
+ type InstalledModule,
182
+ } from '../entities/slot-multiplier'
183
+ import type {EntitySlot} from '../data/recipes-runtime'
184
+
185
+ export function computeBaseCapacity(itemId: number, stats: Record<string, number>): number {
186
+ switch (itemId) {
187
+ case ITEM_SHIP_T1_PACKED:
188
+ case ITEM_EXTRACTOR_T1_PACKED:
189
+ case ITEM_FACTORY_T1_PACKED:
190
+ case ITEM_CONTAINER_T1_PACKED:
191
+ return computeShipHullCapabilities(stats).capacity
192
+ case ITEM_WAREHOUSE_T1_PACKED:
193
+ return computeWarehouseHullCapabilities(stats).capacity
194
+ case ITEM_CONTAINER_T2_PACKED:
195
+ return computeContainerT2Capabilities(stats).capacity
196
+ default:
197
+ return 0
198
+ }
199
+ }
200
+
201
+ export function computeWarpCapabilities(stats: Record<string, number>): {
202
+ range: number
203
+ } {
204
+ const res = stats.resonance
205
+ return {range: 100 + res * 3}
206
+ }
207
+
208
+ export function computeWarehouseHullCapabilities(stats: Record<string, number>): {
209
+ hullmass: number
210
+ capacity: number
211
+ } {
212
+ const statSum = stats.strength + stats.hardness + stats.saturation
213
+ const exponent = statSum / 2997.0
214
+ return {
215
+ hullmass: computeBaseHullmass(stats),
216
+ capacity: Math.floor(20000000 * 10 ** exponent),
217
+ }
218
+ }
219
+
220
+ export interface ComputedCapabilities {
221
+ hullmass: number
222
+ capacity: number
223
+ engines?: {thrust: number; drain: number}
224
+ generator?: {capacity: number; recharge: number}
225
+ gatherer?: {yield: number; drain: number; depth: number; speed: number}
226
+ loaders?: {mass: number; thrust: number; quantity: number}
227
+ crafter?: {speed: number; drain: number}
228
+ hauler?: {capacity: number; efficiency: number; drain: number}
229
+ warp?: {range: number}
230
+ }
231
+
232
+ export function computeEntityCapabilities(
233
+ stats: Record<string, number>,
234
+ itemId: number,
235
+ modules: InstalledModule[],
236
+ layout: EntitySlot[]
237
+ ): ComputedCapabilities {
238
+ let totalThrust = 0
239
+ let totalEngineDrain = 0
240
+ let hasEngine = false
241
+
242
+ let totalGenCapacity = 0
243
+ let totalGenRecharge = 0
244
+ let hasGenerator = false
245
+
246
+ let totalLoaderMass = 0
247
+ let totalLoaderThrust = 0
248
+ let totalLoaderQuantity = 0
249
+ let hasLoader = false
250
+
251
+ let totalGathYield = 0
252
+ let totalGathDrain = 0
253
+ let maxGathDepth = 0
254
+ let totalGathSpeed = 0
255
+ let hasGatherer = false
256
+
257
+ let totalStorageBonus = 0
258
+ const baseCapacity = computeBaseCapacity(itemId, stats)
259
+ let installedModuleMass = 0
260
+
261
+ let totalCrafterSpeed = 0
262
+ let totalCrafterDrain = 0
263
+ let hasCrafter = false
264
+
265
+ let totalHaulerCapacity = 0
266
+ let weightedHaulerEffNum = 0n
267
+ let totalHaulerDrain = 0
268
+ let hasHauler = false
269
+
270
+ let totalWarpRange = 0
271
+ let hasWarp = false
272
+
273
+ for (const mod of modules) {
274
+ const item = getItem(mod.itemId)
275
+ const modType = getModuleCapabilityType(mod.itemId)
276
+ const amp = getSlotAmp(layout, mod.slotIndex)
277
+ const decodedStats = decodeCraftedItemStats(mod.itemId, mod.stats)
278
+ installedModuleMass += item.mass
279
+
280
+ if (modType === MODULE_ENGINE) {
281
+ hasEngine = true
282
+ const caps = computeEngineCapabilities(decodedStats)
283
+ totalThrust += applySlotMultiplier(caps.thrust, amp)
284
+ totalEngineDrain += caps.drain
285
+ } else if (modType === MODULE_GENERATOR) {
286
+ hasGenerator = true
287
+ const caps = computeGeneratorCapabilities(decodedStats)
288
+ totalGenCapacity += applySlotMultiplier(caps.capacity, amp)
289
+ totalGenRecharge += applySlotMultiplier(caps.recharge, amp)
290
+ } else if (modType === MODULE_GATHERER) {
291
+ hasGatherer = true
292
+ const tier = item.tier
293
+ const caps = computeGathererCapabilities(decodedStats, tier)
294
+ totalGathYield += applySlotMultiplier(caps.yield, amp)
295
+ totalGathDrain += caps.drain
296
+ if (caps.depth > maxGathDepth) maxGathDepth = caps.depth
297
+ totalGathSpeed += applySlotMultiplier(caps.speed, amp)
298
+ } else if (modType === MODULE_LOADER) {
299
+ hasLoader = true
300
+ const caps = computeLoaderCapabilities(decodedStats)
301
+ totalLoaderMass += caps.mass
302
+ totalLoaderThrust += applySlotMultiplier(caps.thrust, amp)
303
+ totalLoaderQuantity += caps.quantity
304
+ } else if (modType === MODULE_STORAGE) {
305
+ const caps = computeStorageCapabilities(decodedStats, baseCapacity)
306
+ totalStorageBonus += caps.capacityBonus
307
+ } else if (modType === MODULE_CRAFTER) {
308
+ hasCrafter = true
309
+ const caps = computeCrafterCapabilities(decodedStats)
310
+ totalCrafterSpeed += applySlotMultiplier(caps.speed, amp)
311
+ totalCrafterDrain += caps.drain
312
+ } else if (modType === MODULE_HAULER) {
313
+ hasHauler = true
314
+ const caps = computeHaulerCapabilities(decodedStats)
315
+ const eff = applySlotMultiplier(caps.efficiency, amp)
316
+ totalHaulerCapacity += caps.capacity
317
+ weightedHaulerEffNum += BigInt(eff) * BigInt(caps.capacity)
318
+ totalHaulerDrain += caps.drain
319
+ } else if (modType === MODULE_WARP) {
320
+ hasWarp = true
321
+ const caps = computeWarpCapabilities(decodedStats)
322
+ totalWarpRange += applySlotMultiplier(caps.range, amp)
323
+ }
324
+ }
325
+
326
+ const result: ComputedCapabilities = {
327
+ hullmass: computeBaseHullmass(stats) + installedModuleMass,
328
+ capacity: baseCapacity + totalStorageBonus,
329
+ }
330
+
331
+ if (hasEngine) {
332
+ result.engines = {thrust: totalThrust, drain: totalEngineDrain}
333
+ }
334
+ if (hasGenerator) {
335
+ result.generator = {
336
+ capacity: clampUint16(totalGenCapacity),
337
+ recharge: clampUint16(totalGenRecharge),
338
+ }
339
+ }
340
+ if (hasGatherer) {
341
+ result.gatherer = {
342
+ yield: clampUint16(totalGathYield),
343
+ drain: totalGathDrain,
344
+ depth: maxGathDepth,
345
+ speed: clampUint16(totalGathSpeed),
346
+ }
347
+ }
348
+ if (hasLoader) {
349
+ result.loaders = {
350
+ mass: totalLoaderMass,
351
+ thrust: clampUint16(totalLoaderThrust),
352
+ quantity: totalLoaderQuantity,
353
+ }
354
+ }
355
+ if (hasCrafter) {
356
+ result.crafter = {speed: clampUint16(totalCrafterSpeed), drain: totalCrafterDrain}
357
+ }
358
+ if (hasHauler) {
359
+ const efficiency =
360
+ totalHaulerCapacity > 0 ? Number(weightedHaulerEffNum / BigInt(totalHaulerCapacity)) : 0
361
+ result.hauler = {
362
+ capacity: totalHaulerCapacity,
363
+ efficiency: clampUint16(efficiency),
364
+ drain: totalHaulerDrain,
365
+ }
366
+ }
367
+ if (hasWarp) {
368
+ result.warp = {range: totalWarpRange}
369
+ }
370
+
371
+ return result
372
+ }
373
+
374
+ export function computeContainerCapabilities(stats: Record<string, number>): {
375
+ hullmass: number
376
+ capacity: number
377
+ } {
378
+ return computeShipHullCapabilities(stats)
379
+ }
380
+
381
+ export function computeContainerT2Capabilities(stats: Record<string, number>): {
382
+ hullmass: number
383
+ capacity: number
384
+ } {
385
+ const strength = stats.strength
386
+ const density = stats.density
387
+ const hardness = stats.hardness
388
+ const saturation = stats.saturation
389
+
390
+ const hullmass = 70000 - 50 * density
391
+
392
+ const statSum = strength + hardness + saturation
393
+ const exponent = statSum / 2500
394
+ const capacity = Math.floor(1500000 * 10 ** exponent)
395
+
396
+ return {hullmass, capacity}
397
+ }
@@ -340,7 +340,7 @@ export function computeCraftedOutputStats(
340
340
  * returns a UInt64 whose bit-packed form matches what the contract writes
341
341
  * to cargo_item.stats on gather.
342
342
  *
343
- * Use this whenever off-chain code simulates a gather (testmap, player
343
+ * Use this whenever off-chain code simulates a gather (webapp, player
344
344
  * scanners that project cargo outcomes) and needs a value that matches
345
345
  * what on-chain cargo would carry.
346
346
  */
@@ -0,0 +1,98 @@
1
+ import {UInt64} from '@wharfkit/antelope'
2
+ import {ServerContract} from '../contracts'
3
+ import {
4
+ CAP_DEMOLISH,
5
+ CAP_MODULES,
6
+ CAP_UNDEPLOY,
7
+ CAP_WRAP,
8
+ type EntityClass,
9
+ getEntityClass,
10
+ kindCan,
11
+ } from '../data/kind-registry'
12
+ import {InventoryAccessor} from './inventory-accessor'
13
+ import {Location} from './location'
14
+ import {ScheduleAccessor} from '../scheduling/accessor'
15
+ import * as schedule from '../scheduling/schedule'
16
+ import type {EntityInventory} from './entity-inventory'
17
+
18
+ export class Entity extends ServerContract.Types.entity_info {
19
+ private _sched?: ScheduleAccessor
20
+ private _inv?: InventoryAccessor
21
+
22
+ get name(): string {
23
+ return this.entity_name
24
+ }
25
+
26
+ get location(): Location {
27
+ return Location.from(this.coordinates)
28
+ }
29
+
30
+ get isIdle(): boolean {
31
+ return this.is_idle
32
+ }
33
+
34
+ get sched(): ScheduleAccessor {
35
+ this._sched ??= new ScheduleAccessor(this)
36
+ return this._sched
37
+ }
38
+
39
+ get inv(): InventoryAccessor {
40
+ this._inv ??= new InventoryAccessor(this)
41
+ return this._inv
42
+ }
43
+
44
+ get inventory(): EntityInventory[] {
45
+ return this.inv.items
46
+ }
47
+
48
+ get totalCargoMass(): UInt64 {
49
+ return this.inv.totalMass
50
+ }
51
+
52
+ get maxCapacity(): UInt64 {
53
+ return UInt64.from(this.capacity ?? 0)
54
+ }
55
+
56
+ get availableCapacity(): UInt64 {
57
+ const cargo = this.totalCargoMass
58
+ const max = this.maxCapacity
59
+ return cargo.gte(max) ? UInt64.from(0) : max.subtracting(cargo)
60
+ }
61
+
62
+ get isFull(): boolean {
63
+ return this.totalCargoMass.gte(this.maxCapacity)
64
+ }
65
+
66
+ get totalMass(): UInt64 {
67
+ const hull = this.hullmass ? UInt64.from(this.hullmass) : UInt64.from(0)
68
+ return hull.adding(this.totalCargoMass)
69
+ }
70
+
71
+ get entityClass(): EntityClass {
72
+ return getEntityClass(this.type)
73
+ }
74
+
75
+ get canWrap(): boolean {
76
+ return kindCan(this.type, CAP_WRAP)
77
+ }
78
+
79
+ get canUndeploy(): boolean {
80
+ return kindCan(this.type, CAP_UNDEPLOY)
81
+ }
82
+
83
+ get canDemolish(): boolean {
84
+ return kindCan(this.type, CAP_DEMOLISH)
85
+ }
86
+
87
+ get canUseModules(): boolean {
88
+ return kindCan(this.type, CAP_MODULES)
89
+ }
90
+
91
+ isLoading(now: Date): boolean {
92
+ return schedule.isLoading(this, now)
93
+ }
94
+
95
+ isUnloading(now: Date): boolean {
96
+ return schedule.isUnloading(this, now)
97
+ }
98
+ }